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}