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, prelude::*};
use crate::{FrameInfo, Module, code_memory::CodeMemory};
use alloc::collections::btree_map::{BTreeMap, Entry};
use alloc::sync::Arc;
use core::ops::Range;
use core::ptr::NonNull;
use wasmtime_environ::VMSharedTypeIndex;
#[derive(Default)]
pub struct ModuleRegistry {
loaded_code: BTreeMap<StoreCodePC, LoadedCode>,
store_code: BTreeMap<EngineCodePC, StoreCodePC>,
modules: BTreeMap<RegisteredModuleId, Module>,
}
struct LoadedCode {
code: StoreCode,
modules: BTreeMap<usize, RegisteredModuleId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegisteredModuleId(CompiledModuleId);
fn assert_no_overlap(loaded_code: &BTreeMap<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);
}
}
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) -> 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)?;
}
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,
) -> Result<RegisteredModuleId> {
self.register(module.id(), module.engine_code(), Some(module), engine)
.map(|id| id.unwrap())
}
#[cfg(feature = "component-model")]
pub fn register_component(&mut self, component: &Component, engine: &Engine) -> Result<()> {
self.register(component.id(), component.engine_code(), None, engine)?;
Ok(())
}
fn register(
&mut self,
compiled_id: CompiledModuleId,
code: &Arc<EngineCode>,
module: Option<&Module>,
engine: &Engine,
) -> Result<Option<RegisteredModuleId>> {
let id = module.map(|module| {
let id = RegisteredModuleId(compiled_id);
self.modules.entry(id).or_insert_with(|| module.clone());
id
});
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: BTreeMap::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);
}
}
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 = BTreeMap<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>) {
if address.is_empty() {
return;
}
let start = address.start;
let end = address.end - 1;
let prev = global_code().write().insert(end, (start, image.clone()));
assert!(prev.is_none());
}
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(())
}