Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add APIs to lookup values in Linker #1480

Merged
merged 2 commits into from
Apr 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 79 additions & 6 deletions crates/api/src/linker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store,
};
use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
use std::collections::hash_map::{Entry, HashMap};
use std::rc::Rc;

Expand Down Expand Up @@ -270,6 +270,27 @@ impl Linker {
Ok(self)
}

/// Aliases one module's name as another.
///
/// This method will alias all currently defined under `module` to also be
/// defined under the name `as_module` too.
///
/// # Errors
///
/// Returns an error if any shadowing violations happen while defining new
/// items.
pub fn alias(&mut self, module: &str, as_module: &str) -> Result<()> {
let items = self
.iter()
.filter(|(m, _, _)| *m == module)
.map(|(_, name, item)| (name.to_string(), item.clone()))
.collect::<Vec<_>>();
for (name, item) in items {
self.define(as_module, &name, item)?;
}
Ok(())
}

fn insert(&mut self, module: &str, name: &str, ty: &ExternType, item: Extern) -> Result<()> {
let key = self.import_key(module, name, ty);
match self.map.entry(key) {
Expand Down Expand Up @@ -357,7 +378,7 @@ impl Linker {
pub fn instantiate(&self, module: &Module) -> Result<Instance> {
let mut imports = Vec::new();
for import in module.imports() {
if let Some(item) = self.import_get(import) {
if let Some(item) = self.get(import) {
imports.push(item.clone());
continue;
}
Expand Down Expand Up @@ -395,7 +416,30 @@ impl Linker {
Instance::new(module, &imports)
}

fn import_get(&self, import: &ImportType) -> Option<&Extern> {
/// Returns the [`Store`] that this linker is connected to.
pub fn store(&self) -> &Store {
&self.store
}

/// Returns an iterator over all items defined in this `Linker`.
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
///
/// The iterator returned will yield 3-tuples where the first two elements
/// are the module name and item name for the external item, and the third
/// item is the item itself that is defined.
///
/// Note that multiple `Extern` items may be defined for the same
/// module/name pair.
pub fn iter(&self) -> impl Iterator<Item = (&str, &str, &Extern)> {
self.map
.iter()
.map(move |(key, item)| (&*self.strings[key.module], &*self.strings[key.name], item))
}

/// Looks up a value in this `Linker` which matches the `import` type
/// provided.
///
/// Returns `None` if no match was found.
pub fn get(&self, import: &ImportType) -> Option<&Extern> {
let key = ImportKey {
module: *self.string2idx.get(import.module())?,
name: *self.string2idx.get(import.name())?,
Expand All @@ -404,8 +448,37 @@ impl Linker {
self.map.get(&key)
}

/// Returns the [`Store`] that this linker is connected to.
pub fn store(&self) -> &Store {
&self.store
/// Returns all items defined for the `module` and `name` pair.
///
/// This may return an empty iterator, but it may also return multiple items
/// if the module/name have been defined twice.
pub fn get_by_name<'a: 'p, 'p>(
&'a self,
module: &'p str,
name: &'p str,
) -> impl Iterator<Item = &'a Extern> + 'p {
self.map
.iter()
.filter(move |(key, _item)| {
&*self.strings[key.module] == module && &*self.strings[key.name] == name
})
.map(|(_, item)| item)
}

/// Returns the single item defined for the `module` and `name` pair.
///
/// Unlike the similar [`Linker::get_by_name`] method this function returns
/// a single `&Extern` item. If the `module` and `name` pair isn't defined
/// in this linker then an error is returned. If more than one value exists
/// for the `module` and `name` pairs, then an error is returned as well.
pub fn get_one_by_name(&self, module: &str, name: &str) -> Result<&Extern> {
let mut items = self.get_by_name(module, name);
let ret = items
.next()
.ok_or_else(|| anyhow!("no item named `{}` in `{}`", name, module))?;
if items.next().is_some() {
bail!("too many items named `{}` in `{}`", name, module);
}
Ok(ret)
}
}
57 changes: 30 additions & 27 deletions crates/wast/src/wast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::spectest::link_spectest;
use anyhow::{anyhow, bail, Context as _, Result};
use std::collections::HashMap;
use std::path::Path;
use std::str;
use wasmtime::*;
Expand Down Expand Up @@ -28,8 +27,9 @@ pub struct WastContext {
/// Wast files have a concept of a "current" module, which is the most
/// recently defined.
current: Option<Instance>,

instances: HashMap<String, Instance>,
sunfishcode marked this conversation as resolved.
Show resolved Hide resolved
// FIXME(#1479) this is only needed to retain correct trap information after
// we've dropped previous `Instance` values.
modules: Vec<Module>,
linker: Linker,
store: Store,
}
Expand Down Expand Up @@ -58,28 +58,27 @@ impl WastContext {
linker.allow_shadowing(true);
Self {
current: None,
instances: HashMap::new(),
linker,
store,
modules: Vec::new(),
}
}

fn get_instance(&self, instance_name: Option<&str>) -> Result<Instance> {
match instance_name {
Some(name) => self
.instances
.get(name)
.cloned()
.ok_or_else(|| anyhow!("failed to find instance named `{}`", name)),
fn get_export(&self, module: Option<&str>, name: &str) -> Result<&Extern> {
match module {
Some(module) => self.linker.get_one_by_name(module, name),
None => self
.current
.clone()
.ok_or_else(|| anyhow!("no previous instance found")),
.as_ref()
.ok_or_else(|| anyhow!("no previous instance found"))?
.get_export(name)
.ok_or_else(|| anyhow!("no item named `{}` found", name)),
}
}

fn instantiate(&self, module: &[u8]) -> Result<Outcome<Instance>> {
fn instantiate(&mut self, module: &[u8]) -> Result<Outcome<Instance>> {
let module = Module::new(&self.store, module)?;
self.modules.push(module.clone());
let instance = match self.linker.instantiate(&module) {
Ok(i) => i,
Err(e) => return e.downcast::<Trap>().map(Outcome::Trap),
Expand Down Expand Up @@ -125,7 +124,6 @@ impl WastContext {
Outcome::Trap(e) => bail!("instantiation failed with: {}", e.message()),
};
if let Some(name) = instance_name {
self.instances.insert(name.to_string(), instance.clone());
self.linker.instance(name, &instance)?;
}
self.current = Some(instance);
Expand All @@ -134,10 +132,17 @@ impl WastContext {

/// Register an instance to make it available for performing actions.
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
let instance = self.get_instance(name)?.clone();
self.linker.instance(as_name, &instance)?;
self.instances.insert(as_name.to_string(), instance);
Ok(())
match name {
Some(name) => self.linker.alias(name, as_name),
None => {
let current = self
.current
.as_ref()
.ok_or(anyhow!("no previous instance"))?;
self.linker.instance(as_name, current)?;
Ok(())
}
}
}

/// Invoke an exported function from an instance.
Expand All @@ -147,10 +152,9 @@ impl WastContext {
field: &str,
args: &[Val],
) -> Result<Outcome> {
let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?;
let func = instance
.get_export(field)
.and_then(|e| e.func())
let func = self
.get_export(instance_name, field)?
.func()
.ok_or_else(|| anyhow!("no function named `{}`", field))?;
Ok(match func.call(args) {
Ok(result) => Outcome::Ok(result.into()),
Expand All @@ -160,10 +164,9 @@ impl WastContext {

/// Get the value of an exported global from an instance.
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?;
let global = instance
.get_export(field)
.and_then(|e| e.global())
let global = self
.get_export(instance_name, field)?
.global()
.ok_or_else(|| anyhow!("no global named `{}`", field))?;
Ok(Outcome::Ok(vec![global.get()]))
}
Expand Down