1use crate::code::{EngineCode, EngineCodePC, ModuleWithCode, StoreCode, StoreCodePC};
4#[cfg(feature = "component-model")]
5use crate::component::Component;
6use crate::runtime::vm::VMWasmCallFunction;
7use crate::sync::{OnceLock, RwLock};
8use crate::vm::CompiledModuleId;
9use crate::{Engine, FrameInfo, Module, code_memory::CodeMemory, prelude::*};
10use alloc::sync::Arc;
11#[cfg(not(feature = "debug"))]
12use core::marker::PhantomData;
13use core::ops::Range;
14use core::ptr::NonNull;
15use wasmtime_environ::{
16 CompiledFunctionsTable, FuncKey, StaticModuleIndex, VMSharedTypeIndex,
17 collections::btree_map::Entry,
18};
19
20#[derive(Default)]
58pub struct ModuleRegistry {
59 loaded_code: TryBTreeMap<StoreCodePC, LoadedCode>,
68
69 store_code: TryBTreeMap<EngineCodePC, StoreCodePC>,
73
74 modules: TryBTreeMap<RegisteredModuleId, Module>,
79}
80
81struct LoadedCode {
82 code: StoreCode,
84
85 index: Arc<CompiledFunctionsTable>,
91
92 modules: TrySecondaryMap<StaticModuleIndex, Option<RegisteredModuleId>>,
98}
99
100#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
106pub struct RegisteredModuleId(CompiledModuleId);
107
108fn assert_no_overlap(
109 loaded_code: &TryBTreeMap<StoreCodePC, LoadedCode>,
110 range: Range<StoreCodePC>,
111) {
112 if let Some((start, _)) = loaded_code.range(range.start..).next() {
113 assert!(start >= range.end);
114 }
115 if let Some((_, code)) = loaded_code.range(..range.end).next_back() {
116 assert!(code.code.text_range().end <= range.start);
117 }
118}
119
120#[cfg(feature = "debug")]
121pub struct RegisterBreakpointState<'a>(pub(crate) &'a crate::runtime::debug::BreakpointState);
122#[cfg(not(feature = "debug"))]
123pub struct RegisterBreakpointState<'a>(pub(crate) PhantomData<&'a ()>);
124
125impl<'a> RegisterBreakpointState<'a> {
126 #[cfg(feature = "debug")]
127 fn update(&self, code: &mut StoreCode, module: &Module) -> Result<()> {
128 self.0.patch_new_module(code, module)
129 }
130 #[cfg(not(feature = "debug"))]
131 fn update(&self, _code: &mut StoreCode, _module: &Module) -> Result<()> {
132 Ok(())
133 }
134}
135
136enum ModuleOrComponent<'a> {
137 Module(&'a Module),
138 #[cfg(feature = "component-model")]
139 Component(&'a Component),
140}
141
142impl ModuleRegistry {
143 pub fn module_by_id(&self, id: RegisteredModuleId) -> Option<&Module> {
145 self.modules.get(id)
146 }
147
148 pub fn module_by_compiled_id(&self, id: CompiledModuleId) -> Option<&Module> {
150 self.modules.get(RegisteredModuleId(id))
151 }
152
153 fn loaded_code_by_pc(&self, pc: usize) -> Option<(&LoadedCode, usize)> {
160 let (_, code) = self
161 .loaded_code
162 .range(..=StoreCodePC::from_raw(pc))
163 .next_back()?;
164 let offset = StoreCodePC::offset_of(code.code.text_range(), pc)?;
165 Some((code, offset))
166 }
167
168 pub fn store_code_by_pc(&self, pc: usize) -> Option<(&StoreCode, usize)> {
174 let (code, pc) = self.loaded_code_by_pc(pc)?;
175 Some((&code.code, pc))
176 }
177
178 pub fn store_code(&self, engine_code: &EngineCode) -> Option<&StoreCode> {
180 let store_code_pc = self.store_code_base(engine_code)?;
181 let (_, code) = self.loaded_code.range(store_code_pc..).next()?;
182 Some(&code.code)
183 }
184
185 pub fn store_code_base(&self, engine_code: &EngineCode) -> Option<StoreCodePC> {
187 self.store_code.get(engine_code.text_range().start).cloned()
188 }
189
190 pub fn store_code_base_or_register(
193 &mut self,
194 module: &Module,
195 breakpoint_state: RegisterBreakpointState,
196 ) -> Result<StoreCodePC> {
197 let key = module.engine_code().text_range().start;
198 if !self.store_code.contains_key(key) {
199 let engine = module.engine().clone();
200 self.register_module(module, &engine, breakpoint_state)?;
201 }
202 Ok(*self.store_code.get(key).unwrap())
203 }
204
205 pub fn store_code_mut(&mut self, store_code_base: StoreCodePC) -> Option<&mut StoreCode> {
207 let (_, code) = self.loaded_code.range_mut(store_code_base..).next()?;
208 assert_eq!(code.code.text_range().start, store_code_base);
209 Some(&mut code.code)
210 }
211
212 #[cfg(any(feature = "coredump", feature = "debug"))]
214 pub fn all_modules(&self) -> impl Iterator<Item = &'_ Module> + '_ {
215 self.modules.values()
216 }
217
218 pub fn register_module(
220 &mut self,
221 module: &Module,
222 engine: &Engine,
223 breakpoint_state: RegisterBreakpointState,
224 ) -> Result<RegisteredModuleId> {
225 self.register(ModuleOrComponent::Module(module), engine, breakpoint_state)
226 .map(|id| id.unwrap())
227 }
228
229 #[cfg(feature = "component-model")]
230 pub fn register_component(
231 &mut self,
232 component: &Component,
233 engine: &Engine,
234 breakpoint_state: RegisterBreakpointState,
235 ) -> Result<()> {
236 self.register(
237 ModuleOrComponent::Component(component),
238 engine,
239 breakpoint_state,
240 )?;
241 Ok(())
242 }
243
244 fn register(
246 &mut self,
247 module_or_component: ModuleOrComponent<'_>,
248 engine: &Engine,
249 breakpoint_state: RegisterBreakpointState,
250 ) -> Result<Option<RegisteredModuleId>> {
251 let compiled_id = match module_or_component {
252 ModuleOrComponent::Module(module) => module.id(),
253 #[cfg(feature = "component-model")]
254 ModuleOrComponent::Component(component) => component.id(),
255 };
256 let code = match module_or_component {
257 ModuleOrComponent::Module(module) => module.engine_code(),
258 #[cfg(feature = "component-model")]
259 ModuleOrComponent::Component(component) => component.engine_code(),
260 };
261 let id = match module_or_component {
263 ModuleOrComponent::Module(module) => {
264 let id = RegisteredModuleId(compiled_id);
265 self.modules.entry(id).or_insert_with(|| module.clone())?;
266 Some(id)
267 }
268 #[cfg(feature = "component-model")]
269 ModuleOrComponent::Component(_) => None,
270 };
271
272 let store_code_pc = match self.store_code.entry(code.text_range().start) {
274 Entry::Vacant(v) => {
275 let store_code = StoreCode::new(engine, code)?;
276 let store_code_pc = store_code.text_range().start;
277 assert_no_overlap(&self.loaded_code, store_code.text_range());
278 let index = match module_or_component {
279 ModuleOrComponent::Module(module) => module.index(),
280 #[cfg(feature = "component-model")]
281 ModuleOrComponent::Component(component) => component.index(),
282 };
283 self.loaded_code.insert(
284 store_code_pc,
285 LoadedCode {
286 code: store_code,
287 index: index.clone(),
288 modules: Default::default(),
289 },
290 )?;
291 *v.insert(store_code_pc)?
292 }
293 Entry::Occupied(o) => *o.get(),
294 };
295
296 if let (ModuleOrComponent::Module(module), Some(id)) = (module_or_component, id) {
298 let loaded_code = self
299 .loaded_code
300 .get_mut(store_code_pc)
301 .expect("loaded_code must have entry for StoreCodePC");
302 loaded_code
303 .modules
304 .insert(module.env_module().module_index, Some(id))?;
305 breakpoint_state.update(&mut loaded_code.code, module)?;
306 }
307
308 Ok(id)
309 }
310
311 pub(crate) fn lookup_frame_info<'a>(
320 &'a self,
321 pc: usize,
322 ) -> Option<(FrameInfo, ModuleWithCode<'a>)> {
323 let (code, text_offset) = self.loaded_code_by_pc(pc)?;
324 let module_index = match code
325 .index
326 .func_by_text_offset(u32::try_from(text_offset).ok()?)?
327 {
328 FuncKey::DefinedWasmFunction(module, _) => module,
329 _ => return None,
330 };
331 let module_id = (*code.modules.get(module_index)?)?;
332 let module = self
333 .modules
334 .get(module_id)
335 .expect("referenced module ID not found");
336 let info = FrameInfo::new(module.clone(), text_offset)?;
337 let module_with_code = ModuleWithCode::from_raw(module, &code.code);
338 Some((info, module_with_code))
339 }
340
341 pub fn wasm_to_array_trampoline(
342 &self,
343 sig: VMSharedTypeIndex,
344 ) -> Option<NonNull<VMWasmCallFunction>> {
345 for module in self.modules.values() {
353 if let Some(trampoline) = module.wasm_to_array_trampoline(sig) {
354 return Some(trampoline);
355 }
356 }
357 None
358 }
359}
360
361fn global_code() -> &'static RwLock<GlobalRegistry> {
375 static GLOBAL_CODE: OnceLock<RwLock<GlobalRegistry>> = OnceLock::new();
376 GLOBAL_CODE.get_or_init(Default::default)
377}
378
379type GlobalRegistry = TryBTreeMap<usize, (usize, Arc<CodeMemory>)>;
380
381pub fn lookup_code(pc: usize) -> Option<(Arc<CodeMemory>, usize)> {
384 let all_modules = global_code().read();
385 let (_end, (start, module)) = all_modules.range(pc..).next()?;
386 let text_offset = pc.checked_sub(*start)?;
387 Some((module.clone(), text_offset))
388}
389
390pub fn register_code(image: &Arc<CodeMemory>, address: Range<usize>) -> Result<(), OutOfMemory> {
399 if address.is_empty() {
400 return Ok(());
401 }
402 let start = address.start;
403 let end = address.end - 1;
404 let prev = global_code().write().insert(end, (start, image.clone()))?;
405 assert!(prev.is_none());
406 Ok(())
407}
408
409pub fn unregister_code(address: Range<usize>) {
413 if address.is_empty() {
414 return;
415 }
416 let end = address.end - 1;
417 let code = global_code().write().remove(end);
418 assert!(code.is_some());
419}
420
421#[test]
422#[cfg_attr(miri, ignore)]
423fn test_frame_info() -> Result<(), crate::Error> {
424 use crate::*;
425
426 let mut store = Store::<()>::default();
427 let module = Module::new(
428 store.engine(),
429 r#"
430 (module
431 (func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
432 (func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y)))
433 (func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y)))
434 (func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y)))
435 (func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y)))
436 (func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y)))
437 (func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y)))
438 )
439 "#,
440 )?;
441 Instance::new(&mut store, &module, &[])?;
443
444 for (i, range) in module.compiled_module().finished_function_ranges() {
447 let base = module.engine_code().text_range().start.raw();
448 let start = base + range.start;
449 let end = base + range.end;
450 for pc in start..end {
451 let (frame, _) = store
452 .as_context()
453 .0
454 .modules()
455 .lookup_frame_info(pc)
456 .unwrap();
457 assert!(
458 frame.func_index() == i.as_u32(),
459 "lookup of {:#x} returned {}, expected {}",
460 pc,
461 frame.func_index(),
462 i.as_u32()
463 );
464 }
465 }
466 Ok(())
467}