Refactor (#1524)
* Compute instance exports on demand. Instead having instances eagerly compute a Vec of Externs, and bumping the refcount for each Extern, compute Externs on demand. This also enables `Instance::get_export` to avoid doing a linear search. This also means that the closure returned by `get0` and friends now holds an `InstanceHandle` to dynamically hold the instance live rather than being scoped to a lifetime. * Compute module imports and exports on demand too. And compute Extern::ty on demand too. * Add a utility function for computing an ExternType. * Add a utility function for looking up a function's signature. * Add a utility function for computing the ValType of a Global. * Rename wasmtime_environ::Export to EntityIndex. This helps differentiate it from other Export types in the tree, and describes what it is. * Fix a typo in a comment. * Simplify module imports and exports. * Make `Instance::exports` return the export names. This significantly simplifies the public API, as it's relatively common to need the names, and this avoids the need to do a zip with `Module::exports`. This also changes `ImportType` and `ExportType` to have public members instead of private members and accessors, as I find that simplifies the usage particularly in cases where there are temporary instances. * Remove `Instance::module`. This doesn't quite remove `Instance`'s `module` member, it gets a step closer. * Use a InstanceHandle utility function. * Don't consume self in the `Func::get*` methods. Instead, just create a closure containing the instance handle and the export for them to call. * Use `ExactSizeIterator` to avoid needing separate `num_*` methods. * Rename `Extern::func()` etc. to `into_func()` etc. * Revise examples to avoid using `nth`. * Add convenience methods to instance for getting specific extern types. * Use the convenience functions in more tests and examples. * Avoid cloning strings for `ImportType` and `ExportType`. * Remove more obviated clone() calls. * Simplify `Func`'s closure state. * Make wasmtime::Export's fields private. This makes them more consistent with ExportType. * Fix compilation error. * Make a lifetime parameter explicit, and use better lifetime names. Instead of 'me, use 'instance and 'module to make it clear what the lifetime is. * More lifetime cleanups.
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
use crate::resolver::Resolver;
|
||||
use more_asserts::assert_ge;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use wasmtime_environ::{MemoryPlan, MemoryStyle, Module, TablePlan};
|
||||
use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, TablePlan};
|
||||
use wasmtime_runtime::{
|
||||
Export, Imports, InstanceHandle, LinkError, SignatureRegistry, VMFunctionImport,
|
||||
VMGlobalImport, VMMemoryImport, VMTableImport,
|
||||
@@ -22,156 +23,139 @@ pub fn resolve_imports(
|
||||
) -> 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(f) => {
|
||||
let import_signature = &module.local.signatures[module.local.functions[index]];
|
||||
let signature = signatures.lookup(f.signature).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, signature, import_signature
|
||||
)));
|
||||
}
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(f.vmctx) });
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: f.address,
|
||||
vmctx: f.vmctx,
|
||||
});
|
||||
}
|
||||
Export::Table(_) | Export::Memory(_) | Export::Global(_) => {
|
||||
let mut function_imports = PrimaryMap::with_capacity(module.local.num_imported_funcs);
|
||||
let mut table_imports = PrimaryMap::with_capacity(module.local.num_imported_tables);
|
||||
let mut memory_imports = PrimaryMap::with_capacity(module.local.num_imported_memories);
|
||||
let mut global_imports = PrimaryMap::with_capacity(module.local.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.local.func_signature(*func_index);
|
||||
let signature = signatures.lookup(f.signature).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: export incompatible with function import",
|
||||
module_name, field
|
||||
"{}/{}: incompatible import type: exported function with signature {} \
|
||||
incompatible with function import with signature {}",
|
||||
module_name, field_name, signature, import_signature
|
||||
)));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(f.vmctx) });
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: f.address,
|
||||
vmctx: f.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
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(t) => {
|
||||
let import_table = &module.local.table_plans[index];
|
||||
if !is_table_compatible(&t.table, import_table) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: exported table incompatible with \
|
||||
(EntityIndex::Table(table_index), Some(Export::Table(t))) => {
|
||||
let import_table = &module.local.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,
|
||||
)));
|
||||
}
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(t.vmctx) });
|
||||
table_imports.push(VMTableImport {
|
||||
from: t.definition,
|
||||
vmctx: t.vmctx,
|
||||
});
|
||||
}
|
||||
Export::Global(_) | Export::Memory(_) | Export::Function(_) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: export incompatible with table import",
|
||||
module_name, field
|
||||
module_name, field_name,
|
||||
)));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(t.vmctx) });
|
||||
table_imports.push(VMTableImport {
|
||||
from: t.definition,
|
||||
vmctx: t.vmctx,
|
||||
});
|
||||
}
|
||||
(EntityIndex::Table(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"unknown import: no provided import table for {}/{}",
|
||||
module_name, field
|
||||
"{}/{}: 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
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(m) => {
|
||||
let import_memory = &module.local.memory_plans[index];
|
||||
if !is_memory_compatible(&m.memory, import_memory) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: exported memory incompatible with \
|
||||
(EntityIndex::Memory(memory_index), Some(Export::Memory(m))) => {
|
||||
let import_memory = &module.local.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
|
||||
)));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(m.vmctx) });
|
||||
memory_imports.push(VMMemoryImport {
|
||||
from: m.definition,
|
||||
vmctx: m.vmctx,
|
||||
});
|
||||
}
|
||||
Export::Table(_) | Export::Global(_) | Export::Function(_) => {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: incompatible import type: export incompatible with memory import",
|
||||
module_name, field
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
||||
// 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);
|
||||
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(m.vmctx) });
|
||||
memory_imports.push(VMMemoryImport {
|
||||
from: m.definition,
|
||||
vmctx: m.vmctx,
|
||||
});
|
||||
}
|
||||
(EntityIndex::Memory(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"unknown import: no provided import memory for {}/{}",
|
||||
module_name, field
|
||||
"{}/{}: 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
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(_) => {
|
||||
(EntityIndex::Global(global_index), Some(Export::Global(g))) => {
|
||||
let imported_global = module.local.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
|
||||
global import",
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
Export::Global(g) => {
|
||||
let imported_global = module.local.globals[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
|
||||
)));
|
||||
}
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(g.vmctx) });
|
||||
global_imports.push(VMGlobalImport { from: g.definition });
|
||||
}
|
||||
},
|
||||
None => {
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(g.vmctx) });
|
||||
global_imports.push(VMGlobalImport { from: g.definition });
|
||||
}
|
||||
(EntityIndex::Global(_), Some(_)) => {
|
||||
return Err(LinkError(format!(
|
||||
"unknown import: no provided import global for {}/{}",
|
||||
module_name, field
|
||||
"{}/{}: 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
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user