From ddfadaeb384c11d2928fcf89dd1bd4e40692367b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 18 Aug 2021 16:47:47 -0500 Subject: [PATCH] Add a cranelift compile-time feature to wasmtime (#3206) * Remove unnecessary into_iter/map Forgotten from a previous refactoring, this variable was already of the right type! * Move `wasmtime_jit::Compiler` into `wasmtime` This `Compiler` struct is mostly a historical artifact at this point and wasn't necessarily pulling much weight any more. This organization also doesn't lend itself super well to compiling out `cranelift` when the `Compiler` here is used for both parallel iteration configuration settings as well as compilation. The movement into `wasmtime` is relatively small, with `Module::build_artifacts` being the main function added here which is a merging of the previous functions removed from the `wasmtime-jit` crate. * Add a `cranelift` compile-time feature to `wasmtime` This commit concludes the saga of refactoring Wasmtime and making Cranelift an optional dependency by adding a new Cargo feature to the `wasmtime` crate called `cranelift`, which is enabled by default. This feature is implemented by having a new cfg for `wasmtime` itself, `cfg(compiler)`, which is used wherever compilation is necessary. This bubbles up to disable APIs such as `Module::new`, `Func::new`, `Engine::precompile_module`, and a number of `Config` methods affecting compiler configuration. Checks are added to CI that when built in this mode Wasmtime continues to successfully build. It's hoped that although this is effectively "sprinkle `#[cfg]` until things compile" this won't be too too bad to maintain over time since it's also an use case we're interested in supporting. With `cranelift` disabled the only way to create a `Module` is with the `Module::deserialize` method, which requires some form of precompiled artifact. Two consequences of this change are: * `Module::serialize` is also disabled in this mode. The reason for this is that serialized modules contain ISA/shared flags encoded in them which were used to produce the compiled code. There's no storage for this if compilation is disabled. This could probably be re-enabled in the future if necessary, but it may not end up being all that necessary. * Deserialized modules are not checked to ensure that their ISA/shared flags are compatible with the host CPU. This is actually already the case, though, with normal modules. We'll likely want to fix this in the future using a shared implementation for both these locations. Documentation should be updated to indicate that `cranelift` can be disabled, although it's not really the most prominent documentation because this is expected to be a somewhat niche use case (albeit important, just not too common). * Always enable cranelift for the C API * Fix doc example builds * Fix check tests on GitHub Actions --- .github/workflows/main.yml | 24 +-- Cargo.lock | 6 +- Cargo.toml | 4 +- crates/c-api/Cargo.toml | 2 +- crates/jit/Cargo.toml | 9 - crates/jit/src/compiler.rs | 180 -------------------- crates/jit/src/instantiate.rs | 103 +++-------- crates/jit/src/lib.rs | 2 - crates/wasmtime/Cargo.toml | 30 ++-- crates/wasmtime/build.rs | 20 +++ crates/wasmtime/src/config.rs | 96 ++++++----- crates/wasmtime/src/engine.rs | 58 +++++-- crates/wasmtime/src/func.rs | 7 +- crates/wasmtime/src/lib.rs | 9 +- crates/wasmtime/src/linker.rs | 12 +- crates/wasmtime/src/module.rs | 164 +++++++++++++++--- crates/wasmtime/src/module/serialization.rs | 85 ++++----- crates/wasmtime/src/trampoline.rs | 2 +- crates/wasmtime/src/trampoline/func.rs | 2 +- crates/wast/Cargo.toml | 2 +- src/commands/settings.rs | 3 +- src/obj.rs | 34 ++-- 22 files changed, 418 insertions(+), 436 deletions(-) delete mode 100644 crates/jit/src/compiler.rs create mode 100644 crates/wasmtime/build.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c0bfb7351..cf6762132a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -75,7 +75,7 @@ jobs: echo "${{ runner.tool_cache }}/mdbook/bin" >> $GITHUB_PATH cargo install --root ${{ runner.tool_cache }}/mdbook --version ${{ env.CARGO_MDBOOK_VERSION }} mdbook - run: (cd docs && mdbook build) - - run: cargo build -p wasmtime-wasi --features wasmtime/wat + - run: cargo build -p wasmtime-wasi --features wasmtime/wat,wasmtime/cranelift - run: (cd docs && mdbook test -L ../target/debug/deps) # Build Rust API documentation @@ -124,18 +124,20 @@ jobs: - uses: ./.github/actions/install-rust # Check some feature combinations of the `wasmtime` crate - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --no-default-features - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --features wat - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --features lightbeam - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --features jitdump - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --features cache - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --features async - - run: cargo check --manifest-path crates/wasmtime/Cargo.toml --features uffd + - run: cargo check -p wasmtime --no-default-features + - run: cargo check -p wasmtime --no-default-features --features wat + - run: cargo check -p wasmtime --no-default-features --features lightbeam + - run: cargo check -p wasmtime --no-default-features --features jitdump + - run: cargo check -p wasmtime --no-default-features --features cache + - run: cargo check -p wasmtime --no-default-features --features async + - run: cargo check -p wasmtime --no-default-features --features uffd + - run: cargo check -p wasmtime --no-default-features --features cranelift + - run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache # Check some feature combinations of the `wasmtime-c-api` crate - - run: cargo check --manifest-path crates/c-api/Cargo.toml --no-default-features - - run: cargo check --manifest-path crates/c-api/Cargo.toml --features wat - - run: cargo check --manifest-path crates/c-api/Cargo.toml --features wasi + - run: cargo check -p wasmtime-c-api --no-default-features + - run: cargo check -p wasmtime-c-api --no-default-features --features wat + - run: cargo check -p wasmtime-c-api --no-default-features --features wasi # Check a few builds of the cranelift backend # - only x86 backend support, diff --git a/Cargo.lock b/Cargo.lock index 806030fe31..b3012f34c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3558,6 +3558,7 @@ dependencies = [ "log", "paste", "psm", + "rayon", "region", "rustc-demangle", "serde", @@ -3567,6 +3568,7 @@ dependencies = [ "wasi-cap-std-sync", "wasmparser", "wasmtime-cache", + "wasmtime-cranelift", "wasmtime-environ", "wasmtime-fiber", "wasmtime-jit", @@ -3667,6 +3669,7 @@ dependencies = [ "wasmparser", "wasmtime", "wasmtime-cache", + "wasmtime-cranelift", "wasmtime-environ", "wasmtime-fuzzing", "wasmtime-jit", @@ -3776,15 +3779,12 @@ dependencies = [ "log", "more-asserts", "object", - "rayon", "region", "serde", "target-lexicon", "thiserror", "wasmparser", - "wasmtime-cranelift", "wasmtime-environ", - "wasmtime-lightbeam", "wasmtime-profiling", "wasmtime-runtime", "winapi", diff --git a/Cargo.toml b/Cargo.toml index 993817caeb..1d128937eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,9 @@ path = "src/bin/wasmtime.rs" doc = false [dependencies] -wasmtime = { path = "crates/wasmtime", version = "0.29.0", default-features = false, features = ['cache'] } +wasmtime = { path = "crates/wasmtime", version = "0.29.0", default-features = false, features = ['cache', 'cranelift'] } wasmtime-cache = { path = "crates/cache", version = "0.29.0" } +wasmtime-cranelift = { path = "crates/cranelift", version = "0.29.0" } wasmtime-environ = { path = "crates/environ", version = "0.29.0" } wasmtime-jit = { path = "crates/jit", version = "0.29.0" } wasmtime-wast = { path = "crates/wast", version = "0.29.0" } @@ -70,6 +71,7 @@ members = [ "cranelift", "crates/bench-api", "crates/c-api", + "crates/lightbeam/wasmtime", "crates/misc/run-examples", "examples/fib-debug/wasm", "examples/wasi/wasm", diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index f901612fdb..9ff8f45336 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -20,7 +20,7 @@ doctest = false env_logger = "0.8" anyhow = "1.0" once_cell = "1.3" -wasmtime = { path = "../wasmtime", default-features = false } +wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift'] } wasmtime-c-api-macros = { path = "macros" } # Optional dependency for the `wat2wasm` API diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index cd1db4f15c..168560a5c7 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -13,10 +13,7 @@ edition = "2018" [dependencies] wasmtime-environ = { path = "../environ", version = "0.29.0" } wasmtime-runtime = { path = "../runtime", version = "0.29.0" } -wasmtime-cranelift = { path = "../cranelift", version = "0.29.0" } -wasmtime-lightbeam = { path = "../lightbeam/wasmtime", version = "0.29.0", optional = true } wasmtime-profiling = { path = "../profiling", version = "0.29.0" } -rayon = { version = "1.0", optional = true } region = "2.2.0" thiserror = "1.0.4" target-lexicon = { version = "0.12.0", default-features = false } @@ -34,14 +31,8 @@ addr2line = { version = "0.16.0", default-features = false } winapi = { version = "0.3.8", features = ["winnt", "impl-default"] } [features] -lightbeam = ["wasmtime-lightbeam"] jitdump = ["wasmtime-profiling/jitdump"] vtune = ["wasmtime-profiling/vtune"] -parallel-compilation = ["rayon"] -all-arch = ["wasmtime-cranelift/all-arch"] - -# Use the old x86 backend. -old-x86-backend = ["wasmtime-cranelift/old-x86-backend"] [badges] maintenance = { status = "actively-developed" } diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs deleted file mode 100644 index dab70ac5f6..0000000000 --- a/crates/jit/src/compiler.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! JIT compilation. - -use crate::instantiate::SetupError; -#[cfg(feature = "parallel-compilation")] -use rayon::prelude::*; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::hash::{Hash, Hasher}; -use std::mem; -use wasmparser::WasmFeatures; -use wasmtime_environ::{ - Compiler as EnvCompiler, CompilerBuilder, DefinedFuncIndex, FunctionInfo, ModuleTranslation, - PrimaryMap, Tunables, TypeTables, -}; - -/// Select which kind of compilation to use. -#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq)] -pub enum CompilationStrategy { - /// Let Wasmtime pick the strategy. - Auto, - - /// Compile all functions with Cranelift. - Cranelift, - - /// Compile all functions with Lightbeam. - #[cfg(feature = "lightbeam")] - Lightbeam, -} - -/// A WebAssembly code JIT compiler. -pub struct Compiler { - compiler: Box, - tunables: Tunables, - features: WasmFeatures, - parallel_compilation: bool, -} - -impl Compiler { - /// Creates a new compiler builder for the provided compilation strategy. - pub fn builder(strategy: CompilationStrategy) -> Box { - match strategy { - CompilationStrategy::Auto | CompilationStrategy::Cranelift => { - wasmtime_cranelift::builder() - } - #[cfg(feature = "lightbeam")] - CompilationStrategy::Lightbeam => unimplemented!(), - } - } - - /// Creates a new instance of a `Compiler` from the provided compiler - /// builder. - pub fn new( - builder: &dyn CompilerBuilder, - tunables: Tunables, - features: WasmFeatures, - parallel_compilation: bool, - ) -> Compiler { - Compiler { - compiler: builder.build(), - tunables, - features, - parallel_compilation, - } - } -} - -fn _assert_compiler_send_sync() { - fn _assert() {} - _assert::(); -} - -#[allow(missing_docs)] -pub struct Compilation { - pub obj: Vec, - pub funcs: PrimaryMap, -} - -impl Compiler { - /// Return the tunables in use by this engine. - pub fn tunables(&self) -> &Tunables { - &self.tunables - } - - /// Return the enabled wasm features. - pub fn features(&self) -> &WasmFeatures { - &self.features - } - - /// Return the underlying compiler in use - pub fn compiler(&self) -> &dyn EnvCompiler { - &*self.compiler - } - - /// Returns the target this compiler is compiling for. - pub fn triple(&self) -> &target_lexicon::Triple { - self.compiler.triple() - } - - /// Compile the given function bodies. - pub fn compile<'data>( - &self, - translation: &mut ModuleTranslation, - types: &TypeTables, - ) -> Result { - let functions = mem::take(&mut translation.function_body_inputs); - let functions = functions.into_iter().collect::>(); - - let funcs = self - .run_maybe_parallel(functions, |(index, func)| { - self.compiler - .compile_function(translation, index, func, &self.tunables, types) - })? - .into_iter() - .collect(); - - let (obj, funcs) = self.compiler.emit_obj( - &translation, - types, - funcs, - self.tunables.generate_native_debuginfo, - )?; - - Ok(Compilation { obj, funcs }) - } - - /// Run the given closure in parallel if the compiler is configured to do so. - pub(crate) fn run_maybe_parallel< - A: Send, - B: Send, - E: Send, - F: Fn(A) -> Result + Send + Sync, - >( - &self, - input: Vec, - f: F, - ) -> Result, E> { - if self.parallel_compilation { - #[cfg(feature = "parallel-compilation")] - return input - .into_par_iter() - .map(|a| f(a)) - .collect::, E>>(); - } - - // In case the parallel-compilation feature is disabled or the parallel_compilation config - // was turned off dynamically fallback to the non-parallel version. - input - .into_iter() - .map(|a| f(a)) - .collect::, E>>() - } -} - -impl Hash for Compiler { - fn hash(&self, hasher: &mut H) { - let Compiler { - compiler, - tunables, - features, - parallel_compilation: _, - } = self; - - compiler.triple().hash(hasher); - compiler - .flags() - .into_iter() - .collect::>() - .hash(hasher); - compiler - .isa_flags() - .into_iter() - .collect::>() - .hash(hasher); - tunables.hash(hasher); - features.hash(hasher); - - // Catch accidental bugs of reusing across crate versions. - env!("CARGO_PKG_VERSION").hash(hasher); - } -} diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index 1af8dd9276..6108a85f17 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -4,7 +4,6 @@ //! steps. use crate::code_memory::CodeMemory; -use crate::compiler::{Compilation, Compiler}; use crate::debug::create_gdbjit_image; use crate::link::link_module; use anyhow::Result; @@ -14,8 +13,8 @@ use std::sync::Arc; use thiserror::Error; use wasmtime_environ::{ CompileError, DebugInfoData, DefinedFuncIndex, FunctionInfo, InstanceSignature, - InstanceTypeIndex, Module, ModuleEnvironment, ModuleSignature, ModuleTranslation, - ModuleTypeIndex, PrimaryMap, SignatureIndex, StackMapInformation, WasmFuncType, + InstanceTypeIndex, Module, ModuleSignature, ModuleTranslation, ModuleTypeIndex, PrimaryMap, + SignatureIndex, StackMapInformation, Tunables, WasmFuncType, }; use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{GdbJitImageRegistration, InstantiationError, VMFunctionBody, VMTrampoline}; @@ -84,70 +83,32 @@ struct DebugInfo { } impl CompilationArtifacts { - /// Creates a `CompilationArtifacts` for a singular translated wasm module. - /// - /// The `use_paged_init` argument controls whether or not an attempt is made to - /// organize linear memory initialization data as entire pages or to leave - /// the memory initialization data as individual segments. - pub fn build( - compiler: &Compiler, - data: &[u8], - use_paged_mem_init: bool, - ) -> Result<(usize, Vec, TypeTables), SetupError> { - let (main_module, translations, types) = - ModuleEnvironment::new(compiler.tunables(), compiler.features()) - .translate(data) - .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; - - let list = compiler.run_maybe_parallel::<_, _, SetupError, _>( - translations, - |mut translation| { - let Compilation { obj, funcs } = compiler.compile(&mut translation, &types)?; - - let ModuleTranslation { - mut module, - debuginfo, - has_unparsed_debuginfo, - .. - } = translation; - - if use_paged_mem_init { - if let Some(init) = module.memory_initialization.to_paged(&module) { - module.memory_initialization = init; - } - } - - Ok(CompilationArtifacts { - module: Arc::new(module), - obj: obj.into_boxed_slice(), - funcs: funcs - .into_iter() - .map(|(_, func)| FunctionInfo { - stack_maps: func.stack_maps, - traps: func.traps, - address_map: func.address_map, - }) - .collect(), - native_debug_info_present: compiler.tunables().generate_native_debuginfo, - debug_info: if compiler.tunables().parse_wasm_debuginfo { - Some(debuginfo.into()) - } else { - None - }, - has_unparsed_debuginfo, - }) + /// Creates a new `CompilationArtifacts` from the final results of + /// compilation. + pub fn new( + translation: ModuleTranslation<'_>, + obj: Vec, + funcs: PrimaryMap, + tunables: &Tunables, + ) -> CompilationArtifacts { + let ModuleTranslation { + module, + debuginfo, + has_unparsed_debuginfo, + .. + } = translation; + CompilationArtifacts { + module: Arc::new(module), + obj: obj.into_boxed_slice(), + funcs, + native_debug_info_present: tunables.generate_native_debuginfo, + debug_info: if tunables.parse_wasm_debuginfo { + Some(debuginfo.into()) + } else { + None }, - )?; - - Ok(( - main_module, - list, - TypeTables { - wasm_signatures: types.wasm_signatures, - module_signatures: types.module_signatures, - instance_signatures: types.instance_signatures, - }, - )) + has_unparsed_debuginfo, + } } } @@ -189,16 +150,6 @@ pub struct CompiledModule { } impl CompiledModule { - /// Creates a list of compiled modules from the given list of compilation - /// artifacts. - pub fn from_artifacts_list( - artifacts: Vec, - profiler: &dyn ProfilingAgent, - compiler: &Compiler, - ) -> Result>, SetupError> { - compiler.run_maybe_parallel(artifacts, |a| CompiledModule::from_artifacts(a, profiler)) - } - /// Creates `CompiledModule` directly from `CompilationArtifacts`. pub fn from_artifacts( artifacts: CompilationArtifacts, diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 1226f76d13..5dfd52e933 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -21,14 +21,12 @@ )] mod code_memory; -mod compiler; mod debug; mod instantiate; mod link; mod unwind; pub use crate::code_memory::CodeMemory; -pub use crate::compiler::{Compilation, CompilationStrategy, Compiler}; pub use crate::instantiate::{ CompilationArtifacts, CompiledModule, ModuleCode, SetupError, SymbolizeContext, TypeTables, }; diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 4c0ee28516..02d50f1775 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -19,6 +19,7 @@ wasmtime-jit = { path = "../jit", version = "0.29.0" } wasmtime-cache = { path = "../cache", version = "0.29.0", optional = true } wasmtime-profiling = { path = "../profiling", version = "0.29.0" } wasmtime-fiber = { path = "../fiber", version = "0.29.0", optional = true } +wasmtime-cranelift = { path = "../cranelift", version = "0.29.0", optional = true } target-lexicon = { version = "0.12.0", default-features = false } wasmparser = "0.80" anyhow = "1.0.19" @@ -37,6 +38,7 @@ indexmap = "1.6" paste = "1.0.3" psm = "0.1.11" lazy_static = "1.4" +rayon = { version = "1.0", optional = true } [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.7" @@ -50,12 +52,16 @@ wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync" } maintenance = { status = "actively-developed" } [features] -default = ['async', 'cache', 'wat', 'jitdump', 'parallel-compilation'] +default = ['async', 'cache', 'wat', 'jitdump', 'parallel-compilation', 'cranelift'] -# Enables experimental support for the lightbeam codegen backend, an alternative -# to cranelift. Requires Nightly Rust currently, and this is not enabled by -# default. -lightbeam = ["wasmtime-jit/lightbeam"] +# An on-by-default feature enabling runtime compilation of WebAssembly modules +# with the Cranelift compiler. Cranelift is the default compilation backend of +# Wasmtime. If disabled then WebAssembly modules can only be created from +# precompiled WebAssembly modules. +cranelift = ["wasmtime-cranelift"] + +# Deprecated, does not actually do anything any more. +lightbeam = [] # Enables support for the `perf` jitdump profiler jitdump = ["wasmtime-jit/jitdump"] @@ -63,14 +69,14 @@ jitdump = ["wasmtime-jit/jitdump"] # Enables support for the `VTune` profiler vtune = ["wasmtime-jit/vtune"] -# Enables parallel compilation of WebAssembly code -parallel-compilation = ["wasmtime-jit/parallel-compilation"] +# Enables parallel compilation of WebAssembly code. +parallel-compilation = ["rayon"] # Enables support for automatic cache configuration to be enabled in `Config`. cache = ["wasmtime-cache"] -# Use the old x86 backend. -old-x86-backend = ["wasmtime-jit/old-x86-backend"] +# Use Cranelift's old x86 backend. +old-x86-backend = ["wasmtime-cranelift/old-x86-backend"] # Enables support for "async stores" as well as defining host functions as # `async fn` and calling functions asynchronously. @@ -79,8 +85,10 @@ async = ["wasmtime-fiber", "wasmtime-runtime/async"] # Enables userfaultfd support in the runtime's pooling allocator when building on Linux uffd = ["wasmtime-runtime/uffd"] -# Enables support for all architectures in JIT and the `wasmtime compile` CLI command. -all-arch = ["wasmtime-jit/all-arch"] +# Enables support for all architectures in Cranelift, allowing +# cross-compilation using the `wasmtime` crate's API, notably the +# `Engine::precompile_module` function. +all-arch = ["wasmtime-cranelift/all-arch"] # Enables trap handling using POSIX signals instead of Mach exceptions on MacOS. # It is useful for applications that do not bind their own exception ports and diff --git a/crates/wasmtime/build.rs b/crates/wasmtime/build.rs new file mode 100644 index 0000000000..9555ff2475 --- /dev/null +++ b/crates/wasmtime/build.rs @@ -0,0 +1,20 @@ +fn main() { + // Code in the `wasmtime` crate will use #[cfg(compiler)] conditional + // compilation when runtime compilation is supported or not. This #[cfg] is + // defined by this build script here, and is guarded with a conditional. + // Currently this conditional is #[cfg(feature = "cranelift")] since that's + // the only supported compiler. + // + // Note that #[doc(cfg)] throughout the `wasmtime` crate points here. We + // want the rustdoc documentation to accurately reflect the requirements for + // APIs, so the condition here is duplicated into all #[doc(cfg)] + // attributes. If this condition is updated to emit #[cfg(compiler)] more + // frequently then all rustdoc attributes also need to be updated with the + // new condition to ensure the documentation accurately reflects when an API + // is available. + if cfg!(feature = "cranelift") { + println!("cargo:rustc-cfg=compiler"); + } + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index ca28bda5a9..9ff2a6eeec 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -11,7 +11,6 @@ use wasmparser::WasmFeatures; #[cfg(feature = "cache")] use wasmtime_cache::CacheConfig; use wasmtime_environ::{CompilerBuilder, Tunables}; -use wasmtime_jit::{CompilationStrategy, Compiler}; use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent}; use wasmtime_runtime::{ InstanceAllocator, OnDemandInstanceAllocator, PoolingInstanceAllocator, RuntimeMemoryCreator, @@ -337,6 +336,7 @@ impl Default for InstanceAllocationStrategy { /// This structure exposed a builder-like interface and is primarily consumed by /// [`Engine::new()`](crate::Engine::new) pub struct Config { + #[cfg(compiler)] pub(crate) compiler: Box, pub(crate) tunables: Tunables, #[cfg(feature = "cache")] @@ -360,7 +360,8 @@ impl Config { pub fn new() -> Self { let mut ret = Self { tunables: Tunables::default(), - compiler: Compiler::builder(CompilationStrategy::Auto), + #[cfg(compiler)] + compiler: compiler_builder(Strategy::Auto).unwrap(), #[cfg(feature = "cache")] cache_config: CacheConfig::new_cache_disabled(), profiler: Arc::new(NullProfilerAgent), @@ -375,8 +376,11 @@ impl Config { deserialize_check_wasmtime_version: true, parallel_compilation: true, }; - ret.cranelift_debug_verifier(false); - ret.cranelift_opt_level(OptLevel::Speed); + #[cfg(compiler)] + { + ret.cranelift_debug_verifier(false); + ret.cranelift_opt_level(OptLevel::Speed); + } ret.wasm_reference_types(true); ret.wasm_multi_value(true); ret.wasm_bulk_memory(true); @@ -396,6 +400,8 @@ impl Config { /// # Errors /// /// This method will error if the given target triple is not supported. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn target(&mut self, target: &str) -> Result<&mut Self> { use std::str::FromStr; self.compiler @@ -655,9 +661,12 @@ impl Config { pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self { self.features.reference_types = enable; - self.compiler - .set("enable_safepoints", if enable { "true" } else { "false" }) - .unwrap(); + #[cfg(compiler)] + { + self.compiler + .set("enable_safepoints", if enable { "true" } else { "false" }) + .unwrap(); + } // The reference types proposal depends on the bulk memory proposal. if enable { @@ -689,10 +698,13 @@ impl Config { /// [proposal]: https://github.com/webassembly/simd pub fn wasm_simd(&mut self, enable: bool) -> &mut Self { self.features.simd = enable; - let val = if enable { "true" } else { "false" }; - self.compiler - .set("enable_simd", val) - .expect("should be valid flag"); + #[cfg(compiler)] + { + let val = if enable { "true" } else { "false" }; + self.compiler + .set("enable_simd", val) + .expect("should be valid flag"); + } self } @@ -780,18 +792,10 @@ impl Config { /// Some compilation strategies require compile-time options of `wasmtime` /// itself to be set, but if they're not set and the strategy is specified /// here then an error will be returned. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn strategy(&mut self, strategy: Strategy) -> Result<&mut Self> { - let strategy = match strategy { - Strategy::Auto => CompilationStrategy::Auto, - Strategy::Cranelift => CompilationStrategy::Cranelift, - #[cfg(feature = "lightbeam")] - Strategy::Lightbeam => CompilationStrategy::Lightbeam, - #[cfg(not(feature = "lightbeam"))] - Strategy::Lightbeam => { - anyhow::bail!("lightbeam compilation strategy wasn't enabled at compile time"); - } - }; - self.compiler = Compiler::builder(strategy); + self.compiler = compiler_builder(strategy)?; Ok(self) } @@ -816,6 +820,8 @@ impl Config { /// developers of wasmtime itself. /// /// The default value for this is `false` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn cranelift_debug_verifier(&mut self, enable: bool) -> &mut Self { let val = if enable { "true" } else { "false" }; self.compiler @@ -831,6 +837,8 @@ impl Config { /// more information see the documentation of [`OptLevel`]. /// /// The default value for this is `OptLevel::None`. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn cranelift_opt_level(&mut self, level: OptLevel) -> &mut Self { let val = match level { OptLevel::None => "none", @@ -851,6 +859,8 @@ impl Config { /// This is not required by the WebAssembly spec, so it is not enabled by default. /// /// The default value for this is `false` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn cranelift_nan_canonicalization(&mut self, enable: bool) -> &mut Self { let val = if enable { "true" } else { "false" }; self.compiler @@ -873,6 +883,8 @@ impl Config { /// # Errors /// /// This method can fail if the flag's name does not exist. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub unsafe fn cranelift_flag_enable(&mut self, flag: &str) -> Result<&mut Self> { self.compiler.enable(flag)?; Ok(self) @@ -891,6 +903,8 @@ impl Config { /// /// This method can fail if the flag's name does not exist, or the value is not appropriate for /// the flag type. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub unsafe fn cranelift_flag_set(&mut self, name: &str, value: &str) -> Result<&mut Self> { self.compiler.set(name, value)?; Ok(self) @@ -1203,17 +1217,6 @@ impl Config { self } - pub(crate) fn build_compiler(&self, allocator: &dyn InstanceAllocator) -> Compiler { - let mut tunables = self.tunables.clone(); - allocator.adjust_tunables(&mut tunables); - Compiler::new( - &*self.compiler, - tunables, - self.features, - self.parallel_compilation, - ) - } - pub(crate) fn build_allocator(&self) -> Result> { #[cfg(feature = "async")] let stack_size = self.async_stack_size; @@ -1241,6 +1244,19 @@ impl Config { } } +#[cfg(compiler)] +fn compiler_builder(strategy: Strategy) -> Result> { + match strategy { + Strategy::Auto | Strategy::Cranelift => Ok(wasmtime_cranelift::builder()), + #[cfg(feature = "lightbeam")] + Strategy::Lightbeam => unimplemented!(), + #[cfg(not(feature = "lightbeam"))] + Strategy::Lightbeam => { + anyhow::bail!("lightbeam compilation strategy wasn't enabled at compile time"); + } + } +} + fn round_up_to_pages(val: u64) -> u64 { let page_size = region::page::size() as u64; debug_assert!(page_size.is_power_of_two()); @@ -1258,6 +1274,7 @@ impl Default for Config { impl Clone for Config { fn clone(&self) -> Config { Config { + #[cfg(compiler)] compiler: self.compiler.clone(), tunables: self.tunables.clone(), #[cfg(feature = "cache")] @@ -1279,8 +1296,8 @@ impl Clone for Config { impl fmt::Debug for Config { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Config") - .field("debug_info", &self.tunables.generate_native_debuginfo) + let mut f = f.debug_struct("Config"); + f.field("debug_info", &self.tunables.generate_native_debuginfo) .field("parse_wasm_debuginfo", &self.tunables.parse_wasm_debuginfo) .field("wasm_threads", &self.features.threads) .field("wasm_reference_types", &self.features.reference_types) @@ -1305,9 +1322,12 @@ impl fmt::Debug for Config { "guard_before_linear_memory", &self.tunables.guard_before_linear_memory, ) - .field("parallel_compilation", &self.parallel_compilation) - .field("compiler", &self.compiler) - .finish() + .field("parallel_compilation", &self.parallel_compilation); + #[cfg(compiler)] + { + f.field("compiler", &self.compiler); + } + f.finish() } } diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index f0c615fb2d..ab37628a6b 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -1,10 +1,11 @@ use crate::signatures::SignatureRegistry; use crate::{Config, Trap}; use anyhow::Result; +#[cfg(feature = "parallel-compilation")] +use rayon::prelude::*; use std::sync::Arc; #[cfg(feature = "cache")] use wasmtime_cache::CacheConfig; -use wasmtime_jit::Compiler; use wasmtime_runtime::{debug_builtins, InstanceAllocator}; /// An `Engine` which is a global context for compilation and management of wasm @@ -36,7 +37,8 @@ pub struct Engine { struct EngineInner { config: Config, - compiler: Compiler, + #[cfg(compiler)] + compiler: Box, allocator: Box, signatures: SignatureRegistry, } @@ -50,13 +52,17 @@ impl Engine { // as configuring signals, vectored exception handlers, etc. wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_pc); debug_builtins::ensure_exported(); - let allocator = config.build_allocator()?; + let registry = SignatureRegistry::new(); + let mut config = config.clone(); + let allocator = config.build_allocator()?; + allocator.adjust_tunables(&mut config.tunables); Ok(Engine { inner: Arc::new(EngineInner { - config: config.clone(), - compiler: config.build_compiler(allocator.as_ref()), + #[cfg(compiler)] + compiler: config.compiler.build(), + config, allocator, signatures: registry, }), @@ -90,8 +96,9 @@ impl Engine { &self.inner.config } - pub(crate) fn compiler(&self) -> &Compiler { - &self.inner.compiler + #[cfg(compiler)] + pub(crate) fn compiler(&self) -> &dyn wasmtime_environ::Compiler { + &*self.inner.compiler } pub(crate) fn allocator(&self) -> &dyn InstanceAllocator { @@ -134,20 +141,39 @@ impl Engine { /// /// [binary]: https://webassembly.github.io/spec/core/binary/index.html /// [text]: https://webassembly.github.io/spec/core/text/index.html + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn precompile_module(&self, bytes: &[u8]) -> Result> { - const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); - #[cfg(feature = "wat")] let bytes = wat::parse_bytes(&bytes)?; + let (_, artifacts, types) = crate::Module::build_artifacts(self, &bytes)?; + crate::module::SerializedModule::from_artifacts(self, &artifacts, &types).to_bytes() + } - let (_, artifacts, types) = wasmtime_jit::CompilationArtifacts::build( - &self.inner.compiler, - &bytes, - USE_PAGED_MEM_INIT, - )?; + pub(crate) fn run_maybe_parallel< + A: Send, + B: Send, + E: Send, + F: Fn(A) -> Result + Send + Sync, + >( + &self, + input: Vec, + f: F, + ) -> Result, E> { + if self.config().parallel_compilation { + #[cfg(feature = "parallel-compilation")] + return input + .into_par_iter() + .map(|a| f(a)) + .collect::, E>>(); + } - crate::module::SerializedModule::from_artifacts(&self.inner.compiler, &artifacts, &types) - .to_bytes() + // In case the parallel-compilation feature is disabled or the parallel_compilation config + // was turned off dynamically fallback to the non-parallel version. + input + .into_iter() + .map(|a| f(a)) + .collect::, E>>() } } diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index aa5c0e579e..3bb4391e12 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -300,6 +300,8 @@ impl Func { /// /// For more information about `Send + Sync + 'static` requirements on the /// `func`, see [`Func::wrap`](#why-send--sync--static). + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn new( mut store: impl AsContextMut, ty: FuncType, @@ -378,8 +380,8 @@ impl Func { /// # Ok(()) /// # } /// ``` - #[cfg(feature = "async")] - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + #[cfg(all(feature = "async", feature = "cranelift"))] + #[cfg_attr(nightlydoc, doc(cfg(all(feature = "async", feature = "cranelift"))))] pub fn new_async(store: impl AsContextMut, ty: FuncType, func: F) -> Func where F: for<'a> Fn( @@ -1883,6 +1885,7 @@ pub(crate) struct HostFunc { impl HostFunc { /// Analog of [`Func::new`] + #[cfg(compiler)] pub fn new( engine: &Engine, ty: FuncType, diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index df8e7a0b2a..b0f7bde3eb 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -234,6 +234,13 @@ //! crate. Be sure to check the API you're using to see if any crate features //! are enabled. //! +//! * `cranelift` - Enabled by default, this features enables using Cranelift at +//! runtime to compile a WebAssembly module to native code. This feature is +//! required to process and compile new WebAssembly modules. If this feature +//! is disabled then the only way to create a [`Module`] is to use the +//! [`Module::deserialize`] function with a precompiled artifact (typically +//! compiled with the same version of Wasmtime, just somewhere else). +//! //! * `cache` - Enabled by default, this feature adds support for wasmtime to //! perform internal caching of modules in a global location. This must still //! be enabled explicitly through [`Config::cache_config_load`] or @@ -358,7 +365,7 @@ //! ``` #![allow(unknown_lints)] -#![deny(missing_docs, broken_intra_doc_links)] +#![deny(missing_docs, rustdoc::broken_intra_doc_links)] #![doc(test(attr(deny(warnings))))] #![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))] #![cfg_attr(nightlydoc, feature(doc_cfg))] diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 69c596b31b..2f99c7004b 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -289,6 +289,8 @@ impl Linker { /// Creates a [`Func::new`]-style function named in this linker. /// /// For more information see [`Linker::func_wrap`]. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn func_new( &mut self, module: &str, @@ -305,8 +307,8 @@ impl Linker { /// Creates a [`Func::new_async`]-style function named in this linker. /// /// For more information see [`Linker::func_wrap`]. - #[cfg(feature = "async")] - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + #[cfg(all(feature = "async", feature = "cranelift"))] + #[cfg_attr(nightlydoc, doc(cfg(all(feature = "async", feature = "cranelift"))))] pub fn func_new_async( &mut self, module: &str, @@ -587,6 +589,8 @@ impl Linker { /// # Ok(()) /// # } /// ``` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn module( &mut self, mut store: impl AsContextMut, @@ -655,8 +659,8 @@ impl Linker { /// Define automatic instantiations of a [`Module`] in this linker. /// /// This is the same as [`Linker::module`], except for async `Store`s. - #[cfg(feature = "async")] - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + #[cfg(all(feature = "async", feature = "cranelift"))] + #[cfg_attr(nightlydoc, doc(cfg(all(feature = "async", feature = "cranelift"))))] pub async fn module_async( &mut self, mut store: impl AsContextMut, diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index b523ec326a..199e227c90 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -5,12 +5,11 @@ use crate::{ use crate::{Engine, ModuleType}; use anyhow::{bail, Context, Result}; use std::fs; +use std::mem; use std::path::Path; use std::sync::Arc; use wasmparser::Validator; -#[cfg(feature = "cache")] -use wasmtime_cache::ModuleCacheEntry; -use wasmtime_environ::{ModuleIndex, PrimaryMap}; +use wasmtime_environ::{ModuleEnvironment, ModuleIndex, PrimaryMap}; use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; mod registry; @@ -176,6 +175,8 @@ impl Module { /// # Ok(()) /// # } /// ``` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { let bytes = bytes.as_ref(); #[cfg(feature = "wat")] @@ -187,6 +188,8 @@ impl Module { /// data. The provided `name` will be used in traps/backtrace details. /// /// See [`Module::new`] for other details. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result { let mut module = Self::new(engine, bytes.as_ref())?; Arc::get_mut(&mut Arc::get_mut(&mut module.inner).unwrap().module) @@ -225,6 +228,8 @@ impl Module { /// # Ok(()) /// # } /// ``` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn from_file(engine: &Engine, file: impl AsRef) -> Result { match Self::new( engine, @@ -276,9 +281,11 @@ impl Module { /// # Ok(()) /// # } /// ``` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { // Check to see that the config's target matches the host - let target = engine.compiler().compiler().triple(); + let target = engine.compiler().triple(); if *target != target_lexicon::Triple::host() { bail!( "target '{}' specified in the configuration does not match the host", @@ -290,40 +297,111 @@ impl Module { // would be inferred for the host, otherwise the JIT might produce unrunnable code // for the features the host's CPU actually has. - const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux")); - cfg_if::cfg_if! { if #[cfg(feature = "cache")] { - let (main_module, artifacts, types) = ModuleCacheEntry::new( + let (main_module, artifacts, types) = wasmtime_cache::ModuleCacheEntry::new( "wasmtime", engine.cache_config(), ) - .get_data((engine.compiler(), binary), |(compiler, binary)| { - CompilationArtifacts::build( - compiler, - binary, - USE_PAGED_MEM_INIT, - ) + .get_data((HashedEngineCompileEnv(engine), binary), |(engine, binary)| { + Module::build_artifacts(engine.0, binary) })?; } else { let (main_module, artifacts, types) = - CompilationArtifacts::build( - engine.compiler(), - binary, - USE_PAGED_MEM_INIT, - )?; + Module::build_artifacts(engine, binary)?; } }; - let modules = CompiledModule::from_artifacts_list( - artifacts, - &*engine.config().profiler, - engine.compiler(), - )?; + let modules = engine.run_maybe_parallel(artifacts, |a| { + CompiledModule::from_artifacts(a, &*engine.config().profiler) + })?; Self::from_parts(engine, modules, main_module, Arc::new(types), &[]) } + /// Converts an input binary-encoded WebAssembly module to compilation + /// artifacts and type information. + /// + /// This is where compilation actually happens of WebAssembly modules and + /// translation/parsing/validation of the binary input occurs. The actual + /// result here is a triple of: + /// + /// * The index into the second field of the "main module". The "main + /// module" in this case is the outermost module described by the `wasm` + /// input, and is here for the module linking proposal. + /// * A list of `CompilationArtifacts` for each module found within `wasm`. + /// Note that if module linking is disabled then this list will always + /// have a size of exactly 1. + /// * Type information about all the modules returned. All returned modules + /// have local type information with indices that refer to these returned + /// tables. + #[cfg(compiler)] + pub(crate) fn build_artifacts( + engine: &Engine, + wasm: &[u8], + ) -> Result<(usize, Vec, TypeTables)> { + let tunables = &engine.config().tunables; + + // First a `ModuleEnvironment` is created which records type information + // about the wasm module. This is where the WebAssembly is parsed and + // validated. Afterwards `types` will have all the type information for + // this module. + let (main_module, translations, types) = + ModuleEnvironment::new(tunables, &engine.config().features) + .translate(wasm) + .context("failed to parse WebAssembly module")?; + + // Perform a two-level map/reduce here to get the final list of + // compilation artifacts. The first level of map/reduce maps over all + // modules found and reduces to collection into a vector. The second + // level of map/reduce here maps over all functions within each wasm + // module found and collects into an ELF image via `emit_obj`. + let list = engine.run_maybe_parallel(translations, |mut translation| -> Result<_> { + let functions = mem::take(&mut translation.function_body_inputs); + let functions = functions.into_iter().collect::>(); + + let funcs = engine + .run_maybe_parallel(functions, |(index, func)| { + engine + .compiler() + .compile_function(&translation, index, func, tunables, &types) + })? + .into_iter() + .collect(); + + let (obj, funcs) = engine.compiler().emit_obj( + &translation, + &types, + funcs, + tunables.generate_native_debuginfo, + )?; + + // If configured, attempt to use paged memory initialization + // instead of the default mode of memory initialization + if cfg!(all(feature = "uffd", target_os = "linux")) { + if let Some(init) = translation + .module + .memory_initialization + .to_paged(&translation.module) + { + translation.module.memory_initialization = init; + } + } + + Ok(CompilationArtifacts::new(translation, obj, funcs, tunables)) + })?; + + Ok(( + main_module, + list, + TypeTables { + wasm_signatures: types.wasm_signatures, + module_signatures: types.module_signatures, + instance_signatures: types.instance_signatures, + }, + )) + } + /// Deserializes an in-memory compiled module previously created with /// [`Module::serialize`] or [`Engine::precompile_module`]. /// @@ -499,6 +577,8 @@ impl Module { /// /// Use `Module::new` or `Module::from_binary` to create the module /// from the bytes. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn serialize(&self) -> Result> { SerializedModule::new(self).to_bytes() } @@ -794,3 +874,41 @@ fn _assert_send_sync() { fn _assert() {} _assert::(); } + +/// This is a helper struct used when caching to hash the state of an `Engine` +/// used for module compilation. +/// +/// The hash computed for this structure is used to key the global wasmtime +/// cache and dictates whether artifacts are reused. Consequently the contents +/// of this hash dictate when artifacts are or aren't re-used. +#[cfg(all(feature = "cache", compiler))] +struct HashedEngineCompileEnv<'a>(&'a Engine); + +#[cfg(all(feature = "cache", compiler))] +impl std::hash::Hash for HashedEngineCompileEnv<'_> { + fn hash(&self, hasher: &mut H) { + use std::collections::BTreeMap; + + // Hash the compiler's state based on its target and configuration. + let compiler = self.0.compiler(); + compiler.triple().hash(hasher); + compiler + .flags() + .into_iter() + .collect::>() + .hash(hasher); + compiler + .isa_flags() + .into_iter() + .collect::>() + .hash(hasher); + + // Hash configuration state read for compilation + let config = self.0.config(); + config.tunables.hash(hasher); + config.features.hash(hasher); + + // Catch accidental bugs of reusing across crate versions. + env!("CARGO_PKG_VERSION").hash(hasher); + } +} diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 9b68dc58f3..0211a2f2c1 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; -use wasmtime_environ::{FlagValue, Tunables}; -use wasmtime_jit::{CompilationArtifacts, CompiledModule, Compiler, TypeTables}; +use wasmtime_environ::{Compiler, FlagValue, Tunables}; +use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; const HEADER: &[u8] = b"\0wasmtime-aot"; @@ -162,8 +162,8 @@ pub struct SerializedModule<'a> { } impl<'a> SerializedModule<'a> { + #[cfg(compiler)] pub fn new(module: &'a Module) -> Self { - let compiler = module.engine().compiler(); let artifacts = module .inner .artifact_upvars @@ -181,38 +181,40 @@ impl<'a> SerializedModule<'a> { .collect::>(); Self::with_data( - compiler, + module.engine(), artifacts, module_upvars, MyCow::Borrowed(module.types()), ) } + #[cfg(compiler)] pub fn from_artifacts( - compiler: &Compiler, + engine: &Engine, artifacts: &'a Vec, types: &'a TypeTables, ) -> Self { Self::with_data( - compiler, + engine, artifacts.iter().map(MyCow::Borrowed).collect(), Vec::new(), MyCow::Borrowed(types), ) } + #[cfg(compiler)] fn with_data( - compiler: &Compiler, + engine: &Engine, artifacts: Vec>, module_upvars: Vec, types: MyCow<'a, TypeTables>, ) -> Self { Self { - target: compiler.triple().to_string(), - shared_flags: compiler.compiler().flags(), - isa_flags: compiler.compiler().isa_flags(), - tunables: compiler.tunables().clone(), - features: compiler.features().into(), + target: engine.compiler().triple().to_string(), + shared_flags: engine.compiler().flags(), + isa_flags: engine.compiler().isa_flags(), + tunables: engine.config().tunables.clone(), + features: (&engine.config().features).into(), artifacts, module_upvars, types, @@ -220,22 +222,32 @@ impl<'a> SerializedModule<'a> { } pub fn into_module(mut self, engine: &Engine) -> Result { - let compiler = engine.compiler(); + // Verify that the module we're loading matches the triple that `engine` + // is configured for. If compilation is disabled within engine then the + // assumed triple is the host itself. + #[cfg(compiler)] + let engine_triple = engine.compiler().triple(); + #[cfg(not(compiler))] + let engine_triple = &target_lexicon::Triple::host(); + self.check_triple(engine_triple)?; - self.check_triple(compiler)?; - self.check_shared_flags(compiler)?; - self.check_isa_flags(compiler)?; - self.check_tunables(compiler)?; - self.check_features(compiler)?; + // FIXME: Similar to `Module::from_binary` it should likely be validated + // here that when `cfg(not(compiler))` is true the isa/shared flags + // enabled for this precompiled module are compatible with the host + // itself, which `engine` is assumed to be running code for. + #[cfg(compiler)] + { + let compiler = engine.compiler(); + self.check_shared_flags(compiler)?; + self.check_isa_flags(compiler)?; + } - let modules = CompiledModule::from_artifacts_list( - self.artifacts - .into_iter() - .map(|i| i.unwrap_owned()) - .collect(), - &*engine.config().profiler, - engine.compiler(), - )?; + self.check_tunables(&engine.config().tunables)?; + self.check_features(&engine.config().features)?; + + let modules = engine.run_maybe_parallel(self.artifacts, |i| { + CompiledModule::from_artifacts(i.unwrap_owned(), &*engine.config().profiler) + })?; assert!(!modules.is_empty()); @@ -304,17 +316,17 @@ impl<'a> SerializedModule<'a> { .context("deserialize compilation artifacts")?) } - fn check_triple(&self, compiler: &Compiler) -> Result<()> { + fn check_triple(&self, other: &target_lexicon::Triple) -> Result<()> { let triple = target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?; - if triple.architecture != compiler.triple().architecture { + if triple.architecture != other.architecture { bail!( "Module was compiled for architecture '{}'", triple.architecture ); } - if triple.operating_system != compiler.triple().operating_system { + if triple.operating_system != other.operating_system { bail!( "Module was compiled for operating system '{}'", triple.operating_system @@ -324,9 +336,9 @@ impl<'a> SerializedModule<'a> { Ok(()) } - fn check_shared_flags(&mut self, compiler: &Compiler) -> Result<()> { + fn check_shared_flags(&mut self, compiler: &dyn Compiler) -> Result<()> { let mut shared_flags = std::mem::take(&mut self.shared_flags); - for (name, host) in compiler.compiler().flags() { + for (name, host) in compiler.flags() { match shared_flags.remove(&name) { Some(v) => { if v != host { @@ -347,9 +359,9 @@ impl<'a> SerializedModule<'a> { Ok(()) } - fn check_isa_flags(&mut self, compiler: &Compiler) -> Result<()> { + fn check_isa_flags(&mut self, compiler: &dyn Compiler) -> Result<()> { let mut isa_flags = std::mem::take(&mut self.isa_flags); - for (name, host) in compiler.compiler().isa_flags() { + for (name, host) in compiler.isa_flags() { match isa_flags.remove(&name) { Some(v) => match (&v, &host) { (FlagValue::Bool(v), FlagValue::Bool(host)) => { @@ -406,7 +418,7 @@ impl<'a> SerializedModule<'a> { ); } - fn check_tunables(&self, compiler: &Compiler) -> Result<()> { + fn check_tunables(&mut self, other: &Tunables) -> Result<()> { let Tunables { static_memory_bound, static_memory_offset_guard_size, @@ -419,8 +431,6 @@ impl<'a> SerializedModule<'a> { guard_before_linear_memory, } = self.tunables; - let other = compiler.tunables(); - Self::check_int( static_memory_bound, other.static_memory_bound, @@ -462,7 +472,7 @@ impl<'a> SerializedModule<'a> { Ok(()) } - fn check_features(&self, compiler: &Compiler) -> Result<()> { + fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> { let WasmFeatures { reference_types, multi_value, @@ -477,7 +487,6 @@ impl<'a> SerializedModule<'a> { memory64, } = self.features; - let other = compiler.features(); Self::check_bool( reference_types, other.reference_types, diff --git a/crates/wasmtime/src/trampoline.rs b/crates/wasmtime/src/trampoline.rs index 1cd93931db..215d24106e 100644 --- a/crates/wasmtime/src/trampoline.rs +++ b/crates/wasmtime/src/trampoline.rs @@ -7,7 +7,7 @@ mod table; pub(crate) use memory::MemoryCreatorProxy; -pub use self::func::{create_function, create_raw_function}; +pub use self::func::*; use self::global::create_global; use self::memory::create_memory; use self::table::create_table; diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index c59de1eddb..9f60c166c2 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -70,13 +70,13 @@ unsafe extern "C" fn stub_fn( } } +#[cfg(compiler)] pub fn create_function( ft: &FuncType, func: Box Result<(), Trap> + Send + Sync>, engine: &Engine, ) -> Result<(InstanceHandle, VMTrampoline)> { let obj = engine - .compiler() .compiler() .emit_trampoline_obj(ft.as_wasm_func_type(), stub_fn as usize)?; diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index a010884a15..7d7d8f7825 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" [dependencies] anyhow = "1.0.19" -wasmtime = { path = "../wasmtime", version = "0.29.0", default-features = false } +wasmtime = { path = "../wasmtime", version = "0.29.0", default-features = false, features = ['cranelift'] } wast = "37.0.0" [badges] diff --git a/src/commands/settings.rs b/src/commands/settings.rs index b063569029..172ae6cba0 100644 --- a/src/commands/settings.rs +++ b/src/commands/settings.rs @@ -5,7 +5,6 @@ use std::collections::BTreeMap; use std::str::FromStr; use structopt::StructOpt; use wasmtime_environ::{FlagValue, Setting, SettingKind}; -use wasmtime_jit::Compiler; /// Displays available Cranelift settings for a target. #[derive(StructOpt)] @@ -19,7 +18,7 @@ pub struct SettingsCommand { impl SettingsCommand { /// Executes the command. pub fn execute(self) -> Result<()> { - let mut builder = Compiler::builder(wasmtime_jit::CompilationStrategy::Auto); + let mut builder = wasmtime_cranelift::builder(); if let Some(target) = &self.target { let target = target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?; builder.target(target)?; diff --git a/src/obj.rs b/src/obj.rs index 8059d52f49..876b6b709b 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -1,9 +1,9 @@ use anyhow::{bail, Context as _, Result}; +use std::mem; use target_lexicon::Triple; use wasmparser::WasmFeatures; use wasmtime::Strategy; -use wasmtime_environ::{ModuleEnvironment, Tunables}; -use wasmtime_jit::Compiler; +use wasmtime_environ::{ModuleEnvironment, PrimaryMap, Tunables}; /// Creates object file from binary wasm data. pub fn compile_to_obj( @@ -14,16 +14,11 @@ pub fn compile_to_obj( opt_level: wasmtime::OptLevel, debug_info: bool, ) -> Result> { - let strategy = match strategy { - Strategy::Auto => wasmtime_jit::CompilationStrategy::Auto, - Strategy::Cranelift => wasmtime_jit::CompilationStrategy::Cranelift, - #[cfg(feature = "lightbeam")] - Strategy::Lightbeam => wasmtime_jit::CompilationStrategy::Lightbeam, - #[cfg(not(feature = "lightbeam"))] - Strategy::Lightbeam => bail!("lightbeam support not enabled"), - s => bail!("unknown compilation strategy {:?}", s), - }; - let mut builder = Compiler::builder(strategy); + match strategy { + Strategy::Cranelift | Strategy::Auto => {} + other => panic!("unsupported strategy {:?}", other), + } + let mut builder = wasmtime_cranelift::builder(); if let Some(target) = target { builder.target(target.clone())?; } @@ -50,12 +45,21 @@ pub fn compile_to_obj( tunables.generate_native_debuginfo = debug_info; tunables.parse_wasm_debuginfo = debug_info; - let compiler = Compiler::new(&*builder, tunables.clone(), features.clone(), true); + let compiler = builder.build(); let environ = ModuleEnvironment::new(&tunables, &features); let (_main_module, mut translation, types) = environ .translate(wasm) .context("failed to translate module")?; assert_eq!(translation.len(), 1); - let compilation = compiler.compile(&mut translation[0], &types)?; - Ok(compilation.obj) + let mut funcs = PrimaryMap::default(); + for (index, func) in mem::take(&mut translation[0].function_body_inputs) { + funcs.push(compiler.compile_function(&translation[0], index, func, &tunables, &types)?); + } + let (obj, _) = compiler.emit_obj( + &translation[0], + &types, + funcs, + tunables.generate_native_debuginfo, + )?; + Ok(obj) }