use crate::code::CodeObject;
#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::prelude::*;
use crate::runtime::vm::VMWasmCallFunction;
use crate::sync::{OnceLock, RwLock};
use crate::{code_memory::CodeMemory, FrameInfo, Module, Trap};
use alloc::collections::btree_map::{BTreeMap, Entry};
use alloc::sync::Arc;
use core::ptr::NonNull;
use wasmtime_environ::VMSharedTypeIndex;
#[derive(Default)]
pub struct ModuleRegistry {
loaded_code: BTreeMap<usize, (usize, LoadedCode)>,
modules_without_code: Vec<Module>,
}
struct LoadedCode {
_code: Arc<CodeObject>,
modules: BTreeMap<usize, Module>,
}
#[derive(Clone, Copy)]
pub enum RegisteredModuleId {
WithoutCode(usize),
LoadedCode(usize),
}
impl ModuleRegistry {
pub fn lookup_module_by_id(&self, id: RegisteredModuleId) -> Option<&Module> {
match id {
RegisteredModuleId::WithoutCode(idx) => self.modules_without_code.get(idx),
RegisteredModuleId::LoadedCode(pc) => {
let (module, _) = self.module_and_offset(pc)?;
Some(module)
}
}
}
pub fn lookup_module_info(&self, pc: usize) -> Option<&dyn crate::runtime::vm::ModuleInfo> {
let (module, _) = self.module_and_offset(pc)?;
Some(module.module_info())
}
fn code(&self, pc: usize) -> Option<(&LoadedCode, usize)> {
let (end, (start, code)) = self.loaded_code.range(pc..).next()?;
if pc < *start || *end < pc {
return None;
}
Some((code, pc - *start))
}
fn module_and_offset(&self, pc: usize) -> Option<(&Module, usize)> {
let (code, offset) = self.code(pc)?;
Some((code.module(pc)?, offset))
}
pub fn all_modules(&self) -> impl Iterator<Item = &'_ Module> + '_ {
self.loaded_code
.values()
.flat_map(|(_, code)| code.modules.values())
.chain(self.modules_without_code.iter())
}
pub fn register_module(&mut self, module: &Module) -> RegisteredModuleId {
self.register(module.code_object(), Some(module)).unwrap()
}
#[cfg(feature = "component-model")]
pub fn register_component(&mut self, component: &Component) {
self.register(component.code_object(), None);
}
fn register(
&mut self,
code: &Arc<CodeObject>,
module: Option<&Module>,
) -> Option<RegisteredModuleId> {
let text = code.code_memory().text();
if text.is_empty() {
return module.map(|module| {
let id = RegisteredModuleId::WithoutCode(self.modules_without_code.len());
self.modules_without_code.push(module.clone());
id
});
}
let start_addr = text.as_ptr() as usize;
let end_addr = start_addr + text.len() - 1;
let id = module.map(|_| RegisteredModuleId::LoadedCode(start_addr));
if let Some((other_start, prev)) = self.loaded_code.get_mut(&end_addr) {
assert_eq!(*other_start, start_addr);
if let Some(module) = module {
prev.push_module(module);
}
return id;
}
if let Some((_, (prev_start, _))) = self.loaded_code.range(start_addr..).next() {
assert!(*prev_start > end_addr);
}
if let Some((prev_end, _)) = self.loaded_code.range(..=start_addr).next_back() {
assert!(*prev_end < start_addr);
}
let mut item = LoadedCode {
_code: code.clone(),
modules: Default::default(),
};
if let Some(module) = module {
item.push_module(module);
}
let prev = self.loaded_code.insert(end_addr, (start_addr, item));
assert!(prev.is_none());
id
}
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
let (module, offset) = self.module_and_offset(pc)?;
let info = FrameInfo::new(module.clone(), offset)?;
Some((info, module))
}
pub fn wasm_to_array_trampoline(
&self,
sig: VMSharedTypeIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
for (_, code) in self.loaded_code.values() {
for module in code.modules.values() {
if let Some(trampoline) = module.runtime_info().wasm_to_array_trampoline(sig) {
return Some(trampoline);
}
}
}
None
}
}
impl LoadedCode {
fn push_module(&mut self, module: &Module) {
let func = match module.compiled_module().finished_functions().next() {
Some((_, func)) => func,
None => return,
};
let start = func.as_ptr() as usize;
match self.modules.entry(start) {
Entry::Occupied(m) => {
debug_assert!(Arc::ptr_eq(&module.inner, &m.get().inner));
}
Entry::Vacant(v) => {
v.insert(module.clone());
}
}
}
fn module(&self, pc: usize) -> Option<&Module> {
let (_start, module) = self.modules.range(..=pc).next_back()?;
Some(module)
}
}
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 get_wasm_trap(pc: usize) -> Option<Trap> {
let (code, text_offset) = {
let all_modules = global_code().read();
let (end, (start, module)) = match all_modules.range(pc..).next() {
Some(info) => info,
None => return None,
};
if pc < *start || *end < pc {
return None;
}
(module.clone(), pc - *start)
};
wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset)
}
pub fn register_code(code: &Arc<CodeMemory>) {
let text = code.text();
if text.is_empty() {
return;
}
let start = text.as_ptr() as usize;
let end = start + text.len() - 1;
let prev = global_code().write().insert(end, (start, code.clone()));
assert!(prev.is_none());
}
pub fn unregister_code(code: &Arc<CodeMemory>) {
let text = code.text();
if text.is_empty() {
return;
}
let end = (text.as_ptr() as usize) + text.len() - 1;
let code = global_code().write().remove(&end);
assert!(code.is_some());
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_frame_info() -> Result<(), anyhow::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, alloc) in module.compiled_module().finished_functions() {
let (start, end) = {
let ptr = alloc.as_ptr();
let len = alloc.len();
(ptr as usize, ptr as usize + len)
};
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(())
}