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:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user