use crate::component::func::HostFunc;
use crate::component::instance::RuntimeImport;
use crate::component::matching::{InstanceType, TypeChecker};
use crate::component::types;
use crate::component::{
Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, ResourceType, Val,
};
use crate::prelude::*;
use crate::{AsContextMut, Engine, Module, StoreContextMut};
use alloc::sync::Arc;
use anyhow::{bail, Context, Result};
use core::future::Future;
use core::marker;
use core::pin::Pin;
use hashbrown::hash_map::{Entry, HashMap};
use semver::Version;
use wasmtime_environ::PrimaryMap;
pub struct Linker<T> {
engine: Engine,
strings: Strings,
map: NameMap,
path: Vec<usize>,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
impl<T> Clone for Linker<T> {
fn clone(&self) -> Linker<T> {
Linker {
engine: self.engine.clone(),
strings: self.strings.clone(),
map: self.map.clone(),
path: self.path.clone(),
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
}
#[derive(Clone, Default)]
pub struct Strings {
string2idx: HashMap<Arc<str>, usize>,
strings: Vec<Arc<str>>,
}
pub struct LinkerInstance<'a, T> {
engine: &'a Engine,
path: &'a mut Vec<usize>,
path_len: usize,
strings: &'a mut Strings,
map: &'a mut NameMap,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
#[derive(Clone, Default)]
pub(crate) struct NameMap {
definitions: HashMap<usize, Definition>,
alternate_lookups: HashMap<usize, (usize, Version)>,
}
#[derive(Clone)]
pub(crate) enum Definition {
Instance(NameMap),
Func(Arc<HostFunc>),
Module(Module),
Resource(ResourceType, Arc<crate::func::HostFunc>),
}
impl<T> Linker<T> {
pub fn new(engine: &Engine) -> Linker<T> {
Linker {
engine: engine.clone(),
strings: Strings::default(),
map: NameMap::default(),
allow_shadowing: false,
path: Vec::new(),
_marker: marker::PhantomData,
}
}
pub fn engine(&self) -> &Engine {
&self.engine
}
pub fn allow_shadowing(&mut self, allow: bool) -> &mut Self {
self.allow_shadowing = allow;
self
}
pub fn root(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
engine: &self.engine,
path: &mut self.path,
path_len: 0,
strings: &mut self.strings,
map: &mut self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.root().into_instance(name)
}
fn typecheck<'a>(&'a self, component: &'a Component) -> Result<TypeChecker<'a>> {
let mut cx = TypeChecker {
types: component.types(),
strings: &self.strings,
imported_resources: Default::default(),
};
let env_component = component.env_component();
for (_idx, (name, ty)) in env_component.import_types.iter() {
let import = self.map.get(name, &self.strings);
cx.definition(ty, import)
.with_context(|| format!("component imports {desc} `{name}`, but a matching implementation was not found in the linker", desc = ty.desc()))?;
}
Ok(cx)
}
pub fn substituted_component_type(&self, component: &Component) -> Result<types::Component> {
let cx = self.typecheck(&component)?;
Ok(types::Component::from(
component.ty(),
&InstanceType {
types: cx.types,
resources: &cx.imported_resources,
},
))
}
pub fn instantiate_pre(&self, component: &Component) -> Result<InstancePre<T>> {
self.typecheck(&component)?;
let env_component = component.env_component();
let mut imports = PrimaryMap::with_capacity(env_component.imports.len());
for (idx, (import, names)) in env_component.imports.iter() {
let (root, _) = &env_component.import_types[*import];
let mut cur = self.map.get(root, &self.strings).unwrap();
for name in names {
cur = match cur {
Definition::Instance(map) => map.get(&name, &self.strings).unwrap(),
_ => unreachable!(),
};
}
let import = match cur {
Definition::Module(m) => RuntimeImport::Module(m.clone()),
Definition::Func(f) => RuntimeImport::Func(f.clone()),
Definition::Resource(t, dtor) => RuntimeImport::Resource {
ty: t.clone(),
_dtor: dtor.clone(),
dtor_funcref: component.resource_drop_func_ref(dtor),
},
Definition::Instance(_) => unreachable!(),
};
let i = imports.push(import);
assert_eq!(i, idx);
}
Ok(unsafe { InstancePre::new_unchecked(component.clone(), imports) })
}
pub fn instantiate(
&self,
store: impl AsContextMut<Data = T>,
component: &Component,
) -> Result<Instance> {
assert!(
!store.as_context().async_support(),
"must use async instantiation when async support is enabled"
);
self.instantiate_pre(component)?.instantiate(store)
}
#[cfg(feature = "async")]
pub async fn instantiate_async(
&self,
store: impl AsContextMut<Data = T>,
component: &Component,
) -> Result<Instance>
where
T: Send,
{
assert!(
store.as_context().async_support(),
"must use sync instantiation when async support is disabled"
);
self.instantiate_pre(component)?
.instantiate_async(store)
.await
}
pub fn define_unknown_imports_as_traps(&mut self, component: &Component) -> Result<()> {
use wasmtime_environ::component::ComponentTypes;
use wasmtime_environ::component::TypeDef;
fn stub_item<T>(
linker: &mut LinkerInstance<T>,
item_name: &str,
item_def: &TypeDef,
parent_instance: Option<&str>,
types: &ComponentTypes,
) -> Result<()> {
if !matches!(item_def, TypeDef::ComponentInstance(_)) && linker.get(item_name).is_some()
{
return Ok(());
}
match item_def {
TypeDef::ComponentFunc(_) => {
let fully_qualified_name = parent_instance
.map(|parent| format!("{}#{}", parent, item_name))
.unwrap_or_else(|| item_name.to_owned());
linker.func_new(&item_name, move |_, _, _| {
bail!("unknown import: `{fully_qualified_name}` has not been defined")
})?;
}
TypeDef::ComponentInstance(i) => {
let instance = &types[*i];
let mut linker_instance = linker.instance(item_name)?;
for (export_name, export) in instance.exports.iter() {
stub_item(
&mut linker_instance,
export_name,
export,
Some(item_name),
types,
)?;
}
}
TypeDef::Resource(_) => {
let ty = crate::component::ResourceType::host::<()>();
linker.resource(item_name, ty, |_, _| Ok(()))?;
}
TypeDef::Component(_) | TypeDef::Module(_) => {
bail!("unable to define {} imports as traps", item_def.desc())
}
_ => {}
}
Ok(())
}
for (_, (import_name, import_type)) in &component.env_component().import_types {
stub_item(
&mut self.root(),
import_name,
import_type,
None,
component.types(),
)?;
}
Ok(())
}
}
impl<T> LinkerInstance<'_, T> {
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
engine: self.engine,
path: self.path,
path_len: self.path_len,
strings: self.strings,
map: self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
pub fn func_wrap<F, Params, Return>(&mut self, name: &str, func: F) -> Result<()>
where
F: Fn(StoreContextMut<T>, Params) -> Result<Return> + Send + Sync + 'static,
Params: ComponentNamedList + Lift + 'static,
Return: ComponentNamedList + Lower + 'static,
{
self.insert(name, Definition::Func(HostFunc::from_closure(func)))?;
Ok(())
}
#[cfg(feature = "async")]
pub fn func_wrap_async<Params, Return, F>(&mut self, name: &str, f: F) -> Result<()>
where
F: for<'a> Fn(
StoreContextMut<'a, T>,
Params,
) -> Box<dyn Future<Output = Result<Return>> + Send + 'a>
+ Send
+ Sync
+ 'static,
Params: ComponentNamedList + Lift + 'static,
Return: ComponentNamedList + Lower + 'static,
{
assert!(
self.engine.config().async_support,
"cannot use `func_wrap_async` without enabling async support in the config"
);
let ff = move |mut store: StoreContextMut<'_, T>, params: Params| -> Result<Return> {
let async_cx = store.as_context_mut().0.async_cx().expect("async cx");
let mut future = Pin::from(f(store.as_context_mut(), params));
unsafe { async_cx.block_on(future.as_mut()) }?
};
self.func_wrap(name, ff)
}
pub fn func_new(
&mut self,
name: &str,
func: impl Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
) -> Result<()> {
self.insert(name, Definition::Func(HostFunc::new_dynamic(func)))?;
Ok(())
}
#[cfg(feature = "async")]
pub fn func_new_async<F>(&mut self, name: &str, f: F) -> Result<()>
where
F: for<'a> Fn(
StoreContextMut<'a, T>,
&'a [Val],
&'a mut [Val],
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
+ Send
+ Sync
+ 'static,
{
assert!(
self.engine.config().async_support,
"cannot use `func_new_async` without enabling async support in the config"
);
let ff = move |mut store: StoreContextMut<'_, T>, params: &[Val], results: &mut [Val]| {
let async_cx = store.as_context_mut().0.async_cx().expect("async cx");
let mut future = Pin::from(f(store.as_context_mut(), params, results));
unsafe { async_cx.block_on(future.as_mut()) }?
};
self.func_new(name, ff)
}
pub fn module(&mut self, name: &str, module: &Module) -> Result<()> {
self.insert(name, Definition::Module(module.clone()))?;
Ok(())
}
pub fn resource(
&mut self,
name: &str,
ty: ResourceType,
dtor: impl Fn(StoreContextMut<'_, T>, u32) -> Result<()> + Send + Sync + 'static,
) -> Result<()> {
let dtor = Arc::new(crate::func::HostFunc::wrap_inner(
&self.engine,
move |mut cx: crate::Caller<'_, T>, (param,): (u32,)| dtor(cx.as_context_mut(), param),
));
self.insert(name, Definition::Resource(ty, dtor))?;
Ok(())
}
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.as_mut().into_instance(name)
}
pub fn into_instance(mut self, name: &str) -> Result<Self> {
let name = self.insert(name, Definition::Instance(NameMap::default()))?;
self.map = match self.map.definitions.get_mut(&name) {
Some(Definition::Instance(map)) => map,
_ => unreachable!(),
};
self.path.truncate(self.path_len);
self.path.push(name);
self.path_len += 1;
Ok(self)
}
fn insert(&mut self, name: &str, item: Definition) -> Result<usize> {
self.map
.insert(name, &mut self.strings, self.allow_shadowing, item)
}
fn get(&self, name: &str) -> Option<&Definition> {
self.map.get(name, &self.strings)
}
}
impl NameMap {
pub(crate) fn get(&self, name: &str, strings: &Strings) -> Option<&Definition> {
let candidate = strings.lookup(name).and_then(|k| self.definitions.get(&k));
if let Some(def) = candidate {
return Some(def);
}
let (alternate_name, _version) = alternate_lookup_key(name)?;
let alternate_key = strings.lookup(alternate_name)?;
let (exact_key, _version) = self.alternate_lookups.get(&alternate_key)?;
self.definitions.get(exact_key)
}
fn insert(
&mut self,
name: &str,
strings: &mut Strings,
allow_shadowing: bool,
item: Definition,
) -> Result<usize> {
let key = strings.intern(name);
match self.definitions.entry(key) {
Entry::Occupied(_) if !allow_shadowing => {
bail!("import of `{}` defined twice", strings.strings[key])
}
Entry::Occupied(mut e) => {
e.insert(item);
}
Entry::Vacant(v) => {
v.insert(item);
}
}
if let Some((alternate_key, version)) = alternate_lookup_key(name) {
let alternate_key = strings.intern(alternate_key);
match self.alternate_lookups.entry(alternate_key) {
Entry::Occupied(mut e) => {
let (_, prev_version) = e.get();
if version > *prev_version {
e.insert((key, version));
}
}
Entry::Vacant(v) => {
v.insert((key, version));
}
}
}
Ok(key)
}
}
fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
let at = name.find('@')?;
let version_string = &name[at + 1..];
let version = Version::parse(version_string).ok()?;
if !version.pre.is_empty() {
None
} else if version.major != 0 {
let first_dot = version_string.find('.')? + at + 1;
Some((&name[..first_dot], version))
} else if version.minor != 0 {
let first_dot = version_string.find('.')? + at + 1;
let second_dot = name[first_dot + 1..].find('.')? + first_dot + 1;
Some((&name[..second_dot], version))
} else {
None
}
}
impl Strings {
fn intern(&mut self, string: &str) -> usize {
if let Some(idx) = self.string2idx.get(string) {
return *idx;
}
let string: Arc<str> = string.into();
let idx = self.strings.len();
self.strings.push(string.clone());
self.string2idx.insert(string, idx);
idx
}
pub fn lookup(&self, string: &str) -> Option<usize> {
self.string2idx.get(string).cloned()
}
}
#[cfg(test)]
mod tests {
#[test]
fn alternate_lookup_key() {
fn alt(s: &str) -> Option<&str> {
super::alternate_lookup_key(s).map(|(s, _)| s)
}
assert_eq!(alt("x"), None);
assert_eq!(alt("x:y/z"), None);
assert_eq!(alt("x:y/z@1.0.0"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@1.1.0"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@1.1.2"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@2.1.2"), Some("x:y/z@2"));
assert_eq!(alt("x:y/z@2.1.2+abc"), Some("x:y/z@2"));
assert_eq!(alt("x:y/z@0.1.2"), Some("x:y/z@0.1"));
assert_eq!(alt("x:y/z@0.1.3"), Some("x:y/z@0.1"));
assert_eq!(alt("x:y/z@0.2.3"), Some("x:y/z@0.2"));
assert_eq!(alt("x:y/z@0.2.3+abc"), Some("x:y/z@0.2"));
assert_eq!(alt("x:y/z@0.0.1"), None);
assert_eq!(alt("x:y/z@0.0.1-pre"), None);
assert_eq!(alt("x:y/z@0.1.0-pre"), None);
assert_eq!(alt("x:y/z@1.0.0-pre"), None);
}
}