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:
committed by
Alex Crichton
parent
e474a9e822
commit
5b8be5f262
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -20,36 +20,22 @@ 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,
|
if let Some(trap) = take_api_trap() {
|
||||||
&mut resolver,
|
trap.into()
|
||||||
store.global_exports().clone(),
|
} else if let InstantiationError::StartTrap(trap) = e {
|
||||||
store.engine().config().debug_info,
|
Trap::from_jit(trap).into()
|
||||||
)?;
|
} else {
|
||||||
|
e.into()
|
||||||
// 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() {
|
|
||||||
trap.into()
|
|
||||||
} else if let InstantiationError::StartTrap(trap) = e {
|
|
||||||
Trap::from_jit(trap).into()
|
|
||||||
} else {
|
|
||||||
e.into()
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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());
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
289
crates/jit/src/imports.rs
Normal 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
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user