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
This commit is contained in:
Alex Crichton
2021-08-18 16:47:47 -05:00
committed by GitHub
parent 7a19b8fe2c
commit ddfadaeb38
22 changed files with 418 additions and 436 deletions

View File

@@ -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" }

View File

@@ -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<dyn EnvCompiler>,
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<dyn CompilerBuilder> {
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<T: Send + Sync>() {}
_assert::<Compiler>();
}
#[allow(missing_docs)]
pub struct Compilation {
pub obj: Vec<u8>,
pub funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
}
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<Compilation, SetupError> {
let functions = mem::take(&mut translation.function_body_inputs);
let functions = functions.into_iter().collect::<Vec<_>>();
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<B, E> + Send + Sync,
>(
&self,
input: Vec<A>,
f: F,
) -> Result<Vec<B>, E> {
if self.parallel_compilation {
#[cfg(feature = "parallel-compilation")]
return input
.into_par_iter()
.map(|a| f(a))
.collect::<Result<Vec<B>, 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::<Result<Vec<B>, E>>()
}
}
impl Hash for Compiler {
fn hash<H: Hasher>(&self, hasher: &mut H) {
let Compiler {
compiler,
tunables,
features,
parallel_compilation: _,
} = self;
compiler.triple().hash(hasher);
compiler
.flags()
.into_iter()
.collect::<BTreeMap<_, _>>()
.hash(hasher);
compiler
.isa_flags()
.into_iter()
.collect::<BTreeMap<_, _>>()
.hash(hasher);
tunables.hash(hasher);
features.hash(hasher);
// Catch accidental bugs of reusing across crate versions.
env!("CARGO_PKG_VERSION").hash(hasher);
}
}

View File

@@ -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<CompilationArtifacts>, 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<u8>,
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
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<CompilationArtifacts>,
profiler: &dyn ProfilingAgent,
compiler: &Compiler,
) -> Result<Vec<Arc<Self>>, SetupError> {
compiler.run_maybe_parallel(artifacts, |a| CompiledModule::from_artifacts(a, profiler))
}
/// Creates `CompiledModule` directly from `CompilationArtifacts`.
pub fn from_artifacts(
artifacts: CompilationArtifacts,

View File

@@ -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,
};