Files
wasmtime/crates/jit/src/compiler.rs
Alex Crichton 503129ad91 Add a method to share Config across machines (#2608)
With `Module::{serialize,deserialize}` it should be possible to share
wasmtime modules across machines or CPUs. Serialization, however, embeds
a hash of all configuration values, including cranelift compilation
settings. By default wasmtime's selection of the native ISA would enable
ISA flags according to CPU features available on the host, but the same
CPU features may not be available across two machines.

This commit adds a `Config::cranelift_clear_cpu_flags` method which
allows clearing the target-specific ISA flags that are automatically
inferred by default for the native CPU. Options can then be
incrementally built back up as-desired with teh `cranelift_other_flag`
method.
2021-01-26 15:59:12 -06:00

199 lines
5.8 KiB
Rust

//! JIT compilation.
use crate::instantiate::SetupError;
use crate::object::{build_object, ObjectUnwindInfo};
use object::write::Object;
#[cfg(feature = "parallel-compilation")]
use rayon::prelude::*;
use std::hash::{Hash, Hasher};
use std::mem;
use wasmparser::WasmFeatures;
use wasmtime_debug::{emit_dwarf, DwarfSection};
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
use wasmtime_environ::wasm::{DefinedMemoryIndex, MemoryIndex};
use wasmtime_environ::{
CompiledFunctions, Compiler as EnvCompiler, DebugInfoData, Module, ModuleMemoryOffset,
ModuleTranslation, Tunables, TypeTables, VMOffsets,
};
/// Select which kind of compilation to use.
#[derive(Copy, Clone, Debug, Hash)]
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.
///
/// A `Compiler` instance owns the executable memory that it allocates.
///
/// TODO: Evolve this to support streaming rather than requiring a `&[u8]`
/// containing a whole wasm module at once.
///
/// TODO: Consider using cranelift-module.
pub struct Compiler {
isa: Box<dyn TargetIsa>,
compiler: Box<dyn EnvCompiler>,
strategy: CompilationStrategy,
tunables: Tunables,
features: WasmFeatures,
}
impl Compiler {
/// Construct a new `Compiler`.
pub fn new(
isa: Box<dyn TargetIsa>,
strategy: CompilationStrategy,
tunables: Tunables,
features: WasmFeatures,
) -> Self {
Self {
isa,
strategy,
compiler: match strategy {
CompilationStrategy::Auto | CompilationStrategy::Cranelift => {
Box::new(wasmtime_cranelift::Cranelift::default())
}
#[cfg(feature = "lightbeam")]
CompilationStrategy::Lightbeam => Box::new(wasmtime_lightbeam::Lightbeam),
},
tunables,
features,
}
}
}
fn _assert_compiler_send_sync() {
fn _assert<T: Send + Sync>() {}
_assert::<Compiler>();
}
fn transform_dwarf_data(
isa: &dyn TargetIsa,
module: &Module,
debug_data: &DebugInfoData,
funcs: &CompiledFunctions,
) -> Result<Vec<DwarfSection>, SetupError> {
let target_config = isa.frontend_config();
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
let memory_offset = if ofs.num_imported_memories > 0 {
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
} else if ofs.num_defined_memories > 0 {
ModuleMemoryOffset::Defined(ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)))
} else {
ModuleMemoryOffset::None
};
emit_dwarf(isa, debug_data, funcs, &memory_offset).map_err(SetupError::DebugInfo)
}
#[allow(missing_docs)]
pub struct Compilation {
pub obj: Object,
pub unwind_info: Vec<ObjectUnwindInfo>,
pub funcs: CompiledFunctions,
}
impl Compiler {
/// Return the isa.
pub fn isa(&self) -> &dyn TargetIsa {
self.isa.as_ref()
}
/// Return the target's frontend configuration settings.
pub fn frontend_config(&self) -> TargetFrontendConfig {
self.isa.frontend_config()
}
/// 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
}
/// 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 = maybe_parallel!(functions.(into_iter | into_par_iter))
.map(|(index, func)| {
self.compiler.compile_function(
translation,
index,
func,
&*self.isa,
&self.tunables,
types,
)
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.collect::<CompiledFunctions>();
let dwarf_sections = if self.tunables.generate_native_debuginfo && !funcs.is_empty() {
transform_dwarf_data(
&*self.isa,
&translation.module,
&translation.debuginfo,
&funcs,
)?
} else {
vec![]
};
let (obj, unwind_info) =
build_object(&*self.isa, &translation, types, &funcs, dwarf_sections)?;
Ok(Compilation {
obj,
unwind_info,
funcs,
})
}
}
impl Hash for Compiler {
fn hash<H: Hasher>(&self, hasher: &mut H) {
let Compiler {
strategy,
compiler: _,
isa,
tunables,
features,
} = self;
// Hash compiler's flags: compilation strategy, isa, frontend config,
// misc tunables.
strategy.hash(hasher);
isa.triple().hash(hasher);
isa.hash_all_flags(hasher);
isa.frontend_config().hash(hasher);
tunables.hash(hasher);
features.hash(hasher);
// Catch accidental bugs of reusing across crate versions.
env!("CARGO_PKG_VERSION").hash(hasher);
// TODO: ... and should we hash anything else? There's a lot of stuff in
// `TargetIsa`, like registers/encodings/etc. Should we be hashing that
// too? It seems like wasmtime doesn't configure it too too much, but
// this may become an issue at some point.
}
}