diff --git a/Cargo.lock b/Cargo.lock index 1e5742b1ce9..68eeeda0d1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2765,6 +2765,7 @@ dependencies = [ "thiserror", "wasm-bindgen", "wasm-bindgen-test", + "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-compiler-llvm", @@ -2780,6 +2781,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-artifact" +version = "2.2.1" +dependencies = [ + "enumset", + "loupe", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-bin-fuzz" version = "0.0.0" @@ -2890,10 +2902,30 @@ dependencies = [ "target-lexicon 0.12.3", "thiserror", "wasmer-types", - "wasmer-vm", "wasmparser 0.83.0", ] +[[package]] +name = "wasmer-compiler-cli" +version = "2.2.1" +dependencies = [ + "anyhow", + "atty", + "bytesize", + "cfg-if 1.0.0", + "colored 2.0.0", + "distance", + "fern", + "log", + "structopt", + "tempfile", + "unix_mode", + "wasmer-compiler", + "wasmer-compiler-singlepass", + "wasmer-engine-universal-artifact", + "wasmer-types", +] + [[package]] name = "wasmer-compiler-cranelift" version = "2.2.1" @@ -2955,7 +2987,6 @@ dependencies = [ "target-lexicon 0.12.3", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] @@ -2998,6 +3029,7 @@ dependencies = [ "serde_bytes", "target-lexicon 0.12.3", "thiserror", + "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -3012,6 +3044,7 @@ dependencies = [ "loupe", "serde", "serde_bytes", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-types", @@ -3033,6 +3066,7 @@ dependencies = [ "serde", "tempfile", "tracing", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -3054,6 +3088,7 @@ dependencies = [ "serde", "tempfile", "tracing", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -3066,7 +3101,6 @@ name = "wasmer-engine-universal" version = "2.2.1" dependencies = [ "cfg-if 1.0.0", - "enum-iterator", "enumset", "leb128", "loupe", @@ -3074,11 +3108,26 @@ dependencies = [ "rkyv", "wasmer-compiler", "wasmer-engine", + "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] +[[package]] +name = "wasmer-engine-universal-artifact" +version = "2.2.1" +dependencies = [ + "enum-iterator", + "enumset", + "loupe", + "rkyv", + "thiserror", + "wasmer-artifact", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-integration-tests-cli" version = "2.2.1" @@ -3115,8 +3164,11 @@ dependencies = [ name = "wasmer-types" version = "2.2.1" dependencies = [ + "backtrace", + "enum-iterator", "indexmap", "loupe", + "more-asserts", "rkyv", "serde", "thiserror", @@ -3155,6 +3207,7 @@ dependencies = [ "scopeguard", "serde", "thiserror", + "wasmer-artifact", "wasmer-types", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index f1273bf1226..eb2d96bf65a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "lib/cache", "lib/c-api", "lib/cli", + "lib/cli-compiler", "lib/compiler", "lib/compiler-cranelift", "lib/compiler-singlepass", diff --git a/Makefile b/Makefile index 00b2a1c1c1d..6fba03fab3e 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ ifneq ($(ENABLE_LLVM), 0) endif endif -exclude_tests := --exclude wasmer-c-api --exclude wasmer-cli +exclude_tests := --exclude wasmer-c-api --exclude wasmer-cli --exclude wasmer-compiler-cli # Is failing to compile in Linux for some reason exclude_tests += --exclude wasmer-wasi-experimental-io-devices # We run integration tests separately (it requires building the c-api) @@ -379,6 +379,9 @@ build-wasmer-debug: bench: cargo bench $(compiler_features) +build-wasmer-wasm: + cargo build --release --manifest-path lib/cli-compiler/Cargo.toml --target wasm32-wasi --features singlepass,universal --bin wasmer-compiler + # For best results ensure the release profile looks like the following # in Cargo.toml: # [profile.release] diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 2427d99c3e4..10730f5548b 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -22,6 +22,7 @@ edition = "2018" # Shared dependencies. [dependencies] # - Mandatory shared dependencies. +wasmer-artifact = { path = "../artifact", version = "=2.2.1" } indexmap = { version = "1.6", features = ["serde-1"] } cfg-if = "1.0" thiserror = "1.0" diff --git a/lib/api/src/js/mod.rs b/lib/api/src/js/mod.rs index 95e7d15ccec..66c107c8cc5 100644 --- a/lib/api/src/js/mod.rs +++ b/lib/api/src/js/mod.rs @@ -41,7 +41,6 @@ mod resolver; mod store; mod trap; mod types; -mod utils; mod wasm_bindgen_polyfill; /// Implement [`WasmerEnv`] for your type with `#[derive(WasmerEnv)]`. @@ -75,8 +74,8 @@ pub use crate::js::types::{ TableType, Val, ValType, }; pub use crate::js::types::{Val as Value, ValType as Type}; -pub use crate::js::utils::is_wasm; +pub use wasmer_types::is_wasm; pub use wasmer_types::{ Atomically, Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, MemoryView, Pages, ValueType, WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE, diff --git a/lib/api/src/sys/mod.rs b/lib/api/src/sys/mod.rs index 157a26a4a64..ab7d82e3f7d 100644 --- a/lib/api/src/sys/mod.rs +++ b/lib/api/src/sys/mod.rs @@ -10,7 +10,6 @@ mod ptr; mod store; mod tunables; mod types; -mod utils; /// Implement [`WasmerEnv`] for your type with `#[derive(WasmerEnv)]`. /// @@ -45,7 +44,6 @@ pub use crate::sys::types::{ TableType, Val, ValType, }; pub use crate::sys::types::{Val as Value, ValType as Type}; -pub use crate::sys::utils::is_wasm; pub use target_lexicon::{Architecture, CallingConvention, OperatingSystem, Triple, HOST}; #[cfg(feature = "compiler")] pub use wasmer_compiler::{ @@ -59,6 +57,7 @@ pub use wasmer_engine::{ ChainableNamedResolver, DeserializeError, Engine, Export, FrameInfo, LinkError, NamedResolver, NamedResolverChain, Resolver, RuntimeError, SerializeError, Tunables, }; +pub use wasmer_types::is_wasm; #[cfg(feature = "experimental-reference-types-extern-ref")] pub use wasmer_types::ExternRef; pub use wasmer_types::{ diff --git a/lib/api/src/sys/utils.rs b/lib/api/src/sys/utils.rs deleted file mode 100644 index b3f067f3334..00000000000 --- a/lib/api/src/sys/utils.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// Check if the provided bytes are wasm-like -pub fn is_wasm(bytes: impl AsRef<[u8]>) -> bool { - bytes.as_ref().starts_with(b"\0asm") -} diff --git a/lib/artifact/Cargo.toml b/lib/artifact/Cargo.toml new file mode 100644 index 00000000000..a57fcc8a312 --- /dev/null +++ b/lib/artifact/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "wasmer-artifact" +version = "2.2.1" +description = "Wasmer Artifact abstraction" +categories = ["wasm"] +keywords = ["wasm", "webassembly", "engine"] +authors = ["Wasmer Engineering Team "] +repository = "https://github.com/wasmerio/wasmer" +license = "MIT OR Apache-2.0 WITH LLVM-exception " +readme = "README.md" +edition = "2018" + +[dependencies] +wasmer-types = { path = "../types", version = "=2.2.1" } +wasmer-compiler = { path = "../compiler", version = "=2.2.1" } +loupe = "0.1" +thiserror = "1.0" +enumset = "1.0" + +[badges] +maintenance = { status = "actively-developed" } + diff --git a/lib/artifact/README.md b/lib/artifact/README.md new file mode 100644 index 00000000000..515aea657a9 --- /dev/null +++ b/lib/artifact/README.md @@ -0,0 +1,20 @@ +# `wasmer-artifact` [![Build Status](https://github.com/wasmerio/wasmer/workflows/build/badge.svg?style=flat-square)](https://github.com/wasmerio/wasmer/actions?query=workflow%3Abuild) [![Join Wasmer Slack](https://img.shields.io/static/v1?label=Slack&message=join%20chat&color=brighgreen&style=flat-square)](https://slack.wasmer.io) [![MIT License](https://img.shields.io/github/license/wasmerio/wasmer.svg?style=flat-square)](https://github.com/wasmerio/wasmer/blob/master/LICENSE) + + +This crate is the general abstraction for creating Artifacts in Wasmer. + +### Acknowledgments + +This project borrowed some of the code of the trap implementation from +the [`wasmtime-api`], the code since then has evolved significantly. + +Please check [Wasmer `ATTRIBUTIONS`] to further see licenses and other +attributions of the project. + + +[`wasmer-engine-universal`]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-universal +[`wasmer-engine-dylib`]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-dylib +[`wasmer-engine-staticlib`]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-staticlib +[`wasmer-engine-dummy`]: https://github.com/wasmerio/wasmer/tree/master/tests/lib/engine-dummy +[`wasmtime-api`]: https://crates.io/crates/wasmtime +[Wasmer `ATTRIBUTIONS`]: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md diff --git a/lib/artifact/src/artifact.rs b/lib/artifact/src/artifact.rs new file mode 100644 index 00000000000..5b5561aec63 --- /dev/null +++ b/lib/artifact/src/artifact.rs @@ -0,0 +1,161 @@ +use crate::{DeserializeError, SerializeError}; +use enumset::EnumSet; +use loupe::MemoryUsage; +use std::any::Any; +use std::convert::TryInto; +use std::path::Path; +use std::sync::Arc; +use std::{fs, mem}; +use wasmer_compiler::{CpuFeature, Features}; +use wasmer_types::entity::PrimaryMap; +use wasmer_types::{ + MemoryIndex, MemoryStyle, ModuleInfo, OwnedDataInitializer, TableIndex, TableStyle, +}; + +/// An `Artifact` is the product that the `Engine` +/// implementation produce and use. +/// +/// The `Artifact` contains the compiled data for a given +/// module as well as extra information needed to run the +/// module at runtime, such as [`ModuleInfo`] and [`Features`]. +pub trait ArtifactCreate: Send + Sync + Upcastable + MemoryUsage { + /// Return a reference-counted pointer to the module + fn module(&self) -> Arc; + + /// Return a pointer to a module. + fn module_ref(&self) -> &ModuleInfo; + + /// Gets a mutable reference to the info. + /// + /// Note: this will return `None` if the module is already instantiated. + fn module_mut(&mut self) -> Option<&mut ModuleInfo>; + + /// Returns the features for this Artifact + fn features(&self) -> &Features; + + /// Returns the CPU features for this Artifact + fn cpu_features(&self) -> EnumSet; + + /// Returns the memory styles associated with this `Artifact`. + fn memory_styles(&self) -> &PrimaryMap; + + /// Returns the table plans associated with this `Artifact`. + fn table_styles(&self) -> &PrimaryMap; + + /// Returns data initializers to pass to `InstanceHandle::initialize` + fn data_initializers(&self) -> &[OwnedDataInitializer]; + + /// Serializes an artifact into bytes + fn serialize(&self) -> Result, SerializeError>; + + /// Serializes an artifact into a file path + fn serialize_to_file(&self, path: &Path) -> Result<(), SerializeError> { + let serialized = self.serialize()?; + fs::write(&path, serialized)?; + Ok(()) + } +} + +// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 . +/// Trait needed to get downcasting of `Engine`s to work. +pub trait Upcastable { + /// upcast ref + fn upcast_any_ref(&'_ self) -> &'_ dyn Any; + /// upcast mut ref + fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any; + /// upcast boxed dyn + fn upcast_any_box(self: Box) -> Box; +} + +impl Upcastable for T { + #[inline] + fn upcast_any_ref(&'_ self) -> &'_ dyn Any { + self + } + #[inline] + fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any { + self + } + #[inline] + fn upcast_any_box(self: Box) -> Box { + self + } +} + +impl dyn ArtifactCreate + 'static { + /// Try to downcast the artifact into a given type. + #[inline] + pub fn downcast_ref(&'_ self) -> Option<&'_ T> { + self.upcast_any_ref().downcast_ref::() + } + + /// Try to downcast the artifact into a given type mutably. + #[inline] + pub fn downcast_mut(&'_ mut self) -> Option<&'_ mut T> { + self.upcast_any_mut().downcast_mut::() + } +} + +/// Metadata header which holds an ABI version and the length of the remaining +/// metadata. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct MetadataHeader { + magic: [u8; 8], + version: u32, + len: u32, +} + +impl MetadataHeader { + /// Current ABI version. Increment this any time breaking changes are made + /// to the format of the serialized data. + const CURRENT_VERSION: u32 = 1; + + /// Magic number to identify wasmer metadata. + const MAGIC: [u8; 8] = *b"WASMER\0\0"; + + /// Length of the metadata header. + pub const LEN: usize = 16; + + /// Alignment of the metadata. + pub const ALIGN: usize = 16; + + /// Creates a new header for metadata of the given length. + pub fn new(len: usize) -> [u8; 16] { + let header = MetadataHeader { + magic: Self::MAGIC, + version: Self::CURRENT_VERSION, + len: len.try_into().expect("metadata exceeds maximum length"), + }; + unsafe { mem::transmute(header) } + } + + /// Parses the header and returns the length of the metadata following it. + pub fn parse(bytes: &[u8]) -> Result { + if bytes.as_ptr() as usize % 16 != 0 { + return Err(DeserializeError::CorruptedBinary( + "misaligned metadata".to_string(), + )); + } + let bytes: [u8; 16] = bytes + .get(..16) + .ok_or_else(|| { + DeserializeError::CorruptedBinary("invalid metadata header".to_string()) + })? + .try_into() + .unwrap(); + let header: MetadataHeader = unsafe { mem::transmute(bytes) }; + if header.magic != Self::MAGIC { + return Err(DeserializeError::Incompatible( + "The provided bytes were not serialized by Wasmer".to_string(), + )); + } + if header.version != Self::CURRENT_VERSION { + return Err(DeserializeError::Incompatible( + "The provided bytes were serialized by an incompatible version of Wasmer" + .to_string(), + )); + } + Ok(header.len as usize) + } +} diff --git a/lib/artifact/src/error.rs b/lib/artifact/src/error.rs new file mode 100644 index 00000000000..7fd6d7b6e17 --- /dev/null +++ b/lib/artifact/src/error.rs @@ -0,0 +1,66 @@ +//! The WebAssembly possible errors +use std::io; +use thiserror::Error; +use wasmer_compiler::CompileError; +use wasmer_types::ExternType; + +/// The Serialize error can occur when serializing a +/// compiled Module into a binary. +#[derive(Error, Debug)] +pub enum SerializeError { + /// An IO error + #[error(transparent)] + Io(#[from] io::Error), + /// A generic serialization error + #[error("{0}")] + Generic(String), +} + +/// The Deserialize error can occur when loading a +/// compiled Module from a binary. +#[derive(Error, Debug)] +pub enum DeserializeError { + /// An IO error + #[error(transparent)] + Io(#[from] io::Error), + /// A generic deserialization error + #[error("{0}")] + Generic(String), + /// Incompatible serialized binary + #[error("incompatible binary: {0}")] + Incompatible(String), + /// The provided binary is corrupted + #[error("corrupted binary: {0}")] + CorruptedBinary(String), + /// The binary was valid, but we got an error when + /// trying to allocate the required resources. + #[error(transparent)] + Compiler(CompileError), +} + +/// An ImportError. +/// +/// Note: this error is not standard to WebAssembly, but it's +/// useful to determine the import issue on the API side. +#[derive(Error, Debug)] +pub enum ImportError { + /// Incompatible Import Type. + /// This error occurs when the import types mismatch. + #[error("incompatible import type. Expected {0:?} but received {1:?}")] + IncompatibleType(ExternType, ExternType), + + /// Unknown Import. + /// This error occurs when an import was expected but not provided. + #[error("unknown import. Expected {0:?}")] + UnknownImport(ExternType), +} + +/// An error while preinstantiating a module. +/// +#[derive(Error, Debug)] +pub enum PreInstantiationError { + /// The module was compiled with a CPU feature that is not available on + /// the current host. + #[error("module compiled with CPU feature that is missing from host")] + CpuFeature(String), +} diff --git a/lib/artifact/src/funcbody.rs b/lib/artifact/src/funcbody.rs new file mode 100644 index 00000000000..662623baa83 --- /dev/null +++ b/lib/artifact/src/funcbody.rs @@ -0,0 +1,17 @@ +/// A placeholder byte-sized type which is just used to provide some amount of type +/// safety when dealing with pointers to JIT-compiled function bodies. Note that it's +/// deliberately not Copy, as we shouldn't be carelessly copying function body bytes +/// around. +#[repr(C)] +pub struct VMFunctionBody(u8); + +#[cfg(test)] +mod test_vmfunction_body { + use super::VMFunctionBody; + use std::mem::size_of; + + #[test] + fn check_vmfunction_body_offsets() { + assert_eq!(size_of::(), 1); + } +} diff --git a/lib/artifact/src/lib.rs b/lib/artifact/src/lib.rs new file mode 100644 index 00000000000..66f91a75b41 --- /dev/null +++ b/lib/artifact/src/lib.rs @@ -0,0 +1,55 @@ +//! Generic Artifact abstraction for Wasmer Engines. + +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![warn(unused_import_braces)] +#![cfg_attr( + feature = "cargo-clippy", + allow(clippy::new_without_default, clippy::new_without_default) +)] +#![cfg_attr( + feature = "cargo-clippy", + warn( + clippy::float_arithmetic, + clippy::mut_mut, + clippy::nonminimal_bool, + clippy::option_map_unwrap_or, + clippy::option_map_unwrap_or_else, + clippy::print_stdout, + clippy::unicode_not_nfc, + clippy::use_self + ) +)] + +mod artifact; +mod error; +mod funcbody; + +pub use crate::artifact::{ArtifactCreate, MetadataHeader, Upcastable}; +pub use crate::error::{DeserializeError, ImportError, PreInstantiationError, SerializeError}; +pub use crate::funcbody::VMFunctionBody; +use loupe::MemoryUsage; + +/// Version number of this crate. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// A safe wrapper around `VMFunctionBody`. +#[derive(Clone, Copy, Debug, MemoryUsage)] +#[repr(transparent)] +pub struct FunctionBodyPtr(pub *const VMFunctionBody); + +impl std::ops::Deref for FunctionBodyPtr { + type Target = *const VMFunctionBody; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// # Safety +/// The VMFunctionBody that this points to is opaque, so there's no data to +/// read or write through this pointer. This is essentially a usize. +unsafe impl Send for FunctionBodyPtr {} +/// # Safety +/// The VMFunctionBody that this points to is opaque, so there's no data to +/// read or write through this pointer. This is essentially a usize. +unsafe impl Sync for FunctionBodyPtr {} diff --git a/lib/cli-compiler/Cargo.toml b/lib/cli-compiler/Cargo.toml new file mode 100644 index 00000000000..fcc01acd650 --- /dev/null +++ b/lib/cli-compiler/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "wasmer-compiler-cli" +version = "2.2.1" +description = "Wasmer Compiler CLI" +categories = ["wasm", "command-line-interface"] +keywords = ["wasm", "webassembly", "cli"] +authors = ["Wasmer Engineering Team "] +repository = "https://github.com/wasmerio/wasmer" +license = "MIT" +readme = "README.md" +edition = "2018" +default-run = "wasmer-compiler" +build = "build.rs" + +[[bin]] +name = "wasmer-compiler" +path = "src/bin/wasmer_compiler.rs" +doc = false + +[dependencies] +wasmer-engine-universal-artifact = { version = "=2.2.1", path = "../universal-artifact", features = ["compiler"] } +wasmer-compiler = { version = "=2.2.1", path = "../compiler" } +wasmer-types = { version = "=2.2.1", path = "../types" } +atty = "0.2" +colored = "2.0" +anyhow = "1.0" +structopt = { version = "0.3", features = ["suggestions"] } +# For the function names autosuggestion +distance = "0.4" +# For the inspect subcommand +bytesize = "1.0" +cfg-if = "1.0" +# For debug feature +fern = { version = "0.6", features = ["colored"], optional = true } +log = { version = "0.4", optional = true } +tempfile = "3" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +wasmer-compiler-singlepass = { version = "=2.2.1", path = "../compiler-singlepass", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasmer-compiler-singlepass = { version = "=2.2.1", path = "../compiler-singlepass", optional = true, default-features = false, features = ["wasm"] } + +[target.'cfg(target_os = "linux")'.dependencies] +unix_mode = "0.1.3" + +[features] +# Don't add the compiler features in default, please add them on the Makefile +# since we might want to autoconfigure them depending on the availability on the host. +default = [ + "universal", +] +engine = [] +universal = [] +compiler = [ + "wasmer-compiler/translator", +] +singlepass = [ + "wasmer-compiler-singlepass", + "compiler", +] +debug = ["fern", "log"] +disable-all-logging = [] +jit = ["universal"] diff --git a/lib/cli-compiler/README.md b/lib/cli-compiler/README.md new file mode 100644 index 00000000000..512321d19bf --- /dev/null +++ b/lib/cli-compiler/README.md @@ -0,0 +1,34 @@ +# `wasmer-cli-compiler` [![Build Status](https://github.com/wasmerio/wasmer/workflows/build/badge.svg?style=flat-square)](https://github.com/wasmerio/wasmer/actions?query=workflow%3Abuild) [![Join Wasmer Slack](https://img.shields.io/static/v1?label=Slack&message=join%20chat&color=brighgreen&style=flat-square)](https://slack.wasmer.io) [![MIT License](https://img.shields.io/github/license/wasmerio/wasmer.svg?style=flat-square)](https://github.com/wasmerio/wasmer/blob/master/LICENSE) + +This crate is the Wasmer Compiler only CLI. + + +## Features + +The Compiler only Wasmer supports the following features: +* `universal` (default): support for the [Universal engine]. +* `wasi` (default): support for [WASI]. +* `experimental-io-devices`: support for experimental IO devices in WASI. +* `emscripten` (default): support for [Emscripten]. +* `singlepass`: support for the [Singlepass compiler]. + +[Universal engine]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-universal/ +[WASI]: https://github.com/wasmerio/wasmer/tree/master/lib/wasi/ +[Emscripten]: https://github.com/wasmerio/wasmer/tree/master/lib/emscripten/ +[Singlepass compiler]: https://github.com/wasmerio/wasmer/tree/master/lib/compiler-singlepass/ + +## CLI commands + +Once you have Wasmer installed, you can start executing WebAssembly files easily: + +Get the current Wasmer version: + +```bash +wasmer-compiler -V +``` + +Compile a WebAssembly file: + +```bash +wasmer-compiler compile myfile.wasm -o myfile.wasmu --singlepass --universal +``` diff --git a/lib/cli-compiler/build.rs b/lib/cli-compiler/build.rs new file mode 100644 index 00000000000..36fe64763ed --- /dev/null +++ b/lib/cli-compiler/build.rs @@ -0,0 +1,4 @@ +pub fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=WASMER_INSTALL_PREFIX"); +} diff --git a/lib/cli-compiler/src/bin/wasmer_compiler.rs b/lib/cli-compiler/src/bin/wasmer_compiler.rs new file mode 100644 index 00000000000..372ee665eb3 --- /dev/null +++ b/lib/cli-compiler/src/bin/wasmer_compiler.rs @@ -0,0 +1,13 @@ +use wasmer_compiler_cli::cli::wasmer_main; + +#[cfg(not(any(feature = "cranelift", feature = "singlepass", feature = "llvm")))] +compile_error!( + "Either enable at least one compiler, or compile the wasmer-headless binary instead" +); + +#[cfg(featue = "run")] +compile_error!("Cannot enable run with the compile-only build"); + +fn main() { + wasmer_main(); +} diff --git a/lib/cli-compiler/src/cli.rs b/lib/cli-compiler/src/cli.rs new file mode 100644 index 00000000000..177edea1c93 --- /dev/null +++ b/lib/cli-compiler/src/cli.rs @@ -0,0 +1,75 @@ +//! The logic for the Wasmer CLI tool. + +use crate::commands::Compile; +use crate::commands::{Config, Validate}; +use crate::error::PrettyError; +use anyhow::Result; + +use structopt::{clap::ErrorKind, StructOpt}; + +#[derive(StructOpt)] +#[structopt( + name = "wasmer-compiler", + about = "WebAssembly standalone Compiler.", + author +)] +/// The options for the wasmer Command Line Interface +enum WasmerCLIOptions { + /// Validate a WebAssembly binary + #[structopt(name = "validate")] + Validate(Validate), + + /// Compile a WebAssembly binary + #[structopt(name = "compile")] + Compile(Compile), + + /// Get various configuration information needed + /// to compile programs which use Wasmer + #[structopt(name = "config")] + Config(Config), +} + +impl WasmerCLIOptions { + fn execute(&self) -> Result<()> { + match self { + Self::Validate(validate) => validate.execute(), + Self::Compile(compile) => compile.execute(), + Self::Config(config) => config.execute(), + } + } +} + +/// The main function for the Wasmer CLI tool. +pub fn wasmer_main() { + // We allow windows to print properly colors + #[cfg(windows)] + colored::control::set_virtual_terminal(true).unwrap(); + + // We try to run wasmer with the normal arguments. + // Eg. `wasmer ` + // In case that fails, we fallback trying the Run subcommand directly. + // Eg. `wasmer myfile.wasm --dir=.` + // + // In case we've been run as wasmer-binfmt-interpreter myfile.wasm args, + // we assume that we're registered via binfmt_misc + let args = std::env::args().collect::>(); + let command = args.get(1); + let options = { + match command.unwrap_or(&"".to_string()).as_ref() { + "compile" | "config" | "help" | "inspect" | "validate" => WasmerCLIOptions::from_args(), + _ => { + WasmerCLIOptions::from_iter_safe(args.iter()).unwrap_or_else(|e| { + match e.kind { + // This fixes a issue that: + // 1. Shows the version twice when doing `wasmer -V` + // 2. Shows the run help (instead of normal help) when doing `wasmer --help` + ErrorKind::VersionDisplayed | ErrorKind::HelpDisplayed => e.exit(), + _ => WasmerCLIOptions::Compile(Compile::from_args()), + } + }) + } + } + }; + + PrettyError::report(options.execute()); +} diff --git a/lib/cli-compiler/src/commands.rs b/lib/cli-compiler/src/commands.rs new file mode 100644 index 00000000000..74418834fa4 --- /dev/null +++ b/lib/cli-compiler/src/commands.rs @@ -0,0 +1,7 @@ +//! The commands available in the Wasmer binary. +mod compile; +mod config; +mod validate; + +pub use compile::*; +pub use {config::*, validate::*}; diff --git a/lib/cli-compiler/src/commands/compile.rs b/lib/cli-compiler/src/commands/compile.rs new file mode 100644 index 00000000000..b6570b51a25 --- /dev/null +++ b/lib/cli-compiler/src/commands/compile.rs @@ -0,0 +1,124 @@ +use crate::store::{EngineType, StoreOptions}; +use crate::warning; +use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; +use wasmer_compiler::{CompileError, CpuFeature, ModuleEnvironment, Target, Triple}; +use wasmer_engine_universal_artifact::{ArtifactCreate, UniversalArtifactBuild}; +use wasmer_types::entity::PrimaryMap; +use wasmer_types::{MemoryIndex, MemoryStyle, TableIndex, TableStyle}; + +#[derive(Debug, StructOpt)] +/// The options for the `wasmer compile` subcommand +pub struct Compile { + /// Input file + #[structopt(name = "FILE", parse(from_os_str))] + path: PathBuf, + + /// Output file + #[structopt(name = "OUTPUT PATH", short = "o", parse(from_os_str))] + output: PathBuf, + + /// Compilation Target triple + #[structopt(long = "target")] + target_triple: Option, + + #[structopt(flatten)] + store: StoreOptions, + + #[structopt(short = "m", multiple = true, number_of_values = 1)] + cpu_features: Vec, +} + +impl Compile { + /// Runs logic for the `compile` subcommand + pub fn execute(&self) -> Result<()> { + self.inner_execute() + .context(format!("failed to compile `{}`", self.path.display())) + } + + pub(crate) fn get_recommend_extension( + engine_type: &EngineType, + target_triple: &Triple, + ) -> Result<&'static str> { + Ok(match engine_type { + EngineType::Universal => { + wasmer_engine_universal_artifact::UniversalArtifactBuild::get_default_extension( + target_triple, + ) + } + }) + } + + fn inner_execute(&self) -> Result<()> { + let target = self + .target_triple + .as_ref() + .map(|target_triple| { + let mut features = self + .cpu_features + .clone() + .into_iter() + .fold(CpuFeature::set(), |a, b| a | b); + // Cranelift requires SSE2, so we have this "hack" for now to facilitate + // usage + features |= CpuFeature::SSE2; + Target::new(target_triple.clone(), features) + }) + .unwrap_or_default(); + let (mut engine, engine_type, compiler_type) = + self.store.get_engine_for_target(target.clone())?; + let output_filename = self + .output + .file_stem() + .map(|osstr| osstr.to_string_lossy().to_string()) + .unwrap_or_default(); + let recommended_extension = Self::get_recommend_extension(&engine_type, target.triple())?; + match self.output.extension() { + Some(ext) => { + if ext != recommended_extension { + warning!("the output file has a wrong extension. We recommend using `{}.{}` for the chosen target", &output_filename, &recommended_extension) + } + } + None => { + warning!("the output file has no extension. We recommend using `{}.{}` for the chosen target", &output_filename, &recommended_extension) + } + } + let tunables = self.store.get_tunables_for_target(&target)?; + + println!("Engine: {}", engine_type.to_string()); + println!("Compiler: {}", compiler_type.to_string()); + println!("Target: {}", target.triple()); + + // compile and save the artifact (without using module from api) + let path: &Path = self.path.as_ref(); + let wasm_bytes = std::fs::read(path)?; + let environ = ModuleEnvironment::new(); + let translation = environ.translate(&wasm_bytes).map_err(CompileError::Wasm)?; + let module = translation.module; + let memory_styles: PrimaryMap = module + .memories + .values() + .map(|memory_type| tunables.memory_style(memory_type)) + .collect(); + let table_styles: PrimaryMap = module + .tables + .values() + .map(|table_type| tunables.table_style(table_type)) + .collect(); + let artifact = UniversalArtifactBuild::new( + &mut engine, + &wasm_bytes, + &target, + memory_styles, + table_styles, + )?; + artifact.serialize_to_file(self.output.as_ref())?; + eprintln!( + "✔ File compiled successfully to `{}`.", + self.output.display(), + ); + + Ok(()) + } +} diff --git a/lib/cli-compiler/src/commands/config.rs b/lib/cli-compiler/src/commands/config.rs new file mode 100644 index 00000000000..0ded2bf4540 --- /dev/null +++ b/lib/cli-compiler/src/commands/config.rs @@ -0,0 +1,102 @@ +use crate::VERSION; +use anyhow::{Context, Result}; +use std::env; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +/// The options for the `wasmer config` subcommand +pub struct Config { + /// Print the installation prefix. + #[structopt(long, conflicts_with = "pkg-config")] + prefix: bool, + + /// Directory containing Wasmer executables. + #[structopt(long, conflicts_with = "pkg-config")] + bindir: bool, + + /// Directory containing Wasmer headers. + #[structopt(long, conflicts_with = "pkg-config")] + includedir: bool, + + /// Directory containing Wasmer libraries. + #[structopt(long, conflicts_with = "pkg-config")] + libdir: bool, + + /// Libraries needed to link against Wasmer components. + #[structopt(long, conflicts_with = "pkg-config")] + libs: bool, + + /// C compiler flags for files that include Wasmer headers. + #[structopt(long, conflicts_with = "pkg-config")] + cflags: bool, + + /// It outputs the necessary details for compiling + /// and linking a program to Wasmer, using the `pkg-config` format. + #[structopt(long)] + pkg_config: bool, +} + +impl Config { + /// Runs logic for the `config` subcommand + pub fn execute(&self) -> Result<()> { + self.inner_execute() + .context("failed to retrieve the wasmer config".to_string()) + } + fn inner_execute(&self) -> Result<()> { + let key = "WASMER_DIR"; + let wasmer_dir = env::var(key) + .or_else(|e| { + option_env!("WASMER_INSTALL_PREFIX") + .map(str::to_string) + .ok_or(e) + }) + .context(format!( + "failed to retrieve the {} environment variables", + key + ))?; + + let prefix = PathBuf::from(wasmer_dir); + + let prefixdir = prefix.display().to_string(); + let bindir = prefix.join("bin").display().to_string(); + let includedir = prefix.join("include").display().to_string(); + let libdir = prefix.join("lib").display().to_string(); + let cflags = format!("-I{}", includedir); + let libs = format!("-L{} -lwasmer", libdir); + + if self.pkg_config { + println!("prefix={}", prefixdir); + println!("exec_prefix={}", bindir); + println!("includedir={}", includedir); + println!("libdir={}", libdir); + println!(); + println!("Name: wasmer"); + println!("Description: The Wasmer library for running WebAssembly"); + println!("Version: {}", VERSION); + println!("Cflags: {}", cflags); + println!("Libs: {}", libs); + return Ok(()); + } + + if self.prefix { + println!("{}", prefixdir); + } + if self.bindir { + println!("{}", bindir); + } + if self.includedir { + println!("{}", includedir); + } + if self.libdir { + println!("{}", libdir); + } + if self.libs { + println!("{}", libs); + } + if self.cflags { + println!("{}", cflags); + } + Ok(()) + } +} diff --git a/lib/cli-compiler/src/commands/validate.rs b/lib/cli-compiler/src/commands/validate.rs new file mode 100644 index 00000000000..497912f3ef6 --- /dev/null +++ b/lib/cli-compiler/src/commands/validate.rs @@ -0,0 +1,40 @@ +use crate::store::StoreOptions; +use anyhow::{bail, Context, Result}; +use std::path::PathBuf; +use std::str::FromStr; +use structopt::StructOpt; +use wasmer_compiler::{CpuFeature, Target, Triple}; +use wasmer_types::is_wasm; + +#[derive(Debug, StructOpt)] +/// The options for the `wasmer validate` subcommand +pub struct Validate { + /// File to validate as WebAssembly + #[structopt(name = "FILE", parse(from_os_str))] + path: PathBuf, + + #[structopt(flatten)] + store: StoreOptions, +} + +impl Validate { + /// Runs logic for the `validate` subcommand + pub fn execute(&self) -> Result<()> { + self.inner_execute() + .context(format!("failed to validate `{}`", self.path.display())) + } + fn inner_execute(&self) -> Result<()> { + let target = Target::new( + Triple::from_str("x86_64-linux-gnu").unwrap(), + CpuFeature::SSE2 | CpuFeature::AVX, + ); + let (engine, _engine_type, _compiler_type) = self.store.get_engine_for_target(target)?; + let module_contents = std::fs::read(&self.path)?; + if !is_wasm(&module_contents) { + bail!("`wasmer validate` only validates WebAssembly files"); + } + engine.validate(&module_contents)?; + eprintln!("Validation passed for `{}`.", self.path.display()); + Ok(()) + } +} diff --git a/lib/cli-compiler/src/common.rs b/lib/cli-compiler/src/common.rs new file mode 100644 index 00000000000..863e154103f --- /dev/null +++ b/lib/cli-compiler/src/common.rs @@ -0,0 +1,53 @@ +//! Common module with common used structures across different +//! commands. +use crate::VERSION; +use std::env; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(Debug, StructOpt, Clone, Default)] +/// The WebAssembly features that can be passed through the +/// Command Line args. +pub struct WasmFeatures { + /// Enable support for the SIMD proposal. + #[structopt(long = "enable-simd")] + pub simd: bool, + + /// Enable support for the threads proposal. + #[structopt(long = "enable-threads")] + pub threads: bool, + + /// Enable support for the reference types proposal. + #[structopt(long = "enable-reference-types")] + pub reference_types: bool, + + /// Enable support for the multi value proposal. + #[structopt(long = "enable-multi-value")] + pub multi_value: bool, + + /// Enable support for the bulk memory proposal. + #[structopt(long = "enable-bulk-memory")] + pub bulk_memory: bool, + + /// Enable support for all pre-standard proposals. + #[structopt(long = "enable-all")] + pub all: bool, +} + +/// Get the cache dir +pub fn get_cache_dir() -> PathBuf { + match env::var("WASMER_CACHE_DIR") { + Ok(dir) => { + let mut path = PathBuf::from(dir); + path.push(VERSION); + path + } + Err(_) => { + // We use a temporal directory for saving cache files + let mut temp_dir = env::temp_dir(); + temp_dir.push("wasmer"); + temp_dir.push(VERSION); + temp_dir + } + } +} diff --git a/lib/cli-compiler/src/error.rs b/lib/cli-compiler/src/error.rs new file mode 100644 index 00000000000..f23b50339c0 --- /dev/null +++ b/lib/cli-compiler/src/error.rs @@ -0,0 +1,114 @@ +//! Implements `PretyError` to print pretty errors in the CLI (when they happen) + +use anyhow::{Chain, Error}; +use colored::*; +use std::fmt::{self, Debug, Write}; + +/// A `PrettyError` for printing `anyhow::Error` nicely. +pub struct PrettyError { + error: Error, +} + +/// A macro that prints a warning with nice colors +#[macro_export] +macro_rules! warning { + ($($arg:tt)*) => ({ + use colored::*; + eprintln!("{}: {}", "warning".yellow().bold(), format!($($arg)*)); + }) +} + +impl PrettyError { + /// Process a `Result` printing any errors and exiting + /// the process after + pub fn report(result: Result) -> ! { + std::process::exit(match result { + Ok(_t) => 0, + Err(error) => { + eprintln!("{:?}", PrettyError { error }); + 1 + } + }); + } +} + +impl Debug for PrettyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let error = &self.error; + + if f.alternate() { + return Debug::fmt(&error, f); + } + + write!(f, "{}", format!("{}: {}", "error".red(), error).bold())?; + // write!(f, "{}", error)?; + + if let Some(cause) = error.source() { + // write!(f, "\n{}:", "caused by".bold().blue())?; + let chain = Chain::new(cause); + let (total_errors, _) = chain.size_hint(); + for (n, error) in chain.enumerate() { + writeln!(f)?; + let mut indented = Indented { + inner: f, + number: Some(n + 1), + is_last: n == total_errors - 1, + started: false, + }; + write!(indented, "{}", error)?; + } + } + Ok(()) + } +} + +struct Indented<'a, D> { + inner: &'a mut D, + number: Option, + started: bool, + is_last: bool, +} + +impl Write for Indented<'_, T> +where + T: Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + for (i, line) in s.split('\n').enumerate() { + if !self.started { + self.started = true; + match self.number { + Some(number) => { + if !self.is_last { + write!( + self.inner, + "{} {: >4} ", + "│".bold().blue(), + format!("{}:", number).dimmed() + )? + } else { + write!( + self.inner, + "{}{: >2}: ", + "╰─▶".bold().blue(), + format!("{}", number).bold().blue() + )? + } + } + None => self.inner.write_str(" ")?, + } + } else if i > 0 { + self.inner.write_char('\n')?; + if self.number.is_some() { + self.inner.write_str(" ")?; + } else { + self.inner.write_str(" ")?; + } + } + + self.inner.write_str(line)?; + } + + Ok(()) + } +} diff --git a/lib/cli-compiler/src/lib.rs b/lib/cli-compiler/src/lib.rs new file mode 100644 index 00000000000..17b1d4ca248 --- /dev/null +++ b/lib/cli-compiler/src/lib.rs @@ -0,0 +1,30 @@ +//! The Wasmer binary lib + +#![deny( + missing_docs, + dead_code, + nonstandard_style, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns, + unstable_features +)] +#![doc(html_favicon_url = "https://wasmer.io/images/icons/favicon-32x32.png")] +#![doc(html_logo_url = "https://github.com/wasmerio.png?size=200")] + +#[macro_use] +extern crate anyhow; + +pub mod commands; +pub mod common; +#[macro_use] +pub mod error; +pub mod cli; +#[cfg(feature = "debug")] +pub mod logging; +pub mod store; +pub mod utils; + +/// Version number of this crate. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/lib/cli-compiler/src/logging.rs b/lib/cli-compiler/src/logging.rs new file mode 100644 index 00000000000..d60a45fbf33 --- /dev/null +++ b/lib/cli-compiler/src/logging.rs @@ -0,0 +1,68 @@ +//! Logging functions for the debug feature. +use crate::utils::wasmer_should_print_color; +use anyhow::Result; +use fern::colors::{Color, ColoredLevelConfig}; +use std::time; + +/// The debug level +pub type DebugLevel = log::LevelFilter; + +/// Subroutine to instantiate the loggers +pub fn set_up_logging(verbose: u8) -> Result<(), String> { + let colors_line = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + .trace(Color::BrightBlack); + let should_color = wasmer_should_print_color(); + + let colors_level = colors_line.info(Color::Green); + let level = match verbose { + 1 => DebugLevel::Debug, + _ => DebugLevel::Trace, + }; + let dispatch = fern::Dispatch::new() + .level(level) + .chain({ + let base = if should_color { + fern::Dispatch::new().format(move |out, message, record| { + let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH).expect("Can't get time"); + out.finish(format_args!( + "{color_line}[{seconds}.{millis} {level} {target}{color_line}]{ansi_close} {message}", + color_line = format_args!( + "\x1B[{}m", + colors_line.get_color(&record.level()).to_fg_str() + ), + seconds = time.as_secs(), + millis = time.subsec_millis(), + level = colors_level.color(record.level()), + target = record.target(), + ansi_close = "\x1B[0m", + message = message, + )); + }) + } else { + // default formatter without color + fern::Dispatch::new().format(move |out, message, record| { + let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH).expect("Can't get time"); + out.finish(format_args!( + "[{seconds}.{millis} {level} {target}] {message}", + seconds = time.as_secs(), + millis = time.subsec_millis(), + level = record.level(), + target = record.target(), + message = message, + )); + }) + }; + + base + .filter(|metadata| { + metadata.target().starts_with("wasmer") + }) + .chain(std::io::stdout()) + }); + + dispatch.apply().map_err(|e| format!("{}", e))?; + + Ok(()) +} diff --git a/lib/cli-compiler/src/store.rs b/lib/cli-compiler/src/store.rs new file mode 100644 index 00000000000..a9d108dc9e7 --- /dev/null +++ b/lib/cli-compiler/src/store.rs @@ -0,0 +1,410 @@ +//! Common module with common used structures across different +//! commands. + +use crate::common::WasmFeatures; +use anyhow::Result; +use std::string::ToString; +#[allow(unused_imports)] +use std::sync::Arc; +use structopt::StructOpt; +use wasmer_compiler::{CompilerConfig, Features, PointerWidth, Target}; +use wasmer_engine_universal_artifact::UniversalEngineBuilder; +use wasmer_types::{MemoryStyle, MemoryType, Pages, TableStyle, TableType}; + +/// Minimul Subset of Tunable parameters for WebAssembly compilation. +#[derive(Clone)] +pub struct SubsetTunables { + /// For static heaps, the size in wasm pages of the heap protected by bounds checking. + pub static_memory_bound: Pages, + + /// The size in bytes of the offset guard for static heaps. + pub static_memory_offset_guard_size: u64, + + /// The size in bytes of the offset guard for dynamic heaps. + pub dynamic_memory_offset_guard_size: u64, +} + +impl SubsetTunables { + /// Get the `BaseTunables` for a specific Target + pub fn for_target(target: &Target) -> Self { + let triple = target.triple(); + let pointer_width: PointerWidth = triple.pointer_width().unwrap(); + let (static_memory_bound, static_memory_offset_guard_size): (Pages, u64) = + match pointer_width { + PointerWidth::U16 => (0x400.into(), 0x1000), + PointerWidth::U32 => (0x4000.into(), 0x1_0000), + // Static Memory Bound: + // Allocating 4 GiB of address space let us avoid the + // need for explicit bounds checks. + // Static Memory Guard size: + // Allocating 2 GiB of address space lets us translate wasm + // offsets into x86 offsets as aggressively as we can. + PointerWidth::U64 => (0x1_0000.into(), 0x8000_0000), + }; + + // Allocate a small guard to optimize common cases but without + // wasting too much memory. + // The Windows memory manager seems more laxed than the other ones + // And a guard of just 1 page may not be enough is some borderline cases + // So using 2 pages for guard on this platform + #[cfg(target_os = "windows")] + let dynamic_memory_offset_guard_size: u64 = 0x2_0000; + #[cfg(not(target_os = "windows"))] + let dynamic_memory_offset_guard_size: u64 = 0x1_0000; + + Self { + static_memory_bound, + static_memory_offset_guard_size, + dynamic_memory_offset_guard_size, + } + } + /// Get a `MemoryStyle` for the provided `MemoryType` + pub fn memory_style(&self, memory: &MemoryType) -> MemoryStyle { + // A heap with a maximum that doesn't exceed the static memory bound specified by the + // tunables make it static. + // + // If the module doesn't declare an explicit maximum treat it as 4GiB. + let maximum = memory.maximum.unwrap_or_else(Pages::max_value); + if maximum <= self.static_memory_bound { + MemoryStyle::Static { + // Bound can be larger than the maximum for performance reasons + bound: self.static_memory_bound, + offset_guard_size: self.static_memory_offset_guard_size, + } + } else { + MemoryStyle::Dynamic { + offset_guard_size: self.dynamic_memory_offset_guard_size, + } + } + } + + /// Get a [`TableStyle`] for the provided [`TableType`]. + pub fn table_style(&self, _table: &TableType) -> TableStyle { + TableStyle::CallerChecksSignature + } +} + +#[derive(Debug, Clone, StructOpt, Default)] +/// The compiler and engine options +pub struct StoreOptions { + #[structopt(flatten)] + compiler: CompilerOptions, +} + +#[derive(Debug, Clone, StructOpt, Default)] +/// The compiler options +pub struct CompilerOptions { + /// Use Singlepass compiler. + #[structopt(long, conflicts_with_all = &["cranelift", "llvm"])] + singlepass: bool, + + /// Use Cranelift compiler. + #[structopt(long, conflicts_with_all = &["singlepass", "llvm"])] + cranelift: bool, + + /// Use LLVM compiler. + #[structopt(long, conflicts_with_all = &["singlepass", "cranelift"])] + llvm: bool, + + /// Enable compiler internal verification. + #[structopt(long)] + enable_verifier: bool, + + /// LLVM debug directory, where IR and object files will be written to. + #[cfg(feature = "llvm")] + #[structopt(long, parse(from_os_str))] + llvm_debug_dir: Option, + + #[structopt(flatten)] + features: WasmFeatures, +} + +impl CompilerOptions { + fn get_compiler(&self) -> Result { + if self.cranelift { + Ok(CompilerType::Cranelift) + } else if self.llvm { + Ok(CompilerType::LLVM) + } else if self.singlepass { + Ok(CompilerType::Singlepass) + } else { + // Auto mode, we choose the best compiler for that platform + cfg_if::cfg_if! { + if #[cfg(all(feature = "cranelift", any(target_arch = "x86_64", target_arch = "aarch64")))] { + Ok(CompilerType::Cranelift) + } + else if #[cfg(all(feature = "singlepass", target_arch = "x86_64"))] { + Ok(CompilerType::Singlepass) + } + else if #[cfg(feature = "llvm")] { + Ok(CompilerType::LLVM) + } else { + bail!("There are no available compilers for your architecture"); + } + } + } + } + + /// Get the enaled Wasm features. + pub fn get_features(&self, mut features: Features) -> Result { + if self.features.threads || self.features.all { + features.threads(true); + } + if self.features.multi_value || self.features.all { + features.multi_value(true); + } + if self.features.simd || self.features.all { + features.simd(true); + } + if self.features.bulk_memory || self.features.all { + features.bulk_memory(true); + } + if self.features.reference_types || self.features.all { + features.reference_types(true); + } + Ok(features) + } + + fn get_engine_by_type( + &self, + target: Target, + compiler_config: Box, + engine_type: EngineType, + ) -> Result { + let features = self.get_features(compiler_config.default_features_for_target(&target))?; + let engine: UniversalEngineBuilder = match engine_type { + EngineType::Universal => { + UniversalEngineBuilder::new(Some(compiler_config.compiler()), features) + } + }; + + Ok(engine) + } + + /// Get the Compiler Config for the current options + #[allow(unused_variables)] + pub(crate) fn get_compiler_config(&self) -> Result<(Box, CompilerType)> { + let compiler = self.get_compiler()?; + let compiler_config: Box = match compiler { + CompilerType::Headless => bail!("The headless engine can't be chosen"), + #[cfg(feature = "singlepass")] + CompilerType::Singlepass => { + let mut config = wasmer_compiler_singlepass::Singlepass::new(); + if self.enable_verifier { + config.enable_verifier(); + } + Box::new(config) + } + #[cfg(feature = "cranelift")] + CompilerType::Cranelift => { + let mut config = wasmer_compiler_cranelift::Cranelift::new(); + if self.enable_verifier { + config.enable_verifier(); + } + Box::new(config) + } + #[cfg(feature = "llvm")] + CompilerType::LLVM => { + use std::fmt; + use std::fs::File; + use std::io::Write; + use wasmer_compiler_llvm::{ + CompiledKind, InkwellMemoryBuffer, InkwellModule, LLVMCallbacks, LLVM, + }; + use wasmer_types::entity::EntityRef; + let mut config = LLVM::new(); + struct Callbacks { + debug_dir: PathBuf, + } + impl Callbacks { + fn new(debug_dir: PathBuf) -> Result { + // Create the debug dir in case it doesn't exist + std::fs::create_dir_all(&debug_dir)?; + Ok(Self { debug_dir }) + } + } + // Converts a kind into a filename, that we will use to dump + // the contents of the IR object file to. + fn types_to_signature(types: &[Type]) -> String { + types + .iter() + .map(|ty| match ty { + Type::I32 => "i".to_string(), + Type::I64 => "I".to_string(), + Type::F32 => "f".to_string(), + Type::F64 => "F".to_string(), + Type::V128 => "v".to_string(), + Type::ExternRef => "e".to_string(), + Type::FuncRef => "r".to_string(), + }) + .collect::>() + .join("") + } + // Converts a kind into a filename, that we will use to dump + // the contents of the IR object file to. + fn function_kind_to_filename(kind: &CompiledKind) -> String { + match kind { + CompiledKind::Local(local_index) => { + format!("function_{}", local_index.index()) + } + CompiledKind::FunctionCallTrampoline(func_type) => format!( + "trampoline_call_{}_{}", + types_to_signature(&func_type.params()), + types_to_signature(&func_type.results()) + ), + CompiledKind::DynamicFunctionTrampoline(func_type) => format!( + "trampoline_dynamic_{}_{}", + types_to_signature(&func_type.params()), + types_to_signature(&func_type.results()) + ), + CompiledKind::Module => "module".into(), + } + } + impl LLVMCallbacks for Callbacks { + fn preopt_ir(&self, kind: &CompiledKind, module: &InkwellModule) { + let mut path = self.debug_dir.clone(); + path.push(format!("{}.preopt.ll", function_kind_to_filename(kind))); + module + .print_to_file(&path) + .expect("Error while dumping pre optimized LLVM IR"); + } + fn postopt_ir(&self, kind: &CompiledKind, module: &InkwellModule) { + let mut path = self.debug_dir.clone(); + path.push(format!("{}.postopt.ll", function_kind_to_filename(kind))); + module + .print_to_file(&path) + .expect("Error while dumping post optimized LLVM IR"); + } + fn obj_memory_buffer( + &self, + kind: &CompiledKind, + memory_buffer: &InkwellMemoryBuffer, + ) { + let mut path = self.debug_dir.clone(); + path.push(format!("{}.o", function_kind_to_filename(kind))); + let mem_buf_slice = memory_buffer.as_slice(); + let mut file = File::create(path) + .expect("Error while creating debug object file from LLVM IR"); + let mut pos = 0; + while pos < mem_buf_slice.len() { + pos += file.write(&mem_buf_slice[pos..]).unwrap(); + } + } + } + + impl fmt::Debug for Callbacks { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "LLVMCallbacks") + } + } + + if let Some(ref llvm_debug_dir) = self.llvm_debug_dir { + config.callbacks(Some(Arc::new(Callbacks::new(llvm_debug_dir.clone())?))); + } + if self.enable_verifier { + config.enable_verifier(); + } + Box::new(config) + } + #[cfg(not(all(feature = "singlepass", feature = "cranelift", feature = "llvm",)))] + compiler => { + bail!( + "The `{}` compiler is not included in this binary.", + compiler.to_string() + ) + } + }; + + #[allow(unreachable_code)] + Ok((compiler_config, compiler)) + } +} + +/// The compiler used for the store +#[derive(Debug, PartialEq, Eq)] +pub enum CompilerType { + /// Singlepass compiler + Singlepass, + /// Cranelift compiler + Cranelift, + /// LLVM compiler + LLVM, + /// Headless compiler + Headless, +} + +impl CompilerType { + /// Return all enabled compilers + pub fn enabled() -> Vec { + vec![ + #[cfg(feature = "singlepass")] + Self::Singlepass, + #[cfg(feature = "cranelift")] + Self::Cranelift, + #[cfg(feature = "llvm")] + Self::LLVM, + ] + } +} + +impl ToString for CompilerType { + fn to_string(&self) -> String { + match self { + Self::Singlepass => "singlepass".to_string(), + Self::Cranelift => "cranelift".to_string(), + Self::LLVM => "llvm".to_string(), + Self::Headless => "headless".to_string(), + } + } +} + +/// The engine used for the store +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum EngineType { + /// Universal Engine + Universal, +} + +impl ToString for EngineType { + fn to_string(&self) -> String { + match self { + Self::Universal => "universal".to_string(), + } + } +} + +impl StoreOptions { + /// Get a UniversalEngineBulder for the Target + pub fn get_engine_for_target( + &self, + target: Target, + ) -> Result<(UniversalEngineBuilder, EngineType, CompilerType)> { + let (compiler_config, compiler_type) = self.compiler.get_compiler_config()?; + let (engine, engine_type) = self.get_engine_with_compiler(target, compiler_config)?; + Ok((engine, engine_type, compiler_type)) + } + + /// Get default EngineType + pub fn get_engine(&self) -> Result { + Ok(EngineType::Universal) + } + + fn get_engine_with_compiler( + &self, + target: Target, + compiler_config: Box, + ) -> Result<(UniversalEngineBuilder, EngineType)> { + let engine_type = self.get_engine()?; + let engine = self + .compiler + .get_engine_by_type(target, compiler_config, engine_type)?; + + Ok((engine, engine_type)) + } + + /// Get (Subset)Tunables for the Target + pub fn get_tunables_for_target(&self, target: &Target) -> Result { + let tunables = SubsetTunables::for_target(target); + Ok(tunables) + } +} diff --git a/lib/cli-compiler/src/utils.rs b/lib/cli-compiler/src/utils.rs new file mode 100644 index 00000000000..a032f26f40b --- /dev/null +++ b/lib/cli-compiler/src/utils.rs @@ -0,0 +1,92 @@ +//! Utility functions for the WebAssembly module +use anyhow::{bail, Result}; +use std::env; +use std::path::PathBuf; + +/// Whether or not Wasmer should print with color +pub fn wasmer_should_print_color() -> bool { + env::var("WASMER_COLOR") + .ok() + .and_then(|inner| inner.parse::().ok()) + .unwrap_or_else(|| atty::is(atty::Stream::Stdout)) +} + +fn retrieve_alias_pathbuf(alias: &str, real_dir: &str) -> Result<(String, PathBuf)> { + let pb = PathBuf::from(&real_dir); + if let Ok(pb_metadata) = pb.metadata() { + if !pb_metadata.is_dir() { + bail!("\"{}\" exists, but it is not a directory", &real_dir); + } + } else { + bail!("Directory \"{}\" does not exist", &real_dir); + } + Ok((alias.to_string(), pb)) +} + +/// Parses a mapdir from a string +pub fn parse_mapdir(entry: &str) -> Result<(String, PathBuf)> { + // We try first splitting by `::` + if let [alias, real_dir] = entry.split("::").collect::>()[..] { + retrieve_alias_pathbuf(alias, real_dir) + } + // And then we try splitting by `:` (for compatibility with previous API) + else if let [alias, real_dir] = entry.split(':').collect::>()[..] { + retrieve_alias_pathbuf(alias, real_dir) + } else { + bail!( + "Directory mappings must consist of two paths separate by a `::` or `:`. Found {}", + &entry + ) + } +} + +/// Parses an environment variable. +pub fn parse_envvar(entry: &str) -> Result<(String, String)> { + let entry = entry.trim(); + + match entry.find('=') { + None => bail!( + "Environment variable must be of the form `=`; found `{}`", + &entry + ), + + Some(0) => bail!( + "Environment variable is not well formed, the `name` is missing in `=`; got `{}`", + &entry + ), + + Some(position) if position == entry.len() - 1 => bail!( + "Environment variable is not well formed, the `value` is missing in `=`; got `{}`", + &entry + ), + + Some(position) => Ok((entry[..position].into(), entry[position + 1..].into())), + } +} + +#[cfg(test)] +mod tests { + use super::parse_envvar; + + #[test] + fn test_parse_envvar() { + assert_eq!( + parse_envvar("A").unwrap_err().to_string(), + "Environment variable must be of the form `=`; found `A`" + ); + assert_eq!( + parse_envvar("=A").unwrap_err().to_string(), + "Environment variable is not well formed, the `name` is missing in `=`; got `=A`" + ); + assert_eq!( + parse_envvar("A=").unwrap_err().to_string(), + "Environment variable is not well formed, the `value` is missing in `=`; got `A=`" + ); + assert_eq!(parse_envvar("A=B").unwrap(), ("A".into(), "B".into())); + assert_eq!(parse_envvar(" A=B\t").unwrap(), ("A".into(), "B".into())); + assert_eq!( + parse_envvar("A=B=C=D").unwrap(), + ("A".into(), "B=C=D".into()) + ); + } +} diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index a0b5cbdae5b..2303da10dd6 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -119,4 +119,4 @@ headless-minimal = ["headless", "disable-all-logging", "wasi", "dylib", "univers # Deprecated features. jit = ["universal"] -native = ["dylib"] +native = ["dylib"] \ No newline at end of file diff --git a/lib/compiler-cranelift/src/translator/translation_utils.rs b/lib/compiler-cranelift/src/translator/translation_utils.rs index a1b825a07c0..a0277c8b866 100644 --- a/lib/compiler-cranelift/src/translator/translation_utils.rs +++ b/lib/compiler-cranelift/src/translator/translation_utils.rs @@ -11,8 +11,7 @@ use wasmer_compiler::wasm_unsupported; use wasmer_compiler::wasmparser; use wasmer_compiler::RelocationKind; use wasmer_compiler::{WasmError, WasmResult}; -use wasmer_types::{FunctionType, Type}; -use wasmer_vm::libcalls::LibCall; +use wasmer_types::{FunctionType, LibCall, Type}; /// Helper function translate a Function signature into Cranelift Ir pub fn signature_to_cranelift_ir( diff --git a/lib/compiler-singlepass/Cargo.toml b/lib/compiler-singlepass/Cargo.toml index 8227ec4c963..1fbfc0c2f24 100644 --- a/lib/compiler-singlepass/Cargo.toml +++ b/lib/compiler-singlepass/Cargo.toml @@ -13,9 +13,7 @@ edition = "2018" [dependencies] wasmer-compiler = { path = "../compiler", version = "=2.2.1", features = ["translator"], default-features = false } -wasmer-vm = { path = "../vm", version = "=2.2.1" } wasmer-types = { path = "../types", version = "=2.2.1", default-features = false, features = ["std"] } -rayon = { version = "1.5", optional = true } hashbrown = { version = "0.11", optional = true } gimli = { version = "0.26", optional = true } more-asserts = "0.2" @@ -26,6 +24,9 @@ byteorder = "1.3" smallvec = "1.6" loupe = "0.1" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +rayon = { version = "1.5", optional = true } + [dev-dependencies] target-lexicon = { version = "0.12.2", default-features = false } @@ -34,6 +35,7 @@ maintenance = { status = "actively-developed" } [features] default = ["std", "rayon", "unwind", "avx"] +wasm = ["std", "unwind", "avx"] std = ["wasmer-compiler/std", "wasmer-types/std"] core = ["hashbrown", "wasmer-types/core"] unwind = ["gimli"] diff --git a/lib/compiler-singlepass/src/codegen.rs b/lib/compiler-singlepass/src/codegen.rs index 93debeb2a06..580eaec52b3 100644 --- a/lib/compiler-singlepass/src/codegen.rs +++ b/lib/compiler-singlepass/src/codegen.rs @@ -19,13 +19,10 @@ use wasmer_compiler::{ }; use wasmer_types::{ entity::{EntityRef, PrimaryMap}, - FunctionType, + FunctionIndex, FunctionType, GlobalIndex, LocalFunctionIndex, LocalMemoryIndex, MemoryIndex, + MemoryStyle, ModuleInfo, SignatureIndex, TableIndex, TableStyle, TrapCode, Type, + VMBuiltinFunctionIndex, VMOffsets, }; -use wasmer_types::{ - FunctionIndex, GlobalIndex, LocalFunctionIndex, LocalMemoryIndex, MemoryIndex, ModuleInfo, - SignatureIndex, TableIndex, Type, -}; -use wasmer_vm::{MemoryStyle, TableStyle, TrapCode, VMBuiltinFunctionIndex, VMOffsets}; /// The singlepass per-function code generator. pub struct FuncGen<'a, M: Machine> { diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 958a6b3fe7c..776ccc943ca 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -29,9 +29,9 @@ use wasmer_compiler::{ }; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{ - FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, ModuleInfo, TableIndex, + FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, ModuleInfo, TableIndex, TrapCode, + VMOffsets, }; -use wasmer_vm::{TrapCode, VMOffsets}; /// A compiler that compiles a WebAssembly module with Singlepass. /// It does the compilation in one pass @@ -315,7 +315,7 @@ mod tests { use std::str::FromStr; use target_lexicon::triple; use wasmer_compiler::{CpuFeature, Features, Triple}; - use wasmer_vm::{MemoryStyle, TableStyle}; + use wasmer_types::{MemoryStyle, TableStyle}; fn dummy_compilation_ingredients<'a>() -> ( CompileModuleInfo, diff --git a/lib/compiler-singlepass/src/emitter_arm64.rs b/lib/compiler-singlepass/src/emitter_arm64.rs index 33588d7d148..5a7b0c74da6 100644 --- a/lib/compiler-singlepass/src/emitter_arm64.rs +++ b/lib/compiler-singlepass/src/emitter_arm64.rs @@ -12,8 +12,7 @@ use dynasmrt::{ use wasmer_compiler::{ CallingConvention, CustomSection, CustomSectionProtection, FunctionBody, SectionBody, }; -use wasmer_types::{FunctionIndex, FunctionType, Type}; -use wasmer_vm::VMOffsets; +use wasmer_types::{FunctionIndex, FunctionType, Type, VMOffsets}; type Assembler = VecAssembler; diff --git a/lib/compiler-singlepass/src/machine.rs b/lib/compiler-singlepass/src/machine.rs index 753640b209c..b55ff38efd3 100644 --- a/lib/compiler-singlepass/src/machine.rs +++ b/lib/compiler-singlepass/src/machine.rs @@ -12,8 +12,7 @@ use wasmer_compiler::{ Architecture, CallingConvention, CpuFeature, CustomSection, FunctionBody, InstructionAddressMap, Relocation, RelocationTarget, Target, TrapInformation, }; -use wasmer_types::{FunctionIndex, FunctionType}; -use wasmer_vm::{TrapCode, VMOffsets}; +use wasmer_types::{FunctionIndex, FunctionType, TrapCode, VMOffsets}; pub type Label = DynamicLabel; pub type Offset = AssemblyOffset; diff --git a/lib/compiler-singlepass/src/machine_arm64.rs b/lib/compiler-singlepass/src/machine_arm64.rs index 90c27323952..3e98b36b235 100644 --- a/lib/compiler-singlepass/src/machine_arm64.rs +++ b/lib/compiler-singlepass/src/machine_arm64.rs @@ -14,8 +14,7 @@ use wasmer_compiler::{ CallingConvention, CustomSection, FunctionBody, InstructionAddressMap, Relocation, RelocationKind, RelocationTarget, SourceLoc, TrapInformation, }; -use wasmer_types::{FunctionIndex, FunctionType}; -use wasmer_vm::{TrapCode, VMOffsets}; +use wasmer_types::{FunctionIndex, FunctionType, TrapCode, VMOffsets}; type Assembler = VecAssembler; type Location = AbstractLocation; diff --git a/lib/compiler-singlepass/src/machine_x64.rs b/lib/compiler-singlepass/src/machine_x64.rs index 36be802904a..c1eb953bae1 100644 --- a/lib/compiler-singlepass/src/machine_x64.rs +++ b/lib/compiler-singlepass/src/machine_x64.rs @@ -18,8 +18,7 @@ use wasmer_compiler::{ InstructionAddressMap, Relocation, RelocationKind, RelocationTarget, SectionBody, SourceLoc, TrapInformation, }; -use wasmer_types::{FunctionIndex, FunctionType, Type}; -use wasmer_vm::{TrapCode, VMOffsets}; +use wasmer_types::{FunctionIndex, FunctionType, TrapCode, Type, VMOffsets}; type Assembler = VecAssembler; diff --git a/lib/compiler/Cargo.toml b/lib/compiler/Cargo.toml index 3579b58fe0e..ba1ddbc40e5 100644 --- a/lib/compiler/Cargo.toml +++ b/lib/compiler/Cargo.toml @@ -11,7 +11,6 @@ readme = "README.md" edition = "2018" [dependencies] -wasmer-vm = { path = "../vm", version = "=2.2.1" } wasmer-types = { path = "../types", version = "=2.2.1", default-features = false } wasmparser = { version = "0.83", optional = true, default-features = false } target-lexicon = { version = "0.12.2", default-features = false } @@ -33,7 +32,7 @@ translator = ["wasmparser"] std = ["wasmer-types/std"] core = ["hashbrown", "wasmer-types/core"] enable-serde = ["serde", "serde_bytes", "wasmer-types/enable-serde"] -enable-rkyv = ["rkyv", "wasmer-vm/enable-rkyv", "wasmer-types/enable-rkyv"] +enable-rkyv = ["rkyv", "wasmer-types/enable-rkyv"] [badges] maintenance = { status = "experimental" } diff --git a/lib/compiler/src/module.rs b/lib/compiler/src/module.rs index 6e9c5ce279c..73b88775125 100644 --- a/lib/compiler/src/module.rs +++ b/lib/compiler/src/module.rs @@ -5,8 +5,7 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use wasmer_types::entity::PrimaryMap; -use wasmer_types::{Features, MemoryIndex, ModuleInfo, TableIndex}; -use wasmer_vm::{MemoryStyle, TableStyle}; +use wasmer_types::{Features, MemoryIndex, MemoryStyle, ModuleInfo, TableIndex, TableStyle}; /// The required info for compiling a module. /// diff --git a/lib/compiler/src/relocation.rs b/lib/compiler/src/relocation.rs index 63971fffe91..3246f09c5da 100644 --- a/lib/compiler/src/relocation.rs +++ b/lib/compiler/src/relocation.rs @@ -19,8 +19,7 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use wasmer_types::entity::PrimaryMap; -use wasmer_types::LocalFunctionIndex; -use wasmer_vm::libcalls::LibCall; +use wasmer_types::{LibCall, LocalFunctionIndex}; /// Relocation kinds for every ISA. #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] diff --git a/lib/compiler/src/trap.rs b/lib/compiler/src/trap.rs index 2d016c13122..d4119110df1 100644 --- a/lib/compiler/src/trap.rs +++ b/lib/compiler/src/trap.rs @@ -4,7 +4,7 @@ use loupe::MemoryUsage; use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; -use wasmer_vm::TrapCode; +use wasmer_types::TrapCode; /// Information about trap. #[cfg_attr(feature = "enable-serde", derive(Deserialize, Serialize))] diff --git a/lib/engine-dylib/Cargo.toml b/lib/engine-dylib/Cargo.toml index da1219406eb..826c5d72271 100644 --- a/lib/engine-dylib/Cargo.toml +++ b/lib/engine-dylib/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" edition = "2018" [dependencies] +wasmer-artifact = { path = "../artifact", version = "=2.2.1" } wasmer-types = { path = "../types", version = "=2.2.1" } wasmer-compiler = { path = "../compiler", version = "=2.2.1" } wasmer-vm = { path = "../vm", version = "=2.2.1", features = ["enable-rkyv"] } diff --git a/lib/engine-dylib/src/artifact.rs b/lib/engine-dylib/src/artifact.rs index 45ae04191ac..7ea5b848586 100644 --- a/lib/engine-dylib/src/artifact.rs +++ b/lib/engine-dylib/src/artifact.rs @@ -19,6 +19,7 @@ use tempfile::NamedTempFile; use tracing::log::error; #[cfg(feature = "compiler")] use tracing::trace; +use wasmer_artifact::ArtifactCreate; use wasmer_compiler::{ Architecture, CompileError, CompiledFunctionFrameInfo, CpuFeature, Features, FunctionAddressMap, OperatingSystem, Symbol, SymbolRegistry, Triple, @@ -678,7 +679,7 @@ impl DylibArtifact { } } -impl Artifact for DylibArtifact { +impl ArtifactCreate for DylibArtifact { fn module(&self) -> Arc { self.metadata.compile_info.module.clone() } @@ -691,6 +692,70 @@ impl Artifact for DylibArtifact { Arc::get_mut(&mut self.metadata.compile_info.module) } + fn features(&self) -> &Features { + &self.metadata.compile_info.features + } + + fn cpu_features(&self) -> enumset::EnumSet { + EnumSet::from_u64(self.metadata.cpu_features) + } + + fn data_initializers(&self) -> &[OwnedDataInitializer] { + &*self.metadata.data_initializers + } + + fn memory_styles(&self) -> &PrimaryMap { + &self.metadata.compile_info.memory_styles + } + + fn table_styles(&self) -> &PrimaryMap { + &self.metadata.compile_info.table_styles + } + + /// Serialize a `DylibArtifact`. + fn serialize(&self) -> Result, SerializeError> { + Ok(std::fs::read(&self.dylib_path)?) + } + + /// Serialize a `DylibArtifact` to a portable file + #[cfg(feature = "compiler")] + fn serialize_to_file(&self, path: &Path) -> Result<(), SerializeError> { + let serialized = self.serialize()?; + std::fs::write(&path, serialized)?; + + /* + When you write the artifact to a new file it still has the 'Mach-O Identifier' + of the original file, and so this can causes linker issues when adding + the new file to an XCode project. + + The below code renames the ID of the file so that it references itself through + an @executable_path prefix. Basically it tells XCode to find this file + inside of the projects' list of 'linked executables'. + + You need to be running MacOS for the following to actually work though. + */ + let has_extension = path.extension().is_some(); + if has_extension && path.extension().unwrap() == "dylib" { + let filename = path.file_name().unwrap().to_str().unwrap(); + let parent_dir = path.parent().unwrap(); + let absolute_path = std::fs::canonicalize(&parent_dir) + .unwrap() + .into_os_string() + .into_string() + .unwrap(); + + Command::new("install_name_tool") + .arg("-id") + .arg(format!("@executable_path/{}", &filename)) + .arg(&filename) + .current_dir(&absolute_path) + .output()?; + } + + Ok(()) + } +} +impl Artifact for DylibArtifact { fn register_frame_info(&self) { let mut info = self.frame_info_registration.lock().unwrap(); @@ -821,26 +886,6 @@ impl Artifact for DylibArtifact { ); } - fn features(&self) -> &Features { - &self.metadata.compile_info.features - } - - fn cpu_features(&self) -> enumset::EnumSet { - EnumSet::from_u64(self.metadata.cpu_features) - } - - fn data_initializers(&self) -> &[OwnedDataInitializer] { - &*self.metadata.data_initializers - } - - fn memory_styles(&self) -> &PrimaryMap { - &self.metadata.compile_info.memory_styles - } - - fn table_styles(&self) -> &PrimaryMap { - &self.metadata.compile_info.table_styles - } - fn finished_functions(&self) -> &BoxedSlice { &self.finished_functions } @@ -864,47 +909,4 @@ impl Artifact for DylibArtifact { fn preinstantiate(&self) -> Result<(), InstantiationError> { Ok(()) } - - /// Serialize a `DylibArtifact`. - fn serialize(&self) -> Result, SerializeError> { - Ok(std::fs::read(&self.dylib_path)?) - } - - /// Serialize a `DylibArtifact` to a portable file - #[cfg(feature = "compiler")] - fn serialize_to_file(&self, path: &Path) -> Result<(), SerializeError> { - let serialized = self.serialize()?; - std::fs::write(&path, serialized)?; - - /* - When you write the artifact to a new file it still has the 'Mach-O Identifier' - of the original file, and so this can causes linker issues when adding - the new file to an XCode project. - - The below code renames the ID of the file so that it references itself through - an @executable_path prefix. Basically it tells XCode to find this file - inside of the projects' list of 'linked executables'. - - You need to be running MacOS for the following to actually work though. - */ - let has_extension = path.extension().is_some(); - if has_extension && path.extension().unwrap() == "dylib" { - let filename = path.file_name().unwrap().to_str().unwrap(); - let parent_dir = path.parent().unwrap(); - let absolute_path = std::fs::canonicalize(&parent_dir) - .unwrap() - .into_os_string() - .into_string() - .unwrap(); - - Command::new("install_name_tool") - .arg("-id") - .arg(format!("@executable_path/{}", &filename)) - .arg(&filename) - .current_dir(&absolute_path) - .output()?; - } - - Ok(()) - } } diff --git a/lib/engine-dylib/src/trampoline.rs b/lib/engine-dylib/src/trampoline.rs index 12d60e2b6c3..3254d270bbf 100644 --- a/lib/engine-dylib/src/trampoline.rs +++ b/lib/engine-dylib/src/trampoline.rs @@ -17,7 +17,7 @@ use object::{ BinaryFormat, RelocationEncoding, RelocationKind, SymbolFlags, SymbolKind, SymbolScope, }; use wasmer_compiler::{Architecture, Target}; -use wasmer_vm::libcalls::LibCall; +use wasmer_vm::libcalls::{function_pointer, LibCall}; /// Symbol exported from the dynamic library which points to the trampoline table. pub const WASMER_TRAMPOLINES_SYMBOL: &[u8] = b"WASMER_TRAMPOLINES"; @@ -161,6 +161,6 @@ pub fn emit_trampolines(obj: &mut Object, target: &Target) { /// Fills in the libcall trampoline table at the given address. pub unsafe fn fill_trampoline_table(table: *mut usize) { for libcall in LibCall::into_enum_iter() { - *table.add(libcall as usize) = libcall.function_pointer(); + *table.add(libcall as usize) = function_pointer(libcall); } } diff --git a/lib/engine-staticlib/Cargo.toml b/lib/engine-staticlib/Cargo.toml index 531e143fd40..b9c4182aca4 100644 --- a/lib/engine-staticlib/Cargo.toml +++ b/lib/engine-staticlib/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" edition = "2018" [dependencies] +wasmer-artifact = { path = "../artifact", version = "=2.2.1" } wasmer-types = { path = "../types", version = "=2.2.1" } wasmer-compiler = { path = "../compiler", version = "=2.2.1" } wasmer-vm = { path = "../vm", version = "=2.2.1" } diff --git a/lib/engine-staticlib/src/artifact.rs b/lib/engine-staticlib/src/artifact.rs index 400a4c91693..c27e711fb8d 100644 --- a/lib/engine-staticlib/src/artifact.rs +++ b/lib/engine-staticlib/src/artifact.rs @@ -9,6 +9,7 @@ use std::collections::BTreeMap; use std::error::Error; use std::mem; use std::sync::Arc; +use wasmer_artifact::ArtifactCreate; use wasmer_compiler::{ CompileError, CpuFeature, Features, OperatingSystem, SymbolRegistry, Triple, }; @@ -436,7 +437,7 @@ impl StaticlibArtifact { } } -impl Artifact for StaticlibArtifact { +impl ArtifactCreate for StaticlibArtifact { fn module(&self) -> Arc { self.metadata.compile_info.module.clone() } @@ -449,10 +450,6 @@ impl Artifact for StaticlibArtifact { Arc::get_mut(&mut self.metadata.compile_info.module) } - fn register_frame_info(&self) { - // Do nothing for now - } - fn features(&self) -> &Features { &self.metadata.compile_info.features } @@ -473,6 +470,16 @@ impl Artifact for StaticlibArtifact { &self.metadata.compile_info.table_styles } + /// Serialize a StaticlibArtifact + fn serialize(&self) -> Result, SerializeError> { + Ok(self.module_bytes.clone()) + } +} +impl Artifact for StaticlibArtifact { + fn register_frame_info(&self) { + // Do nothing for now + } + fn finished_functions(&self) -> &BoxedSlice { &self.finished_functions } @@ -492,7 +499,6 @@ impl Artifact for StaticlibArtifact { fn func_data_registry(&self) -> &FuncDataRegistry { &self.func_data_registry } - fn preinstantiate(&self) -> Result<(), InstantiationError> { if self.is_compiled { panic!( @@ -502,9 +508,4 @@ impl Artifact for StaticlibArtifact { } Ok(()) } - - /// Serialize a StaticlibArtifact - fn serialize(&self) -> Result, SerializeError> { - Ok(self.module_bytes.clone()) - } } diff --git a/lib/engine-universal/Cargo.toml b/lib/engine-universal/Cargo.toml index fbf106dc1b7..ea151ca946e 100644 --- a/lib/engine-universal/Cargo.toml +++ b/lib/engine-universal/Cargo.toml @@ -11,6 +11,9 @@ readme = "README.md" edition = "2018" [dependencies] +wasmer-engine-universal-artifact = { path = "../universal-artifact", version = "=2.2.1", features = [ + "compiler", +] } wasmer-types = { path = "../types", version = "=2.2.1", features = [ "enable-rkyv", ] } @@ -21,13 +24,14 @@ wasmer-compiler = { path = "../compiler", version = "=2.2.1", features = [ wasmer-vm = { path = "../vm", version = "=2.2.1", features = ["enable-rkyv"] } wasmer-engine = { path = "../engine", version = "=2.2.1" } # flexbuffers = { path = "../../../flatbuffers/rust/flexbuffers", version = "0.1.0" } -region = "3.0" cfg-if = "1.0" leb128 = "0.2" rkyv = "0.7.20" loupe = "0.1" enumset = "1.0" -enum-iterator = "0.7.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +region = { version = "3.0" } [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["winnt", "impl-default"] } diff --git a/lib/engine-universal/src/artifact.rs b/lib/engine-universal/src/artifact.rs index 672eb1a76e7..e8abc5e0bdb 100644 --- a/lib/engine-universal/src/artifact.rs +++ b/lib/engine-universal/src/artifact.rs @@ -1,25 +1,22 @@ -//! Define `UniversalArtifact` to allow compiling and instantiating to be -//! done as separate steps. +//! Define `UniversalArtifact`, based on `UniversalArtifactBuild` +//! to allow compiling and instantiating to be done as separate steps. use crate::engine::{UniversalEngine, UniversalEngineInner}; use crate::link::link_module; -#[cfg(feature = "compiler")] -use crate::serialize::SerializableCompilation; -use crate::serialize::SerializableModule; -use crate::trampoline::{libcall_trampoline_len, make_libcall_trampolines}; use enumset::EnumSet; use loupe::MemoryUsage; -use std::mem; use std::sync::{Arc, Mutex}; -use wasmer_compiler::{CompileError, CpuFeature, Features, Triple}; #[cfg(feature = "compiler")] -use wasmer_compiler::{CompileModuleInfo, ModuleEnvironment, ModuleMiddlewareChain}; +use wasmer_compiler::ModuleEnvironment; +use wasmer_compiler::{CompileError, CpuFeature, Features, Triple}; use wasmer_engine::{ register_frame_info, Artifact, DeserializeError, FunctionExtent, GlobalFrameInfoRegistration, MetadataHeader, SerializeError, }; #[cfg(feature = "compiler")] use wasmer_engine::{Engine, Tunables}; +use wasmer_engine_universal_artifact::ArtifactCreate; +use wasmer_engine_universal_artifact::{SerializableModule, UniversalArtifactBuild}; use wasmer_types::entity::{BoxedSlice, PrimaryMap}; use wasmer_types::{ FunctionIndex, LocalFunctionIndex, MemoryIndex, ModuleInfo, OwnedDataInitializer, @@ -33,7 +30,7 @@ use wasmer_vm::{ /// A compiled wasm module, ready to be instantiated. #[derive(MemoryUsage)] pub struct UniversalArtifact { - serializable: SerializableModule, + artifact: UniversalArtifactBuild, finished_functions: BoxedSlice, #[loupe(skip)] finished_function_call_trampolines: BoxedSlice, @@ -45,14 +42,7 @@ pub struct UniversalArtifact { } impl UniversalArtifact { - const MAGIC_HEADER: &'static [u8; 16] = b"wasmer-universal"; - - /// Check if the provided bytes look like a serialized `UniversalArtifact`. - pub fn is_deserializable(bytes: &[u8]) -> bool { - bytes.starts_with(Self::MAGIC_HEADER) - } - - /// Compile a data buffer into a `UniversalArtifact`, which may then be instantiated. + /// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated. #[cfg(feature = "compiler")] pub fn new( engine: &UniversalEngine, @@ -61,17 +51,8 @@ impl UniversalArtifact { ) -> Result { let environ = ModuleEnvironment::new(); let mut inner_engine = engine.inner_mut(); - let features = inner_engine.features(); - let translation = environ.translate(data).map_err(CompileError::Wasm)?; - - let compiler = inner_engine.compiler()?; - - // We try to apply the middleware first - let mut module = translation.module; - let middlewares = compiler.get_middlewares(); - middlewares.apply_on_module_info(&mut module); - + let module = translation.module; let memory_styles: PrimaryMap = module .memories .values() @@ -83,65 +64,18 @@ impl UniversalArtifact { .map(|table_type| tunables.table_style(table_type)) .collect(); - let compile_info = CompileModuleInfo { - module: Arc::new(module), - features: features.clone(), + let artifact = UniversalArtifactBuild::new( + inner_engine.builder_mut(), + data, + engine.target(), memory_styles, table_styles, - }; - - // Compile the Module - let compilation = compiler.compile_module( - &engine.target(), - &compile_info, - // SAFETY: Calling `unwrap` is correct since - // `environ.translate()` above will write some data into - // `module_translation_state`. - translation.module_translation_state.as_ref().unwrap(), - translation.function_body_inputs, )?; - let function_call_trampolines = compilation.get_function_call_trampolines(); - let dynamic_function_trampolines = compilation.get_dynamic_function_trampolines(); - - let data_initializers = translation - .data_initializers - .iter() - .map(OwnedDataInitializer::new) - .collect::>() - .into_boxed_slice(); - let frame_infos = compilation.get_frame_info(); - - // Synthesize a custom section to hold the libcall trampolines. - let mut custom_sections = compilation.get_custom_sections(); - let mut custom_section_relocations = compilation.get_custom_section_relocations(); - let libcall_trampolines_section = make_libcall_trampolines(engine.target()); - custom_section_relocations.push(libcall_trampolines_section.relocations.clone()); - let libcall_trampolines = custom_sections.push(libcall_trampolines_section); - let libcall_trampoline_len = libcall_trampoline_len(engine.target()) as u32; - - let serializable_compilation = SerializableCompilation { - function_bodies: compilation.get_function_bodies(), - function_relocations: compilation.get_relocations(), - function_frame_info: frame_infos, - function_call_trampolines, - dynamic_function_trampolines, - custom_sections, - custom_section_relocations, - debug: compilation.get_debug(), - libcall_trampolines, - libcall_trampoline_len, - }; - let serializable = SerializableModule { - compilation: serializable_compilation, - compile_info, - data_initializers, - cpu_features: engine.target().cpu_features().as_u64(), - }; - Self::from_parts(&mut inner_engine, serializable) + Self::from_parts(&mut inner_engine, artifact) } - /// Compile a data buffer into a `UniversalArtifact`, which may then be instantiated. + /// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated. #[cfg(not(feature = "compiler"))] pub fn new(_engine: &UniversalEngine, _data: &[u8]) -> Result { Err(CompileError::Codegen( @@ -149,72 +83,71 @@ impl UniversalArtifact { )) } - /// Deserialize a UniversalArtifact + /// Deserialize a UniversalArtifactBuild /// /// # Safety /// This function is unsafe because rkyv reads directly without validating /// the data. pub unsafe fn deserialize( - universal: &UniversalEngine, + engine: &UniversalEngine, bytes: &[u8], ) -> Result { - if !Self::is_deserializable(bytes) { + if !UniversalArtifactBuild::is_deserializable(bytes) { return Err(DeserializeError::Incompatible( "The provided bytes are not wasmer-universal".to_string(), )); } - let bytes = &bytes[Self::MAGIC_HEADER.len()..]; + let bytes = &bytes[UniversalArtifactBuild::MAGIC_HEADER.len()..]; let metadata_len = MetadataHeader::parse(bytes)?; let metadata_slice: &[u8] = &bytes[MetadataHeader::LEN..][..metadata_len]; let serializable = SerializableModule::deserialize(metadata_slice)?; - Self::from_parts(&mut universal.inner_mut(), serializable) - .map_err(DeserializeError::Compiler) + let artifact = UniversalArtifactBuild::from_serializable(serializable); + let mut inner_engine = engine.inner_mut(); + Self::from_parts(&mut inner_engine, artifact).map_err(DeserializeError::Compiler) } - /// Construct a `UniversalArtifact` from component parts. + /// Construct a `UniversalArtifactBuild` from component parts. pub fn from_parts( - inner_engine: &mut UniversalEngineInner, - serializable: SerializableModule, + engine_inner: &mut UniversalEngineInner, + artifact: UniversalArtifactBuild, ) -> Result { let ( finished_functions, finished_function_call_trampolines, finished_dynamic_function_trampolines, custom_sections, - ) = inner_engine.allocate( - &serializable.compile_info.module, - &serializable.compilation.function_bodies, - &serializable.compilation.function_call_trampolines, - &serializable.compilation.dynamic_function_trampolines, - &serializable.compilation.custom_sections, + ) = engine_inner.allocate( + artifact.module_ref(), + artifact.get_function_bodies_ref(), + artifact.get_function_call_trampolines_ref(), + artifact.get_dynamic_function_trampolines_ref(), + artifact.get_custom_sections_ref(), )?; link_module( - &serializable.compile_info.module, + artifact.module_ref(), &finished_functions, - serializable.compilation.function_relocations.clone(), + artifact.get_function_relocations().clone(), &custom_sections, - &serializable.compilation.custom_section_relocations, - serializable.compilation.libcall_trampolines, - serializable.compilation.libcall_trampoline_len as usize, + artifact.get_custom_section_relocations_ref(), + artifact.get_libcall_trampolines(), + artifact.get_libcall_trampoline_len(), ); // Compute indices into the shared signature table. let signatures = { - let signature_registry = inner_engine.signatures(); - serializable - .compile_info - .module + let signature_registry = engine_inner.signatures(); + artifact + .module() .signatures .values() .map(|sig| signature_registry.register(sig)) .collect::>() }; - let eh_frame = match &serializable.compilation.debug { + let eh_frame = match artifact.get_debug_ref() { Some(debug) => { - let eh_frame_section_size = serializable.compilation.custom_sections - [debug.eh_frame] + let eh_frame_section_size = artifact.get_custom_sections_ref()[debug.eh_frame] .bytes .len(); let eh_frame_section_pointer = custom_sections[debug.eh_frame]; @@ -226,9 +159,9 @@ impl UniversalArtifact { }; // Make all code compiled thus far executable. - inner_engine.publish_compiled_code(); + engine_inner.publish_compiled_code(); - inner_engine.publish_eh_frame(eh_frame)?; + engine_inner.publish_eh_frame(eh_frame)?; let finished_function_lengths = finished_functions .values() @@ -245,10 +178,10 @@ impl UniversalArtifact { let finished_dynamic_function_trampolines = finished_dynamic_function_trampolines.into_boxed_slice(); let signatures = signatures.into_boxed_slice(); - let func_data_registry = inner_engine.func_data().clone(); + let func_data_registry = engine_inner.func_data().clone(); Ok(Self { - serializable, + artifact, finished_functions, finished_function_call_trampolines, finished_dynamic_function_trampolines, @@ -258,28 +191,55 @@ impl UniversalArtifact { func_data_registry, }) } - /// Get the default extension when serializing this artifact - pub fn get_default_extension(_triple: &Triple) -> &'static str { - // `.wasmu` is the default extension for all the triples. It - // stands for “Wasm Universal”. - "wasmu" + pub fn get_default_extension(triple: &Triple) -> &'static str { + UniversalArtifactBuild::get_default_extension(triple) + } + /// Check if the provided bytes look like a serialized `UniversalArtifactBuild`. + pub fn is_deserializable(bytes: &[u8]) -> bool { + UniversalArtifactBuild::is_deserializable(bytes) } } -impl Artifact for UniversalArtifact { +impl ArtifactCreate for UniversalArtifact { fn module(&self) -> Arc { - self.serializable.compile_info.module.clone() + self.artifact.module() } fn module_ref(&self) -> &ModuleInfo { - &self.serializable.compile_info.module + self.artifact.module_ref() } fn module_mut(&mut self) -> Option<&mut ModuleInfo> { - Arc::get_mut(&mut self.serializable.compile_info.module) + self.artifact.module_mut() + } + + fn features(&self) -> &Features { + self.artifact.features() + } + + fn cpu_features(&self) -> EnumSet { + self.artifact.cpu_features() + } + + fn data_initializers(&self) -> &[OwnedDataInitializer] { + self.artifact.data_initializers() + } + + fn memory_styles(&self) -> &PrimaryMap { + self.artifact.memory_styles() + } + + fn table_styles(&self) -> &PrimaryMap { + self.artifact.table_styles() } + fn serialize(&self) -> Result, SerializeError> { + self.artifact.serialize() + } +} + +impl Artifact for UniversalArtifact { fn register_frame_info(&self) { let mut info = self.frame_info_registration.lock().unwrap(); @@ -296,34 +256,14 @@ impl Artifact for UniversalArtifact { .collect::>() .into_boxed_slice(); - let frame_infos = &self.serializable.compilation.function_frame_info; + let frame_infos = self.artifact.get_frame_info_ref(); *info = register_frame_info( - self.serializable.compile_info.module.clone(), + self.artifact.module(), &finished_function_extents, frame_infos.clone(), ); } - fn features(&self) -> &Features { - &self.serializable.compile_info.features - } - - fn cpu_features(&self) -> EnumSet { - EnumSet::from_u64(self.serializable.cpu_features) - } - - fn data_initializers(&self) -> &[OwnedDataInitializer] { - &*self.serializable.data_initializers - } - - fn memory_styles(&self) -> &PrimaryMap { - &self.serializable.compile_info.memory_styles - } - - fn table_styles(&self) -> &PrimaryMap { - &self.serializable.compile_info.table_styles - } - fn finished_functions(&self) -> &BoxedSlice { &self.finished_functions } @@ -343,14 +283,4 @@ impl Artifact for UniversalArtifact { fn func_data_registry(&self) -> &FuncDataRegistry { &self.func_data_registry } - fn serialize(&self) -> Result, SerializeError> { - let serialized_data = self.serializable.serialize()?; - assert!(mem::align_of::() <= MetadataHeader::ALIGN); - - let mut metadata_binary = vec![]; - metadata_binary.extend(Self::MAGIC_HEADER); - metadata_binary.extend(MetadataHeader::new(serialized_data.len())); - metadata_binary.extend(serialized_data); - Ok(metadata_binary) - } } diff --git a/lib/engine-universal/src/engine.rs b/lib/engine-universal/src/engine.rs index ecc34931212..72e5b18f22a 100644 --- a/lib/engine-universal/src/engine.rs +++ b/lib/engine-universal/src/engine.rs @@ -1,6 +1,7 @@ //! Universal compilation. -use crate::{CodeMemory, UniversalArtifact}; +use crate::CodeMemory; +use crate::UniversalArtifact; use loupe::MemoryUsage; use std::sync::{Arc, Mutex}; #[cfg(feature = "compiler")] @@ -9,6 +10,7 @@ use wasmer_compiler::{ CompileError, CustomSection, CustomSectionProtection, FunctionBody, SectionIndex, Target, }; use wasmer_engine::{Artifact, DeserializeError, Engine, EngineId, FunctionExtent, Tunables}; +use wasmer_engine_universal_artifact::UniversalEngineBuilder; use wasmer_types::entity::PrimaryMap; use wasmer_types::{ Features, FunctionIndex, FunctionType, LocalFunctionIndex, ModuleInfo, SignatureIndex, @@ -33,11 +35,10 @@ impl UniversalEngine { pub fn new(compiler: Box, target: Target, features: Features) -> Self { Self { inner: Arc::new(Mutex::new(UniversalEngineInner { - compiler: Some(compiler), + builder: UniversalEngineBuilder::new(Some(compiler), features), code_memory: vec![], signatures: SignatureRegistry::new(), func_data: Arc::new(FuncDataRegistry::new()), - features, })), target: Arc::new(target), engine_id: EngineId::default(), @@ -60,12 +61,10 @@ impl UniversalEngine { pub fn headless() -> Self { Self { inner: Arc::new(Mutex::new(UniversalEngineInner { - #[cfg(feature = "compiler")] - compiler: None, + builder: UniversalEngineBuilder::new(None, Features::default()), code_memory: vec![], signatures: SignatureRegistry::new(), func_data: Arc::new(FuncDataRegistry::new()), - features: Features::default(), })), target: Arc::new(Target::default()), engine_id: EngineId::default(), @@ -149,11 +148,8 @@ impl Engine for UniversalEngine { /// The inner contents of `UniversalEngine` #[derive(MemoryUsage)] pub struct UniversalEngineInner { - /// The compiler - #[cfg(feature = "compiler")] - compiler: Option>, - /// The features to compile the Wasm module with - features: Features, + /// The builder (include compiler and cpu features) + builder: UniversalEngineBuilder, /// The code memory is responsible of publishing the compiled /// functions to memory. code_memory: Vec, @@ -168,32 +164,22 @@ pub struct UniversalEngineInner { impl UniversalEngineInner { /// Gets the compiler associated to this engine. - #[cfg(feature = "compiler")] pub fn compiler(&self) -> Result<&dyn Compiler, CompileError> { - if self.compiler.is_none() { - return Err(CompileError::Codegen("The UniversalEngine is operating in headless mode, so it can only execute already compiled Modules.".to_string())); - } - Ok(&**self.compiler.as_ref().unwrap()) + self.builder.compiler() } /// Validate the module - #[cfg(feature = "compiler")] pub fn validate<'data>(&self, data: &'data [u8]) -> Result<(), CompileError> { - self.compiler()?.validate_module(self.features(), data) - } - - /// Validate the module - #[cfg(not(feature = "compiler"))] - pub fn validate<'data>(&self, _data: &'data [u8]) -> Result<(), CompileError> { - Err(CompileError::Validate( - "The UniversalEngine is not compiled with compiler support, which is required for validating" - .to_string(), - )) + self.builder.validate(data) } /// The Wasm features pub fn features(&self) -> &Features { - &self.features + self.builder.features() + } + + pub fn builder_mut(&mut self) -> &mut UniversalEngineBuilder { + &mut self.builder } /// Allocate compiled functions into memory diff --git a/lib/engine-universal/src/lib.rs b/lib/engine-universal/src/lib.rs index b552dae2a2d..68afde89054 100644 --- a/lib/engine-universal/src/lib.rs +++ b/lib/engine-universal/src/lib.rs @@ -28,8 +28,6 @@ mod builder; mod code_memory; mod engine; mod link; -mod serialize; -mod trampoline; mod unwind; pub use crate::artifact::UniversalArtifact; diff --git a/lib/engine-universal/src/link.rs b/lib/engine-universal/src/link.rs index f427bce57e1..757c8c1867e 100644 --- a/lib/engine-universal/src/link.rs +++ b/lib/engine-universal/src/link.rs @@ -1,11 +1,12 @@ //! Linking for Universal-compiled code. -use crate::trampoline::get_libcall_trampoline; use std::ptr::{read_unaligned, write_unaligned}; use wasmer_compiler::{Relocation, RelocationKind, RelocationTarget, Relocations, SectionIndex}; use wasmer_engine::FunctionExtent; +use wasmer_engine_universal_artifact::get_libcall_trampoline; use wasmer_types::entity::PrimaryMap; use wasmer_types::{LocalFunctionIndex, ModuleInfo}; +use wasmer_vm::libcalls::function_pointer; use wasmer_vm::SectionBodyPtr; fn apply_relocation( @@ -22,7 +23,7 @@ fn apply_relocation( // Use the direct target of the libcall if the relocation supports // a full 64-bit address. Otherwise use a trampoline. if r.kind == RelocationKind::Abs8 || r.kind == RelocationKind::X86PCRel8 { - libcall.function_pointer() + function_pointer(libcall) } else { get_libcall_trampoline( libcall, @@ -37,17 +38,14 @@ fn apply_relocation( }; match r.kind { - #[cfg(target_pointer_width = "64")] RelocationKind::Abs8 => unsafe { let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64); write_unaligned(reloc_address as *mut u64, reloc_delta); }, - #[cfg(target_pointer_width = "32")] RelocationKind::X86PCRel4 => unsafe { let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64); write_unaligned(reloc_address as *mut u32, reloc_delta as _); }, - #[cfg(target_pointer_width = "64")] RelocationKind::X86PCRel8 => unsafe { let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64); write_unaligned(reloc_address as *mut u64, reloc_delta); diff --git a/lib/engine/Cargo.toml b/lib/engine/Cargo.toml index 79fdc2b8ede..f2683758516 100644 --- a/lib/engine/Cargo.toml +++ b/lib/engine/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" [dependencies] wasmer-types = { path = "../types", version = "=2.2.1" } wasmer-compiler = { path = "../compiler", version = "=2.2.1" } -wasmer-vm = { path = "../vm", version = "=2.2.1" } +wasmer-artifact = { path = "../artifact", version = "=2.2.1" } target-lexicon = { version = "0.12.2", default-features = false } # flexbuffers = { path = "../../../flatbuffers/rust/flexbuffers", version = "0.1.0" } backtrace = "0.3" @@ -27,5 +27,8 @@ lazy_static = "1.4" loupe = "0.1" enumset = "1.0" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +wasmer-vm = { path = "../vm", version = "=2.2.1" } + [badges] maintenance = { status = "actively-developed" } diff --git a/lib/engine/src/artifact.rs b/lib/engine/src/artifact.rs index 9b29f43fb40..4754990d801 100644 --- a/lib/engine/src/artifact.rs +++ b/lib/engine/src/artifact.rs @@ -1,63 +1,31 @@ -use crate::{ - resolve_imports, DeserializeError, InstantiationError, Resolver, RuntimeError, SerializeError, - Tunables, -}; -use enumset::EnumSet; +use crate::{resolve_imports, InstantiationError, Resolver, RuntimeError, Tunables}; use loupe::MemoryUsage; use std::any::Any; -use std::convert::TryInto; -use std::path::Path; -use std::sync::Arc; -use std::{fs, mem}; -use wasmer_compiler::{CpuFeature, Features}; -use wasmer_types::entity::{BoxedSlice, PrimaryMap}; -use wasmer_types::{ - DataInitializer, FunctionIndex, LocalFunctionIndex, MemoryIndex, ModuleInfo, - OwnedDataInitializer, SignatureIndex, TableIndex, -}; +pub use wasmer_artifact::MetadataHeader; +use wasmer_artifact::{ArtifactCreate, Upcastable}; +use wasmer_compiler::CpuFeature; +use wasmer_types::entity::BoxedSlice; +use wasmer_types::{DataInitializer, FunctionIndex, LocalFunctionIndex, SignatureIndex}; use wasmer_vm::{ - FuncDataRegistry, FunctionBodyPtr, InstanceAllocator, InstanceHandle, MemoryStyle, TableStyle, - TrapHandler, VMSharedSignatureIndex, VMTrampoline, + FuncDataRegistry, FunctionBodyPtr, InstanceAllocator, InstanceHandle, TrapHandler, + VMSharedSignatureIndex, VMTrampoline, }; /// An `Artifact` is the product that the `Engine` /// implementation produce and use. /// -/// The `Artifact` contains the compiled data for a given -/// module as well as extra information needed to run the -/// module at runtime, such as [`ModuleInfo`] and [`Features`]. -pub trait Artifact: Send + Sync + Upcastable + MemoryUsage { - /// Return a reference-counted pointer to the module - fn module(&self) -> Arc; - - /// Return a pointer to a module. - fn module_ref(&self) -> &ModuleInfo; - - /// Gets a mutable reference to the info. - /// - /// Note: this will return `None` if the module is already instantiated. - fn module_mut(&mut self) -> Option<&mut ModuleInfo>; +/// An `Artifact` is the product that the `Engine` +/// implementation produce and use. +/// +/// The `ArtifactRun` contains the extra information needed to run the +/// module at runtime, such as [`ModuleInfo`] and [`Features`]. +pub trait Artifact: Send + Sync + Upcastable + MemoryUsage + ArtifactCreate { /// Register thie `Artifact` stack frame information into the global scope. /// /// This is required to ensure that any traps can be properly symbolicated. fn register_frame_info(&self); - /// Returns the features for this Artifact - fn features(&self) -> &Features; - - /// Returns the CPU features for this Artifact - fn cpu_features(&self) -> EnumSet; - - /// Returns the memory styles associated with this `Artifact`. - fn memory_styles(&self) -> &PrimaryMap; - - /// Returns the table plans associated with this `Artifact`. - fn table_styles(&self) -> &PrimaryMap; - - /// Returns data initializers to pass to `InstanceHandle::initialize` - fn data_initializers(&self) -> &[OwnedDataInitializer]; - /// Returns the functions allocated in memory or this `Artifact` /// ready to be run. fn finished_functions(&self) -> &BoxedSlice; @@ -76,21 +44,10 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage { /// Get the func data registry fn func_data_registry(&self) -> &FuncDataRegistry; - /// Serializes an artifact into bytes - fn serialize(&self) -> Result, SerializeError>; - - /// Serializes an artifact into a file path - fn serialize_to_file(&self, path: &Path) -> Result<(), SerializeError> { - let serialized = self.serialize()?; - fs::write(&path, serialized)?; - Ok(()) - } - /// Do preinstantiation logic that is executed before instantiating fn preinstantiate(&self) -> Result<(), InstantiationError> { Ok(()) } - /// Crate an `Instance` from this `Artifact`. /// /// # Safety @@ -168,7 +125,6 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage { .map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))?; Ok(handle) } - /// Finishes the instantiation of a just created `InstanceHandle`. /// /// # Safety @@ -193,29 +149,6 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage { } } -// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 . -/// Trait needed to get downcasting of `Engine`s to work. -pub trait Upcastable { - fn upcast_any_ref(&'_ self) -> &'_ dyn Any; - fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any; - fn upcast_any_box(self: Box) -> Box; -} - -impl Upcastable for T { - #[inline] - fn upcast_any_ref(&'_ self) -> &'_ dyn Any { - self - } - #[inline] - fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any { - self - } - #[inline] - fn upcast_any_box(self: Box) -> Box { - self - } -} - impl dyn Artifact + 'static { /// Try to downcast the artifact into a given type. #[inline] @@ -229,67 +162,3 @@ impl dyn Artifact + 'static { self.upcast_any_mut().downcast_mut::() } } - -/// Metadata header which holds an ABI version and the length of the remaining -/// metadata. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct MetadataHeader { - magic: [u8; 8], - version: u32, - len: u32, -} - -impl MetadataHeader { - /// Current ABI version. Increment this any time breaking changes are made - /// to the format of the serialized data. - const CURRENT_VERSION: u32 = 1; - - /// Magic number to identify wasmer metadata. - const MAGIC: [u8; 8] = *b"WASMER\0\0"; - - /// Length of the metadata header. - pub const LEN: usize = 16; - - /// Alignment of the metadata. - pub const ALIGN: usize = 16; - - /// Creates a new header for metadata of the given length. - pub fn new(len: usize) -> [u8; 16] { - let header = MetadataHeader { - magic: Self::MAGIC, - version: Self::CURRENT_VERSION, - len: len.try_into().expect("metadata exceeds maximum length"), - }; - unsafe { mem::transmute(header) } - } - - /// Parses the header and returns the length of the metadata following it. - pub fn parse(bytes: &[u8]) -> Result { - if bytes.as_ptr() as usize % 16 != 0 { - return Err(DeserializeError::CorruptedBinary( - "misaligned metadata".to_string(), - )); - } - let bytes: [u8; 16] = bytes - .get(..16) - .ok_or_else(|| { - DeserializeError::CorruptedBinary("invalid metadata header".to_string()) - })? - .try_into() - .unwrap(); - let header: MetadataHeader = unsafe { mem::transmute(bytes) }; - if header.magic != Self::MAGIC { - return Err(DeserializeError::Incompatible( - "The provided bytes were not serialized by Wasmer".to_string(), - )); - } - if header.version != Self::CURRENT_VERSION { - return Err(DeserializeError::Incompatible( - "The provided bytes were serialized by an incompatible version of Wasmer" - .to_string(), - )); - } - Ok(header.len as usize) - } -} diff --git a/lib/engine/src/error.rs b/lib/engine/src/error.rs index 5874e5d8bea..fc377849c01 100644 --- a/lib/engine/src/error.rs +++ b/lib/engine/src/error.rs @@ -1,60 +1,7 @@ //! The WebAssembly possible errors use crate::trap::RuntimeError; -use std::io; use thiserror::Error; -use wasmer_compiler::CompileError; -use wasmer_types::ExternType; - -/// The Serialize error can occur when serializing a -/// compiled Module into a binary. -#[derive(Error, Debug)] -pub enum SerializeError { - /// An IO error - #[error(transparent)] - Io(#[from] io::Error), - /// A generic serialization error - #[error("{0}")] - Generic(String), -} - -/// The Deserialize error can occur when loading a -/// compiled Module from a binary. -#[derive(Error, Debug)] -pub enum DeserializeError { - /// An IO error - #[error(transparent)] - Io(#[from] io::Error), - /// A generic deserialization error - #[error("{0}")] - Generic(String), - /// Incompatible serialized binary - #[error("incompatible binary: {0}")] - Incompatible(String), - /// The provided binary is corrupted - #[error("corrupted binary: {0}")] - CorruptedBinary(String), - /// The binary was valid, but we got an error when - /// trying to allocate the required resources. - #[error(transparent)] - Compiler(CompileError), -} - -/// An ImportError. -/// -/// Note: this error is not standard to WebAssembly, but it's -/// useful to determine the import issue on the API side. -#[derive(Error, Debug)] -pub enum ImportError { - /// Incompatible Import Type. - /// This error occurs when the import types mismatch. - #[error("incompatible import type. Expected {0:?} but received {1:?}")] - IncompatibleType(ExternType, ExternType), - - /// Unknown Import. - /// This error occurs when an import was expected but not provided. - #[error("unknown import. Expected {0:?}")] - UnknownImport(ExternType), -} +pub use wasmer_artifact::{DeserializeError, ImportError, SerializeError}; /// The WebAssembly.LinkError object indicates an error during /// module instantiation (besides traps from the start function). diff --git a/lib/engine/src/lib.rs b/lib/engine/src/lib.rs index f364077f50a..35737b8d0ef 100644 --- a/lib/engine/src/lib.rs +++ b/lib/engine/src/lib.rs @@ -31,11 +31,9 @@ mod resolver; mod trap; mod tunables; -pub use crate::artifact::{Artifact, MetadataHeader}; +pub use crate::artifact::Artifact; pub use crate::engine::{Engine, EngineId}; -pub use crate::error::{ - DeserializeError, ImportError, InstantiationError, LinkError, SerializeError, -}; +pub use crate::error::{InstantiationError, LinkError}; pub use crate::export::{Export, ExportFunction, ExportFunctionMetadata}; pub use crate::resolver::{ resolve_imports, ChainableNamedResolver, NamedResolver, NamedResolverChain, NullResolver, @@ -43,6 +41,8 @@ pub use crate::resolver::{ }; pub use crate::trap::*; pub use crate::tunables::Tunables; +pub use wasmer_artifact::{ArtifactCreate, MetadataHeader}; +pub use wasmer_artifact::{DeserializeError, ImportError, SerializeError}; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/lib/types/Cargo.toml b/lib/types/Cargo.toml index 3348db675a1..99d67ddc076 100644 --- a/lib/types/Cargo.toml +++ b/lib/types/Cargo.toml @@ -13,9 +13,12 @@ edition = "2018" [dependencies] serde = { version = "1.0", features = ["derive", "rc"], optional = true, default-features = false } thiserror = "1.0" +more-asserts = "0.2" indexmap = { version = "1.6", features = ["serde-1"] } rkyv = { version = "0.7.20", optional = true } loupe = { version = "0.1", features = ["enable-indexmap"] } +enum-iterator = "0.7.0" +backtrace = "0.3" [features] default = ["std", "enable-serde", "enable-rkyv"] diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 4337b902725..439dafe10d7 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -59,12 +59,18 @@ mod extern_ref; mod features; mod indexes; mod initializers; +mod libcalls; +mod memory; mod memory_view; mod module; mod native; +mod table; +mod trapcode; mod types; mod units; +mod utils; mod values; +mod vmoffsets; /// The entity module, with common helpers for Rust structures pub mod entity; @@ -93,5 +99,13 @@ pub use types::{ #[cfg(feature = "enable-rkyv")] pub use archives::ArchivableIndexMap; +pub use crate::libcalls::LibCall; +pub use crate::memory::MemoryStyle; +pub use crate::table::TableStyle; +pub use crate::trapcode::TrapCode; +pub use crate::vmoffsets::{TargetSharedSignatureIndex, VMBuiltinFunctionIndex, VMOffsets}; + +pub use crate::utils::is_wasm; + /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/lib/types/src/libcalls.rs b/lib/types/src/libcalls.rs new file mode 100644 index 00000000000..15fc8c9db80 --- /dev/null +++ b/lib/types/src/libcalls.rs @@ -0,0 +1,162 @@ +use enum_iterator::IntoEnumIterator; +use loupe::MemoryUsage; +#[cfg(feature = "enable-rkyv")] +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// The name of a runtime library routine. +/// +/// This list is likely to grow over time. +#[cfg_attr( + feature = "enable-rkyv", + derive(RkyvSerialize, RkyvDeserialize, Archive) +)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, MemoryUsage, IntoEnumIterator)] +pub enum LibCall { + /// ceil.f32 + CeilF32, + + /// ceil.f64 + CeilF64, + + /// floor.f32 + FloorF32, + + /// floor.f64 + FloorF64, + + /// nearest.f32 + NearestF32, + + /// nearest.f64 + NearestF64, + + /// trunc.f32 + TruncF32, + + /// trunc.f64 + TruncF64, + + /// memory.size for local functions + Memory32Size, + + /// memory.size for imported functions + ImportedMemory32Size, + + /// table.copy + TableCopy, + + /// table.init + TableInit, + + /// table.fill + TableFill, + + /// table.size for local tables + TableSize, + + /// table.size for imported tables + ImportedTableSize, + + /// table.get for local tables + TableGet, + + /// table.get for imported tables + ImportedTableGet, + + /// table.set for local tables + TableSet, + + /// table.set for imported tables + ImportedTableSet, + + /// table.grow for local tables + TableGrow, + + /// table.grow for imported tables + ImportedTableGrow, + + /// ref.func + FuncRef, + + /// elem.drop + ElemDrop, + + /// memory.copy for local memories + Memory32Copy, + + /// memory.copy for imported memories + ImportedMemory32Copy, + + /// memory.fill for local memories + Memory32Fill, + + /// memory.fill for imported memories + ImportedMemory32Fill, + + /// memory.init + Memory32Init, + + /// data.drop + DataDrop, + + /// A custom trap + RaiseTrap, + + /// probe for stack overflow. These are emitted for functions which need + /// when the `enable_probestack` setting is true. + Probestack, +} + +impl LibCall { + /// Return the function name associated to the libcall. + pub fn to_function_name(&self) -> &str { + match self { + Self::CeilF32 => "wasmer_vm_f32_ceil", + Self::CeilF64 => "wasmer_vm_f64_ceil", + Self::FloorF32 => "wasmer_vm_f32_floor", + Self::FloorF64 => "wasmer_vm_f64_floor", + Self::NearestF32 => "wasmer_vm_f32_nearest", + Self::NearestF64 => "wasmer_vm_f64_nearest", + Self::TruncF32 => "wasmer_vm_f32_trunc", + Self::TruncF64 => "wasmer_vm_f64_trunc", + Self::Memory32Size => "wasmer_vm_memory32_size", + Self::ImportedMemory32Size => "wasmer_vm_imported_memory32_size", + Self::TableCopy => "wasmer_vm_table_copy", + Self::TableInit => "wasmer_vm_table_init", + Self::TableFill => "wasmer_vm_table_fill", + Self::TableSize => "wasmer_vm_table_size", + Self::ImportedTableSize => "wasmer_vm_imported_table_size", + Self::TableGet => "wasmer_vm_table_get", + Self::ImportedTableGet => "wasmer_vm_imported_table_get", + Self::TableSet => "wasmer_vm_table_set", + Self::ImportedTableSet => "wasmer_vm_imported_table_set", + Self::TableGrow => "wasmer_vm_table_grow", + Self::ImportedTableGrow => "wasmer_vm_imported_table_grow", + Self::FuncRef => "wasmer_vm_func_ref", + Self::ElemDrop => "wasmer_vm_elem_drop", + Self::Memory32Copy => "wasmer_vm_memory32_copy", + Self::ImportedMemory32Copy => "wasmer_vm_imported_memory32_copy", + Self::Memory32Fill => "wasmer_vm_memory32_fill", + Self::ImportedMemory32Fill => "wasmer_vm_imported_memory32_fill", + Self::Memory32Init => "wasmer_vm_memory32_init", + Self::DataDrop => "wasmer_vm_data_drop", + Self::RaiseTrap => "wasmer_vm_raise_trap", + // We have to do this because macOS requires a leading `_` and it's not + // a normal function, it's a static variable, so we have to do it manually. + #[cfg(target_vendor = "apple")] + Self::Probestack => "_wasmer_vm_probestack", + #[cfg(not(target_vendor = "apple"))] + Self::Probestack => "wasmer_vm_probestack", + } + } +} + +impl fmt::Display for LibCall { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs new file mode 100644 index 00000000000..920abc06129 --- /dev/null +++ b/lib/types/src/memory.rs @@ -0,0 +1,46 @@ +use crate::Pages; +use loupe::MemoryUsage; +#[cfg(feature = "enable-rkyv")] +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; + +/// Implementation styles for WebAssembly linear memory. +#[derive(Debug, Clone, PartialEq, Eq, Hash, MemoryUsage)] +#[cfg_attr( + feature = "enable-rkyv", + derive(RkyvSerialize, RkyvDeserialize, Archive) +)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub enum MemoryStyle { + /// The actual memory can be resized and moved. + Dynamic { + /// Our chosen offset-guard size. + /// + /// It represents the size in bytes of extra guard pages after the end + /// to optimize loads and stores with constant offsets. + offset_guard_size: u64, + }, + /// Address space is allocated up front. + Static { + /// The number of mapped and unmapped pages. + bound: Pages, + /// Our chosen offset-guard size. + /// + /// It represents the size in bytes of extra guard pages after the end + /// to optimize loads and stores with constant offsets. + offset_guard_size: u64, + }, +} + +impl MemoryStyle { + /// Returns the offset-guard size + pub fn offset_guard_size(&self) -> u64 { + match self { + Self::Dynamic { offset_guard_size } => *offset_guard_size, + Self::Static { + offset_guard_size, .. + } => *offset_guard_size, + } + } +} diff --git a/lib/types/src/table.rs b/lib/types/src/table.rs new file mode 100644 index 00000000000..345649eed31 --- /dev/null +++ b/lib/types/src/table.rs @@ -0,0 +1,17 @@ +use loupe::MemoryUsage; +#[cfg(feature = "enable-rkyv")] +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; + +/// Implementation styles for WebAssembly tables. +#[derive(Debug, Clone, Hash, PartialEq, Eq, MemoryUsage)] +#[cfg_attr( + feature = "enable-rkyv", + derive(RkyvSerialize, RkyvDeserialize, Archive) +)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub enum TableStyle { + /// Signatures are stored in the table and checked in the caller. + CallerChecksSignature, +} diff --git a/lib/vm/src/trap/trapcode.rs b/lib/types/src/trapcode.rs similarity index 97% rename from lib/vm/src/trap/trapcode.rs rename to lib/types/src/trapcode.rs index 1fd02176401..648898fd6e5 100644 --- a/lib/vm/src/trap/trapcode.rs +++ b/lib/types/src/trapcode.rs @@ -8,17 +8,19 @@ use core::str::FromStr; use loupe::MemoryUsage; #[cfg(feature = "enable-rkyv")] use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +#[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use thiserror::Error; /// A trap code describing the reason for a trap. /// /// All trap instructions have an explicit trap code. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Error, MemoryUsage)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Error, MemoryUsage)] #[cfg_attr( feature = "enable-rkyv", derive(RkyvSerialize, RkyvDeserialize, Archive) )] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[repr(u32)] pub enum TrapCode { /// The current stack space was exhausted. diff --git a/lib/api/src/js/utils.rs b/lib/types/src/utils.rs similarity index 100% rename from lib/api/src/js/utils.rs rename to lib/types/src/utils.rs diff --git a/lib/vm/src/vmoffsets.rs b/lib/types/src/vmoffsets.rs similarity index 84% rename from lib/vm/src/vmoffsets.rs rename to lib/types/src/vmoffsets.rs index 2a1df9cf66f..211c4575358 100644 --- a/lib/vm/src/vmoffsets.rs +++ b/lib/types/src/vmoffsets.rs @@ -6,14 +6,134 @@ #![deny(broken_intra_doc_links)] -use crate::VMBuiltinFunctionIndex; -use loupe::MemoryUsage; -use more_asserts::assert_lt; -use std::convert::TryFrom; -use wasmer_types::{ +use crate::{ FunctionIndex, GlobalIndex, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, ModuleInfo, SignatureIndex, TableIndex, }; +use loupe::MemoryUsage; +use more_asserts::assert_lt; +use std::convert::TryFrom; + +/// An index type for builtin functions. +#[derive(Copy, Clone, Debug)] +pub struct VMBuiltinFunctionIndex(u32); + +impl VMBuiltinFunctionIndex { + /// Returns an index for wasm's `memory.grow` builtin function. + pub const fn get_memory32_grow_index() -> Self { + Self(0) + } + /// Returns an index for wasm's imported `memory.grow` builtin function. + pub const fn get_imported_memory32_grow_index() -> Self { + Self(1) + } + /// Returns an index for wasm's `memory.size` builtin function. + pub const fn get_memory32_size_index() -> Self { + Self(2) + } + /// Returns an index for wasm's imported `memory.size` builtin function. + pub const fn get_imported_memory32_size_index() -> Self { + Self(3) + } + /// Returns an index for wasm's `table.copy` when both tables are locally + /// defined. + pub const fn get_table_copy_index() -> Self { + Self(4) + } + /// Returns an index for wasm's `table.init`. + pub const fn get_table_init_index() -> Self { + Self(5) + } + /// Returns an index for wasm's `elem.drop`. + pub const fn get_elem_drop_index() -> Self { + Self(6) + } + /// Returns an index for wasm's `memory.copy` for locally defined memories. + pub const fn get_memory_copy_index() -> Self { + Self(7) + } + /// Returns an index for wasm's `memory.copy` for imported memories. + pub const fn get_imported_memory_copy_index() -> Self { + Self(8) + } + /// Returns an index for wasm's `memory.fill` for locally defined memories. + pub const fn get_memory_fill_index() -> Self { + Self(9) + } + /// Returns an index for wasm's `memory.fill` for imported memories. + pub const fn get_imported_memory_fill_index() -> Self { + Self(10) + } + /// Returns an index for wasm's `memory.init` instruction. + pub const fn get_memory_init_index() -> Self { + Self(11) + } + /// Returns an index for wasm's `data.drop` instruction. + pub const fn get_data_drop_index() -> Self { + Self(12) + } + /// Returns an index for wasm's `raise_trap` instruction. + pub const fn get_raise_trap_index() -> Self { + Self(13) + } + /// Returns an index for wasm's `table.size` instruction for local tables. + pub const fn get_table_size_index() -> Self { + Self(14) + } + /// Returns an index for wasm's `table.size` instruction for imported tables. + pub const fn get_imported_table_size_index() -> Self { + Self(15) + } + /// Returns an index for wasm's `table.grow` instruction for local tables. + pub const fn get_table_grow_index() -> Self { + Self(16) + } + /// Returns an index for wasm's `table.grow` instruction for imported tables. + pub const fn get_imported_table_grow_index() -> Self { + Self(17) + } + /// Returns an index for wasm's `table.get` instruction for local tables. + pub const fn get_table_get_index() -> Self { + Self(18) + } + /// Returns an index for wasm's `table.get` instruction for imported tables. + pub const fn get_imported_table_get_index() -> Self { + Self(19) + } + /// Returns an index for wasm's `table.set` instruction for local tables. + pub const fn get_table_set_index() -> Self { + Self(20) + } + /// Returns an index for wasm's `table.set` instruction for imported tables. + pub const fn get_imported_table_set_index() -> Self { + Self(21) + } + /// Returns an index for wasm's `func.ref` instruction. + pub const fn get_func_ref_index() -> Self { + Self(22) + } + /// Returns an index for wasm's `table.fill` instruction for local tables. + pub const fn get_table_fill_index() -> Self { + Self(23) + } + /// Returns an index for a function to increment the externref count. + pub const fn get_externref_inc_index() -> Self { + Self(24) + } + /// Returns an index for a function to decrement the externref count. + pub const fn get_externref_dec_index() -> Self { + Self(25) + } + /// Returns the total number of builtin functions. + pub const fn builtin_functions_total_number() -> u32 { + 26 + } + + /// Return the index as an u32 number. + pub const fn index(self) -> u32 { + self.0 + } +} #[cfg(target_pointer_width = "32")] fn cast_to_u32(sz: usize) -> u32 { diff --git a/lib/universal-artifact/Cargo.toml b/lib/universal-artifact/Cargo.toml new file mode 100644 index 00000000000..fe3ec1846e0 --- /dev/null +++ b/lib/universal-artifact/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "wasmer-engine-universal-artifact" +version = "2.2.1" +description = "Wasmer Engine Universal Artifact abstraction" +categories = ["wasm"] +keywords = ["wasm", "webassembly", "engine"] +authors = ["Wasmer Engineering Team "] +repository = "https://github.com/wasmerio/wasmer" +license = "MIT OR Apache-2.0 WITH LLVM-exception " +readme = "README.md" +edition = "2018" + +[dependencies] +wasmer-artifact = { path = "../artifact", version = "=2.2.1" } +wasmer-types = { path = "../types", version = "=2.2.1" } +wasmer-compiler = { path = "../compiler", version = "=2.2.1" } +loupe = "0.1" +thiserror = "1.0" +enumset = "1.0" +rkyv = "0.7.20" +enum-iterator = "0.7.0" + +[features] +compiler = [] + +[badges] +maintenance = { status = "actively-developed" } + diff --git a/lib/universal-artifact/README.md b/lib/universal-artifact/README.md new file mode 100644 index 00000000000..c02f1c11bc3 --- /dev/null +++ b/lib/universal-artifact/README.md @@ -0,0 +1,20 @@ +# `wasmer-engine-universal-artifact` [![Build Status](https://github.com/wasmerio/wasmer/workflows/build/badge.svg?style=flat-square)](https://github.com/wasmerio/wasmer/actions?query=workflow%3Abuild) [![Join Wasmer Slack](https://img.shields.io/static/v1?label=Slack&message=join%20chat&color=brighgreen&style=flat-square)](https://slack.wasmer.io) [![MIT License](https://img.shields.io/github/license/wasmerio/wasmer.svg?style=flat-square)](https://github.com/wasmerio/wasmer/blob/master/LICENSE) + + +This crate is the general abstraction for generating Artifacts in Wasmer. + +### Acknowledgments + +This project borrowed some of the code of the trap implementation from +the [`wasmtime-api`], the code since then has evolved significantly. + +Please check [Wasmer `ATTRIBUTIONS`] to further see licenses and other +attributions of the project. + + +[`wasmer-engine-universal`]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-universal +[`wasmer-engine-dylib`]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-dylib +[`wasmer-engine-staticlib`]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-staticlib +[`wasmer-engine-dummy`]: https://github.com/wasmerio/wasmer/tree/master/tests/lib/engine-dummy +[`wasmtime-api`]: https://crates.io/crates/wasmtime +[Wasmer `ATTRIBUTIONS`]: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md diff --git a/lib/universal-artifact/src/artifact.rs b/lib/universal-artifact/src/artifact.rs new file mode 100644 index 00000000000..a28e0836513 --- /dev/null +++ b/lib/universal-artifact/src/artifact.rs @@ -0,0 +1,233 @@ +//! Define `UniversalArtifactBuild` to allow compiling and instantiating to be +//! done as separate steps. + +use crate::serialize::SerializableCompilation; +use crate::serialize::SerializableModule; +#[cfg(feature = "compiler")] +use crate::trampoline::{libcall_trampoline_len, make_libcall_trampolines}; +use crate::{ArtifactCreate, UniversalEngineBuilder}; +use enumset::EnumSet; +use loupe::MemoryUsage; +use std::mem; +use std::sync::Arc; +use wasmer_artifact::{MetadataHeader, SerializeError}; +use wasmer_compiler::{ + CompileError, CompileModuleInfo, CompiledFunctionFrameInfo, CpuFeature, CustomSection, Dwarf, + Features, FunctionBody, ModuleEnvironment, ModuleMiddlewareChain, Relocation, SectionIndex, + Target, Triple, +}; +use wasmer_types::entity::PrimaryMap; +use wasmer_types::{ + FunctionIndex, LocalFunctionIndex, MemoryIndex, MemoryStyle, ModuleInfo, OwnedDataInitializer, + SignatureIndex, TableIndex, TableStyle, +}; + +/// A compiled wasm module, ready to be instantiated. +#[derive(MemoryUsage)] +pub struct UniversalArtifactBuild { + serializable: SerializableModule, +} + +impl UniversalArtifactBuild { + /// Header signature for wasmu binary + pub const MAGIC_HEADER: &'static [u8; 16] = b"wasmer-universal"; + + /// Check if the provided bytes look like a serialized `UniversalArtifactBuild`. + pub fn is_deserializable(bytes: &[u8]) -> bool { + bytes.starts_with(Self::MAGIC_HEADER) + } + + /// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated. + #[cfg(feature = "compiler")] + pub fn new( + inner_engine: &mut UniversalEngineBuilder, + data: &[u8], + target: &Target, + memory_styles: PrimaryMap, + table_styles: PrimaryMap, + ) -> Result { + let environ = ModuleEnvironment::new(); + let features = inner_engine.features(); + + let translation = environ.translate(data).map_err(CompileError::Wasm)?; + + let compiler = inner_engine.compiler()?; + + // We try to apply the middleware first + let mut module = translation.module; + let middlewares = compiler.get_middlewares(); + middlewares.apply_on_module_info(&mut module); + + let compile_info = CompileModuleInfo { + module: Arc::new(module), + features: features.clone(), + memory_styles, + table_styles, + }; + + // Compile the Module + let compilation = compiler.compile_module( + target, + &compile_info, + // SAFETY: Calling `unwrap` is correct since + // `environ.translate()` above will write some data into + // `module_translation_state`. + translation.module_translation_state.as_ref().unwrap(), + translation.function_body_inputs, + )?; + let function_call_trampolines = compilation.get_function_call_trampolines(); + let dynamic_function_trampolines = compilation.get_dynamic_function_trampolines(); + + let data_initializers = translation + .data_initializers + .iter() + .map(OwnedDataInitializer::new) + .collect::>() + .into_boxed_slice(); + + let frame_infos = compilation.get_frame_info(); + + // Synthesize a custom section to hold the libcall trampolines. + let mut custom_sections = compilation.get_custom_sections(); + let mut custom_section_relocations = compilation.get_custom_section_relocations(); + let libcall_trampolines_section = make_libcall_trampolines(target); + custom_section_relocations.push(libcall_trampolines_section.relocations.clone()); + let libcall_trampolines = custom_sections.push(libcall_trampolines_section); + let libcall_trampoline_len = libcall_trampoline_len(target) as u32; + + let serializable_compilation = SerializableCompilation { + function_bodies: compilation.get_function_bodies(), + function_relocations: compilation.get_relocations(), + function_frame_info: frame_infos, + function_call_trampolines, + dynamic_function_trampolines, + custom_sections, + custom_section_relocations, + debug: compilation.get_debug(), + libcall_trampolines, + libcall_trampoline_len, + }; + let serializable = SerializableModule { + compilation: serializable_compilation, + compile_info, + data_initializers, + cpu_features: target.cpu_features().as_u64(), + }; + Ok(Self { serializable }) + } + + /// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated. + #[cfg(not(feature = "compiler"))] + pub fn new(_engine: &UniversalEngineBuilder, _data: &[u8]) -> Result { + Err(CompileError::Codegen( + "Compilation is not enabled in the engine".to_string(), + )) + } + + /// Create a new UniversalArtifactBuild from a SerializableModule + pub fn from_serializable(serializable: SerializableModule) -> Self { + Self { serializable } + } + + /// Get the default extension when serializing this artifact + pub fn get_default_extension(_triple: &Triple) -> &'static str { + // `.wasmu` is the default extension for all the triples. It + // stands for “Wasm Universal”. + "wasmu" + } + + /// Get Functions Bodies ref + pub fn get_function_bodies_ref(&self) -> &PrimaryMap { + &self.serializable.compilation.function_bodies + } + + /// Get Functions Call Trampolines ref + pub fn get_function_call_trampolines_ref(&self) -> &PrimaryMap { + &self.serializable.compilation.function_call_trampolines + } + + /// Get Dynamic Functions Call Trampolines ref + pub fn get_dynamic_function_trampolines_ref(&self) -> &PrimaryMap { + &self.serializable.compilation.dynamic_function_trampolines + } + + /// Get Custom Sections ref + pub fn get_custom_sections_ref(&self) -> &PrimaryMap { + &self.serializable.compilation.custom_sections + } + + /// Get Function Relocations + pub fn get_function_relocations(&self) -> PrimaryMap> { + self.serializable.compilation.function_relocations.clone() + } + + /// Get Function Relocations ref + pub fn get_custom_section_relocations_ref(&self) -> &PrimaryMap> { + &self.serializable.compilation.custom_section_relocations + } + + /// Get LibCall Trampoline Section Index + pub fn get_libcall_trampolines(&self) -> SectionIndex { + self.serializable.compilation.libcall_trampolines + } + + /// Get LibCall Trampoline Length + pub fn get_libcall_trampoline_len(&self) -> usize { + self.serializable.compilation.libcall_trampoline_len as usize + } + + /// Get Debug optional Dwarf ref + pub fn get_debug_ref(&self) -> &Option { + &self.serializable.compilation.debug + } + + /// Get Function Relocations ref + pub fn get_frame_info_ref(&self) -> &PrimaryMap { + &self.serializable.compilation.function_frame_info + } +} + +impl ArtifactCreate for UniversalArtifactBuild { + fn module(&self) -> Arc { + self.serializable.compile_info.module.clone() + } + + fn module_ref(&self) -> &ModuleInfo { + &self.serializable.compile_info.module + } + + fn module_mut(&mut self) -> Option<&mut ModuleInfo> { + Arc::get_mut(&mut self.serializable.compile_info.module) + } + + fn features(&self) -> &Features { + &self.serializable.compile_info.features + } + + fn cpu_features(&self) -> EnumSet { + EnumSet::from_u64(self.serializable.cpu_features) + } + + fn data_initializers(&self) -> &[OwnedDataInitializer] { + &*self.serializable.data_initializers + } + + fn memory_styles(&self) -> &PrimaryMap { + &self.serializable.compile_info.memory_styles + } + + fn table_styles(&self) -> &PrimaryMap { + &self.serializable.compile_info.table_styles + } + + fn serialize(&self) -> Result, SerializeError> { + let serialized_data = self.serializable.serialize()?; + assert!(mem::align_of::() <= MetadataHeader::ALIGN); + + let mut metadata_binary = vec![]; + metadata_binary.extend(Self::MAGIC_HEADER); + metadata_binary.extend(MetadataHeader::new(serialized_data.len())); + metadata_binary.extend(serialized_data); + Ok(metadata_binary) + } +} diff --git a/lib/universal-artifact/src/engine.rs b/lib/universal-artifact/src/engine.rs new file mode 100644 index 00000000000..fe57b3156c8 --- /dev/null +++ b/lib/universal-artifact/src/engine.rs @@ -0,0 +1,63 @@ +//! Universal compilation. + +use loupe::MemoryUsage; +use wasmer_compiler::CompileError; +#[cfg(feature = "compiler")] +use wasmer_compiler::Compiler; +use wasmer_types::Features; + +/// The Builder contents of `UniversalEngine` +#[derive(MemoryUsage)] +pub struct UniversalEngineBuilder { + /// The compiler + #[cfg(feature = "compiler")] + compiler: Option>, + /// The features to compile the Wasm module with + features: Features, +} + +impl UniversalEngineBuilder { + /// Create a new builder with pre-made components + pub fn new(compiler: Option>, features: Features) -> Self { + UniversalEngineBuilder { compiler, features } + } + + /// Gets the compiler associated to this engine. + #[cfg(feature = "compiler")] + pub fn compiler(&self) -> Result<&dyn Compiler, CompileError> { + if self.compiler.is_none() { + return Err(CompileError::Codegen( + "The UniversalEngine is not compiled in.".to_string(), + )); + } + Ok(&**self.compiler.as_ref().unwrap()) + } + + /// Gets the compiler associated to this engine. + #[cfg(not(feature = "compiler"))] + pub fn compiler(&self) -> Result<&dyn Compiler, CompileError> { + return Err(CompileError::Codegen( + "The UniversalEngine is not compiled in.".to_string(), + )); + } + + /// Validate the module + #[cfg(feature = "compiler")] + pub fn validate<'data>(&self, data: &'data [u8]) -> Result<(), CompileError> { + self.compiler()?.validate_module(self.features(), data) + } + + /// Validate the module + #[cfg(not(feature = "compiler"))] + pub fn validate<'data>(&self, _data: &'data [u8]) -> Result<(), CompileError> { + Err(CompileError::Validate( + "The UniversalEngine is not compiled with compiler support, which is required for validating" + .to_string(), + )) + } + + /// The Wasm features + pub fn features(&self) -> &Features { + &self.features + } +} diff --git a/lib/universal-artifact/src/lib.rs b/lib/universal-artifact/src/lib.rs new file mode 100644 index 00000000000..952d6d3bd84 --- /dev/null +++ b/lib/universal-artifact/src/lib.rs @@ -0,0 +1,35 @@ +//! Generic Artifact abstraction for Wasmer Engines. + +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![warn(unused_import_braces)] +#![cfg_attr( + feature = "cargo-clippy", + allow(clippy::new_without_default, clippy::new_without_default) +)] +#![cfg_attr( + feature = "cargo-clippy", + warn( + clippy::float_arithmetic, + clippy::mut_mut, + clippy::nonminimal_bool, + clippy::option_map_unwrap_or, + clippy::option_map_unwrap_or_else, + clippy::print_stdout, + clippy::unicode_not_nfc, + clippy::use_self + ) +)] + +mod artifact; +mod engine; +mod serialize; +mod trampoline; + +pub use crate::artifact::UniversalArtifactBuild; +pub use crate::engine::UniversalEngineBuilder; +pub use crate::serialize::SerializableModule; +pub use crate::trampoline::*; +pub use wasmer_artifact::{ArtifactCreate, MetadataHeader, Upcastable}; + +/// Version number of this crate. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/lib/universal-artifact/src/serialize.rs b/lib/universal-artifact/src/serialize.rs new file mode 100644 index 00000000000..2840dd76e4c --- /dev/null +++ b/lib/universal-artifact/src/serialize.rs @@ -0,0 +1,110 @@ +use loupe::MemoryUsage; +use rkyv::{ + archived_value, de::deserializers::SharedDeserializeMap, ser::serializers::AllocSerializer, + ser::Serializer as RkyvSerializer, Archive, Deserialize as RkyvDeserialize, + Serialize as RkyvSerialize, +}; +use wasmer_artifact::{DeserializeError, SerializeError}; +use wasmer_compiler::{ + CompileModuleInfo, CompiledFunctionFrameInfo, CustomSection, Dwarf, FunctionBody, Relocation, + SectionIndex, +}; +use wasmer_types::entity::PrimaryMap; +use wasmer_types::{FunctionIndex, LocalFunctionIndex, OwnedDataInitializer, SignatureIndex}; + +/// The compilation related data for a serialized modules +#[derive(MemoryUsage, Archive, RkyvDeserialize, RkyvSerialize)] +pub struct SerializableCompilation { + pub function_bodies: PrimaryMap, + pub function_relocations: PrimaryMap>, + pub function_frame_info: PrimaryMap, + pub function_call_trampolines: PrimaryMap, + pub dynamic_function_trampolines: PrimaryMap, + pub custom_sections: PrimaryMap, + pub custom_section_relocations: PrimaryMap>, + // The section indices corresponding to the Dwarf debug info + pub debug: Option, + // Custom section containing libcall trampolines. + pub libcall_trampolines: SectionIndex, + // Length of each libcall trampoline. + pub libcall_trampoline_len: u32, +} + +/// Serializable struct that is able to serialize from and to +/// a `UniversalArtifactInfo`. +#[derive(MemoryUsage, Archive, RkyvDeserialize, RkyvSerialize)] +pub struct SerializableModule { + /// The main serializable compilation object + pub compilation: SerializableCompilation, + /// Compilation informations + pub compile_info: CompileModuleInfo, + /// Datas initializers + pub data_initializers: Box<[OwnedDataInitializer]>, + /// CPU Feature flags for this compilation + pub cpu_features: u64, +} + +fn to_serialize_error(err: impl std::error::Error) -> SerializeError { + SerializeError::Generic(format!("{}", err)) +} + +impl SerializableModule { + /// Serialize a Module into bytes + /// The bytes will have the following format: + /// RKYV serialization (any length) + POS (8 bytes) + pub fn serialize(&self) -> Result, SerializeError> { + let mut serializer = AllocSerializer::<4096>::default(); + let pos = serializer + .serialize_value(self) + .map_err(to_serialize_error)? as u64; + let mut serialized_data = serializer.into_serializer().into_inner(); + serialized_data.extend_from_slice(&pos.to_le_bytes()); + Ok(serialized_data.to_vec()) + } + + /// Deserialize a Module from a slice. + /// The slice must have the following format: + /// RKYV serialization (any length) + POS (8 bytes) + /// + /// # Safety + /// + /// This method is unsafe since it deserializes data directly + /// from memory. + /// Right now we are not doing any extra work for validation, but + /// `rkyv` has an option to do bytecheck on the serialized data before + /// serializing (via `rkyv::check_archived_value`). + pub unsafe fn deserialize(metadata_slice: &[u8]) -> Result { + let archived = Self::archive_from_slice(metadata_slice)?; + Self::deserialize_from_archive(archived) + } + + /// # Safety + /// + /// This method is unsafe. + /// Please check `SerializableModule::deserialize` for more details. + unsafe fn archive_from_slice<'a>( + metadata_slice: &'a [u8], + ) -> Result<&'a ArchivedSerializableModule, DeserializeError> { + if metadata_slice.len() < 8 { + return Err(DeserializeError::Incompatible( + "invalid serialized data".into(), + )); + } + let mut pos: [u8; 8] = Default::default(); + pos.copy_from_slice(&metadata_slice[metadata_slice.len() - 8..metadata_slice.len()]); + let pos: u64 = u64::from_le_bytes(pos); + Ok(archived_value::( + &metadata_slice[..metadata_slice.len() - 8], + pos as usize, + )) + } + + /// Deserialize a compilation module from an archive + pub fn deserialize_from_archive( + archived: &ArchivedSerializableModule, + ) -> Result { + let mut deserializer = SharedDeserializeMap::new(); + RkyvDeserialize::deserialize(archived, &mut deserializer) + .map_err(|e| DeserializeError::CorruptedBinary(format!("{:?}", e))) + } +} diff --git a/lib/engine-universal/src/trampoline.rs b/lib/universal-artifact/src/trampoline.rs similarity index 98% rename from lib/engine-universal/src/trampoline.rs rename to lib/universal-artifact/src/trampoline.rs index 7c4c62b9729..65932be48ae 100644 --- a/lib/engine-universal/src/trampoline.rs +++ b/lib/universal-artifact/src/trampoline.rs @@ -8,7 +8,7 @@ use wasmer_compiler::{ Architecture, CustomSection, CustomSectionProtection, Relocation, RelocationKind, RelocationTarget, SectionBody, Target, }; -use wasmer_vm::libcalls::LibCall; +use wasmer_types::LibCall; // SystemV says that both x16 and x17 are available as intra-procedural scratch // registers but Apple's ABI restricts us to use x17. diff --git a/lib/vm/Cargo.toml b/lib/vm/Cargo.toml index ed92a6b90ab..21359bbbd09 100644 --- a/lib/vm/Cargo.toml +++ b/lib/vm/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [dependencies] wasmer-types = { path = "../types", version = "=2.2.1" } -region = "3.0" +wasmer-artifact = { path = "../artifact", version = "=2.2.1" } libc = { version = "^0.2", default-features = false } memoffset = "0.6" indexmap = { version = "1.6", features = ["serde-1"] } @@ -24,9 +24,10 @@ serde = { version = "1.0", features = ["derive", "rc"] } rkyv = { version = "0.7.20", optional = true } loupe = { version = "0.1", features = ["enable-indexmap"] } enum-iterator = "0.7.0" -corosensei = { version = "0.1.2" } scopeguard = "1.1.0" lazy_static = "1.4.0" +region = { version = "3.0" } +corosensei = { version = "0.1.2" } [target.'cfg(target_vendor = "apple")'.dependencies] mach = "0.3.2" diff --git a/lib/vm/src/export.rs b/lib/vm/src/export.rs index f9567962109..473e8727892 100644 --- a/lib/vm/src/export.rs +++ b/lib/vm/src/export.rs @@ -3,12 +3,12 @@ use crate::global::Global; use crate::instance::WeakOrStrongInstanceRef; -use crate::memory::{Memory, MemoryStyle}; -use crate::table::{Table, TableStyle}; +use crate::memory::Memory; +use crate::table::Table; use crate::vmcontext::{VMFunctionBody, VMFunctionEnvironment, VMFunctionKind, VMTrampoline}; use loupe::MemoryUsage; use std::sync::Arc; -use wasmer_types::{FunctionType, MemoryType, TableType}; +use wasmer_types::{FunctionType, MemoryStyle, MemoryType, TableStyle, TableType}; /// The value of an export passed from one instance to another. #[derive(Debug)] diff --git a/lib/vm/src/lib.rs b/lib/vm/src/lib.rs index 1214b921d84..8056736a28a 100644 --- a/lib/vm/src/lib.rs +++ b/lib/vm/src/lib.rs @@ -32,7 +32,6 @@ mod sig_registry; mod table; mod trap; mod vmcontext; -mod vmoffsets; pub mod libcalls; @@ -44,52 +43,33 @@ pub use crate::instance::{ ImportFunctionEnv, ImportInitializerFuncPtr, InstanceAllocator, InstanceHandle, WeakOrStrongInstanceRef, }; -pub use crate::memory::{LinearMemory, Memory, MemoryError, MemoryStyle}; +pub use crate::memory::{LinearMemory, Memory, MemoryError}; pub use crate::mmap::Mmap; pub use crate::probestack::PROBESTACK; pub use crate::sig_registry::SignatureRegistry; -pub use crate::table::{LinearTable, Table, TableElement, TableStyle}; +pub use crate::table::{LinearTable, Table, TableElement}; pub use crate::trap::*; pub use crate::vmcontext::{ - VMBuiltinFunctionIndex, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, - VMFunctionBody, VMFunctionEnvironment, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, - VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, - VMTableImport, VMTrampoline, + VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, VMFunctionEnvironment, + VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, + VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, }; -pub use crate::vmoffsets::{TargetSharedSignatureIndex, VMOffsets}; -use loupe::MemoryUsage; -pub use wasmer_types::VMExternRef; +pub use wasmer_artifact::{FunctionBodyPtr, VMFunctionBody}; +pub use wasmer_types::LibCall; +pub use wasmer_types::MemoryStyle; +pub use wasmer_types::TableStyle; #[deprecated( since = "2.1.0", note = "ModuleInfo, ExportsIterator, ImportsIterator should be imported from wasmer_types." )] pub use wasmer_types::{ExportsIterator, ImportsIterator, ModuleInfo}; +pub use wasmer_types::{ + TargetSharedSignatureIndex, VMBuiltinFunctionIndex, VMExternRef, VMOffsets, +}; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -/// A safe wrapper around `VMFunctionBody`. -#[derive(Clone, Copy, Debug, MemoryUsage)] -#[repr(transparent)] -pub struct FunctionBodyPtr(pub *const VMFunctionBody); - -impl std::ops::Deref for FunctionBodyPtr { - type Target = *const VMFunctionBody; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// # Safety -/// The VMFunctionBody that this points to is opaque, so there's no data to -/// read or write through this pointer. This is essentially a usize. -unsafe impl Send for FunctionBodyPtr {} -/// # Safety -/// The VMFunctionBody that this points to is opaque, so there's no data to -/// read or write through this pointer. This is essentially a usize. -unsafe impl Sync for FunctionBodyPtr {} - /// Pointers to section data. #[derive(Clone, Copy, Debug)] #[repr(transparent)] diff --git a/lib/vm/src/libcalls.rs b/lib/vm/src/libcalls.rs index 83149f6ac12..198e311ccff 100644 --- a/lib/vm/src/libcalls.rs +++ b/lib/vm/src/libcalls.rs @@ -43,12 +43,7 @@ use crate::table::{RawTableElement, TableElement}; use crate::trap::{raise_lib_trap, Trap, TrapCode}; use crate::vmcontext::VMContext; use crate::{on_host_stack, VMExternRef}; -use enum_iterator::IntoEnumIterator; -use loupe::MemoryUsage; -#[cfg(feature = "enable-rkyv")] -use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; -use serde::{Deserialize, Serialize}; -use std::fmt; +pub use wasmer_types::LibCall; use wasmer_types::{ DataIndex, ElemIndex, FunctionIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, TableIndex, Type, @@ -694,195 +689,39 @@ pub unsafe extern "C" fn wasmer_vm_raise_trap(trap_code: TrapCode) -> ! { #[no_mangle] pub static wasmer_vm_probestack: unsafe extern "C" fn() = PROBESTACK; -/// The name of a runtime library routine. -/// -/// This list is likely to grow over time. -#[cfg_attr( - feature = "enable-rkyv", - derive(RkyvSerialize, RkyvDeserialize, Archive) -)] -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, MemoryUsage, IntoEnumIterator, -)] -pub enum LibCall { - /// ceil.f32 - CeilF32, - - /// ceil.f64 - CeilF64, - - /// floor.f32 - FloorF32, - - /// floor.f64 - FloorF64, - - /// nearest.f32 - NearestF32, - - /// nearest.f64 - NearestF64, - - /// trunc.f32 - TruncF32, - - /// trunc.f64 - TruncF64, - - /// memory.size for local functions - Memory32Size, - - /// memory.size for imported functions - ImportedMemory32Size, - - /// table.copy - TableCopy, - - /// table.init - TableInit, - - /// table.fill - TableFill, - - /// table.size for local tables - TableSize, - - /// table.size for imported tables - ImportedTableSize, - - /// table.get for local tables - TableGet, - - /// table.get for imported tables - ImportedTableGet, - - /// table.set for local tables - TableSet, - - /// table.set for imported tables - ImportedTableSet, - - /// table.grow for local tables - TableGrow, - - /// table.grow for imported tables - ImportedTableGrow, - - /// ref.func - FuncRef, - - /// elem.drop - ElemDrop, - - /// memory.copy for local memories - Memory32Copy, - - /// memory.copy for imported memories - ImportedMemory32Copy, - - /// memory.fill for local memories - Memory32Fill, - - /// memory.fill for imported memories - ImportedMemory32Fill, - - /// memory.init - Memory32Init, - - /// data.drop - DataDrop, - - /// A custom trap - RaiseTrap, - - /// probe for stack overflow. These are emitted for functions which need - /// when the `enable_probestack` setting is true. - Probestack, -} - -impl LibCall { - /// The function pointer to a libcall - pub fn function_pointer(self) -> usize { - match self { - Self::CeilF32 => wasmer_vm_f32_ceil as usize, - Self::CeilF64 => wasmer_vm_f64_ceil as usize, - Self::FloorF32 => wasmer_vm_f32_floor as usize, - Self::FloorF64 => wasmer_vm_f64_floor as usize, - Self::NearestF32 => wasmer_vm_f32_nearest as usize, - Self::NearestF64 => wasmer_vm_f64_nearest as usize, - Self::TruncF32 => wasmer_vm_f32_trunc as usize, - Self::TruncF64 => wasmer_vm_f64_trunc as usize, - Self::Memory32Size => wasmer_vm_memory32_size as usize, - Self::ImportedMemory32Size => wasmer_vm_imported_memory32_size as usize, - Self::TableCopy => wasmer_vm_table_copy as usize, - Self::TableInit => wasmer_vm_table_init as usize, - Self::TableFill => wasmer_vm_table_fill as usize, - Self::TableSize => wasmer_vm_table_size as usize, - Self::ImportedTableSize => wasmer_vm_imported_table_size as usize, - Self::TableGet => wasmer_vm_table_get as usize, - Self::ImportedTableGet => wasmer_vm_imported_table_get as usize, - Self::TableSet => wasmer_vm_table_set as usize, - Self::ImportedTableSet => wasmer_vm_imported_table_set as usize, - Self::TableGrow => wasmer_vm_table_grow as usize, - Self::ImportedTableGrow => wasmer_vm_imported_table_grow as usize, - Self::FuncRef => wasmer_vm_func_ref as usize, - Self::ElemDrop => wasmer_vm_elem_drop as usize, - Self::Memory32Copy => wasmer_vm_memory32_copy as usize, - Self::ImportedMemory32Copy => wasmer_vm_imported_memory32_copy as usize, - Self::Memory32Fill => wasmer_vm_memory32_fill as usize, - Self::ImportedMemory32Fill => wasmer_vm_memory32_fill as usize, - Self::Memory32Init => wasmer_vm_memory32_init as usize, - Self::DataDrop => wasmer_vm_data_drop as usize, - Self::Probestack => wasmer_vm_probestack as usize, - Self::RaiseTrap => wasmer_vm_raise_trap as usize, - } - } - - /// Return the function name associated to the libcall. - pub fn to_function_name(&self) -> &str { - match self { - Self::CeilF32 => "wasmer_vm_f32_ceil", - Self::CeilF64 => "wasmer_vm_f64_ceil", - Self::FloorF32 => "wasmer_vm_f32_floor", - Self::FloorF64 => "wasmer_vm_f64_floor", - Self::NearestF32 => "wasmer_vm_f32_nearest", - Self::NearestF64 => "wasmer_vm_f64_nearest", - Self::TruncF32 => "wasmer_vm_f32_trunc", - Self::TruncF64 => "wasmer_vm_f64_trunc", - Self::Memory32Size => "wasmer_vm_memory32_size", - Self::ImportedMemory32Size => "wasmer_vm_imported_memory32_size", - Self::TableCopy => "wasmer_vm_table_copy", - Self::TableInit => "wasmer_vm_table_init", - Self::TableFill => "wasmer_vm_table_fill", - Self::TableSize => "wasmer_vm_table_size", - Self::ImportedTableSize => "wasmer_vm_imported_table_size", - Self::TableGet => "wasmer_vm_table_get", - Self::ImportedTableGet => "wasmer_vm_imported_table_get", - Self::TableSet => "wasmer_vm_table_set", - Self::ImportedTableSet => "wasmer_vm_imported_table_set", - Self::TableGrow => "wasmer_vm_table_grow", - Self::ImportedTableGrow => "wasmer_vm_imported_table_grow", - Self::FuncRef => "wasmer_vm_func_ref", - Self::ElemDrop => "wasmer_vm_elem_drop", - Self::Memory32Copy => "wasmer_vm_memory32_copy", - Self::ImportedMemory32Copy => "wasmer_vm_imported_memory32_copy", - Self::Memory32Fill => "wasmer_vm_memory32_fill", - Self::ImportedMemory32Fill => "wasmer_vm_imported_memory32_fill", - Self::Memory32Init => "wasmer_vm_memory32_init", - Self::DataDrop => "wasmer_vm_data_drop", - Self::RaiseTrap => "wasmer_vm_raise_trap", - // We have to do this because macOS requires a leading `_` and it's not - // a normal function, it's a static variable, so we have to do it manually. - #[cfg(target_vendor = "apple")] - Self::Probestack => "_wasmer_vm_probestack", - #[cfg(not(target_vendor = "apple"))] - Self::Probestack => "wasmer_vm_probestack", - } - } -} - -impl fmt::Display for LibCall { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) +/// The function pointer to a libcall +pub fn function_pointer(libcall: LibCall) -> usize { + match libcall { + LibCall::CeilF32 => wasmer_vm_f32_ceil as usize, + LibCall::CeilF64 => wasmer_vm_f64_ceil as usize, + LibCall::FloorF32 => wasmer_vm_f32_floor as usize, + LibCall::FloorF64 => wasmer_vm_f64_floor as usize, + LibCall::NearestF32 => wasmer_vm_f32_nearest as usize, + LibCall::NearestF64 => wasmer_vm_f64_nearest as usize, + LibCall::TruncF32 => wasmer_vm_f32_trunc as usize, + LibCall::TruncF64 => wasmer_vm_f64_trunc as usize, + LibCall::Memory32Size => wasmer_vm_memory32_size as usize, + LibCall::ImportedMemory32Size => wasmer_vm_imported_memory32_size as usize, + LibCall::TableCopy => wasmer_vm_table_copy as usize, + LibCall::TableInit => wasmer_vm_table_init as usize, + LibCall::TableFill => wasmer_vm_table_fill as usize, + LibCall::TableSize => wasmer_vm_table_size as usize, + LibCall::ImportedTableSize => wasmer_vm_imported_table_size as usize, + LibCall::TableGet => wasmer_vm_table_get as usize, + LibCall::ImportedTableGet => wasmer_vm_imported_table_get as usize, + LibCall::TableSet => wasmer_vm_table_set as usize, + LibCall::ImportedTableSet => wasmer_vm_imported_table_set as usize, + LibCall::TableGrow => wasmer_vm_table_grow as usize, + LibCall::ImportedTableGrow => wasmer_vm_imported_table_grow as usize, + LibCall::FuncRef => wasmer_vm_func_ref as usize, + LibCall::ElemDrop => wasmer_vm_elem_drop as usize, + LibCall::Memory32Copy => wasmer_vm_memory32_copy as usize, + LibCall::ImportedMemory32Copy => wasmer_vm_imported_memory32_copy as usize, + LibCall::Memory32Fill => wasmer_vm_memory32_fill as usize, + LibCall::ImportedMemory32Fill => wasmer_vm_memory32_fill as usize, + LibCall::Memory32Init => wasmer_vm_memory32_init as usize, + LibCall::DataDrop => wasmer_vm_data_drop as usize, + LibCall::Probestack => wasmer_vm_probestack as usize, + LibCall::RaiseTrap => wasmer_vm_raise_trap as usize, } } diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index 0465d3dbf1f..75919bb7f8b 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -9,9 +9,6 @@ use crate::mmap::Mmap; use crate::vmcontext::VMMemoryDefinition; use loupe::MemoryUsage; use more_asserts::assert_ge; -#[cfg(feature = "enable-rkyv")] -use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; -use serde::{Deserialize, Serialize}; use std::borrow::BorrowMut; use std::cell::UnsafeCell; use std::convert::TryInto; @@ -19,7 +16,7 @@ use std::fmt; use std::ptr::NonNull; use std::sync::Mutex; use thiserror::Error; -use wasmer_types::{Bytes, MemoryType, Pages}; +use wasmer_types::{Bytes, MemoryStyle, MemoryType, Pages}; /// Error type describing things that can go wrong when operating on Wasm Memories. #[derive(Error, Debug, Clone, PartialEq, Hash)] @@ -63,45 +60,6 @@ pub enum MemoryError { Generic(String), } -/// Implementation styles for WebAssembly linear memory. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, MemoryUsage)] -#[cfg_attr( - feature = "enable-rkyv", - derive(RkyvSerialize, RkyvDeserialize, Archive) -)] -pub enum MemoryStyle { - /// The actual memory can be resized and moved. - Dynamic { - /// Our chosen offset-guard size. - /// - /// It represents the size in bytes of extra guard pages after the end - /// to optimize loads and stores with constant offsets. - offset_guard_size: u64, - }, - /// Address space is allocated up front. - Static { - /// The number of mapped and unmapped pages. - bound: Pages, - /// Our chosen offset-guard size. - /// - /// It represents the size in bytes of extra guard pages after the end - /// to optimize loads and stores with constant offsets. - offset_guard_size: u64, - }, -} - -impl MemoryStyle { - /// Returns the offset-guard size - pub fn offset_guard_size(&self) -> u64 { - match self { - Self::Dynamic { offset_guard_size } => *offset_guard_size, - Self::Static { - offset_guard_size, .. - } => *offset_guard_size, - } - } -} - /// Trait for implementing Wasm Memory used by Wasmer. pub trait Memory: fmt::Debug + Send + Sync + MemoryUsage { /// Returns the memory type for this memory. diff --git a/lib/vm/src/table.rs b/lib/vm/src/table.rs index 6f8abe4fdd1..3f7c8187a5b 100644 --- a/lib/vm/src/table.rs +++ b/lib/vm/src/table.rs @@ -6,31 +6,17 @@ //! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories. use crate::func_data_registry::VMFuncRef; -use crate::trap::{Trap, TrapCode}; use crate::vmcontext::VMTableDefinition; +use crate::Trap; use crate::VMExternRef; use loupe::{MemoryUsage, MemoryUsageTracker}; -#[cfg(feature = "enable-rkyv")] -use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; -use serde::{Deserialize, Serialize}; use std::borrow::{Borrow, BorrowMut}; use std::cell::UnsafeCell; use std::convert::TryFrom; use std::fmt; use std::ptr::NonNull; use std::sync::Mutex; -use wasmer_types::{ExternRef, TableType, Type as ValType}; - -/// Implementation styles for WebAssembly tables. -#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, MemoryUsage)] -#[cfg_attr( - feature = "enable-rkyv", - derive(RkyvSerialize, RkyvDeserialize, Archive) -)] -pub enum TableStyle { - /// Signatures are stored in the table and checked in the caller. - CallerChecksSignature, -} +use wasmer_types::{ExternRef, TableStyle, TableType, TrapCode, Type as ValType}; /// Trait for implementing the interface of a Wasm table. pub trait Table: fmt::Debug + Send + Sync + MemoryUsage { diff --git a/lib/vm/src/trap/mod.rs b/lib/vm/src/trap/mod.rs index 6d406f56a79..ca764576ba4 100644 --- a/lib/vm/src/trap/mod.rs +++ b/lib/vm/src/trap/mod.rs @@ -3,12 +3,13 @@ //! This is the module that facilitates the usage of Traps //! in Wasmer Runtime -mod trapcode; +mod trap; mod traphandlers; -pub use trapcode::TrapCode; +pub use trap::Trap; pub use traphandlers::{ - catch_traps, on_host_stack, raise_lib_trap, raise_user_trap, wasmer_call_trampoline, Trap, + catch_traps, on_host_stack, raise_lib_trap, raise_user_trap, wasmer_call_trampoline, TrapHandler, TrapHandlerFn, }; pub use traphandlers::{init_traps, resume_panic}; +pub use wasmer_types::TrapCode; diff --git a/lib/vm/src/trap/trap.rs b/lib/vm/src/trap/trap.rs new file mode 100644 index 00000000000..fdb20d3f590 --- /dev/null +++ b/lib/vm/src/trap/trap.rs @@ -0,0 +1,72 @@ +use backtrace::Backtrace; +use std::error::Error; +use wasmer_types::TrapCode; + +/// Stores trace message with backtrace. +#[derive(Debug)] +pub enum Trap { + /// A user-raised trap through `raise_user_trap`. + User(Box), + + /// A trap raised from the Wasm generated code + /// + /// Note: this trap is deterministic (assuming a deterministic host implementation) + Wasm { + /// The program counter in generated code where this trap happened. + pc: usize, + /// Native stack backtrace at the time the trap occurred + backtrace: Backtrace, + /// Optional trapcode associated to the signal that caused the trap + signal_trap: Option, + }, + + /// A trap raised from a wasm libcall + /// + /// Note: this trap is deterministic (assuming a deterministic host implementation) + Lib { + /// Code of the trap. + trap_code: TrapCode, + /// Native stack backtrace at the time the trap occurred + backtrace: Backtrace, + }, + + /// A trap indicating that the runtime was unable to allocate sufficient memory. + /// + /// Note: this trap is nondeterministic, since it depends on the host system. + OOM { + /// Native stack backtrace at the time the OOM occurred + backtrace: Backtrace, + }, +} + +impl Trap { + /// Construct a new Wasm trap with the given source location and backtrace. + /// + /// Internally saves a backtrace when constructed. + pub fn wasm(pc: usize, backtrace: Backtrace, signal_trap: Option) -> Self { + Trap::Wasm { + pc, + backtrace, + signal_trap, + } + } + + /// Construct a new Wasm trap with the given trap code. + /// + /// Internally saves a backtrace when constructed. + pub fn lib(trap_code: TrapCode) -> Self { + let backtrace = Backtrace::new_unresolved(); + Trap::Lib { + trap_code, + backtrace, + } + } + + /// Construct a new OOM trap with the given source location and trap code. + /// + /// Internally saves a backtrace when constructed. + pub fn oom() -> Self { + let backtrace = Backtrace::new_unresolved(); + Trap::OOM { backtrace } + } +} diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index ce19d0da625..baecaf387f3 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -4,8 +4,8 @@ //! WebAssembly trap handling, which is built on top of the lower-level //! signalhandling mechanisms. -use super::trapcode::TrapCode; use crate::vmcontext::{VMFunctionBody, VMFunctionEnvironment, VMTrampoline}; +use crate::Trap; use backtrace::Backtrace; use corosensei::stack::DefaultStack; use corosensei::trap::{CoroutineTrapHandler, TrapHandlerRegs}; @@ -21,6 +21,7 @@ use std::mem::MaybeUninit; use std::ptr::{self, NonNull}; use std::sync::atomic::{compiler_fence, AtomicPtr, Ordering}; use std::sync::{Mutex, Once}; +use wasmer_types::TrapCode; cfg_if::cfg_if! { if #[cfg(unix)] { @@ -523,75 +524,6 @@ pub unsafe fn resume_panic(payload: Box) -> ! { unwind_with(UnwindReason::Panic(payload)) } -/// Stores trace message with backtrace. -#[derive(Debug)] -pub enum Trap { - /// A user-raised trap through `raise_user_trap`. - User(Box), - - /// A trap raised from the Wasm generated code - /// - /// Note: this trap is deterministic (assuming a deterministic host implementation) - Wasm { - /// The program counter in generated code where this trap happened. - pc: usize, - /// Native stack backtrace at the time the trap occurred - backtrace: Backtrace, - /// Optional trapcode associated to the signal that caused the trap - signal_trap: Option, - }, - - /// A trap raised from a wasm libcall - /// - /// Note: this trap is deterministic (assuming a deterministic host implementation) - Lib { - /// Code of the trap. - trap_code: TrapCode, - /// Native stack backtrace at the time the trap occurred - backtrace: Backtrace, - }, - - /// A trap indicating that the runtime was unable to allocate sufficient memory. - /// - /// Note: this trap is nondeterministic, since it depends on the host system. - OOM { - /// Native stack backtrace at the time the OOM occurred - backtrace: Backtrace, - }, -} - -impl Trap { - /// Construct a new Wasm trap with the given source location and backtrace. - /// - /// Internally saves a backtrace when constructed. - pub fn wasm(pc: usize, backtrace: Backtrace, signal_trap: Option) -> Self { - Trap::Wasm { - pc, - backtrace, - signal_trap, - } - } - - /// Construct a new Wasm trap with the given trap code. - /// - /// Internally saves a backtrace when constructed. - pub fn lib(trap_code: TrapCode) -> Self { - let backtrace = Backtrace::new_unresolved(); - Trap::Lib { - trap_code, - backtrace, - } - } - - /// Construct a new OOM trap with the given source location and trap code. - /// - /// Internally saves a backtrace when constructed. - pub fn oom() -> Self { - let backtrace = Backtrace::new_unresolved(); - Trap::OOM { backtrace } - } -} - /// Call the wasm function pointed to by `callee`. /// /// * `vmctx` - the callee vmctx argument diff --git a/lib/vm/src/vmcontext.rs b/lib/vm/src/vmcontext.rs index eae9d02d614..a4d1b3a43a9 100644 --- a/lib/vm/src/vmcontext.rs +++ b/lib/vm/src/vmcontext.rs @@ -10,6 +10,7 @@ use crate::instance::Instance; use crate::memory::Memory; use crate::table::Table; use crate::trap::{Trap, TrapCode}; +use crate::VMBuiltinFunctionIndex; use crate::VMExternRef; use loupe::{MemoryUsage, MemoryUsageTracker, POINTER_BYTE_SIZE}; use std::any::Any; @@ -19,6 +20,7 @@ use std::mem; use std::ptr::{self, NonNull}; use std::sync::Arc; use std::u32; +pub use wasmer_artifact::VMFunctionBody; /// Union representing the first parameter passed when calling a function. /// @@ -168,24 +170,6 @@ mod test_vmdynamicfunction_import_context { } } -/// A placeholder byte-sized type which is just used to provide some amount of type -/// safety when dealing with pointers to JIT-compiled function bodies. Note that it's -/// deliberately not Copy, as we shouldn't be carelessly copying function body bytes -/// around. -#[repr(C)] -pub struct VMFunctionBody(u8); - -#[cfg(test)] -mod test_vmfunction_body { - use super::VMFunctionBody; - use std::mem::size_of; - - #[test] - fn check_vmfunction_body_offsets() { - assert_eq!(size_of::(), 1); - } -} - /// A function kind is a calling convention into and out of wasm code. #[derive(Debug, Copy, Clone, PartialEq, MemoryUsage)] #[repr(C)] @@ -798,9 +782,8 @@ pub struct VMSharedSignatureIndex(u32); #[cfg(test)] mod test_vmshared_signature_index { use super::VMSharedSignatureIndex; - use crate::vmoffsets::{TargetSharedSignatureIndex, VMOffsets}; use std::mem::size_of; - use wasmer_types::ModuleInfo; + use wasmer_types::{ModuleInfo, TargetSharedSignatureIndex, VMOffsets}; #[test] fn check_vmshared_signature_index() { @@ -892,127 +875,6 @@ impl Default for VMCallerCheckedAnyfunc { } } -/// An index type for builtin functions. -#[derive(Copy, Clone, Debug)] -pub struct VMBuiltinFunctionIndex(u32); - -impl VMBuiltinFunctionIndex { - /// Returns an index for wasm's `memory.grow` builtin function. - pub const fn get_memory32_grow_index() -> Self { - Self(0) - } - /// Returns an index for wasm's imported `memory.grow` builtin function. - pub const fn get_imported_memory32_grow_index() -> Self { - Self(1) - } - /// Returns an index for wasm's `memory.size` builtin function. - pub const fn get_memory32_size_index() -> Self { - Self(2) - } - /// Returns an index for wasm's imported `memory.size` builtin function. - pub const fn get_imported_memory32_size_index() -> Self { - Self(3) - } - /// Returns an index for wasm's `table.copy` when both tables are locally - /// defined. - pub const fn get_table_copy_index() -> Self { - Self(4) - } - /// Returns an index for wasm's `table.init`. - pub const fn get_table_init_index() -> Self { - Self(5) - } - /// Returns an index for wasm's `elem.drop`. - pub const fn get_elem_drop_index() -> Self { - Self(6) - } - /// Returns an index for wasm's `memory.copy` for locally defined memories. - pub const fn get_memory_copy_index() -> Self { - Self(7) - } - /// Returns an index for wasm's `memory.copy` for imported memories. - pub const fn get_imported_memory_copy_index() -> Self { - Self(8) - } - /// Returns an index for wasm's `memory.fill` for locally defined memories. - pub const fn get_memory_fill_index() -> Self { - Self(9) - } - /// Returns an index for wasm's `memory.fill` for imported memories. - pub const fn get_imported_memory_fill_index() -> Self { - Self(10) - } - /// Returns an index for wasm's `memory.init` instruction. - pub const fn get_memory_init_index() -> Self { - Self(11) - } - /// Returns an index for wasm's `data.drop` instruction. - pub const fn get_data_drop_index() -> Self { - Self(12) - } - /// Returns an index for wasm's `raise_trap` instruction. - pub const fn get_raise_trap_index() -> Self { - Self(13) - } - /// Returns an index for wasm's `table.size` instruction for local tables. - pub const fn get_table_size_index() -> Self { - Self(14) - } - /// Returns an index for wasm's `table.size` instruction for imported tables. - pub const fn get_imported_table_size_index() -> Self { - Self(15) - } - /// Returns an index for wasm's `table.grow` instruction for local tables. - pub const fn get_table_grow_index() -> Self { - Self(16) - } - /// Returns an index for wasm's `table.grow` instruction for imported tables. - pub const fn get_imported_table_grow_index() -> Self { - Self(17) - } - /// Returns an index for wasm's `table.get` instruction for local tables. - pub const fn get_table_get_index() -> Self { - Self(18) - } - /// Returns an index for wasm's `table.get` instruction for imported tables. - pub const fn get_imported_table_get_index() -> Self { - Self(19) - } - /// Returns an index for wasm's `table.set` instruction for local tables. - pub const fn get_table_set_index() -> Self { - Self(20) - } - /// Returns an index for wasm's `table.set` instruction for imported tables. - pub const fn get_imported_table_set_index() -> Self { - Self(21) - } - /// Returns an index for wasm's `func.ref` instruction. - pub const fn get_func_ref_index() -> Self { - Self(22) - } - /// Returns an index for wasm's `table.fill` instruction for local tables. - pub const fn get_table_fill_index() -> Self { - Self(23) - } - /// Returns an index for a function to increment the externref count. - pub const fn get_externref_inc_index() -> Self { - Self(24) - } - /// Returns an index for a function to decrement the externref count. - pub const fn get_externref_dec_index() -> Self { - Self(25) - } - /// Returns the total number of builtin functions. - pub const fn builtin_functions_total_number() -> u32 { - 26 - } - - /// Return the index as an u32 number. - pub const fn index(self) -> u32 { - self.0 - } -} - /// An array that stores addresses of builtin functions. We translate code /// to use indirect calls. This way, we don't have to patch the code. #[repr(C)] diff --git a/tests/lib/engine-dummy/Cargo.toml b/tests/lib/engine-dummy/Cargo.toml index 61b42112454..a88e48c3f53 100644 --- a/tests/lib/engine-dummy/Cargo.toml +++ b/tests/lib/engine-dummy/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" publish = false [dependencies] +wasmer-artifact = { path = "../../../lib/artifact", version = "=2.2.1" } wasmer-types = { path = "../../../lib/types", version = "=2.2.1" } wasmer-compiler = { path = "../../../lib/compiler", version = "=2.2.1" } wasmer-vm = { path = "../../../lib/vm", version = "=2.2.1" } diff --git a/tests/lib/engine-dummy/src/artifact.rs b/tests/lib/engine-dummy/src/artifact.rs index 9b7523afbb6..d6a061c95a3 100644 --- a/tests/lib/engine-dummy/src/artifact.rs +++ b/tests/lib/engine-dummy/src/artifact.rs @@ -7,6 +7,7 @@ use loupe::MemoryUsage; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; use std::sync::Arc; +use wasmer_artifact::ArtifactCreate; #[cfg(feature = "compiler")] use wasmer_compiler::ModuleEnvironment; use wasmer_compiler::{CompileError, CpuFeature}; @@ -193,7 +194,7 @@ impl DummyArtifact { } } -impl Artifact for DummyArtifact { +impl ArtifactCreate for DummyArtifact { fn module(&self) -> Arc { self.metadata.module.clone() } @@ -206,10 +207,6 @@ impl Artifact for DummyArtifact { Arc::get_mut(&mut self.metadata.module) } - fn register_frame_info(&self) { - // Do nothing, since functions are not generated for the dummy engine - } - fn features(&self) -> &Features { &self.metadata.features } @@ -229,6 +226,28 @@ impl Artifact for DummyArtifact { fn table_styles(&self) -> &PrimaryMap { &self.metadata.table_styles } + #[cfg(feature = "serialize")] + fn serialize(&self) -> Result, SerializeError> { + let bytes = bincode::serialize(&self.metadata) + .map_err(|e| SerializeError::Generic(format!("{:?}", e)))?; + + // Prepend the header. + let mut serialized = Self::MAGIC_HEADER.to_vec(); + serialized.extend(bytes); + Ok(serialized) + } + + #[cfg(not(feature = "serialize"))] + fn serialize(&self) -> Result, SerializeError> { + Err(SerializeError::Generic( + "The serializer feature is not enabled in the DummyEngine", + )) + } +} +impl Artifact for DummyArtifact { + fn register_frame_info(&self) { + // Do nothing, since functions are not generated for the dummy engine + } fn finished_functions(&self) -> &BoxedSlice { &self.finished_functions @@ -249,22 +268,4 @@ impl Artifact for DummyArtifact { fn func_data_registry(&self) -> &FuncDataRegistry { &self.func_data_registry } - - #[cfg(feature = "serialize")] - fn serialize(&self) -> Result, SerializeError> { - let bytes = bincode::serialize(&self.metadata) - .map_err(|e| SerializeError::Generic(format!("{:?}", e)))?; - - // Prepend the header. - let mut serialized = Self::MAGIC_HEADER.to_vec(); - serialized.extend(bytes); - Ok(serialized) - } - - #[cfg(not(feature = "serialize"))] - fn serialize(&self) -> Result, SerializeError> { - Err(SerializeError::Generic( - "The serializer feature is not enabled in the DummyEngine", - )) - } }