Browse Source
* Implement imported/exported modules/instances This commit implements the final piece of the module linking proposal which is to flesh out the support for importing/exporting instances and modules. This ended up having a few changes: * Two more `PrimaryMap` instances are now stored in an `Instance`. The value for instances is `InstanceHandle` (pretty easy) and for modules it's `Box<dyn Any>` (less easy). * The custom host state for `InstanceHandle` for `wasmtime` is now `Arc<TypeTables` to be able to fully reconstruct an instance's types just from its instance. * Type matching for imports now has been updated to take instances/modules into account. One of the main downsides of this implementation is that type matching of imports is duplicated between wasmparser and wasmtime, leading to posssible bugs especially in the subtelties of module linking. I'm not sure how best to unify these two pieces of validation, however, and it may be more trouble than it's worth. cc #2094 * Update wat/wast/wasmparser * Review comments * Fix a bug in publish script to vendor the right witx Currently there's two witx binaries in our repository given the two wasi spec submodules, so this updates the publication script to vendor the right one.pull/2471/head
Alex Crichton
4 years ago
committed by
GitHub
37 changed files with 1016 additions and 345 deletions
@ -0,0 +1,195 @@ |
|||
use crate::Store; |
|||
use std::sync::Arc; |
|||
use wasmtime_environ::wasm::{ |
|||
EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table, |
|||
}; |
|||
use wasmtime_jit::TypeTables; |
|||
|
|||
pub struct MatchCx<'a> { |
|||
pub types: &'a TypeTables, |
|||
pub store: &'a Store, |
|||
} |
|||
|
|||
impl MatchCx<'_> { |
|||
pub fn global(&self, expected: &Global, actual: &crate::Global) -> bool { |
|||
self.global_ty(expected, actual.wasmtime_ty()) |
|||
} |
|||
|
|||
fn global_ty(&self, expected: &Global, actual: &Global) -> bool { |
|||
expected.ty == actual.ty |
|||
&& expected.wasm_ty == actual.wasm_ty |
|||
&& expected.mutability == actual.mutability |
|||
} |
|||
|
|||
pub fn table(&self, expected: &Table, actual: &crate::Table) -> bool { |
|||
self.table_ty(expected, actual.wasmtime_ty()) |
|||
} |
|||
|
|||
fn table_ty(&self, expected: &Table, actual: &Table) -> bool { |
|||
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 fn memory(&self, expected: &Memory, actual: &crate::Memory) -> bool { |
|||
self.memory_ty(expected, actual.wasmtime_ty()) |
|||
} |
|||
|
|||
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> bool { |
|||
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 fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> bool { |
|||
match self |
|||
.store |
|||
.signatures() |
|||
.borrow() |
|||
.lookup(&self.types.wasm_signatures[expected]) |
|||
{ |
|||
Some(idx) => actual.sig_index() == idx, |
|||
// If our expected signature isn't registered, then there's no way
|
|||
// that `actual` can match it.
|
|||
None => false, |
|||
} |
|||
} |
|||
|
|||
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> bool { |
|||
let module = actual.handle.module(); |
|||
self.exports_match( |
|||
expected, |
|||
actual |
|||
.handle |
|||
.host_state() |
|||
.downcast_ref::<Arc<TypeTables>>() |
|||
.unwrap(), |
|||
|name| module.exports.get(name).map(|idx| module.type_of(*idx)), |
|||
) |
|||
} |
|||
|
|||
/// Validates that the type signature of `actual` matches the `expected`
|
|||
/// module type signature.
|
|||
pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> bool { |
|||
let expected_sig = &self.types.module_signatures[expected]; |
|||
let module = actual.compiled_module().module(); |
|||
self.imports_match(expected, actual.types(), module.imports()) |
|||
&& self.exports_match(expected_sig.exports, actual.types(), |name| { |
|||
module.exports.get(name).map(|idx| module.type_of(*idx)) |
|||
}) |
|||
} |
|||
|
|||
/// Validates that the `actual_imports` list of module imports matches the
|
|||
/// `expected` module type signature.
|
|||
///
|
|||
/// Types specified in `actual_imports` are relative to `actual_types`.
|
|||
fn imports_match<'a>( |
|||
&self, |
|||
expected: ModuleTypeIndex, |
|||
actual_types: &TypeTables, |
|||
mut actual_imports: impl Iterator<Item = (&'a str, Option<&'a str>, EntityType)>, |
|||
) -> bool { |
|||
let expected_sig = &self.types.module_signatures[expected]; |
|||
for (_, _, expected) in expected_sig.imports.iter() { |
|||
let (_, _, ty) = match actual_imports.next() { |
|||
Some(e) => e, |
|||
None => return false, |
|||
}; |
|||
if !self.extern_ty_matches(expected, &ty, actual_types) { |
|||
return false; |
|||
} |
|||
} |
|||
actual_imports.next().is_none() |
|||
} |
|||
|
|||
/// Validates that all exports in `expected` are defined by `lookup` within
|
|||
/// `actual_types`.
|
|||
fn exports_match( |
|||
&self, |
|||
expected: InstanceTypeIndex, |
|||
actual_types: &TypeTables, |
|||
lookup: impl Fn(&str) -> Option<EntityType>, |
|||
) -> bool { |
|||
// The `expected` type must be a subset of `actual`, meaning that all
|
|||
// names in `expected` must be present in `actual`. Note that we do
|
|||
// name-based lookup here instead of index-based lookup.
|
|||
self.types.instance_signatures[expected].exports.iter().all( |
|||
|(name, expected)| match lookup(name) { |
|||
Some(ty) => self.extern_ty_matches(expected, &ty, actual_types), |
|||
None => false, |
|||
}, |
|||
) |
|||
} |
|||
|
|||
/// Validates that the `expected` entity matches the `actual_ty` defined
|
|||
/// within `actual_types`.
|
|||
fn extern_ty_matches( |
|||
&self, |
|||
expected: &EntityType, |
|||
actual_ty: &EntityType, |
|||
actual_types: &TypeTables, |
|||
) -> bool { |
|||
match expected { |
|||
EntityType::Global(expected) => match actual_ty { |
|||
EntityType::Global(actual) => self.global_ty(expected, actual), |
|||
_ => false, |
|||
}, |
|||
EntityType::Table(expected) => match actual_ty { |
|||
EntityType::Table(actual) => self.table_ty(expected, actual), |
|||
_ => false, |
|||
}, |
|||
EntityType::Memory(expected) => match actual_ty { |
|||
EntityType::Memory(actual) => self.memory_ty(expected, actual), |
|||
_ => false, |
|||
}, |
|||
EntityType::Function(expected) => match *actual_ty { |
|||
EntityType::Function(actual) => { |
|||
self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual] |
|||
} |
|||
_ => false, |
|||
}, |
|||
EntityType::Instance(expected) => match actual_ty { |
|||
EntityType::Instance(actual) => { |
|||
let sig = &actual_types.instance_signatures[*actual]; |
|||
self.exports_match(*expected, actual_types, |name| { |
|||
sig.exports.get(name).cloned() |
|||
}) |
|||
} |
|||
_ => false, |
|||
}, |
|||
EntityType::Module(expected) => match actual_ty { |
|||
EntityType::Module(actual) => { |
|||
let expected_module_sig = &self.types.module_signatures[*expected]; |
|||
let actual_module_sig = &actual_types.module_signatures[*actual]; |
|||
let actual_instance_sig = |
|||
&actual_types.instance_signatures[actual_module_sig.exports]; |
|||
|
|||
self.imports_match( |
|||
*expected, |
|||
actual_types, |
|||
actual_module_sig.imports.iter().map(|(module, field, ty)| { |
|||
(module.as_str(), field.as_deref(), ty.clone()) |
|||
}), |
|||
) && self.exports_match(expected_module_sig.exports, actual_types, |name| { |
|||
actual_instance_sig.exports.get(name).cloned() |
|||
}) |
|||
} |
|||
_ => false, |
|||
}, |
|||
EntityType::Event(_) => unimplemented!(), |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,348 @@ |
|||
;; subsets of imports |
|||
(module $a |
|||
(module (export "m") |
|||
(func (export "")) |
|||
(func (export "a")) |
|||
(global (export "b") i32 (i32.const 0)) |
|||
) |
|||
) |
|||
|
|||
(module |
|||
(import "a" "m" (module)) |
|||
(import "a" "m" (module (export "" (func)))) |
|||
(import "a" "m" (module (export "a" (func)))) |
|||
(import "a" "m" (module (export "b" (global i32)))) |
|||
(import "a" "m" (module |
|||
(export "" (func)) |
|||
(export "a" (func)) |
|||
)) |
|||
(import "a" "m" (module |
|||
(export "a" (func)) |
|||
(export "" (func)) |
|||
)) |
|||
(import "a" "m" (module |
|||
(export "a" (func)) |
|||
(export "" (func)) |
|||
(export "b" (global i32)) |
|||
)) |
|||
(import "a" "m" (module |
|||
(export "b" (global i32)) |
|||
(export "a" (func)) |
|||
(export "" (func)) |
|||
)) |
|||
) |
|||
|
|||
;; functions |
|||
(module $a |
|||
(module (export "m") |
|||
(func (export "")))) |
|||
|
|||
(module |
|||
(import "a" "m" (module)) |
|||
(import "a" "m" (module (export "" (func)))) |
|||
) |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func (param i32)))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func (result i32)))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (global i32))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 1 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 1))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (instance))))) |
|||
"module types incompatible") |
|||
|
|||
(module $a |
|||
(module (export "m") |
|||
(global (export "") i32 (i32.const 0)))) |
|||
|
|||
;; globals |
|||
(module |
|||
(import "a" "m" (module)) |
|||
(import "a" "m" (module (export "" (global i32)))) |
|||
) |
|||
(assert_unlinkable |
|||
(module |
|||
(import "a" "m" (module (export "" (global (mut i32))))) |
|||
) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (global f32))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 1 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 1))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (instance))))) |
|||
"module types incompatible") |
|||
|
|||
;; tables |
|||
(module $a |
|||
(module (export "m") |
|||
(table (export "") 1 funcref) |
|||
(table (export "max") 1 10 funcref) |
|||
) |
|||
) |
|||
(module |
|||
(import "a" "m" (module)) |
|||
(import "a" "m" (module (export "" (table 1 funcref)))) |
|||
(import "a" "m" (module (export "" (table 0 funcref)))) |
|||
(import "a" "m" (module (export "max" (table 1 10 funcref)))) |
|||
(import "a" "m" (module (export "max" (table 0 10 funcref)))) |
|||
(import "a" "m" (module (export "max" (table 0 11 funcref)))) |
|||
(import "a" "m" (module (export "max" (table 0 funcref)))) |
|||
) |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (global f32))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 2 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 1 10 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "max" (table 2 10 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "max" (table 1 9 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 1))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (instance))))) |
|||
"module types incompatible") |
|||
|
|||
;; memories |
|||
(module $a |
|||
(module (export "m") |
|||
(memory (export "") 1) |
|||
(memory (export "max") 1 10) |
|||
) |
|||
) |
|||
(module |
|||
(import "a" "m" (module)) |
|||
(import "a" "m" (module (export "" (memory 1)))) |
|||
(import "a" "m" (module (export "" (memory 0)))) |
|||
(import "a" "m" (module (export "max" (memory 1 10)))) |
|||
(import "a" "m" (module (export "max" (memory 0 10)))) |
|||
(import "a" "m" (module (export "max" (memory 0 11)))) |
|||
(import "a" "m" (module (export "max" (memory 0)))) |
|||
) |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (global f32))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 1 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 2))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 1 10))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "max" (memory 2 10))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "max" (memory 2))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (instance))))) |
|||
"module types incompatible") |
|||
|
|||
;; modules |
|||
(module $a |
|||
(module (export "m") |
|||
;; export nothing |
|||
(module (export "a")) |
|||
;; export one thing |
|||
(module (export "b") |
|||
(func (export "")) |
|||
) |
|||
;; export a mixture |
|||
(module (export "c") |
|||
(func (export "a")) |
|||
(func (export "b") (result i32) |
|||
i32.const 0) |
|||
(global (export "c") i32 (i32.const 0)) |
|||
) |
|||
;; import one thing |
|||
(module (export "d") |
|||
(import "" (func)) |
|||
) |
|||
;; import a mixture |
|||
(module (export "e") |
|||
(import "" (func)) |
|||
(import "" (func)) |
|||
(import "" (global i32)) |
|||
) |
|||
) |
|||
) |
|||
(module |
|||
(import "a" "m" (module)) |
|||
(import "a" "m" (module (export "a" (module)))) |
|||
(import "a" "m" (module (export "b" (module)))) |
|||
(import "a" "m" (module (export "b" (module (export "" (func)))))) |
|||
(import "a" "m" (module (export "c" (module)))) |
|||
(import "a" "m" (module (export "c" (module |
|||
(export "a" (func)) |
|||
)))) |
|||
(import "a" "m" (module (export "c" (module |
|||
(export "a" (func)) |
|||
(export "b" (func (result i32))) |
|||
)))) |
|||
(import "a" "m" (module (export "c" (module |
|||
(export "c" (global i32)) |
|||
)))) |
|||
(import "a" "m" (module (export "c" (module |
|||
(export "c" (global i32)) |
|||
(export "a" (func)) |
|||
)))) |
|||
|
|||
;; for now import strings aren't matched at all, imports must simply pairwise |
|||
;; line up |
|||
(import "a" "m" (module (export "d" (module (import "" (func)))))) |
|||
(import "a" "m" (module (export "d" (module (import "x" (func)))))) |
|||
(import "a" "m" (module (export "d" (module (import "x" "y" (func)))))) |
|||
|
|||
(import "a" "m" (module (export "e" (module |
|||
(import "x" "y" (func)) |
|||
(import "a" (func)) |
|||
(import "z" (global i32)) |
|||
)))) |
|||
) |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module (export "a" (func))))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "d" (module))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "d" (module (import "" (module))))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (global f32))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 1 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 2))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module (export "foo" (func))))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (instance))))) |
|||
"module types incompatible") |
|||
|
|||
;; instances |
|||
(module $a |
|||
;; export nothing |
|||
(module $m1) |
|||
(instance (export "a") (instantiate $m1)) |
|||
;; export one thing |
|||
(module $m2 |
|||
(func (export "")) |
|||
) |
|||
(instance (export "b") (instantiate $m2)) |
|||
;; export a mixture |
|||
(module $m3 |
|||
(func (export "a")) |
|||
(func (export "b") (result i32) |
|||
i32.const 0) |
|||
(global (export "c") i32 (i32.const 0)) |
|||
) |
|||
(instance (export "c") (instantiate $m3)) |
|||
|
|||
(module (export "m") |
|||
;; export one thing |
|||
(module $m2 |
|||
(func (export "")) |
|||
) |
|||
(instance (export "i") (instantiate $m2)) |
|||
) |
|||
|
|||
) |
|||
(module |
|||
(import "a" "a" (instance)) |
|||
(import "a" "b" (instance)) |
|||
(import "a" "b" (instance (export "" (func)))) |
|||
(import "a" "c" (instance)) |
|||
(import "a" "c" (instance (export "a" (func)))) |
|||
(import "a" "c" (instance (export "b" (func (result i32))))) |
|||
(import "a" "c" (instance (export "c" (global i32)))) |
|||
(import "a" "c" (instance |
|||
(export "a" (func)) |
|||
(export "b" (func (result i32))) |
|||
(export "c" (global i32)) |
|||
)) |
|||
(import "a" "c" (instance |
|||
(export "c" (global i32)) |
|||
(export "a" (func)) |
|||
)) |
|||
|
|||
(import "a" "m" (module (export "i" (instance)))) |
|||
(import "a" "m" (module (export "i" (instance (export "" (func)))))) |
|||
) |
|||
(assert_unlinkable |
|||
(module (import "a" "a" (instance (export "" (global f32))))) |
|||
"instance types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "i" (instance (export "x" (func))))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (func))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (table 1 funcref))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 2))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (memory 1 10))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "max" (memory 2 10))))) |
|||
"module types incompatible") |
|||
(assert_unlinkable |
|||
(module (import "a" "m" (module (export "" (module))))) |
|||
"module types incompatible") |
Loading…
Reference in new issue