Remove the type-driven ability for duplicates in a Linker (#2789)

When `Linker` was first created it was attempted to be created with the
ability to instantiate any wasm modules, including those with duplicate
import strings of different types. In an effort to support this a
`Linker` supports defining the same names twice so long as they're
defined with differently-typed values.

This ended up causing wast testsuite failures module linking is enabled,
however, because the wrong error message is returned. While it would be
possible to fix this there's already the possibility for confusing error
messages today due to the `Linker` trying to take on this type-level
complexity. In a way this is yet-another type checker for wasm imports,
but sort of a bad one because it only supports things like
globals/functions, and otherwise you can only define one `Memory`, for
example, with a particular name.

This commit completely removes this feature from `Linker` to simplify
the implementation and make error messages more straightforward. This
means that any error message coming from a `Linker` is purely "this
thing wasn't defined" rather than a hybrid of "maybe the types didn't
match?". I think this also better aligns with the direction that we see
conventional wasm modules going which is that duplicate imports are not
ever present.
This commit is contained in:
Alex Crichton
2021-03-29 17:26:02 -05:00
committed by GitHub
parent cc0ec75a29
commit a301202b7d
2 changed files with 26 additions and 86 deletions

View File

@@ -1,7 +1,6 @@
use crate::instance::InstanceBuilder; use crate::instance::InstanceBuilder;
use crate::{ use crate::{
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store, Extern, ExternType, Func, FuncType, ImportType, Instance, IntoFunc, Module, Store, Trap,
Trap,
}; };
use anyhow::{anyhow, bail, Context, Error, Result}; use anyhow::{anyhow, bail, Context, Error, Result};
use log::warn; use log::warn;
@@ -29,15 +28,9 @@ use std::rc::Rc;
/// module and then has its own name. This basically follows the wasm standard /// module and then has its own name. This basically follows the wasm standard
/// for modularization. /// for modularization.
/// ///
/// Names in a `Linker` can be defined twice, but only for different signatures /// Names in a `Linker` cannot be defined twice, but allowing duplicates by
/// of items. This means that every item defined in a `Linker` has a unique /// shadowing the previous definition can be controlled with the
/// name/type pair. For example you can define two functions with the module /// [`Linker::allow_shadowing`] method.
/// name `foo` and item name `bar`, so long as they have different function
/// signatures. Currently duplicate memories and tables are not allowed, only
/// one-per-name is allowed.
///
/// Note that allowing duplicates by shadowing the previous definition can be
/// controlled with the [`Linker::allow_shadowing`] method as well.
pub struct Linker { pub struct Linker {
store: Store, store: Store,
string2idx: HashMap<Rc<str>, usize>, string2idx: HashMap<Rc<str>, usize>,
@@ -50,17 +43,6 @@ pub struct Linker {
struct ImportKey { struct ImportKey {
name: usize, name: usize,
module: usize, module: usize,
kind: ImportKind,
}
#[derive(Hash, PartialEq, Eq, Debug)]
enum ImportKind {
Func(FuncType),
Global(GlobalType),
Memory,
Table,
Module,
Instance,
} }
impl Linker { impl Linker {
@@ -517,17 +499,15 @@ impl Linker {
} }
fn insert(&mut self, module: &str, name: Option<&str>, item: Extern) -> Result<()> { fn insert(&mut self, module: &str, name: Option<&str>, item: Extern) -> Result<()> {
let key = self.import_key(module, name, item.ty()); let key = self.import_key(module, name);
let desc = || match name { let desc = || match name {
Some(name) => format!("{}::{}", module, name), Some(name) => format!("{}::{}", module, name),
None => module.to_string(), None => module.to_string(),
}; };
match self.map.entry(key) { match self.map.entry(key) {
Entry::Occupied(o) if !self.allow_shadowing => bail!( Entry::Occupied(_) if !self.allow_shadowing => {
"import of `{}` with kind {:?} defined twice", bail!("import of `{}` defined twice", desc(),)
desc(), }
o.key().kind,
),
Entry::Occupied(mut o) => { Entry::Occupied(mut o) => {
o.insert(item); o.insert(item);
} }
@@ -537,11 +517,7 @@ impl Linker {
if let Extern::Func(_) = &item { if let Extern::Func(_) = &item {
if let Some(name) = name { if let Some(name) = name {
if self.store.get_host_func(module, name).is_some() { if self.store.get_host_func(module, name).is_some() {
bail!( bail!("import of `{}` defined twice", desc(),)
"import of `{}` with kind {:?} defined twice",
desc(),
v.key().kind,
)
} }
} }
} }
@@ -552,24 +528,12 @@ impl Linker {
Ok(()) Ok(())
} }
fn import_key(&mut self, module: &str, name: Option<&str>, ty: ExternType) -> ImportKey { fn import_key(&mut self, module: &str, name: Option<&str>) -> ImportKey {
ImportKey { ImportKey {
module: self.intern_str(module), module: self.intern_str(module),
name: name name: name
.map(|name| self.intern_str(name)) .map(|name| self.intern_str(name))
.unwrap_or(usize::max_value()), .unwrap_or(usize::max_value()),
kind: self.import_kind(ty),
}
}
fn import_kind(&self, ty: ExternType) -> ImportKind {
match ty {
ExternType::Func(f) => ImportKind::Func(f),
ExternType::Global(f) => ImportKind::Global(f),
ExternType::Memory(_) => ImportKind::Memory,
ExternType::Table(_) => ImportKind::Table,
ExternType::Module(_) => ImportKind::Module,
ExternType::Instance(_) => ImportKind::Instance,
} }
} }
@@ -649,33 +613,11 @@ impl Linker {
} }
fn link_error(&self, import: &ImportType) -> Error { fn link_error(&self, import: &ImportType) -> Error {
let mut options = Vec::new();
for i in self.map.keys() {
if &*self.strings[i.module] != import.module()
|| self.strings.get(i.name).map(|s| &**s) != import.name()
{
continue;
}
options.push(format!(" * {:?}\n", i.kind));
}
let desc = match import.name() { let desc = match import.name() {
Some(name) => format!("{}::{}", import.module(), name), Some(name) => format!("{}::{}", import.module(), name),
None => import.module().to_string(), None => import.module().to_string(),
}; };
if options.is_empty() { anyhow!("unknown import: `{}` has not been defined", desc)
return anyhow!("unknown import: `{}` has not been defined", desc);
}
options.sort();
anyhow!(
"incompatible import type for `{}` specified\n\
desired signature was: {:?}\n\
signatures available:\n\n{}",
desc,
import.ty(),
options.concat(),
)
} }
/// Returns the [`Store`] that this linker is connected to. /// Returns the [`Store`] that this linker is connected to.
@@ -829,7 +771,6 @@ impl Linker {
Some(name) => *self.string2idx.get(name)?, Some(name) => *self.string2idx.get(name)?,
None => usize::max_value(), None => usize::max_value(),
}, },
kind: self.import_kind(import.ty()),
}; };
self.map.get(&key).cloned() self.map.get(&key).cloned()
} }

View File

@@ -27,46 +27,45 @@ fn link_twice_bad() -> Result<()> {
let mut linker = Linker::new(&store); let mut linker = Linker::new(&store);
// functions // functions
linker.func("", "", || {})?; linker.func("f", "", || {})?;
assert!(linker.func("", "", || {}).is_err()); assert!(linker.func("f", "", || {}).is_err());
assert!(linker assert!(linker
.func("", "", || -> Result<(), Trap> { loop {} }) .func("f", "", || -> Result<(), Trap> { loop {} })
.is_err()); .is_err());
linker.func("", "", |_: i32| {})?;
// globals // globals
let ty = GlobalType::new(ValType::I32, Mutability::Const); let ty = GlobalType::new(ValType::I32, Mutability::Const);
let global = Global::new(&store, ty, Val::I32(0))?; let global = Global::new(&store, ty, Val::I32(0))?;
linker.define("", "", global.clone())?; linker.define("g", "1", global.clone())?;
assert!(linker.define("", "", global.clone()).is_err()); assert!(linker.define("g", "1", global.clone()).is_err());
let ty = GlobalType::new(ValType::I32, Mutability::Var); let ty = GlobalType::new(ValType::I32, Mutability::Var);
let global = Global::new(&store, ty, Val::I32(0))?; let global = Global::new(&store, ty, Val::I32(0))?;
linker.define("", "", global.clone())?; linker.define("g", "2", global.clone())?;
assert!(linker.define("", "", global.clone()).is_err()); assert!(linker.define("g", "2", global.clone()).is_err());
let ty = GlobalType::new(ValType::I64, Mutability::Const); let ty = GlobalType::new(ValType::I64, Mutability::Const);
let global = Global::new(&store, ty, Val::I64(0))?; let global = Global::new(&store, ty, Val::I64(0))?;
linker.define("", "", global.clone())?; linker.define("g", "3", global.clone())?;
assert!(linker.define("", "", global.clone()).is_err()); assert!(linker.define("g", "3", global.clone()).is_err());
// memories // memories
let ty = MemoryType::new(Limits::new(1, None)); let ty = MemoryType::new(Limits::new(1, None));
let memory = Memory::new(&store, ty); let memory = Memory::new(&store, ty);
linker.define("", "", memory.clone())?; linker.define("m", "", memory.clone())?;
assert!(linker.define("", "", memory.clone()).is_err()); assert!(linker.define("m", "", memory.clone()).is_err());
let ty = MemoryType::new(Limits::new(2, None)); let ty = MemoryType::new(Limits::new(2, None));
let memory = Memory::new(&store, ty); let memory = Memory::new(&store, ty);
assert!(linker.define("", "", memory.clone()).is_err()); assert!(linker.define("m", "", memory.clone()).is_err());
// tables // tables
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
let table = Table::new(&store, ty, Val::FuncRef(None))?; let table = Table::new(&store, ty, Val::FuncRef(None))?;
linker.define("", "", table.clone())?; linker.define("t", "", table.clone())?;
assert!(linker.define("", "", table.clone()).is_err()); assert!(linker.define("t", "", table.clone()).is_err());
let ty = TableType::new(ValType::FuncRef, Limits::new(2, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(2, None));
let table = Table::new(&store, ty, Val::FuncRef(None))?; let table = Table::new(&store, ty, Val::FuncRef(None))?;
assert!(linker.define("", "", table.clone()).is_err()); assert!(linker.define("t", "", table.clone()).is_err());
Ok(()) Ok(())
} }