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