Remove the module linking implementation in Wasmtime (#3958)

* Remove the module linking implementation in Wasmtime

This commit removes the experimental implementation of the module
linking WebAssembly proposal from Wasmtime. The module linking is no
longer intended for core WebAssembly but is instead incorporated into
the component model now at this point. This means that very large parts
of Wasmtime's implementation of module linking are no longer applicable
and would change greatly with an implementation of the component model.

The main purpose of this is to remove Wasmtime's reliance on the support
for module-linking in `wasmparser` and tooling crates. With this
reliance removed we can move over to the `component-model` branch of
`wasmparser` and use the updated support for the component model.
Additionally given the trajectory of the component model proposal the
embedding API of Wasmtime will not look like what it looks like today
for WebAssembly. For example the core wasm `Instance` will not change
and instead a `Component` is likely to be added instead.

Some more rationale for this is in #3941, but the basic idea is that I
feel that it's not going to be viable to develop support for the
component model on a non-`main` branch of Wasmtime. Additionaly I don't
think it's viable, for the same reasons as `wasm-tools`, to support the
old module linking proposal and the new component model at the same
time.

This commit takes a moment to not only delete the existing module
linking implementation but some abstractions are also simplified. For
example module serialization is a bit simpler that there's only one
module. Additionally instantiation is much simpler since the only
initializer we have to deal with are imports and nothing else.

Closes #3941

* Fix doc link

* Update comments
This commit is contained in:
Alex Crichton
2022-03-23 14:57:34 -05:00
committed by GitHub
parent 6a60e8363f
commit 76b82910c9
51 changed files with 400 additions and 4555 deletions

View File

@@ -382,7 +382,6 @@ impl Config {
let mut cfg = wasmtime::Config::new();
cfg.wasm_bulk_memory(true)
.wasm_reference_types(true)
.wasm_module_linking(self.module_config.config.module_linking_enabled)
.wasm_multi_value(self.module_config.config.multi_value_enabled)
.wasm_multi_memory(self.module_config.config.max_memories > 1)
.wasm_simd(self.module_config.config.simd_enabled)

View File

@@ -1,7 +1,6 @@
//! Dummy implementations of things that a Wasm module can import.
use anyhow::Result;
use std::fmt::Write;
use wasmtime::*;
/// Create a set of dummy functions/globals/etc for the given imports.
@@ -9,27 +8,13 @@ pub fn dummy_linker<'module, T>(store: &mut Store<T>, module: &Module) -> Result
let mut linker = Linker::new(store.engine());
linker.allow_shadowing(true);
for import in module.imports() {
match import.name() {
Some(name) => {
linker
.define(import.module(), name, dummy_extern(store, import.ty())?)
.unwrap();
}
None => match import.ty() {
ExternType::Instance(ty) => {
for ty in ty.exports() {
linker
.define(import.module(), ty.name(), dummy_extern(store, ty.ty())?)
.unwrap();
}
}
other => {
linker
.define_name(import.module(), dummy_extern(store, other)?)
.unwrap();
}
},
}
linker
.define(
import.module(),
import.name(),
dummy_extern(store, import.ty())?,
)
.unwrap();
}
Ok(linker)
}
@@ -41,8 +26,6 @@ pub fn dummy_extern<T>(store: &mut Store<T>, ty: ExternType) -> Result<Extern> {
ExternType::Global(global_ty) => Extern::Global(dummy_global(store, global_ty)),
ExternType::Table(table_ty) => Extern::Table(dummy_table(store, table_ty)?),
ExternType::Memory(mem_ty) => Extern::Memory(dummy_memory(store, mem_ty)?),
ExternType::Instance(instance_ty) => Extern::Instance(dummy_instance(store, instance_ty)?),
ExternType::Module(module_ty) => Extern::Module(dummy_module(store.engine(), module_ty)),
})
}
@@ -91,297 +74,12 @@ pub fn dummy_memory<T>(store: &mut Store<T>, ty: MemoryType) -> Result<Memory> {
Memory::new(store, ty)
}
/// Construct a dummy instance for the given instance type.
///
/// This is done by using the expected type to generate a module on-the-fly
/// which we the instantiate.
pub fn dummy_instance<T>(store: &mut Store<T>, ty: InstanceType) -> Result<Instance> {
let mut wat = WatGenerator::new();
for ty in ty.exports() {
wat.export(&ty);
}
let module = Module::new(store.engine(), &wat.finish()).unwrap();
Instance::new(store, &module, &[])
}
/// Construct a dummy module for the given module type.
///
/// This is done by using the expected type to generate a module on-the-fly.
pub fn dummy_module(engine: &Engine, ty: ModuleType) -> Module {
let mut wat = WatGenerator::new();
for ty in ty.imports() {
wat.import(&ty);
}
for ty in ty.exports() {
wat.export(&ty);
}
Module::new(engine, &wat.finish()).unwrap()
}
struct WatGenerator {
tmp: usize,
dst: String,
}
impl WatGenerator {
fn new() -> WatGenerator {
WatGenerator {
tmp: 0,
dst: String::from("(module\n"),
}
}
fn finish(mut self) -> String {
self.dst.push_str(")\n");
self.dst
}
fn import(&mut self, ty: &ImportType<'_>) {
write!(self.dst, "(import ").unwrap();
self.str(ty.module());
write!(self.dst, " ").unwrap();
if let Some(field) = ty.name() {
self.str(field);
write!(self.dst, " ").unwrap();
}
self.item_ty(&ty.ty());
writeln!(self.dst, ")").unwrap();
}
fn item_ty(&mut self, ty: &ExternType) {
match ty {
ExternType::Memory(mem) => {
write!(
self.dst,
"(memory {} {})",
mem.minimum(),
match mem.maximum() {
Some(max) => max.to_string(),
None => String::new(),
}
)
.unwrap();
}
ExternType::Table(table) => {
write!(
self.dst,
"(table {} {} {})",
table.minimum(),
match table.maximum() {
Some(max) => max.to_string(),
None => String::new(),
},
wat_ty(&table.element()),
)
.unwrap();
}
ExternType::Global(ty) => {
if ty.mutability() == Mutability::Const {
write!(self.dst, "(global {})", wat_ty(ty.content())).unwrap();
} else {
write!(self.dst, "(global (mut {}))", wat_ty(ty.content())).unwrap();
}
}
ExternType::Func(ty) => {
write!(self.dst, "(func ").unwrap();
self.func_sig(ty);
write!(self.dst, ")").unwrap();
}
ExternType::Instance(ty) => {
writeln!(self.dst, "(instance").unwrap();
for ty in ty.exports() {
write!(self.dst, "(export ").unwrap();
self.str(ty.name());
write!(self.dst, " ").unwrap();
self.item_ty(&ty.ty());
writeln!(self.dst, ")").unwrap();
}
write!(self.dst, ")").unwrap();
}
ExternType::Module(ty) => {
writeln!(self.dst, "(module").unwrap();
for ty in ty.imports() {
self.import(&ty);
writeln!(self.dst, "").unwrap();
}
for ty in ty.exports() {
write!(self.dst, "(export ").unwrap();
self.str(ty.name());
write!(self.dst, " ").unwrap();
self.item_ty(&ty.ty());
writeln!(self.dst, ")").unwrap();
}
write!(self.dst, ")").unwrap();
}
}
}
fn export(&mut self, ty: &ExportType<'_>) {
let wat_name = format!("item{}", self.tmp);
self.tmp += 1;
let item_ty = ty.ty();
self.item(&wat_name, &item_ty);
write!(self.dst, "(export ").unwrap();
self.str(ty.name());
write!(self.dst, " (").unwrap();
match item_ty {
ExternType::Memory(_) => write!(self.dst, "memory").unwrap(),
ExternType::Global(_) => write!(self.dst, "global").unwrap(),
ExternType::Func(_) => write!(self.dst, "func").unwrap(),
ExternType::Instance(_) => write!(self.dst, "instance").unwrap(),
ExternType::Table(_) => write!(self.dst, "table").unwrap(),
ExternType::Module(_) => write!(self.dst, "module").unwrap(),
}
writeln!(self.dst, " ${}))", wat_name).unwrap();
}
fn item(&mut self, name: &str, ty: &ExternType) {
match ty {
ExternType::Memory(mem) => {
write!(
self.dst,
"(memory ${} {} {})\n",
name,
mem.minimum(),
match mem.maximum() {
Some(max) => max.to_string(),
None => String::new(),
}
)
.unwrap();
}
ExternType::Table(table) => {
write!(
self.dst,
"(table ${} {} {} {})\n",
name,
table.minimum(),
match table.maximum() {
Some(max) => max.to_string(),
None => String::new(),
},
wat_ty(&table.element()),
)
.unwrap();
}
ExternType::Global(ty) => {
write!(self.dst, "(global ${} ", name).unwrap();
if ty.mutability() == Mutability::Var {
write!(self.dst, "(mut ").unwrap();
}
write!(self.dst, "{}", wat_ty(ty.content())).unwrap();
if ty.mutability() == Mutability::Var {
write!(self.dst, ")").unwrap();
}
write!(self.dst, " (").unwrap();
self.value(ty.content());
writeln!(self.dst, "))").unwrap();
}
ExternType::Func(ty) => {
write!(self.dst, "(func ${} ", name).unwrap();
self.func_sig(ty);
for ty in ty.results() {
writeln!(self.dst, "").unwrap();
self.value(&ty);
}
writeln!(self.dst, ")").unwrap();
}
ExternType::Module(ty) => {
writeln!(self.dst, "(module ${}", name).unwrap();
for ty in ty.imports() {
self.import(&ty);
}
for ty in ty.exports() {
self.export(&ty);
}
self.dst.push_str(")\n");
}
ExternType::Instance(ty) => {
writeln!(self.dst, "(module ${}_module", name).unwrap();
for ty in ty.exports() {
self.export(&ty);
}
self.dst.push_str(")\n");
writeln!(self.dst, "(instance ${} (instantiate ${0}_module))", name).unwrap();
}
}
}
fn func_sig(&mut self, ty: &FuncType) {
write!(self.dst, "(param ").unwrap();
for ty in ty.params() {
write!(self.dst, "{} ", wat_ty(&ty)).unwrap();
}
write!(self.dst, ") (result ").unwrap();
for ty in ty.results() {
write!(self.dst, "{} ", wat_ty(&ty)).unwrap();
}
write!(self.dst, ")").unwrap();
}
fn value(&mut self, ty: &ValType) {
match ty {
ValType::I32 => write!(self.dst, "i32.const 0").unwrap(),
ValType::I64 => write!(self.dst, "i64.const 0").unwrap(),
ValType::F32 => write!(self.dst, "f32.const 0").unwrap(),
ValType::F64 => write!(self.dst, "f64.const 0").unwrap(),
ValType::V128 => write!(self.dst, "v128.const i32x4 0 0 0 0").unwrap(),
ValType::ExternRef => write!(self.dst, "ref.null extern").unwrap(),
ValType::FuncRef => write!(self.dst, "ref.null func").unwrap(),
}
}
fn str(&mut self, name: &str) {
let mut bytes = [0; 4];
self.dst.push_str("\"");
for c in name.chars() {
let v = c as u32;
if v >= 0x20 && v < 0x7f && c != '"' && c != '\\' && v < 0xff {
self.dst.push(c);
} else {
for byte in c.encode_utf8(&mut bytes).as_bytes() {
self.hex_byte(*byte);
}
}
}
self.dst.push_str("\"");
}
fn hex_byte(&mut self, byte: u8) {
fn to_hex(b: u8) -> char {
if b < 10 {
(b'0' + b) as char
} else {
(b'a' + b - 10) as char
}
}
self.dst.push('\\');
self.dst.push(to_hex((byte >> 4) & 0xf));
self.dst.push(to_hex(byte & 0xf));
}
}
fn wat_ty(ty: &ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
ValType::F32 => "f32",
ValType::F64 => "f64",
ValType::V128 => "v128",
ValType::ExternRef => "externref",
ValType::FuncRef => "funcref",
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn store() -> Store<()> {
let mut config = Config::default();
config.wasm_module_linking(true);
config.wasm_multi_memory(true);
let engine = wasmtime::Engine::new(&config).unwrap();
Store::new(&engine, ())
@@ -423,171 +121,4 @@ mod tests {
let func = dummy_func(&mut store, func_ty.clone());
assert_eq!(func.ty(&store), func_ty);
}
#[test]
fn dummy_instance_import() {
let mut store = store();
let mut instance_ty = InstanceType::new();
// Functions.
instance_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
instance_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
// Globals.
instance_ty.add_named_export(
"global0",
GlobalType::new(ValType::I32, Mutability::Const).into(),
);
instance_ty.add_named_export(
"global1",
GlobalType::new(ValType::I64, Mutability::Var).into(),
);
// Tables.
instance_ty.add_named_export("table0", TableType::new(ValType::ExternRef, 1, None).into());
instance_ty.add_named_export("table1", TableType::new(ValType::ExternRef, 1, None).into());
// Memories.
instance_ty.add_named_export("memory0", MemoryType::new(1, None).into());
instance_ty.add_named_export("memory1", MemoryType::new(1, None).into());
// Modules.
instance_ty.add_named_export("module0", ModuleType::new().into());
instance_ty.add_named_export("module1", ModuleType::new().into());
// Instances.
instance_ty.add_named_export("instance0", InstanceType::new().into());
instance_ty.add_named_export("instance1", InstanceType::new().into());
let instance = dummy_instance(&mut store, instance_ty.clone()).unwrap();
let mut expected_exports = vec![
"func0",
"func1",
"global0",
"global1",
"table0",
"table1",
"memory0",
"memory1",
"module0",
"module1",
"instance0",
"instance1",
]
.into_iter()
.collect::<HashSet<_>>();
for exp in instance.ty(&store).exports() {
let was_expected = expected_exports.remove(exp.name());
assert!(was_expected);
}
assert!(expected_exports.is_empty());
}
#[test]
fn dummy_module_import() {
let store = store();
let mut module_ty = ModuleType::new();
// Multiple exported and imported functions.
module_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
module_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
module_ty.add_named_import(
"func2",
None,
FuncType::new(vec![ValType::I64], vec![]).into(),
);
module_ty.add_named_import(
"func3",
None,
FuncType::new(vec![], vec![ValType::I32]).into(),
);
// Multiple exported and imported globals.
module_ty.add_named_export(
"global0",
GlobalType::new(ValType::I32, Mutability::Const).into(),
);
module_ty.add_named_export(
"global1",
GlobalType::new(ValType::I64, Mutability::Var).into(),
);
module_ty.add_named_import(
"global2",
None,
GlobalType::new(ValType::I32, Mutability::Var).into(),
);
module_ty.add_named_import(
"global3",
None,
GlobalType::new(ValType::I64, Mutability::Const).into(),
);
// Multiple exported and imported tables.
module_ty.add_named_export("table0", TableType::new(ValType::ExternRef, 1, None).into());
module_ty.add_named_export("table1", TableType::new(ValType::ExternRef, 1, None).into());
module_ty.add_named_import(
"table2",
None,
TableType::new(ValType::ExternRef, 1, None).into(),
);
module_ty.add_named_import(
"table3",
None,
TableType::new(ValType::ExternRef, 1, None).into(),
);
// Multiple exported and imported memories.
module_ty.add_named_export("memory0", MemoryType::new(1, None).into());
module_ty.add_named_export("memory1", MemoryType::new(1, None).into());
module_ty.add_named_import("memory2", None, MemoryType::new(1, None).into());
module_ty.add_named_import("memory3", None, MemoryType::new(1, None).into());
// An exported and an imported module.
module_ty.add_named_export("module0", ModuleType::new().into());
module_ty.add_named_import("module1", None, ModuleType::new().into());
// An exported and an imported instance.
module_ty.add_named_export("instance0", InstanceType::new().into());
module_ty.add_named_import("instance1", None, InstanceType::new().into());
// Create the module.
let module = dummy_module(store.engine(), module_ty);
// Check that we have the expected exports.
assert!(module.get_export("func0").is_some());
assert!(module.get_export("func1").is_some());
assert!(module.get_export("global0").is_some());
assert!(module.get_export("global1").is_some());
assert!(module.get_export("table0").is_some());
assert!(module.get_export("table1").is_some());
assert!(module.get_export("memory0").is_some());
assert!(module.get_export("memory1").is_some());
assert!(module.get_export("instance0").is_some());
assert!(module.get_export("module0").is_some());
// Check that we have the exported imports.
let mut expected_imports = vec![
"func2",
"func3",
"global2",
"global3",
"table2",
"table3",
"memory2",
"memory3",
"instance1",
"module1",
]
.into_iter()
.collect::<HashSet<_>>();
for imp in module.imports() {
assert!(imp.name().is_none());
let was_expected = expected_imports.remove(imp.module());
assert!(was_expected);
}
assert!(expected_imports.is_empty());
}
}