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:
@@ -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" }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user