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}