use crate::{
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store,
Trap,
};
use anyhow::{anyhow, bail, Context, Error, Result};
use log::warn;
use std::collections::hash_map::{Entry, HashMap};
use std::rc::Rc;
pub struct Linker {
store: Store,
string2idx: HashMap<Rc<str>, usize>,
strings: Vec<Rc<str>>,
map: HashMap<ImportKey, Extern>,
allow_shadowing: bool,
}
#[derive(Hash, PartialEq, Eq)]
struct ImportKey {
name: usize,
module: usize,
kind: ImportKind,
}
#[derive(Hash, PartialEq, Eq, Debug)]
enum ImportKind {
Func(FuncType),
Global(GlobalType),
Memory,
Table,
}
impl Linker {
pub fn new(store: &Store) -> Linker {
Linker {
store: store.clone(),
map: HashMap::new(),
string2idx: HashMap::new(),
strings: Vec::new(),
allow_shadowing: false,
}
}
pub fn allow_shadowing(&mut self, allow: bool) -> &mut Linker {
self.allow_shadowing = allow;
self
}
pub fn define(
&mut self,
module: &str,
name: &str,
item: impl Into<Extern>,
) -> Result<&mut Self> {
self._define(module, name, item.into())
}
fn _define(&mut self, module: &str, name: &str, item: Extern) -> Result<&mut Self> {
if !item.comes_from_same_store(&self.store) {
bail!("all linker items must be from the same store");
}
self.insert(module, name, item)?;
Ok(self)
}
pub fn func<Params, Args>(
&mut self,
module: &str,
name: &str,
func: impl IntoFunc<Params, Args>,
) -> Result<&mut Self> {
self._define(module, name, Func::wrap(&self.store, func).into())
}
pub fn instance(&mut self, module_name: &str, instance: &Instance) -> Result<&mut Self> {
if !Store::same(&self.store, instance.store()) {
bail!("all linker items must be from the same store");
}
for export in instance.exports() {
self.insert(module_name, export.name(), export.into_extern())?;
}
Ok(self)
}
pub fn module(&mut self, module_name: &str, module: &Module) -> Result<&mut Self> {
match ModuleKind::categorize(module)? {
ModuleKind::Command => self.command(module_name, module),
ModuleKind::Reactor => {
let instance = self.instantiate(&module)?;
if let Some(export) = instance.get_export("_initialize") {
if let Extern::Func(func) = export {
func.get0::<()>()
.and_then(|f| f().map_err(Into::into))
.context("calling the Reactor initialization function")?;
}
}
self.instance(module_name, &instance)
}
}
}
fn command(&mut self, module_name: &str, module: &Module) -> Result<&mut Self> {
for export in module.exports() {
if let Some(func_ty) = export.ty().func() {
let imports = self.compute_imports(module)?;
let store = self.store.clone();
let module = module.clone();
let export_name = export.name().to_owned();
let func = Func::new(&self.store, func_ty.clone(), move |_, params, results| {
let instance = Instance::new(&store, &module, &imports)?;
let command_results = instance
.get_export(&export_name)
.unwrap()
.into_func()
.unwrap()
.call(params)
.map_err(|error| error.downcast::<Trap>().unwrap())?;
for (result, command_result) in
results.iter_mut().zip(command_results.into_vec())
{
*result = command_result;
}
Ok(())
});
self.insert(module_name, export.name(), Extern::Func(func))?;
} else if export.name() == "memory" && export.ty().memory().is_some() {
} else if export.name() == "__indirect_function_table" && export.ty().table().is_some()
{
} else if export.name() == "table" && export.ty().table().is_some() {
} else if export.name() == "__data_end" && export.ty().global().is_some() {
warn!("command module exporting '__data_end' is deprecated");
} else if export.name() == "__heap_base" && export.ty().global().is_some() {
warn!("command module exporting '__heap_base' is deprecated");
} else if export.name() == "__rtti_base" && export.ty().global().is_some() {
warn!("command module exporting '__rtti_base' is deprecated; pass `--runtime half` to the AssemblyScript compiler");
} else {
bail!("command export '{}' is not a function", export.name());
}
}
Ok(self)
}
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))
.collect::<Vec<_>>();
for (name, item) in items {
self.define(as_module, &name, item)?;
}
Ok(())
}
fn insert(&mut self, module: &str, name: &str, item: Extern) -> Result<()> {
let key = self.import_key(module, name, item.ty());
match self.map.entry(key) {
Entry::Occupied(o) if !self.allow_shadowing => bail!(
"import of `{}::{}` with kind {:?} defined twice",
module,
name,
o.key().kind,
),
Entry::Occupied(mut o) => {
o.insert(item);
}
Entry::Vacant(v) => {
v.insert(item);
}
}
Ok(())
}
fn import_key(&mut self, module: &str, name: &str, ty: ExternType) -> ImportKey {
ImportKey {
module: self.intern_str(module),
name: self.intern_str(name),
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,
}
}
fn intern_str(&mut self, string: &str) -> usize {
if let Some(idx) = self.string2idx.get(string) {
return *idx;
}
let string: Rc<str> = string.into();
let idx = self.strings.len();
self.strings.push(string.clone());
self.string2idx.insert(string, idx);
idx
}
pub fn instantiate(&self, module: &Module) -> Result<Instance> {
let imports = self.compute_imports(module)?;
Instance::new(&self.store, module, &imports)
}
fn compute_imports(&self, module: &Module) -> Result<Vec<Extern>> {
module
.imports()
.map(|import| self.get(&import).ok_or_else(|| self.link_error(&import)))
.collect()
}
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[i.name] != import.name()
{
continue;
}
options.push(format!(" * {:?}\n", i.kind));
}
if options.is_empty() {
return anyhow!(
"unknown import: `{}::{}` has not been defined",
import.module(),
import.name()
);
}
options.sort();
anyhow!(
"incompatible import type for `{}::{}` specified\n\
desired signature was: {:?}\n\
signatures available:\n\n{}",
import.module(),
import.name(),
import.ty(),
options.concat(),
)
}
pub fn store(&self) -> &Store {
&self.store
}
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.clone(),
)
})
}
pub fn get(&self, import: &ImportType) -> Option<Extern> {
let key = ImportKey {
module: *self.string2idx.get(import.module())?,
name: *self.string2idx.get(import.name())?,
kind: self.import_kind(import.ty()),
};
self.map.get(&key).cloned()
}
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)
}
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.clone())
}
pub fn get_default(&self, module: &str) -> Result<Func> {
let mut items = self.get_by_name(module, "");
if let Some(external) = items.next() {
if items.next().is_some() {
bail!("too many items named `` in `{}`", module);
}
if let Extern::Func(func) = external {
return Ok(func.clone());
}
bail!("default export in '{}' is not a function", module);
}
let mut items = self.get_by_name(module, "_start");
if let Some(external) = items.next() {
if items.next().is_some() {
bail!("too many items named `_start` in `{}`", module);
}
if let Extern::Func(func) = external {
return Ok(func.clone());
}
bail!("`_start` in '{}' is not a function", module);
}
Ok(Func::new(
&self.store,
FuncType::new(Vec::new().into_boxed_slice(), Vec::new().into_boxed_slice()),
move |_, _, _| Ok(()),
))
}
}
enum ModuleKind {
Command,
Reactor,
}
impl ModuleKind {
fn categorize(module: &Module) -> Result<ModuleKind> {
let command_start = module.get_export("_start");
let reactor_start = module.get_export("_initialize");
match (command_start, reactor_start) {
(Some(command_start), None) => {
if let Some(_) = command_start.func() {
Ok(ModuleKind::Command)
} else {
bail!("`_start` must be a function")
}
}
(None, Some(reactor_start)) => {
if let Some(_) = reactor_start.func() {
Ok(ModuleKind::Reactor)
} else {
bail!("`_initialize` must be a function")
}
}
(None, None) => {
Ok(ModuleKind::Reactor)
}
(Some(_), Some(_)) => {
bail!("Program cannot be both a Command and a Reactor")
}
}
}
}