wasm/execution/
linker.rs

1use alloc::{
2    collections::btree_map::{BTreeMap, Entry},
3    string::String,
4    vec::Vec,
5};
6
7use crate::{
8    addrs::ModuleAddr, store::InstantiationOutcome, ExternVal, RuntimeError, Store, StoreId,
9    ValidationInfo,
10};
11
12use super::config::Config;
13
14/// A linker used to link a module's imports against extern values previously
15/// defined in this [`Linker`] context.
16///
17/// # Manual Instantiation vs. Instantiation through [`Linker`]
18///
19/// Traditionally, module instances are instantiated via the method
20/// [`Store::module_instantiate_unchecked`], which is part of the official Embedder API
21/// defined by the specification. However, this method accepts a list of extern
22/// values as an argument. Therefore, if the user wants to manually perform
23/// linking they have to figure out the imports of their module, then gather the
24/// correct extern values, and finally call the instantiation method.
25///
26/// This process of manual linking is very tedious and error-prone, which is why
27/// the [`Linker`] exists. It builds on top of the original instantiation method
28/// with [`Linker::module_instantiate_unchecked`]. Internally this method performs name
29/// resolution and then calls the original instantiation. Name resolution is
30/// performed on all extern values which were previously defined in the current
31/// context.
32///
33/// # Extern values
34///
35/// An extern value is represented as a [`ExternVal`]. It contains an address to
36/// some store-allocated instance. In a linker context, every external value is
37/// stored in map with a unique key `(module name, name)`. To define new extern
38/// value in some linker context, use [`Linker::define_unchecked`] or
39/// [`Linker::define_module_instance_unchecked`].
40///
41/// # Relationship with [`Store`]
42///
43/// There is a N-to-1 relationship between the [`Linker`] and the [`Store`].
44/// This means that multiple linkers can be used with the same store, while
45/// every linker may be used only with one specific store.
46///
47/// Due to performance reasons, this bookkeeping is not done by the [`Linker`]
48/// itself. Instead it is the user's responsibility to uphold this requirement.
49#[derive(Clone, Default)]
50pub struct Linker {
51    /// All extern values in the current linker context by their import keys.
52    ///
53    /// It is guaranteed that the addresses of all extern values belong to the
54    /// same [`Store`].
55    extern_vals: BTreeMap<ImportKey, ExternVal>,
56
57    /// This is for the checked API which makes sure that all objects used
58    /// originate from the same [`Store`].
59    ///
60    /// Initially the store id is `None`. Only when a store-specific object or a
61    /// [`Store`] itself is used with a checked method, is this field set.  Once
62    /// initialized it is never written to again.
63    pub(crate) store_id: Option<StoreId>,
64}
65
66impl Linker {
67    /// Creates a new [`Linker`] that is not yet associated to any specific [`Store`].
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Defines a new extern value in the current [`Linker`] context.
73    ///
74    /// # Safety
75    /// It must be made sure that this [`Linker`] is only used with one specific
76    /// [`Store`] and addresses that belong to that store.
77    pub fn define_unchecked(
78        &mut self,
79        module_name: String,
80        name: String,
81        extern_val: ExternVal,
82    ) -> Result<(), RuntimeError> {
83        match self.extern_vals.entry(ImportKey { module_name, name }) {
84            Entry::Vacant(vacant_entry) => {
85                vacant_entry.insert(extern_val);
86                Ok(())
87            }
88            Entry::Occupied(_occupied_entry) => Err(RuntimeError::DuplicateExternDefinition),
89        }
90    }
91
92    /// Defines all exports of some module instance as extern values in the
93    /// current [`Linker`].
94    ///
95    /// # Safety
96    /// It must be guaranteed that this [`Linker`] is only ever used with one
97    /// specific [`Store`] and that the given [`ModuleAddr`] belongs to this
98    /// store.
99    pub fn define_module_instance_unchecked<T: Config>(
100        &mut self,
101        store: &Store<T>,
102        module_name: String,
103        module: ModuleAddr,
104    ) -> Result<(), RuntimeError> {
105        let module = store.modules.get(module);
106        for export in &module.exports {
107            self.define_unchecked(module_name.clone(), export.0.clone(), *export.1)?;
108        }
109
110        Ok(())
111    }
112
113    /// Tries to get some extern value by its module name and name.
114    ///
115    /// It is guaranteed that the address contained by the returned
116    /// [`ExternVal`] is part of the [`Store`] used with this [`Linker`].
117    pub fn get_unchecked(&self, module_name: String, name: String) -> Option<ExternVal> {
118        self.extern_vals
119            .get(&ImportKey { module_name, name })
120            .copied()
121    }
122
123    /// Performs initial linking of a [`ValidationInfo`]'s imports producing a
124    /// list of extern values usable with [`Store::module_instantiate_unchecked`].
125    ///
126    /// # A note on type checking
127    ///
128    /// This method does not perform type checking on the extern values.
129    /// Therefore, using the returned list of extern values may still fail when
130    /// trying to instantiate a module with it.
131    ///
132    /// # Safety
133    /// It must be guaranteed that this [`Linker`] is only ever used with one
134    /// specific [`Store`].
135    // TODO find a better name for this method? Maybe something like `link`?
136    pub fn instantiate_pre_unchecked(
137        &self,
138        validation_info: &ValidationInfo,
139    ) -> Result<Vec<ExternVal>, RuntimeError> {
140        validation_info
141            .imports
142            .iter()
143            .map(|import| {
144                self.get_unchecked(import.module_name.clone(), import.name.clone())
145                    .ok_or(RuntimeError::UnableToResolveExternLookup)
146            })
147            .collect()
148    }
149
150    /// Variant of [`Store::module_instantiate_unchecked`] with automatic name resolution
151    /// in the current [`Linker`] context.
152    ///
153    /// # Safety
154    /// It must be guaranteed that this [`Linker`] is only ever used with one
155    /// specific [`Store`].
156    pub fn module_instantiate_unchecked<'b, T: Config>(
157        &self,
158        store: &mut Store<'b, T>,
159        validation_info: &ValidationInfo<'b>,
160        maybe_fuel: Option<u32>,
161    ) -> Result<InstantiationOutcome, RuntimeError> {
162        store.module_instantiate_unchecked(
163            validation_info,
164            self.instantiate_pre_unchecked(validation_info)?,
165            maybe_fuel,
166        )
167    }
168}
169
170/// A key used by Wasm modules to identify the names of imports.
171///
172/// It consists of a module name and the name of the imported item itself.
173#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
174struct ImportKey {
175    module_name: String,
176    name: String,
177}