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
@@ -156,7 +156,6 @@ impl Context {
|
||||
&mut *self.compiler,
|
||||
data,
|
||||
None,
|
||||
&mut self.namespace,
|
||||
Rc::clone(&self.global_exports),
|
||||
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.
|
||||
|
||||
use crate::compiler::Compiler;
|
||||
use crate::imports::resolve_imports;
|
||||
use crate::link::link_module;
|
||||
use crate::resolver::Resolver;
|
||||
use std::cell::RefCell;
|
||||
@@ -19,7 +20,7 @@ use wasmtime_environ::{
|
||||
ModuleSyncString,
|
||||
};
|
||||
use wasmtime_runtime::{
|
||||
Export, GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, VMFunctionBody,
|
||||
Export, GdbJitImageRegistration, InstanceHandle, InstantiationError, VMFunctionBody,
|
||||
VMSharedSignatureIndex,
|
||||
};
|
||||
|
||||
@@ -50,7 +51,6 @@ pub enum SetupError {
|
||||
struct RawCompiledModule<'data> {
|
||||
module: Module,
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
imports: Imports,
|
||||
data_initializers: Box<[DataInitializer<'data>]>,
|
||||
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
|
||||
dbg_jit_registration: Option<GdbJitImageRegistration>,
|
||||
@@ -62,7 +62,6 @@ impl<'data> RawCompiledModule<'data> {
|
||||
compiler: &mut Compiler,
|
||||
data: &'data [u8],
|
||||
module_name: Option<&str>,
|
||||
resolver: &mut dyn Resolver,
|
||||
debug_info: bool,
|
||||
) -> Result<Self, SetupError> {
|
||||
let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables());
|
||||
@@ -86,14 +85,12 @@ impl<'data> RawCompiledModule<'data> {
|
||||
debug_data,
|
||||
)?;
|
||||
|
||||
let imports = link_module(
|
||||
link_module(
|
||||
&translation.module,
|
||||
&allocated_functions,
|
||||
&jt_offsets,
|
||||
relocations,
|
||||
resolver,
|
||||
)
|
||||
.map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?;
|
||||
);
|
||||
|
||||
// Gather up the pointers to the compiled functions.
|
||||
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
||||
@@ -132,7 +129,6 @@ impl<'data> RawCompiledModule<'data> {
|
||||
Ok(Self {
|
||||
module: translation.module,
|
||||
finished_functions,
|
||||
imports,
|
||||
data_initializers: translation.data_initializers.into_boxed_slice(),
|
||||
signatures: signatures.into_boxed_slice(),
|
||||
dbg_jit_registration,
|
||||
@@ -144,7 +140,6 @@ impl<'data> RawCompiledModule<'data> {
|
||||
pub struct CompiledModule {
|
||||
module: Rc<Module>,
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
imports: Imports,
|
||||
data_initializers: Box<[OwnedDataInitializer]>,
|
||||
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
|
||||
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
|
||||
@@ -157,18 +152,15 @@ impl CompiledModule {
|
||||
compiler: &mut Compiler,
|
||||
data: &'data [u8],
|
||||
module_name: Option<&str>,
|
||||
resolver: &mut dyn Resolver,
|
||||
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
|
||||
debug_info: bool,
|
||||
) -> Result<Self, SetupError> {
|
||||
let raw =
|
||||
RawCompiledModule::<'data>::new(compiler, data, module_name, resolver, debug_info)?;
|
||||
let raw = RawCompiledModule::<'data>::new(compiler, data, module_name, debug_info)?;
|
||||
|
||||
Ok(Self::from_parts(
|
||||
raw.module,
|
||||
global_exports,
|
||||
raw.finished_functions,
|
||||
raw.imports,
|
||||
raw.data_initializers
|
||||
.iter()
|
||||
.map(OwnedDataInitializer::new)
|
||||
@@ -184,7 +176,6 @@ impl CompiledModule {
|
||||
module: Module,
|
||||
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
imports: Imports,
|
||||
data_initializers: Box<[OwnedDataInitializer]>,
|
||||
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
|
||||
dbg_jit_registration: Option<GdbJitImageRegistration>,
|
||||
@@ -193,7 +184,6 @@ impl CompiledModule {
|
||||
module: Rc::new(module),
|
||||
global_exports: Rc::clone(&global_exports),
|
||||
finished_functions,
|
||||
imports,
|
||||
data_initializers,
|
||||
signatures,
|
||||
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
|
||||
/// efficient to call the top-level `instantiate`, since that avoids copying
|
||||
/// 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
|
||||
.data_initializers
|
||||
.iter()
|
||||
@@ -214,11 +207,12 @@ impl CompiledModule {
|
||||
data: &*init.data,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let imports = resolve_imports(&self.module, resolver)?;
|
||||
InstanceHandle::new(
|
||||
Rc::clone(&self.module),
|
||||
Rc::clone(&self.global_exports),
|
||||
self.finished_functions.clone(),
|
||||
self.imports.clone(),
|
||||
imports,
|
||||
&data_initializers,
|
||||
self.signatures.clone(),
|
||||
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>>>>,
|
||||
debug_info: bool,
|
||||
) -> 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(
|
||||
Rc::new(raw.module),
|
||||
global_exports,
|
||||
raw.finished_functions,
|
||||
raw.imports,
|
||||
imports,
|
||||
&*raw.data_initializers,
|
||||
raw.signatures,
|
||||
raw.dbg_jit_registration.map(Rc::new),
|
||||
|
||||
@@ -26,6 +26,7 @@ mod code_memory;
|
||||
mod compiler;
|
||||
mod context;
|
||||
mod function_table;
|
||||
mod imports;
|
||||
mod instantiate;
|
||||
mod link;
|
||||
mod namespace;
|
||||
|
||||
@@ -1,313 +1,22 @@
|
||||
//! Linking for JIT-compiled code.
|
||||
|
||||
use crate::resolver::Resolver;
|
||||
use cranelift_codegen::binemit::Reloc;
|
||||
use cranelift_codegen::ir::JumpTableOffsets;
|
||||
use more_asserts::assert_ge;
|
||||
use std::collections::HashSet;
|
||||
use std::ptr::write_unaligned;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::{
|
||||
DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType,
|
||||
};
|
||||
use wasmtime_environ::{
|
||||
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
||||
};
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::{Module, RelocationTarget, Relocations};
|
||||
use wasmtime_runtime::libcalls;
|
||||
use wasmtime_runtime::{
|
||||
Export, Imports, InstanceHandle, LinkError, VMFunctionBody, VMFunctionImport, VMGlobalImport,
|
||||
VMMemoryImport, VMTableImport,
|
||||
};
|
||||
use wasmtime_runtime::VMFunctionBody;
|
||||
|
||||
/// 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(
|
||||
module: &Module,
|
||||
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>,
|
||||
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 r in function_relocs {
|
||||
|
||||
Reference in New Issue
Block a user