use crate::code::CodeObject;
#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::{FrameInfo, Module, Trap};
use once_cell::sync::Lazy;
use std::collections::btree_map::Entry;
use std::{
collections::BTreeMap,
sync::{Arc, RwLock},
};
use wasmtime_jit::CodeMemory;
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedFuncRef, VMTrampoline};
#[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>,
}
impl ModuleRegistry {
pub fn lookup_module(&self, pc: usize) -> Option<&dyn ModuleInfo> {
self.module(pc).map(|(m, _)| m.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(&self, pc: usize) -> Option<(&Module, usize)> {
let (code, offset) = self.code(pc)?;
Some((code.module(pc)?, offset))
}
pub fn register_module(&mut self, module: &Module) {
self.register(module.code_object(), Some(module))
}
#[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>) {
let text = code.code_memory().text();
if text.is_empty() {
self.modules_without_code.extend(module.cloned());
return;
}
let start_addr = text.as_ptr() as usize;
let end_addr = start_addr + text.len() - 1;
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;
}
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());
}
pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedFuncRef) -> Option<VMTrampoline> {
let (code, _offset) = self.code(anyfunc.func_ptr.as_ptr() as usize)?;
code.code.signatures().trampoline(anyfunc.type_index)
}
pub fn lookup_trap_code(&self, pc: usize) -> Option<Trap> {
let (code, offset) = self.code(pc)?;
wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset)
}
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
let (module, offset) = self.module(pc)?;
let info = FrameInfo::new(module, offset)?;
Some((info, module))
}
}
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)
}
}
static GLOBAL_CODE: Lazy<RwLock<GlobalRegistry>> = Lazy::new(Default::default);
type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>;
pub fn is_wasm_trap_pc(pc: usize) -> bool {
let (code, text_offset) = {
let all_modules = GLOBAL_CODE.read().unwrap();
let (end, (start, module)) = match all_modules.range(pc..).next() {
Some(info) => info,
None => return false,
};
if pc < *start || *end < pc {
return false;
}
(module.clone(), pc - *start)
};
wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset).is_some()
}
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()
.unwrap()
.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().unwrap().remove(&end);
assert!(code.is_some());
}
#[test]
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(())
}