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