use std::cmp;
use std::collections::BTreeMap;
use std::sync::{Arc, RwLock};
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::ir;
use wasmtime_environ::wasm::FuncIndex;
use wasmtime_environ::{FunctionAddressMap, Module, TrapInformation};
use wasmtime_jit::CompiledModule;
lazy_static::lazy_static! {
pub static ref FRAME_INFO: RwLock<GlobalFrameInfo> = Default::default();
}
#[derive(Default)]
pub struct GlobalFrameInfo {
ranges: BTreeMap<usize, ModuleFrameInfo>,
}
pub struct GlobalFrameInfoRegistration {
key: usize,
}
struct ModuleFrameInfo {
start: usize,
functions: BTreeMap<usize, FunctionInfo>,
module: Arc<Module>,
#[allow(dead_code)]
module_code: Arc<dyn std::any::Any + Send + Sync>,
}
struct FunctionInfo {
start: usize,
index: FuncIndex,
traps: Vec<TrapInformation>,
instr_map: FunctionAddressMap,
}
impl GlobalFrameInfo {
pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
let (module, func) = self.func(pc)?;
let rel_pos = pc - func.start;
let pos = match func
.instr_map
.instructions
.binary_search_by_key(&rel_pos, |map| map.code_offset)
{
Ok(pos) => Some(pos),
Err(0) => None,
Err(n) => {
let instr = &func.instr_map.instructions[n - 1];
if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
Some(n - 1)
} else {
None
}
}
};
debug_assert!(pos.is_some(), "failed to find instruction for {:x}", pc);
let instr = match pos {
Some(pos) => func.instr_map.instructions[pos].srcloc,
None => func.instr_map.start_srcloc,
};
Some(FrameInfo {
module_name: module.module.name.clone(),
func_index: func.index.index() as u32,
func_name: module.module.func_names.get(&func.index).cloned(),
instr,
func_start: func.instr_map.start_srcloc,
})
}
pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
let (_module, func) = self.func(pc)?;
let idx = func
.traps
.binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
.ok()?;
Some(&func.traps[idx])
}
fn func(&self, pc: usize) -> Option<(&ModuleFrameInfo, &FunctionInfo)> {
let (end, info) = self.ranges.range(pc..).next()?;
if pc < info.start || *end < pc {
return None;
}
let (end, func) = info.functions.range(pc..).next()?;
if pc < func.start || *end < pc {
return None;
}
Some((info, func))
}
}
impl Drop for GlobalFrameInfoRegistration {
fn drop(&mut self) {
if let Ok(mut info) = FRAME_INFO.write() {
info.ranges.remove(&self.key);
}
}
}
pub fn register(module: &CompiledModule) -> Option<GlobalFrameInfoRegistration> {
let mut min = usize::max_value();
let mut max = 0;
let mut functions = BTreeMap::new();
for (((i, allocated), traps), instrs) in module
.finished_functions()
.iter()
.zip(module.traps().values())
.zip(module.address_transform().values())
{
let (start, end) = unsafe {
let ptr = (**allocated).as_ptr();
let len = (**allocated).len();
(ptr as usize, ptr as usize + len)
};
min = cmp::min(min, start);
max = cmp::max(max, end);
let func = FunctionInfo {
start,
index: module.module().local.func_index(i),
traps: traps.to_vec(),
instr_map: (*instrs).clone(),
};
assert!(functions.insert(end, func).is_none());
}
if functions.len() == 0 {
return None;
}
let mut info = FRAME_INFO.write().unwrap();
if let Some((_, prev)) = info.ranges.range(max..).next() {
assert!(prev.start > max);
}
if let Some((prev_end, _)) = info.ranges.range(..=min).next_back() {
assert!(*prev_end < min);
}
let prev = info.ranges.insert(
max,
ModuleFrameInfo {
start: min,
functions,
module: module.module().clone(),
module_code: module.code().clone(),
},
);
assert!(prev.is_none());
Some(GlobalFrameInfoRegistration { key: max })
}
#[derive(Debug)]
pub struct FrameInfo {
module_name: Option<String>,
func_index: u32,
func_name: Option<String>,
func_start: ir::SourceLoc,
instr: ir::SourceLoc,
}
impl FrameInfo {
pub fn func_index(&self) -> u32 {
self.func_index
}
pub fn module_name(&self) -> Option<&str> {
self.module_name.as_deref()
}
pub fn func_name(&self) -> Option<&str> {
self.func_name.as_deref()
}
pub fn module_offset(&self) -> usize {
self.instr.bits() as usize
}
pub fn func_offset(&self) -> usize {
(self.instr.bits() - self.func_start.bits()) as usize
}
}