wasmtime: Refactor how imports are resolved (#2102)
This commit removes all import resolution handling from the `wasmtime-jit` crate, instead moving the logic to the `wasmtime` crate. Previously `wasmtime-jit` had a generic `Resolver` trait and would do all the import type matching itself, but with the upcoming module-linking implementation this is going to get much trickier. The goal of this commit is to centralize all meaty "preparation" logic for instantiation into one location, probably the `wasmtime` crate itself. Instantiation will soon involve recursive instantiation and management of alias definitions as well. Having everything in one location, especially with access to `Store` so we can persist instances for safety, will be quite convenient. Additionally the `Resolver` trait isn't really necessary any more since imports are, at the lowest level, provided as a list rather than a map of some kind. More generic resolution functionality is provided via `Linker` or user layers on top of `Instance::new` itself. This makes matching up provided items to expected imports much easier as well. Overall this is largely just moving code around, but most of the code in the previous `resolve_imports` phase can be deleted since a lot of it is handled by surrounding pieces of `wasmtime` as well.
This commit is contained in:
@@ -1,263 +0,0 @@
|
||||
//! Module imports resolving logic.
|
||||
|
||||
use crate::resolver::Resolver;
|
||||
use more_asserts::assert_ge;
|
||||
use std::convert::TryInto;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, TablePlan};
|
||||
use wasmtime_runtime::{
|
||||
Export, Imports, LinkError, SignatureRegistry, 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,
|
||||
signatures: &SignatureRegistry,
|
||||
resolver: &mut dyn Resolver,
|
||||
) -> Result<Imports, LinkError> {
|
||||
let mut function_imports = PrimaryMap::with_capacity(module.num_imported_funcs);
|
||||
let mut table_imports = PrimaryMap::with_capacity(module.num_imported_tables);
|
||||
let mut memory_imports = PrimaryMap::with_capacity(module.num_imported_memories);
|
||||
let mut global_imports = PrimaryMap::with_capacity(module.num_imported_globals);
|
||||
|
||||
for (import_idx, (module_name, field_name, import)) in module.imports.iter().enumerate() {
|
||||
let import_idx = import_idx.try_into().unwrap();
|
||||
let export = resolver.resolve(import_idx, module_name, field_name);
|
||||
|
||||
match (import, &export) {
|
||||
(EntityIndex::Function(func_index), Some(Export::Function(f))) => {
|
||||
let import_signature = module.native_func_signature(*func_index);
|
||||
let signature = signatures
|
||||
.lookup_native(unsafe { f.anyfunc.as_ref().type_index })
|
||||
.unwrap();
|
||||
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_name, signature, import_signature
|
||||
)));
|
||||
}
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: unsafe { f.anyfunc.as_ref().func_ptr },
|
||||
vmctx: unsafe { f.anyfunc.as_ref().vmctx },
|
||||
});
|
||||
}
|
||||
(EntityIndex::Function(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: export incompatible with function import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
(EntityIndex::Function(_), None) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: unknown import function: function not provided",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
|
||||
(EntityIndex::Table(table_index), Some(Export::Table(t))) => {
|
||||
let import_table = &module.table_plans[*table_index];
|
||||
if !is_table_compatible(&t.table, import_table) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: exported table incompatible with \
|
||||
table import",
|
||||
module_name, field_name,
|
||||
)));
|
||||
}
|
||||
table_imports.push(VMTableImport {
|
||||
from: t.definition,
|
||||
vmctx: t.vmctx,
|
||||
});
|
||||
}
|
||||
(EntityIndex::Table(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: export incompatible with table import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
(EntityIndex::Table(_), None) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: unknown import table: table not provided",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
|
||||
(EntityIndex::Memory(memory_index), Some(Export::Memory(m))) => {
|
||||
let import_memory = &module.memory_plans[*memory_index];
|
||||
if !is_memory_compatible(&m.memory, import_memory) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: exported memory incompatible with \
|
||||
memory import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
|
||||
// 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,
|
||||
},
|
||||
) = (&m.memory.style, &import_memory.style)
|
||||
{
|
||||
assert_ge!(*bound, *import_bound);
|
||||
}
|
||||
assert_ge!(m.memory.offset_guard_size, import_memory.offset_guard_size);
|
||||
|
||||
memory_imports.push(VMMemoryImport {
|
||||
from: m.definition,
|
||||
vmctx: m.vmctx,
|
||||
});
|
||||
}
|
||||
(EntityIndex::Memory(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: export incompatible with memory import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
(EntityIndex::Memory(_), None) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: unknown import memory: memory not provided",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
|
||||
(EntityIndex::Global(global_index), Some(Export::Global(g))) => {
|
||||
let imported_global = module.globals[*global_index];
|
||||
if !is_global_compatible(&g.global, &imported_global) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: exported global incompatible with \
|
||||
global import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
global_imports.push(VMGlobalImport { from: g.definition });
|
||||
}
|
||||
(EntityIndex::Global(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: export incompatible with global import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
(EntityIndex::Global(_), None) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: unknown import global: global not provided",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Imports::new(
|
||||
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 {
|
||||
wasm_ty: exported_wasm_ty,
|
||||
ty: exported_ty,
|
||||
mutability: exported_mutability,
|
||||
initializer: _exported_initializer,
|
||||
} = exported;
|
||||
let Global {
|
||||
wasm_ty: imported_wasm_ty,
|
||||
ty: imported_ty,
|
||||
mutability: imported_mutability,
|
||||
initializer: _imported_initializer,
|
||||
} = imported;
|
||||
exported_wasm_ty == imported_wasm_ty
|
||||
&& 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 {
|
||||
wasm_ty: exported_wasm_ty,
|
||||
ty: exported_ty,
|
||||
minimum: exported_minimum,
|
||||
maximum: exported_maximum,
|
||||
},
|
||||
style: _exported_style,
|
||||
} = exported;
|
||||
let TablePlan {
|
||||
table:
|
||||
Table {
|
||||
wasm_ty: imported_wasm_ty,
|
||||
ty: imported_ty,
|
||||
minimum: imported_minimum,
|
||||
maximum: imported_maximum,
|
||||
},
|
||||
style: _imported_style,
|
||||
} = imported;
|
||||
|
||||
exported_wasm_ty == imported_wasm_ty
|
||||
&& 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
|
||||
}
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
use crate::code_memory::CodeMemory;
|
||||
use crate::compiler::{Compilation, Compiler};
|
||||
use crate::imports::resolve_imports;
|
||||
use crate::link::link_module;
|
||||
use crate::object::ObjectUnwindInfo;
|
||||
use crate::resolver::Resolver;
|
||||
use object::File as ObjectFile;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::Any;
|
||||
@@ -24,10 +22,10 @@ use wasmtime_environ::{
|
||||
ModuleEnvironment, ModuleTranslation, StackMapInformation, TrapInformation,
|
||||
};
|
||||
use wasmtime_profiling::ProfilingAgent;
|
||||
use wasmtime_runtime::VMInterrupts;
|
||||
use wasmtime_runtime::{
|
||||
GdbJitImageRegistration, InstanceHandle, InstantiationError, RuntimeMemoryCreator,
|
||||
SignatureRegistry, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody, VMTrampoline,
|
||||
GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, RuntimeMemoryCreator,
|
||||
SignatureRegistry, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody, VMInterrupts,
|
||||
VMTrampoline,
|
||||
};
|
||||
|
||||
/// An error condition while setting up a wasm instance, be it validation,
|
||||
@@ -245,7 +243,7 @@ impl CompiledModule {
|
||||
/// See `InstanceHandle::new`
|
||||
pub unsafe fn instantiate(
|
||||
&self,
|
||||
resolver: &mut dyn Resolver,
|
||||
imports: Imports<'_>,
|
||||
signature_registry: &mut SignatureRegistry,
|
||||
mem_creator: Option<&dyn RuntimeMemoryCreator>,
|
||||
interrupts: Arc<VMInterrupts>,
|
||||
@@ -271,7 +269,6 @@ impl CompiledModule {
|
||||
|
||||
let finished_functions = self.finished_functions.0.clone();
|
||||
|
||||
let imports = resolve_imports(&self.module, signature_registry, resolver)?;
|
||||
InstanceHandle::new(
|
||||
self.module.clone(),
|
||||
self.code.clone(),
|
||||
|
||||
@@ -23,11 +23,9 @@
|
||||
|
||||
mod code_memory;
|
||||
mod compiler;
|
||||
mod imports;
|
||||
mod instantiate;
|
||||
mod link;
|
||||
mod object;
|
||||
mod resolver;
|
||||
mod unwind;
|
||||
|
||||
pub mod native;
|
||||
@@ -37,7 +35,6 @@ pub use crate::code_memory::CodeMemory;
|
||||
pub use crate::compiler::{Compilation, CompilationStrategy, Compiler};
|
||||
pub use crate::instantiate::{CompilationArtifacts, CompiledModule, SetupError};
|
||||
pub use crate::link::link_module;
|
||||
pub use crate::resolver::{NullResolver, Resolver};
|
||||
|
||||
/// Version number of this crate.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//! Define the `Resolver` trait, allowing custom resolution for external
|
||||
//! references.
|
||||
|
||||
use wasmtime_runtime::Export;
|
||||
|
||||
/// Import resolver connects imports with available exported values.
|
||||
pub trait Resolver {
|
||||
/// Resolves an import a WebAssembly module to an export it's hooked up to.
|
||||
///
|
||||
/// The `index` provided is the index of the import in the wasm module
|
||||
/// that's being resolved. For example 1 means that it's the second import
|
||||
/// listed in the wasm module.
|
||||
///
|
||||
/// The `module` and `field` arguments provided are the module/field names
|
||||
/// listed on the import itself.
|
||||
fn resolve(&mut self, index: u32, module: &str, field: &str) -> Option<Export>;
|
||||
}
|
||||
|
||||
/// `Resolver` implementation that always resolves to `None`.
|
||||
pub struct NullResolver {}
|
||||
|
||||
impl Resolver for NullResolver {
|
||||
fn resolve(&mut self, _idx: u32, _module: &str, _field: &str) -> Option<Export> {
|
||||
None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user