wasm/execution/
linker.rs

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