Move compilation into Module from Instance. (#822)

* Move compilation into Module from Instance.

* Fix fuzzing

* Use wasmtime::Module in fuzzing crates

Instead of wasmtime_jit.

* Compile eagerly.

* Review fixes.

* Always use the saved name.

* Preserve the former behavior for fuzzing oracle
This commit is contained in:
Sergei Pepyakin
2020-01-16 23:37:10 +01:00
committed by Alex Crichton
parent e474a9e822
commit 5b8be5f262
12 changed files with 378 additions and 413 deletions

3
Cargo.lock generated
View File

@@ -2166,7 +2166,6 @@ dependencies = [
"log", "log",
"wasmtime", "wasmtime",
"wasmtime-fuzzing", "wasmtime-fuzzing",
"wasmtime-jit",
] ]
[[package]] [[package]]
@@ -2181,8 +2180,6 @@ dependencies = [
"wasmparser 0.47.0", "wasmparser 0.47.0",
"wasmprinter", "wasmprinter",
"wasmtime", "wasmtime",
"wasmtime-environ",
"wasmtime-jit",
"wat", "wat",
] ]

View File

@@ -20,28 +20,14 @@ impl Resolver for SimpleResolver<'_> {
} }
} }
fn instantiate_in_context( fn instantiate(
store: &Store, compiled_module: &CompiledModule,
data: &[u8],
imports: &[Extern], imports: &[Extern],
module_name: Option<&str>, ) -> Result<InstanceHandle, Error> {
) -> Result<InstanceHandle> {
let mut resolver = SimpleResolver { imports }; let mut resolver = SimpleResolver { imports };
let mut compiled_module = CompiledModule::new( let instance = compiled_module
&mut store.compiler_mut(), .instantiate(&mut resolver)
data, .map_err(|e| -> Error {
module_name,
&mut resolver,
store.global_exports().clone(),
store.engine().config().debug_info,
)?;
// Register all module signatures
for signature in compiled_module.module().signatures.values() {
store.register_wasmtime_signature(signature);
}
let instance = compiled_module.instantiate().map_err(|e| -> Error {
if let Some(trap) = take_api_trap() { if let Some(trap) = take_api_trap() {
trap.into() trap.into()
} else if let InstantiationError::StartTrap(trap) = e { } else if let InstantiationError::StartTrap(trap) = e {
@@ -63,12 +49,8 @@ pub struct Instance {
impl Instance { impl Instance {
pub fn new(module: &Module, externs: &[Extern]) -> Result<Instance, Error> { pub fn new(module: &Module, externs: &[Extern]) -> Result<Instance, Error> {
let store = module.store(); let store = module.store();
let mut instance_handle = instantiate_in_context( let mut instance_handle =
store, instantiate(module.compiled_module().expect("compiled_module"), externs)?;
module.binary().expect("binary"),
externs,
module.name(),
)?;
let exports = { let exports = {
let mut exports = Vec::with_capacity(module.exports().len()); let mut exports = Vec::with_capacity(module.exports().len());

View File

@@ -9,6 +9,7 @@ use wasmparser::{
validate, CustomSectionKind, ExternalKind, ImportSectionEntryType, ModuleReader, Name, validate, CustomSectionKind, ExternalKind, ImportSectionEntryType, ModuleReader, Name,
OperatorValidatorConfig, SectionCode, ValidatingParserConfig, OperatorValidatorConfig, SectionCode, ValidatingParserConfig,
}; };
use wasmtime_jit::CompiledModule;
fn into_memory_type(mt: wasmparser::MemoryType) -> MemoryType { fn into_memory_type(mt: wasmparser::MemoryType) -> MemoryType {
assert!(!mt.shared); assert!(!mt.shared);
@@ -56,12 +57,6 @@ fn into_table_type(tt: wasmparser::TableType) -> TableType {
TableType::new(ty, limits) TableType::new(ty, limits)
} }
#[derive(Clone)]
pub(crate) enum ModuleCodeSource {
Binary(Box<[u8]>),
Unknown,
}
/// A compiled WebAssembly module, ready to be instantiated. /// A compiled WebAssembly module, ready to be instantiated.
/// ///
/// A `Module` is a compiled in-memory representation of an input WebAssembly /// A `Module` is a compiled in-memory representation of an input WebAssembly
@@ -84,10 +79,10 @@ pub struct Module {
struct ModuleInner { struct ModuleInner {
store: Store, store: Store,
source: ModuleCodeSource,
imports: Box<[ImportType]>, imports: Box<[ImportType]>,
exports: Box<[ExportType]>, exports: Box<[ExportType]>,
name: Option<String>, name: Option<String>,
compiled: Option<CompiledModule>,
} }
impl Module { impl Module {
@@ -131,7 +126,7 @@ impl Module {
// Note that the call to `unsafe` here should be ok because we // Note that the call to `unsafe` here should be ok because we
// previously validated the binary, meaning we're guaranteed to pass a // previously validated the binary, meaning we're guaranteed to pass a
// valid binary for `store`. // valid binary for `store`.
unsafe { Module::new_unchecked(store, binary) } unsafe { Module::new_internal(store, binary, None) }
} }
/// Creates a new WebAssembly `Module` from the given in-memory `binary` /// Creates a new WebAssembly `Module` from the given in-memory `binary`
@@ -139,9 +134,11 @@ impl Module {
/// ///
/// See [`Module::new`] for other details. /// See [`Module::new`] for other details.
pub fn new_with_name(store: &Store, binary: &[u8], name: &str) -> Result<Module> { pub fn new_with_name(store: &Store, binary: &[u8], name: &str) -> Result<Module> {
let mut ret = Module::new(store, binary)?; Module::validate(store, binary)?;
Rc::get_mut(&mut ret.inner).unwrap().name = Some(name.to_string()); // Note that the call to `unsafe` here should be ok because we
Ok(ret) // previously validated the binary, meaning we're guaranteed to pass a
// valid binary for `store`.
unsafe { Module::new_internal(store, binary, Some(name)) }
} }
/// Creates a new WebAssembly `Module` from the given in-memory `binary` /// Creates a new WebAssembly `Module` from the given in-memory `binary`
@@ -171,8 +168,20 @@ impl Module {
/// be somewhat valid for decoding purposes, and the basics of decoding can /// be somewhat valid for decoding purposes, and the basics of decoding can
/// still fail. /// still fail.
pub unsafe fn new_unchecked(store: &Store, binary: &[u8]) -> Result<Module> { pub unsafe fn new_unchecked(store: &Store, binary: &[u8]) -> Result<Module> {
Module::new_internal(store, binary, None)
}
/// Creates a new `Module` and compiles it without doing any validation.
unsafe fn new_internal(store: &Store, binary: &[u8], name: Option<&str>) -> Result<Module> {
let mut ret = Module::empty(store); let mut ret = Module::empty(store);
ret.read_imports_and_exports(binary)?; ret.read_imports_and_exports(binary)?;
let inner = Rc::get_mut(&mut ret.inner).unwrap();
if let Some(name) = name {
// Assign or override the module's name if supplied.
inner.name = Some(name.to_string());
}
inner.compiled = Some(compile(store, binary, inner.name.as_deref())?);
Ok(ret) Ok(ret)
} }
@@ -219,19 +228,16 @@ impl Module {
Module { Module {
inner: Rc::new(ModuleInner { inner: Rc::new(ModuleInner {
store: store.clone(), store: store.clone(),
source: ModuleCodeSource::Unknown,
imports: Box::new([]), imports: Box::new([]),
exports: Box::new([]), exports: Box::new([]),
name: None, name: None,
compiled: None,
}), }),
} }
} }
pub(crate) fn binary(&self) -> Option<&[u8]> { pub(crate) fn compiled_module(&self) -> Option<&CompiledModule> {
match &self.inner.source { self.inner.compiled.as_ref()
ModuleCodeSource::Binary(b) => Some(b),
_ => None,
}
} }
/// Returns identifier/name that this [`Module`] has. This name /// Returns identifier/name that this [`Module`] has. This name
@@ -259,7 +265,6 @@ impl Module {
fn read_imports_and_exports(&mut self, binary: &[u8]) -> Result<()> { fn read_imports_and_exports(&mut self, binary: &[u8]) -> Result<()> {
let inner = Rc::get_mut(&mut self.inner).unwrap(); let inner = Rc::get_mut(&mut self.inner).unwrap();
inner.source = ModuleCodeSource::Binary(binary.into());
let mut reader = ModuleReader::new(binary)?; let mut reader = ModuleReader::new(binary)?;
let mut imports = Vec::new(); let mut imports = Vec::new();
let mut exports = Vec::new(); let mut exports = Vec::new();
@@ -387,3 +392,21 @@ impl Module {
Ok(()) Ok(())
} }
} }
fn compile(store: &Store, binary: &[u8], module_name: Option<&str>) -> Result<CompiledModule> {
let exports = store.global_exports().clone();
let compiled_module = CompiledModule::new(
&mut store.compiler_mut(),
binary,
module_name,
exports,
store.engine().config().debug_info,
)?;
// Register all module signatures
for signature in compiled_module.module().signatures.values() {
store.register_wasmtime_signature(signature);
}
Ok(compiled_module)
}

View File

@@ -17,8 +17,6 @@ log = "0.4.8"
wasmparser = "0.47.0" wasmparser = "0.47.0"
wasmprinter = "0.2.0" wasmprinter = "0.2.0"
wasmtime = { path = "../api", version = "0.9.0" } wasmtime = { path = "../api", version = "0.9.0" }
wasmtime-environ = { path = "../environ", version = "0.9.0" }
wasmtime-jit = { path = "../jit", version = "0.9.0" }
[dev-dependencies] [dev-dependencies]
wat = "1.0" wat = "1.0"

View File

@@ -13,25 +13,15 @@
pub mod dummy; pub mod dummy;
use dummy::{dummy_imports, dummy_value}; use dummy::{dummy_imports, dummy_value};
use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
use wasmtime::*; use wasmtime::*;
use wasmtime_environ::{isa, settings};
use wasmtime_jit::{native, CompilationStrategy, CompiledModule, Compiler, NullResolver};
fn host_isa() -> Box<dyn isa::TargetIsa> {
let flag_builder = settings::builder();
let isa_builder = native::builder();
isa_builder.finish(settings::Flags::new(flag_builder))
}
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
/// panic or segfault or anything else that can be detected "passively". /// panic or segfault or anything else that can be detected "passively".
/// ///
/// Performs initial validation, and returns early if the Wasm is invalid. /// Performs initial validation, and returns early if the Wasm is invalid.
/// ///
/// You can control which compiler is used via passing a `CompilationStrategy`. /// You can control which compiler is used via passing a `Strategy`.
pub fn instantiate(wasm: &[u8], strategy: Strategy) { pub fn instantiate(wasm: &[u8], strategy: Strategy) {
if wasmparser::validate(wasm, None).is_err() { if wasmparser::validate(wasm, None).is_err() {
return; return;
@@ -68,24 +58,13 @@ pub fn instantiate(wasm: &[u8], strategy: Strategy) {
/// ///
/// Performs initial validation, and returns early if the Wasm is invalid. /// Performs initial validation, and returns early if the Wasm is invalid.
/// ///
/// You can control which compiler is used via passing a `CompilationStrategy`. /// You can control which compiler is used via passing a `Strategy`.
pub fn compile(wasm: &[u8], compilation_strategy: CompilationStrategy) { pub fn compile(wasm: &[u8], strategy: Strategy) {
if wasmparser::validate(wasm, None).is_err() { let mut config = Config::new();
return; config.strategy(strategy).unwrap();
} let engine = Engine::new(&config);
let store = Store::new(&engine);
let isa = host_isa(); let _ = Module::new(&store, wasm);
let mut compiler = Compiler::new(isa, compilation_strategy);
let mut resolver = NullResolver {};
let global_exports = Rc::new(RefCell::new(HashMap::new()));
let _ = CompiledModule::new(
&mut compiler,
wasm,
None,
&mut resolver,
global_exports,
false,
);
} }
/// Invoke the given API calls. /// Invoke the given API calls.

View File

@@ -156,7 +156,6 @@ impl Context {
&mut *self.compiler, &mut *self.compiler,
data, data,
None, None,
&mut self.namespace,
Rc::clone(&self.global_exports), Rc::clone(&self.global_exports),
debug_info, debug_info,
) )

289
crates/jit/src/imports.rs Normal file
View File

@@ -0,0 +1,289 @@
//! Module imports resolving logic.
use crate::resolver::Resolver;
use more_asserts::assert_ge;
use std::collections::HashSet;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType};
use wasmtime_environ::{MemoryPlan, MemoryStyle, Module, TablePlan};
use wasmtime_runtime::{
Export, Imports, InstanceHandle, LinkError, VMFunctionImport, VMGlobalImport, VMMemoryImport,
VMTableImport,
};
/// This function allows to match all imports of a `Module` with concrete definitions provided by
/// a `Resolver`.
///
/// If all imports are satisfied returns an `Imports` instance required for a module instantiation.
pub fn resolve_imports(module: &Module, resolver: &mut dyn Resolver) -> Result<Imports, LinkError> {
let mut dependencies = HashSet::new();
let mut function_imports = PrimaryMap::with_capacity(module.imported_funcs.len());
for (index, (module_name, field, import_idx)) in module.imported_funcs.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Function {
address,
signature,
vmctx,
} => {
let import_signature = &module.signatures[module.functions[index]];
if signature != *import_signature {
// TODO: If the difference is in the calling convention,
// we could emit a wrapper function to fix it up.
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported function with signature {} \
incompatible with function import with signature {}",
module_name, field, signature, import_signature
)));
}
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
function_imports.push(VMFunctionImport {
body: address,
vmctx,
});
}
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: export incompatible with function import",
module_name, field
)));
}
},
None => {
return Err(LinkError(format!(
"{}/{}: unknown import function: function not provided",
module_name, field
)));
}
}
}
let mut table_imports = PrimaryMap::with_capacity(module.imported_tables.len());
for (index, (module_name, field, import_idx)) in module.imported_tables.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Table {
definition,
vmctx,
table,
} => {
let import_table = &module.table_plans[index];
if !is_table_compatible(&table, import_table) {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported table incompatible with \
table import",
module_name, field,
)));
}
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
table_imports.push(VMTableImport {
from: definition,
vmctx,
});
}
Export::Global { .. } | Export::Memory { .. } | Export::Function { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: export incompatible with table import",
module_name, field
)));
}
},
None => {
return Err(LinkError(format!(
"unknown import: no provided import table for {}/{}",
module_name, field
)));
}
}
}
let mut memory_imports = PrimaryMap::with_capacity(module.imported_memories.len());
for (index, (module_name, field, import_idx)) in module.imported_memories.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Memory {
definition,
vmctx,
memory,
} => {
let import_memory = &module.memory_plans[index];
if !is_memory_compatible(&memory, import_memory) {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported memory incompatible with \
memory import",
module_name, field
)));
}
// Sanity-check: Ensure that the imported memory has at least
// guard-page protections the importing module expects it to have.
if let (
MemoryStyle::Static { bound },
MemoryStyle::Static {
bound: import_bound,
},
) = (memory.style, &import_memory.style)
{
assert_ge!(bound, *import_bound);
}
assert_ge!(memory.offset_guard_size, import_memory.offset_guard_size);
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
memory_imports.push(VMMemoryImport {
from: definition,
vmctx,
});
}
Export::Table { .. } | Export::Global { .. } | Export::Function { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: export incompatible with memory import",
module_name, field
)));
}
},
None => {
return Err(LinkError(format!(
"unknown import: no provided import memory for {}/{}",
module_name, field
)));
}
}
}
let mut global_imports = PrimaryMap::with_capacity(module.imported_globals.len());
for (index, (module_name, field, import_idx)) in module.imported_globals.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Table { .. } | Export::Memory { .. } | Export::Function { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported global incompatible with \
global import",
module_name, field
)));
}
Export::Global {
definition,
vmctx,
global,
} => {
let imported_global = module.globals[index];
if !is_global_compatible(&global, &imported_global) {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported global incompatible with \
global import",
module_name, field
)));
}
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
global_imports.push(VMGlobalImport { from: definition });
}
},
None => {
return Err(LinkError(format!(
"unknown import: no provided import global for {}/{}",
module_name, field
)));
}
}
}
Ok(Imports::new(
dependencies,
function_imports,
table_imports,
memory_imports,
global_imports,
))
}
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
match imported.initializer {
GlobalInit::Import => (),
_ => panic!("imported Global should have an Imported initializer"),
}
let Global {
ty: exported_ty,
mutability: exported_mutability,
initializer: _exported_initializer,
} = exported;
let Global {
ty: imported_ty,
mutability: imported_mutability,
initializer: _imported_initializer,
} = imported;
exported_ty == imported_ty && imported_mutability == exported_mutability
}
fn is_table_element_type_compatible(
exported_type: TableElementType,
imported_type: TableElementType,
) -> bool {
match exported_type {
TableElementType::Func => match imported_type {
TableElementType::Func => true,
_ => false,
},
TableElementType::Val(exported_val_ty) => match imported_type {
TableElementType::Val(imported_val_ty) => exported_val_ty == imported_val_ty,
_ => false,
},
}
}
fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
let TablePlan {
table:
Table {
ty: exported_ty,
minimum: exported_minimum,
maximum: exported_maximum,
},
style: _exported_style,
} = exported;
let TablePlan {
table:
Table {
ty: imported_ty,
minimum: imported_minimum,
maximum: imported_maximum,
},
style: _imported_style,
} = imported;
is_table_element_type_compatible(*exported_ty, *imported_ty)
&& imported_minimum <= exported_minimum
&& (imported_maximum.is_none()
|| (!exported_maximum.is_none()
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
}
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
let MemoryPlan {
memory:
Memory {
minimum: exported_minimum,
maximum: exported_maximum,
shared: exported_shared,
},
style: _exported_style,
offset_guard_size: _exported_offset_guard_size,
} = exported;
let MemoryPlan {
memory:
Memory {
minimum: imported_minimum,
maximum: imported_maximum,
shared: imported_shared,
},
style: _imported_style,
offset_guard_size: _imported_offset_guard_size,
} = imported;
imported_minimum <= exported_minimum
&& (imported_maximum.is_none()
|| (!exported_maximum.is_none()
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
&& exported_shared == imported_shared
}

View File

@@ -4,6 +4,7 @@
//! steps. //! steps.
use crate::compiler::Compiler; use crate::compiler::Compiler;
use crate::imports::resolve_imports;
use crate::link::link_module; use crate::link::link_module;
use crate::resolver::Resolver; use crate::resolver::Resolver;
use std::cell::RefCell; use std::cell::RefCell;
@@ -19,7 +20,7 @@ use wasmtime_environ::{
ModuleSyncString, ModuleSyncString,
}; };
use wasmtime_runtime::{ use wasmtime_runtime::{
Export, GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, VMFunctionBody, Export, GdbJitImageRegistration, InstanceHandle, InstantiationError, VMFunctionBody,
VMSharedSignatureIndex, VMSharedSignatureIndex,
}; };
@@ -50,7 +51,6 @@ pub enum SetupError {
struct RawCompiledModule<'data> { struct RawCompiledModule<'data> {
module: Module, module: Module,
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>, finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
imports: Imports,
data_initializers: Box<[DataInitializer<'data>]>, data_initializers: Box<[DataInitializer<'data>]>,
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>, signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
dbg_jit_registration: Option<GdbJitImageRegistration>, dbg_jit_registration: Option<GdbJitImageRegistration>,
@@ -62,7 +62,6 @@ impl<'data> RawCompiledModule<'data> {
compiler: &mut Compiler, compiler: &mut Compiler,
data: &'data [u8], data: &'data [u8],
module_name: Option<&str>, module_name: Option<&str>,
resolver: &mut dyn Resolver,
debug_info: bool, debug_info: bool,
) -> Result<Self, SetupError> { ) -> Result<Self, SetupError> {
let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables());
@@ -86,14 +85,12 @@ impl<'data> RawCompiledModule<'data> {
debug_data, debug_data,
)?; )?;
let imports = link_module( link_module(
&translation.module, &translation.module,
&allocated_functions, &allocated_functions,
&jt_offsets, &jt_offsets,
relocations, relocations,
resolver, );
)
.map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?;
// Gather up the pointers to the compiled functions. // Gather up the pointers to the compiled functions.
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> = let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
@@ -132,7 +129,6 @@ impl<'data> RawCompiledModule<'data> {
Ok(Self { Ok(Self {
module: translation.module, module: translation.module,
finished_functions, finished_functions,
imports,
data_initializers: translation.data_initializers.into_boxed_slice(), data_initializers: translation.data_initializers.into_boxed_slice(),
signatures: signatures.into_boxed_slice(), signatures: signatures.into_boxed_slice(),
dbg_jit_registration, dbg_jit_registration,
@@ -144,7 +140,6 @@ impl<'data> RawCompiledModule<'data> {
pub struct CompiledModule { pub struct CompiledModule {
module: Rc<Module>, module: Rc<Module>,
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>, finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
imports: Imports,
data_initializers: Box<[OwnedDataInitializer]>, data_initializers: Box<[OwnedDataInitializer]>,
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>, signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>, global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
@@ -157,18 +152,15 @@ impl CompiledModule {
compiler: &mut Compiler, compiler: &mut Compiler,
data: &'data [u8], data: &'data [u8],
module_name: Option<&str>, module_name: Option<&str>,
resolver: &mut dyn Resolver,
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>, global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
debug_info: bool, debug_info: bool,
) -> Result<Self, SetupError> { ) -> Result<Self, SetupError> {
let raw = let raw = RawCompiledModule::<'data>::new(compiler, data, module_name, debug_info)?;
RawCompiledModule::<'data>::new(compiler, data, module_name, resolver, debug_info)?;
Ok(Self::from_parts( Ok(Self::from_parts(
raw.module, raw.module,
global_exports, global_exports,
raw.finished_functions, raw.finished_functions,
raw.imports,
raw.data_initializers raw.data_initializers
.iter() .iter()
.map(OwnedDataInitializer::new) .map(OwnedDataInitializer::new)
@@ -184,7 +176,6 @@ impl CompiledModule {
module: Module, module: Module,
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>, global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>, finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
imports: Imports,
data_initializers: Box<[OwnedDataInitializer]>, data_initializers: Box<[OwnedDataInitializer]>,
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>, signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
dbg_jit_registration: Option<GdbJitImageRegistration>, dbg_jit_registration: Option<GdbJitImageRegistration>,
@@ -193,7 +184,6 @@ impl CompiledModule {
module: Rc::new(module), module: Rc::new(module),
global_exports: Rc::clone(&global_exports), global_exports: Rc::clone(&global_exports),
finished_functions, finished_functions,
imports,
data_initializers, data_initializers,
signatures, signatures,
dbg_jit_registration: dbg_jit_registration.map(Rc::new), dbg_jit_registration: dbg_jit_registration.map(Rc::new),
@@ -205,7 +195,10 @@ impl CompiledModule {
/// Note that if only one instance of this module is needed, it may be more /// Note that if only one instance of this module is needed, it may be more
/// efficient to call the top-level `instantiate`, since that avoids copying /// efficient to call the top-level `instantiate`, since that avoids copying
/// the data initializers. /// the data initializers.
pub fn instantiate(&mut self) -> Result<InstanceHandle, InstantiationError> { pub fn instantiate(
&self,
resolver: &mut dyn Resolver,
) -> Result<InstanceHandle, InstantiationError> {
let data_initializers = self let data_initializers = self
.data_initializers .data_initializers
.iter() .iter()
@@ -214,11 +207,12 @@ impl CompiledModule {
data: &*init.data, data: &*init.data,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let imports = resolve_imports(&self.module, resolver)?;
InstanceHandle::new( InstanceHandle::new(
Rc::clone(&self.module), Rc::clone(&self.module),
Rc::clone(&self.global_exports), Rc::clone(&self.global_exports),
self.finished_functions.clone(), self.finished_functions.clone(),
self.imports.clone(), imports,
&data_initializers, &data_initializers,
self.signatures.clone(), self.signatures.clone(),
self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)), self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)),
@@ -269,13 +263,14 @@ pub fn instantiate(
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>, global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
debug_info: bool, debug_info: bool,
) -> Result<InstanceHandle, SetupError> { ) -> Result<InstanceHandle, SetupError> {
let raw = RawCompiledModule::new(compiler, data, module_name, resolver, debug_info)?; let raw = RawCompiledModule::new(compiler, data, module_name, debug_info)?;
let imports = resolve_imports(&raw.module, resolver)
.map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?;
InstanceHandle::new( InstanceHandle::new(
Rc::new(raw.module), Rc::new(raw.module),
global_exports, global_exports,
raw.finished_functions, raw.finished_functions,
raw.imports, imports,
&*raw.data_initializers, &*raw.data_initializers,
raw.signatures, raw.signatures,
raw.dbg_jit_registration.map(Rc::new), raw.dbg_jit_registration.map(Rc::new),

View File

@@ -26,6 +26,7 @@ mod code_memory;
mod compiler; mod compiler;
mod context; mod context;
mod function_table; mod function_table;
mod imports;
mod instantiate; mod instantiate;
mod link; mod link;
mod namespace; mod namespace;

View File

@@ -1,313 +1,22 @@
//! Linking for JIT-compiled code. //! Linking for JIT-compiled code.
use crate::resolver::Resolver;
use cranelift_codegen::binemit::Reloc; use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::JumpTableOffsets; use cranelift_codegen::ir::JumpTableOffsets;
use more_asserts::assert_ge;
use std::collections::HashSet;
use std::ptr::write_unaligned; use std::ptr::write_unaligned;
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::{ use wasmtime_environ::wasm::DefinedFuncIndex;
DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType, use wasmtime_environ::{Module, RelocationTarget, Relocations};
};
use wasmtime_environ::{
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
};
use wasmtime_runtime::libcalls; use wasmtime_runtime::libcalls;
use wasmtime_runtime::{ use wasmtime_runtime::VMFunctionBody;
Export, Imports, InstanceHandle, LinkError, VMFunctionBody, VMFunctionImport, VMGlobalImport,
VMMemoryImport, VMTableImport,
};
/// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`. /// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`.
///
/// Performs all required relocations inside the function code, provided the necessary metadata.
pub fn link_module( pub fn link_module(
module: &Module, module: &Module,
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>, jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>,
relocations: Relocations, relocations: Relocations,
resolver: &mut dyn Resolver,
) -> Result<Imports, LinkError> {
let mut dependencies = HashSet::new();
let mut function_imports = PrimaryMap::with_capacity(module.imported_funcs.len());
for (index, (module_name, field, import_idx)) in module.imported_funcs.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Function {
address,
signature,
vmctx,
} => {
let import_signature = &module.signatures[module.functions[index]];
if signature != *import_signature {
// TODO: If the difference is in the calling convention,
// we could emit a wrapper function to fix it up.
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported function with signature {} \
incompatible with function import with signature {}",
module_name, field, signature, import_signature
)));
}
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
function_imports.push(VMFunctionImport {
body: address,
vmctx,
});
}
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: export incompatible with function import",
module_name, field
)));
}
},
None => {
return Err(LinkError(format!(
"{}/{}: unknown import function: function not provided",
module_name, field
)));
}
}
}
let mut table_imports = PrimaryMap::with_capacity(module.imported_tables.len());
for (index, (module_name, field, import_idx)) in module.imported_tables.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Table {
definition,
vmctx,
table,
} => {
let import_table = &module.table_plans[index];
if !is_table_compatible(&table, import_table) {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported table incompatible with \
table import",
module_name, field,
)));
}
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
table_imports.push(VMTableImport {
from: definition,
vmctx,
});
}
Export::Global { .. } | Export::Memory { .. } | Export::Function { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: export incompatible with table import",
module_name, field
)));
}
},
None => {
return Err(LinkError(format!(
"unknown import: no provided import table for {}/{}",
module_name, field
)));
}
}
}
let mut memory_imports = PrimaryMap::with_capacity(module.imported_memories.len());
for (index, (module_name, field, import_idx)) in module.imported_memories.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Memory {
definition,
vmctx,
memory,
} => {
let import_memory = &module.memory_plans[index];
if !is_memory_compatible(&memory, import_memory) {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported memory incompatible with \
memory import",
module_name, field
)));
}
// Sanity-check: Ensure that the imported memory has at least
// guard-page protections the importing module expects it to have.
if let (
MemoryStyle::Static { bound },
MemoryStyle::Static {
bound: import_bound,
},
) = (memory.style, &import_memory.style)
{
assert_ge!(bound, *import_bound);
}
assert_ge!(memory.offset_guard_size, import_memory.offset_guard_size);
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
memory_imports.push(VMMemoryImport {
from: definition,
vmctx,
});
}
Export::Table { .. } | Export::Global { .. } | Export::Function { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: export incompatible with memory import",
module_name, field
)));
}
},
None => {
return Err(LinkError(format!(
"unknown import: no provided import memory for {}/{}",
module_name, field
)));
}
}
}
let mut global_imports = PrimaryMap::with_capacity(module.imported_globals.len());
for (index, (module_name, field, import_idx)) in module.imported_globals.iter() {
match resolver.resolve(*import_idx, module_name, field) {
Some(export_value) => match export_value {
Export::Table { .. } | Export::Memory { .. } | Export::Function { .. } => {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported global incompatible with \
global import",
module_name, field
)));
}
Export::Global {
definition,
vmctx,
global,
} => {
let imported_global = module.globals[index];
if !is_global_compatible(&global, &imported_global) {
return Err(LinkError(format!(
"{}/{}: incompatible import type: exported global incompatible with \
global import",
module_name, field
)));
}
dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) });
global_imports.push(VMGlobalImport { from: definition });
}
},
None => {
return Err(LinkError(format!(
"unknown import: no provided import global for {}/{}",
module_name, field
)));
}
}
}
// Apply relocations, now that we have virtual addresses for everything.
relocate(allocated_functions, jt_offsets, relocations, module);
Ok(Imports::new(
dependencies,
function_imports,
table_imports,
memory_imports,
global_imports,
))
}
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
match imported.initializer {
GlobalInit::Import => (),
_ => panic!("imported Global should have an Imported initializer"),
}
let Global {
ty: exported_ty,
mutability: exported_mutability,
initializer: _exported_initializer,
} = exported;
let Global {
ty: imported_ty,
mutability: imported_mutability,
initializer: _imported_initializer,
} = imported;
exported_ty == imported_ty && imported_mutability == exported_mutability
}
fn is_table_element_type_compatible(
exported_type: TableElementType,
imported_type: TableElementType,
) -> bool {
match exported_type {
TableElementType::Func => match imported_type {
TableElementType::Func => true,
_ => false,
},
TableElementType::Val(exported_val_ty) => match imported_type {
TableElementType::Val(imported_val_ty) => exported_val_ty == imported_val_ty,
_ => false,
},
}
}
fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
let TablePlan {
table:
Table {
ty: exported_ty,
minimum: exported_minimum,
maximum: exported_maximum,
},
style: _exported_style,
} = exported;
let TablePlan {
table:
Table {
ty: imported_ty,
minimum: imported_minimum,
maximum: imported_maximum,
},
style: _imported_style,
} = imported;
is_table_element_type_compatible(*exported_ty, *imported_ty)
&& imported_minimum <= exported_minimum
&& (imported_maximum.is_none()
|| (!exported_maximum.is_none()
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
}
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
let MemoryPlan {
memory:
Memory {
minimum: exported_minimum,
maximum: exported_maximum,
shared: exported_shared,
},
style: _exported_style,
offset_guard_size: _exported_offset_guard_size,
} = exported;
let MemoryPlan {
memory:
Memory {
minimum: imported_minimum,
maximum: imported_maximum,
shared: imported_shared,
},
style: _imported_style,
offset_guard_size: _imported_offset_guard_size,
} = imported;
imported_minimum <= exported_minimum
&& (imported_maximum.is_none()
|| (!exported_maximum.is_none()
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
&& exported_shared == imported_shared
}
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
fn relocate(
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>,
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
module: &Module,
) { ) {
for (i, function_relocs) in relocations.into_iter() { for (i, function_relocs) in relocations.into_iter() {
for r in function_relocs { for r in function_relocs {

View File

@@ -13,7 +13,6 @@ arbitrary = "0.2.0"
env_logger = "0.7.1" env_logger = "0.7.1"
log = "0.4.8" log = "0.4.8"
wasmtime-fuzzing = { path = "../crates/fuzzing", features = ["env_logger"] } wasmtime-fuzzing = { path = "../crates/fuzzing", features = ["env_logger"] }
wasmtime-jit = { path = "../crates/jit" }
wasmtime = { path = "../crates/api" } wasmtime = { path = "../crates/api" }
libfuzzer-sys = "0.2.0" libfuzzer-sys = "0.2.0"

View File

@@ -1,20 +1,14 @@
#![no_main] #![no_main]
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wasmtime::Strategy;
use wasmtime_fuzzing::{oracles, with_log_wasm_test_case}; use wasmtime_fuzzing::{oracles, with_log_wasm_test_case};
use wasmtime_jit::CompilationStrategy;
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
with_log_wasm_test_case!(data, |data| oracles::compile( with_log_wasm_test_case!(data, |data| oracles::compile(data, Strategy::Cranelift));
data,
CompilationStrategy::Cranelift
));
}); });
#[cfg(feature = "lightbeam")] #[cfg(feature = "lightbeam")]
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
with_log_wasm_test_case!(data, |data| oracles::compile( with_log_wasm_test_case!(data, |data| oracles::compile(data, Strategy::Lightbeam));
data,
CompilationStrategy::Lightbeam
));
}); });