1 //! Implements a registry of modules for a store.
2 
3 use crate::code::{EngineCode, EngineCodePC, ModuleWithCode, StoreCode, StoreCodePC};
4 #[cfg(feature = "component-model")]
5 use crate::component::Component;
6 use crate::runtime::vm::VMWasmCallFunction;
7 use crate::sync::{OnceLock, RwLock};
8 use crate::vm::CompiledModuleId;
9 use crate::{Engine, prelude::*};
10 use crate::{FrameInfo, Module, code_memory::CodeMemory};
11 use alloc::collections::btree_map::{BTreeMap, Entry};
12 use alloc::sync::Arc;
13 use core::ops::Range;
14 use core::ptr::NonNull;
15 use wasmtime_environ::VMSharedTypeIndex;
16 
17 /// Used for registering modules with a store.
18 ///
19 /// There are two basic purposes that this registry serves:
20 ///
21 /// - It keeps all modules and their metadata alive as long as the
22 ///   store exists.
23 /// - It owns the [`StoreCode`], i.e. possibly-private-copy of machine
24 ///   code, for all modules that execute in this store.
25 ///
26 /// The registry allows for translation of EngineCode to StoreCode,
27 /// deduplicating by the start address of the EngineCode; and allows
28 /// for looking up modules by "registered module ID", and looking up
29 /// StoreCode and Modules by PC.
30 ///
31 /// Note that multiple modules may be backed by a single
32 /// `StoreCode`. This is specifically the case for components in
33 /// general. When a component is first instantiated, the component
34 /// itself is registered (which loads the StoreCode into the
35 /// registry), then each individual module within that component is
36 /// registered and added to the data structures.
37 ///
38 /// A brief overview of the kinds of compiled object and their
39 /// relationships:
40 ///
41 /// - `Module` is a Wasm module. It owns a `CompiledModule`.
42 /// - `CompiledModule` contains metadata about the module (e.g., a map
43 ///   from Wasm function indices to locations in the machine code),
44 ///   and also owns an `EngineCode`.
45 /// - `EngineCode` holds an `Arc` to a `CodeMemory` with the canonical
46 ///   copy of machine code, as well as some lower-level metadata
47 ///   (signatures and types). It is instantiated by this registry into
48 ///   `StoreCode`.
49 /// - `StoreCode` owns either another `Arc` to the same `CodeMemory`
50 ///   as `EngineCode`, or if guest debugging is enabled and causes us
51 ///   to clone private copies of code for patching per store, owns its
52 ///   own private `CodeMemory` at a different address.
53 /// - Instances hold a `RegisteredModuleId` to be able to look up their modules.
54 #[derive(Default)]
55 pub struct ModuleRegistry {
56     /// StoreCode and Modules associated with it.
57     ///
58     /// Keyed by the start address of the `StoreCode`. We maintain the
59     /// invariant of no overlaps on insertion. We use a range query to
60     /// find the StoreCode for a given PC: take the range `0..=pc`,
61     /// then take the last element of the range. That picks the
62     /// highest start address <= the query, and we can check whether
63     /// it contains the address.
64     loaded_code: BTreeMap<StoreCodePC, LoadedCode>,
65 
66     /// Map from EngineCodePC start to StoreCodePC start. We use this
67     /// to memoize the store-code creation process: each EngineCode is
68     /// instantiated to a StoreCode only once per store.
69     store_code: BTreeMap<EngineCodePC, StoreCodePC>,
70 
71     /// Modules instantiated in this registry.
72     ///
73     /// Every module is placed in this map, but not every module will
74     /// be in a LoadedCode entry, because the module may have no text.
75     modules: BTreeMap<RegisteredModuleId, Module>,
76 }
77 
78 struct LoadedCode {
79     /// The StoreCode in this range.
80     code: StoreCode,
81 
82     /// Map by starting text offset of Modules in this code region.
83     modules: BTreeMap<usize, RegisteredModuleId>,
84 }
85 
86 /// An identifier of a module that has previously been inserted into a
87 /// `ModuleRegistry`.
88 ///
89 /// This is just a newtype around `CompiledModuleId`, which is unique
90 /// within the Engine.
91 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
92 pub struct RegisteredModuleId(CompiledModuleId);
93 
94 fn assert_no_overlap(loaded_code: &BTreeMap<StoreCodePC, LoadedCode>, range: Range<StoreCodePC>) {
95     if let Some((start, _)) = loaded_code.range(range.start..).next() {
96         assert!(*start >= range.end);
97     }
98     if let Some((_, code)) = loaded_code.range(..range.end).next_back() {
99         assert!(code.code.text_range().end <= range.start);
100     }
101 }
102 
103 impl ModuleRegistry {
104     /// Get a previously-registered module by id.
105     pub fn module_by_id(&self, id: RegisteredModuleId) -> Option<&Module> {
106         self.modules.get(&id)
107     }
108 
109     /// Get a module by CompiledModuleId, if present.
110     pub fn module_by_compiled_id(&self, id: CompiledModuleId) -> Option<&Module> {
111         self.modules.get(&RegisteredModuleId(id))
112     }
113 
114     /// Fetches a registered StoreCode and module and an offset within
115     /// it given a program counter value.
116     pub fn module_and_code_by_pc<'a>(&'a self, pc: usize) -> Option<(ModuleWithCode<'a>, usize)> {
117         let (_, code) = self
118             .loaded_code
119             .range(..=StoreCodePC::from_raw(pc))
120             .next_back()?;
121         let offset = StoreCodePC::offset_of(code.code.text_range(), pc)?;
122         let (_, module_id) = code.modules.range(..=offset).next_back()?;
123         let module = self.modules.get(&module_id)?;
124         Some((ModuleWithCode::from_raw(module, &code.code), offset))
125     }
126 
127     /// Fetches the `StoreCode` for a given `EngineCode`.
128     pub fn store_code(&self, engine_code: &EngineCode) -> Option<&StoreCode> {
129         let store_code_pc = self.store_code_base(engine_code)?;
130         let (_, code) = self.loaded_code.range(store_code_pc..).next()?;
131         Some(&code.code)
132     }
133 
134     /// Fetches the base `StoreCodePC` for a given `EngineCode`.
135     pub fn store_code_base(&self, engine_code: &EngineCode) -> Option<StoreCodePC> {
136         self.store_code
137             .get(&engine_code.text_range().start)
138             .cloned()
139     }
140 
141     /// Fetches the base `StoreCodePC` for a given `EngineCode` with
142     /// `Module`, registering the module if not already registered.
143     pub fn store_code_base_or_register(&mut self, module: &Module) -> Result<StoreCodePC> {
144         let key = module.engine_code().text_range().start;
145         if !self.store_code.contains_key(&key) {
146             let engine = module.engine().clone();
147             self.register_module(module, &engine)?;
148         }
149         Ok(*self.store_code.get(&key).unwrap())
150     }
151 
152     /// Fetches a mutable `StoreCode` for a given base `StoreCodePC`.
153     pub fn store_code_mut(&mut self, store_code_base: StoreCodePC) -> Option<&mut StoreCode> {
154         let (_, code) = self.loaded_code.range_mut(store_code_base..).next()?;
155         assert_eq!(code.code.text_range().start, store_code_base);
156         Some(&mut code.code)
157     }
158 
159     /// Gets an iterator over all modules in the registry.
160     #[cfg(any(feature = "coredump", feature = "debug"))]
161     pub fn all_modules(&self) -> impl Iterator<Item = &'_ Module> + '_ {
162         self.modules.values()
163     }
164 
165     /// Registers a new module with the registry.
166     pub fn register_module(
167         &mut self,
168         module: &Module,
169         engine: &Engine,
170     ) -> Result<RegisteredModuleId> {
171         self.register(module.id(), module.engine_code(), Some(module), engine)
172             .map(|id| id.unwrap())
173     }
174 
175     #[cfg(feature = "component-model")]
176     pub fn register_component(&mut self, component: &Component, engine: &Engine) -> Result<()> {
177         self.register(component.id(), component.engine_code(), None, engine)?;
178         Ok(())
179     }
180 
181     /// Registers a new module with the registry.
182     fn register(
183         &mut self,
184         compiled_id: CompiledModuleId,
185         code: &Arc<EngineCode>,
186         module: Option<&Module>,
187         engine: &Engine,
188     ) -> Result<Option<RegisteredModuleId>> {
189         // Register the module, if any.
190         let id = module.map(|module| {
191             let id = RegisteredModuleId(compiled_id);
192             self.modules.entry(id).or_insert_with(|| module.clone());
193             id
194         });
195 
196         // Create a StoreCode if one does not already exist.
197         let store_code_pc = match self.store_code.entry(code.text_range().start) {
198             Entry::Vacant(v) => {
199                 let store_code = StoreCode::new(engine, code)?;
200                 let store_code_pc = store_code.text_range().start;
201                 assert_no_overlap(&self.loaded_code, store_code.text_range());
202                 self.loaded_code.insert(
203                     store_code_pc,
204                     LoadedCode {
205                         code: store_code,
206                         modules: BTreeMap::default(),
207                     },
208                 );
209                 *v.insert(store_code_pc)
210             }
211             Entry::Occupied(o) => *o.get(),
212         };
213 
214         // Add this module to the LoadedCode if not present.
215         if let (Some(module), Some(id)) = (module, id) {
216             if let Some((_, range)) = module.compiled_module().finished_function_ranges().next() {
217                 let loaded_code = self
218                     .loaded_code
219                     .get_mut(&store_code_pc)
220                     .expect("loaded_code must have entry for StoreCodePC");
221                 loaded_code.modules.insert(range.start, id);
222             }
223         }
224 
225         Ok(id)
226     }
227 
228     /// Fetches frame information about a program counter in a backtrace.
229     ///
230     /// Returns an object if this `pc` is known to some previously registered
231     /// module, or returns `None` if no information can be found. The first
232     /// boolean returned indicates whether the original module has unparsed
233     /// debug information due to the compiler's configuration. The second
234     /// boolean indicates whether the engine used to compile this module is
235     /// using environment variables to control debuginfo parsing.
236     pub(crate) fn lookup_frame_info<'a>(
237         &'a self,
238         pc: usize,
239     ) -> Option<(FrameInfo, ModuleWithCode<'a>)> {
240         let (_, code) = self
241             .loaded_code
242             .range(..=StoreCodePC::from_raw(pc))
243             .next_back()?;
244         let text_offset = StoreCodePC::offset_of(code.code.text_range(), pc)?;
245         let (_, module_id) = code.modules.range(..=text_offset).next_back()?;
246         let module = self
247             .modules
248             .get(&module_id)
249             .expect("referenced module ID not found");
250         let info = FrameInfo::new(module.clone(), text_offset)?;
251         let module_with_code = ModuleWithCode::from_raw(module, &code.code);
252         Some((info, module_with_code))
253     }
254 
255     pub fn wasm_to_array_trampoline(
256         &self,
257         sig: VMSharedTypeIndex,
258     ) -> Option<NonNull<VMWasmCallFunction>> {
259         // TODO: We are doing a linear search over each module. This is fine for
260         // now because we typically have very few modules per store (almost
261         // always one, in fact). If this linear search ever becomes a
262         // bottleneck, we could avoid it by incrementally and lazily building a
263         // `VMSharedSignatureIndex` to `SignatureIndex` map.
264         //
265         // See also the comment in `ModuleInner::wasm_to_native_trampoline`.
266         for module in self.modules.values() {
267             if let Some(trampoline) = module.wasm_to_array_trampoline(sig) {
268                 return Some(trampoline);
269             }
270         }
271         None
272     }
273 }
274 
275 // This is the global code registry that stores information for all loaded code
276 // objects that are currently in use by any `Store` in the current process.
277 //
278 // The purpose of this map is to be called from signal handlers to determine
279 // whether a program counter is a wasm trap or not. Specifically macOS has
280 // no contextual information about the thread available, hence the necessity
281 // for global state rather than using thread local state.
282 //
283 // This is similar to `ModuleRegistry` except that it has less information and
284 // supports removal. Any time anything is registered with a `ModuleRegistry`
285 // it is also automatically registered with the singleton global module
286 // registry. When a `ModuleRegistry` is destroyed then all of its entries
287 // are removed from the global registry.
288 fn global_code() -> &'static RwLock<GlobalRegistry> {
289     static GLOBAL_CODE: OnceLock<RwLock<GlobalRegistry>> = OnceLock::new();
290     GLOBAL_CODE.get_or_init(Default::default)
291 }
292 
293 type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>;
294 
295 /// Find which registered region of code contains the given program counter, and
296 /// what offset that PC is within that module's code.
297 pub fn lookup_code(pc: usize) -> Option<(Arc<CodeMemory>, usize)> {
298     let all_modules = global_code().read();
299     let (_end, (start, module)) = all_modules.range(pc..).next()?;
300     let text_offset = pc.checked_sub(*start)?;
301     Some((module.clone(), text_offset))
302 }
303 
304 /// Registers a new region of code.
305 ///
306 /// Must not have been previously registered and must be `unregister`'d to
307 /// prevent leaking memory.
308 ///
309 /// This is required to enable traps to work correctly since the signal handler
310 /// will lookup in the `GLOBAL_CODE` list to determine which a particular pc
311 /// is a trap or not.
312 pub fn register_code(image: &Arc<CodeMemory>, address: Range<usize>) {
313     if address.is_empty() {
314         return;
315     }
316     let start = address.start;
317     let end = address.end - 1;
318     let prev = global_code().write().insert(end, (start, image.clone()));
319     assert!(prev.is_none());
320 }
321 
322 /// Unregisters a code mmap from the global map.
323 ///
324 /// Must have been previously registered with `register`.
325 pub fn unregister_code(address: Range<usize>) {
326     if address.is_empty() {
327         return;
328     }
329     let end = address.end - 1;
330     let code = global_code().write().remove(&end);
331     assert!(code.is_some());
332 }
333 
334 #[test]
335 #[cfg_attr(miri, ignore)]
336 fn test_frame_info() -> Result<(), crate::Error> {
337     use crate::*;
338 
339     let mut store = Store::<()>::default();
340     let module = Module::new(
341         store.engine(),
342         r#"
343             (module
344                 (func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
345                 (func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y)))
346                 (func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y)))
347                 (func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y)))
348                 (func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y)))
349                 (func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y)))
350                 (func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y)))
351             )
352          "#,
353     )?;
354     // Create an instance to ensure the frame information is registered.
355     Instance::new(&mut store, &module, &[])?;
356 
357     // Look for frame info for each function. Assume that StoreCode
358     // does not actually clone in the default configuration.
359     for (i, range) in module.compiled_module().finished_function_ranges() {
360         let base = module.engine_code().text_range().start.raw();
361         let start = base + range.start;
362         let end = base + range.end;
363         for pc in start..end {
364             let (frame, _) = store
365                 .as_context()
366                 .0
367                 .modules()
368                 .lookup_frame_info(pc)
369                 .unwrap();
370             assert!(
371                 frame.func_index() == i.as_u32(),
372                 "lookup of {:#x} returned {}, expected {}",
373                 pc,
374                 frame.func_index(),
375                 i.as_u32()
376             );
377         }
378     }
379     Ok(())
380 }
381