use crate::code::{EngineCode, EngineCodePC, ModuleWithCode, StoreCode, StoreCodePC};
#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::runtime::vm::VMWasmCallFunction;
use crate::sync::{OnceLock, RwLock};
use crate::vm::CompiledModuleId;
use crate::{Engine, FrameInfo, Module, code_memory::CodeMemory, prelude::*};
use alloc::sync::Arc;
#[cfg(not(feature = "debug"))]
use core::marker::PhantomData;
use core::ops::Range;
use core::ptr::NonNull;
use wasmtime_environ::{VMSharedTypeIndex, collections::btree_map::Entry};
#[derive(Default)]
pub struct ModuleRegistry {
loaded_code: TryBTreeMap<StoreCodePC, LoadedCode>,
store_code: TryBTreeMap<EngineCodePC, StoreCodePC>,
modules: TryBTreeMap<RegisteredModuleId, Module>,
}
struct LoadedCode {
code: StoreCode,
modules: TryBTreeMap<usize, RegisteredModuleId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegisteredModuleId(CompiledModuleId);
fn assert_no_overlap(
loaded_code: &TryBTreeMap<StoreCodePC, LoadedCode>,
range: Range<StoreCodePC>,
) {
if let Some((start, _)) = loaded_code.range(range.start..).next() {
assert!(start >= range.end);
}
if let Some((_, code)) = loaded_code.range(..range.end).next_back() {
assert!(code.code.text_range().end <= range.start);
}
}
#[cfg(feature = "debug")]
pub struct RegisterBreakpointState<'a>(pub(crate) &'a crate::runtime::debug::BreakpointState);
#[cfg(not(feature = "debug"))]
pub struct RegisterBreakpointState<'a>(pub(crate) PhantomData<&'a ()>);
impl<'a> RegisterBreakpointState<'a> {
#[cfg(feature = "debug")]
fn update(&self, code: &mut StoreCode, module: &Module) -> Result<()> {
self.0.patch_new_module(code, module)
}
#[cfg(not(feature = "debug"))]
fn update(&self, _code: &mut StoreCode, _module: &Module) -> Result<()> {
Ok(())
}
}
impl ModuleRegistry {
pub fn module_by_id(&self, id: RegisteredModuleId) -> Option<&Module> {
self.modules.get(id)
}
pub fn module_by_compiled_id(&self, id: CompiledModuleId) -> Option<&Module> {
self.modules.get(RegisteredModuleId(id))
}
pub fn module_and_code_by_pc<'a>(&'a self, pc: usize) -> Option<(ModuleWithCode<'a>, usize)> {
let (_, code) = self
.loaded_code
.range(..=StoreCodePC::from_raw(pc))
.next_back()?;
let offset = StoreCodePC::offset_of(code.code.text_range(), pc)?;
let (_, module_id) = code.modules.range(..=offset).next_back()?;
let module = self.modules.get(*module_id)?;
Some((ModuleWithCode::from_raw(module, &code.code), offset))
}
pub fn store_code(&self, engine_code: &EngineCode) -> Option<&StoreCode> {
let store_code_pc = self.store_code_base(engine_code)?;
let (_, code) = self.loaded_code.range(store_code_pc..).next()?;
Some(&code.code)
}
pub fn store_code_base(&self, engine_code: &EngineCode) -> Option<StoreCodePC> {
self.store_code.get(engine_code.text_range().start).cloned()
}
pub fn store_code_base_or_register(
&mut self,
module: &Module,
breakpoint_state: RegisterBreakpointState,
) -> Result<StoreCodePC> {
let key = module.engine_code().text_range().start;
if !self.store_code.contains_key(key) {
let engine = module.engine().clone();
self.register_module(module, &engine, breakpoint_state)?;
}
Ok(*self.store_code.get(key).unwrap())
}
pub fn store_code_mut(&mut self, store_code_base: StoreCodePC) -> Option<&mut StoreCode> {
let (_, code) = self.loaded_code.range_mut(store_code_base..).next()?;
assert_eq!(code.code.text_range().start, store_code_base);
Some(&mut code.code)
}
#[cfg(any(feature = "coredump", feature = "debug"))]
pub fn all_modules(&self) -> impl Iterator<Item = &'_ Module> + '_ {
self.modules.values()
}
pub fn register_module(
&mut self,
module: &Module,
engine: &Engine,
breakpoint_state: RegisterBreakpointState,
) -> Result<RegisteredModuleId> {
self.register(
module.id(),
module.engine_code(),
Some(module),
engine,
breakpoint_state,
)
.map(|id| id.unwrap())
}
#[cfg(feature = "component-model")]
pub fn register_component(
&mut self,
component: &Component,
engine: &Engine,
breakpoint_state: RegisterBreakpointState,
) -> Result<()> {
self.register(
component.id(),
component.engine_code(),
None,
engine,
breakpoint_state,
)?;
Ok(())
}
fn register(
&mut self,
compiled_id: CompiledModuleId,
code: &Arc<EngineCode>,
module: Option<&Module>,
engine: &Engine,
breakpoint_state: RegisterBreakpointState,
) -> Result<Option<RegisteredModuleId>> {
let id = if let Some(module) = module {
let id = RegisteredModuleId(compiled_id);
self.modules.entry(id).or_insert_with(|| module.clone())?;
Some(id)
} else {
None
};
let store_code_pc = match self.store_code.entry(code.text_range().start) {
Entry::Vacant(v) => {
let store_code = StoreCode::new(engine, code)?;
let store_code_pc = store_code.text_range().start;
assert_no_overlap(&self.loaded_code, store_code.text_range());
self.loaded_code.insert(
store_code_pc,
LoadedCode {
code: store_code,
modules: TryBTreeMap::default(),
},
)?;
*v.insert(store_code_pc)?
}
Entry::Occupied(o) => *o.get(),
};
if let (Some(module), Some(id)) = (module, id) {
if let Some((_, range)) = module.compiled_module().finished_function_ranges().next() {
let loaded_code = self
.loaded_code
.get_mut(store_code_pc)
.expect("loaded_code must have entry for StoreCodePC");
loaded_code.modules.insert(range.start, id)?;
breakpoint_state.update(&mut loaded_code.code, module)?;
}
}
Ok(id)
}
pub(crate) fn lookup_frame_info<'a>(
&'a self,
pc: usize,
) -> Option<(FrameInfo, ModuleWithCode<'a>)> {
let (_, code) = self
.loaded_code
.range(..=StoreCodePC::from_raw(pc))
.next_back()?;
let text_offset = StoreCodePC::offset_of(code.code.text_range(), pc)?;
let (_, module_id) = code.modules.range(..=text_offset).next_back()?;
let module = self
.modules
.get(*module_id)
.expect("referenced module ID not found");
let info = FrameInfo::new(module.clone(), text_offset)?;
let module_with_code = ModuleWithCode::from_raw(module, &code.code);
Some((info, module_with_code))
}
pub fn wasm_to_array_trampoline(
&self,
sig: VMSharedTypeIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
for module in self.modules.values() {
if let Some(trampoline) = module.wasm_to_array_trampoline(sig) {
return Some(trampoline);
}
}
None
}
}
fn global_code() -> &'static RwLock<GlobalRegistry> {
static GLOBAL_CODE: OnceLock<RwLock<GlobalRegistry>> = OnceLock::new();
GLOBAL_CODE.get_or_init(Default::default)
}
type GlobalRegistry = TryBTreeMap<usize, (usize, Arc<CodeMemory>)>;
pub fn lookup_code(pc: usize) -> Option<(Arc<CodeMemory>, usize)> {
let all_modules = global_code().read();
let (_end, (start, module)) = all_modules.range(pc..).next()?;
let text_offset = pc.checked_sub(*start)?;
Some((module.clone(), text_offset))
}
pub fn register_code(image: &Arc<CodeMemory>, address: Range<usize>) -> Result<(), OutOfMemory> {
if address.is_empty() {
return Ok(());
}
let start = address.start;
let end = address.end - 1;
let prev = global_code().write().insert(end, (start, image.clone()))?;
assert!(prev.is_none());
Ok(())
}
pub fn unregister_code(address: Range<usize>) {
if address.is_empty() {
return;
}
let end = address.end - 1;
let code = global_code().write().remove(end);
assert!(code.is_some());
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_frame_info() -> Result<(), crate::Error> {
use crate::*;
let mut store = Store::<()>::default();
let module = Module::new(
store.engine(),
r#"
(module
(func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
(func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y)))
(func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y)))
(func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y)))
(func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y)))
(func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y)))
(func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y)))
)
"#,
)?;
Instance::new(&mut store, &module, &[])?;
for (i, range) in module.compiled_module().finished_function_ranges() {
let base = module.engine_code().text_range().start.raw();
let start = base + range.start;
let end = base + range.end;
for pc in start..end {
let (frame, _) = store
.as_context()
.0
.modules()
.lookup_frame_info(pc)
.unwrap();
assert!(
frame.func_index() == i.as_u32(),
"lookup of {:#x} returned {}, expected {}",
pc,
frame.func_index(),
i.as_u32()
);
}
}
Ok(())
}