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:
@@ -86,15 +86,6 @@ impl Extern {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_wasmtime_export(&self) -> wasmtime_runtime::Export {
|
||||
match self {
|
||||
Extern::Func(f) => f.wasmtime_function().clone().into(),
|
||||
Extern::Global(g) => g.wasmtime_export.clone().into(),
|
||||
Extern::Memory(m) => m.wasmtime_export.clone().into(),
|
||||
Extern::Table(t) => t.wasmtime_export.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_export(
|
||||
wasmtime_export: wasmtime_runtime::Export,
|
||||
instance: StoreInstanceHandle,
|
||||
@@ -124,6 +115,15 @@ impl Extern {
|
||||
};
|
||||
Store::same(my_store, store)
|
||||
}
|
||||
|
||||
pub(crate) fn desc(&self) -> &'static str {
|
||||
match self {
|
||||
Extern::Func(_) => "function",
|
||||
Extern::Table(_) => "table",
|
||||
Extern::Memory(_) => "memory",
|
||||
Extern::Global(_) => "global",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Func> for Extern {
|
||||
@@ -293,6 +293,19 @@ impl Global {
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, expected: &wasmtime_environ::wasm::Global) -> bool {
|
||||
let actual = &self.wasmtime_export.global;
|
||||
expected.ty == actual.ty
|
||||
&& expected.wasm_ty == actual.wasm_ty
|
||||
&& expected.mutability == actual.mutability
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMGlobalImport {
|
||||
wasmtime_runtime::VMGlobalImport {
|
||||
from: self.wasmtime_export.definition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A WebAssembly `table`, or an array of values.
|
||||
@@ -524,6 +537,28 @@ impl Table {
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, ty: &wasmtime_environ::TablePlan) -> bool {
|
||||
let expected = &ty.table;
|
||||
let actual = &self.wasmtime_export.table.table;
|
||||
expected.wasm_ty == actual.wasm_ty
|
||||
&& expected.ty == actual.ty
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
Some(actual) => expected >= actual,
|
||||
None => false,
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMTableImport {
|
||||
wasmtime_runtime::VMTableImport {
|
||||
from: self.wasmtime_export.definition,
|
||||
vmctx: self.wasmtime_export.vmctx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A WebAssembly linear memory.
|
||||
@@ -924,6 +959,27 @@ impl Memory {
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, ty: &wasmtime_environ::MemoryPlan) -> bool {
|
||||
let expected = &ty.memory;
|
||||
let actual = &self.wasmtime_export.memory.memory;
|
||||
expected.shared == actual.shared
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
Some(actual) => expected >= actual,
|
||||
None => false,
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMMemoryImport {
|
||||
wasmtime_runtime::VMMemoryImport {
|
||||
from: self.wasmtime_export.definition,
|
||||
vmctx: self.wasmtime_export.vmctx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A linear memory. This trait provides an interface for raw memory buffers which are used
|
||||
|
||||
@@ -635,10 +635,6 @@ impl Func {
|
||||
Ok(results.into())
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_function(&self) -> &wasmtime_runtime::ExportFunction {
|
||||
&self.export
|
||||
}
|
||||
|
||||
pub(crate) fn caller_checked_anyfunc(
|
||||
&self,
|
||||
) -> NonNull<wasmtime_runtime::VMCallerCheckedAnyfunc> {
|
||||
@@ -789,6 +785,20 @@ impl Func {
|
||||
pub fn store(&self) -> &Store {
|
||||
&self.instance.store
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, expected: VMSharedSignatureIndex) -> bool {
|
||||
self.sig_index() == expected
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMFunctionImport {
|
||||
unsafe {
|
||||
let f = self.caller_checked_anyfunc();
|
||||
wasmtime_runtime::VMFunctionImport {
|
||||
body: f.as_ref().func_ptr,
|
||||
vmctx: f.as_ref().vmctx,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Func {
|
||||
|
||||
@@ -1,53 +1,25 @@
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{Engine, Export, Extern, Func, Global, Memory, Module, Store, Table, Trap};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||
use std::any::Any;
|
||||
use std::mem;
|
||||
use wasmtime_environ::EntityIndex;
|
||||
use wasmtime_jit::{CompiledModule, Resolver};
|
||||
use wasmtime_jit::CompiledModule;
|
||||
use wasmtime_runtime::{
|
||||
InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable, VMFunctionBody,
|
||||
Imports, InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable,
|
||||
VMFunctionBody,
|
||||
};
|
||||
|
||||
struct SimpleResolver<'a> {
|
||||
imports: &'a [Extern],
|
||||
}
|
||||
|
||||
impl Resolver for SimpleResolver<'_> {
|
||||
fn resolve(&mut self, idx: u32, _name: &str, _field: &str) -> Option<wasmtime_runtime::Export> {
|
||||
self.imports
|
||||
.get(idx as usize)
|
||||
.map(|i| i.get_wasmtime_export())
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
store: &Store,
|
||||
compiled_module: &CompiledModule,
|
||||
imports: &[Extern],
|
||||
imports: Imports<'_>,
|
||||
host: Box<dyn Any>,
|
||||
) -> Result<StoreInstanceHandle, Error> {
|
||||
// For now we have a restriction that the `Store` that we're working
|
||||
// with is the same for everything involved here.
|
||||
for import in imports {
|
||||
if !import.comes_from_same_store(store) {
|
||||
bail!("cross-`Store` instantiation is not currently supported");
|
||||
}
|
||||
}
|
||||
|
||||
if imports.len() != compiled_module.module().imports.len() {
|
||||
bail!(
|
||||
"wrong number of imports provided, {} != {}",
|
||||
imports.len(),
|
||||
compiled_module.module().imports.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut resolver = SimpleResolver { imports };
|
||||
let config = store.engine().config();
|
||||
let instance = unsafe {
|
||||
let instance = compiled_module.instantiate(
|
||||
&mut resolver,
|
||||
imports,
|
||||
&mut store.signatures_mut(),
|
||||
config.memory_creator.as_ref().map(|a| a as _),
|
||||
store.interrupts().clone(),
|
||||
@@ -196,7 +168,9 @@ impl Instance {
|
||||
frame_info_registration
|
||||
});
|
||||
|
||||
let handle = instantiate(store, module.compiled_module(), imports, host_info)?;
|
||||
let handle = with_imports(store, module.compiled_module(), imports, |imports| {
|
||||
instantiate(store, module.compiled_module(), imports, host_info)
|
||||
})?;
|
||||
|
||||
Ok(Instance {
|
||||
handle,
|
||||
@@ -267,3 +241,82 @@ impl Instance {
|
||||
self.get_export(name)?.into_global()
|
||||
}
|
||||
}
|
||||
|
||||
fn with_imports<R>(
|
||||
store: &Store,
|
||||
module: &CompiledModule,
|
||||
externs: &[Extern],
|
||||
f: impl FnOnce(Imports<'_>) -> Result<R>,
|
||||
) -> Result<R> {
|
||||
let m = module.module();
|
||||
if externs.len() != m.imports.len() {
|
||||
bail!(
|
||||
"wrong number of imports provided, {} != {}",
|
||||
externs.len(),
|
||||
m.imports.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut tables = Vec::new();
|
||||
let mut functions = Vec::new();
|
||||
let mut globals = Vec::new();
|
||||
let mut memories = Vec::new();
|
||||
|
||||
let mut process = |expected: &EntityIndex, actual: &Extern| {
|
||||
// For now we have a restriction that the `Store` that we're working
|
||||
// with is the same for everything involved here.
|
||||
if !actual.comes_from_same_store(store) {
|
||||
bail!("cross-`Store` instantiation is not currently supported");
|
||||
}
|
||||
|
||||
match *expected {
|
||||
EntityIndex::Table(i) => tables.push(match actual {
|
||||
Extern::Table(e) if e.matches_expected(&m.table_plans[i]) => e.vmimport(),
|
||||
Extern::Table(_) => bail!("table types incompatible"),
|
||||
_ => bail!("expected table, but found {}", actual.desc()),
|
||||
}),
|
||||
EntityIndex::Memory(i) => memories.push(match actual {
|
||||
Extern::Memory(e) if e.matches_expected(&m.memory_plans[i]) => e.vmimport(),
|
||||
Extern::Memory(_) => bail!("memory types incompatible"),
|
||||
_ => bail!("expected memory, but found {}", actual.desc()),
|
||||
}),
|
||||
EntityIndex::Global(i) => globals.push(match actual {
|
||||
Extern::Global(e) if e.matches_expected(&m.globals[i]) => e.vmimport(),
|
||||
Extern::Global(_) => bail!("global types incompatible"),
|
||||
_ => bail!("expected global, but found {}", actual.desc()),
|
||||
}),
|
||||
EntityIndex::Function(i) => {
|
||||
let func = match actual {
|
||||
Extern::Func(e) => e,
|
||||
_ => bail!("expected function, but found {}", actual.desc()),
|
||||
};
|
||||
// Look up the `i`th function's type from the module in our
|
||||
// signature registry. If it's not present then we have no
|
||||
// functions registered with that type, so `func` is guaranteed
|
||||
// to not match.
|
||||
let ty = store
|
||||
.signatures_mut()
|
||||
.lookup(&m.signatures[m.functions[i]].0)
|
||||
.ok_or_else(|| anyhow!("function types incompatible"))?;
|
||||
if !func.matches_expected(ty) {
|
||||
bail!("function types incompatible");
|
||||
}
|
||||
functions.push(func.vmimport());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (expected, actual) in m.imports.iter().zip(externs) {
|
||||
process(&expected.2, actual).with_context(|| {
|
||||
format!("incompatible import type for {}/{}", expected.0, expected.1)
|
||||
})?;
|
||||
}
|
||||
|
||||
return f(Imports {
|
||||
tables: &tables,
|
||||
functions: &functions,
|
||||
globals: &globals,
|
||||
memories: &memories,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex};
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::Module;
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody,
|
||||
@@ -20,14 +20,10 @@ pub(crate) fn create_handle(
|
||||
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
|
||||
state: Box<dyn Any>,
|
||||
func_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||
func_imports: &[VMFunctionImport],
|
||||
) -> Result<StoreInstanceHandle> {
|
||||
let imports = Imports::new(
|
||||
func_imports,
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
);
|
||||
let mut imports = Imports::default();
|
||||
imports.functions = func_imports;
|
||||
|
||||
// Compute indices into the shared signature table.
|
||||
let signatures = module
|
||||
|
||||
@@ -262,7 +262,7 @@ pub fn create_handle_with_function(
|
||||
finished_functions,
|
||||
trampolines,
|
||||
Box::new(trampoline_state),
|
||||
PrimaryMap::new(),
|
||||
&[],
|
||||
)
|
||||
.map(|instance| (instance, trampoline))
|
||||
}
|
||||
@@ -298,12 +298,5 @@ pub unsafe fn create_handle_with_raw_function(
|
||||
let sig_id = store.register_signature(ft.to_wasm_func_type(), sig);
|
||||
trampolines.insert(sig_id, trampoline);
|
||||
|
||||
create_handle(
|
||||
module,
|
||||
store,
|
||||
finished_functions,
|
||||
trampolines,
|
||||
state,
|
||||
PrimaryMap::new(),
|
||||
)
|
||||
create_handle(module, store, finished_functions, trampolines, state, &[])
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use wasmtime_runtime::VMFunctionImport;
|
||||
|
||||
pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreInstanceHandle> {
|
||||
let mut module = Module::new();
|
||||
let mut func_imports = PrimaryMap::new();
|
||||
let mut func_imports = Vec::new();
|
||||
let mut externref_init = None;
|
||||
|
||||
let global = wasm::Global {
|
||||
@@ -68,7 +68,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
|
||||
PrimaryMap::new(),
|
||||
Default::default(),
|
||||
Box::new(()),
|
||||
func_imports,
|
||||
&func_imports,
|
||||
)?;
|
||||
|
||||
if let Some(x) = externref_init {
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn create_handle_with_memory(
|
||||
PrimaryMap::new(),
|
||||
Default::default(),
|
||||
Box::new(()),
|
||||
PrimaryMap::new(),
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,6 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
|
||||
PrimaryMap::new(),
|
||||
Default::default(),
|
||||
Box::new(()),
|
||||
PrimaryMap::new(),
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user