use crate::prelude::*;
use crate::runtime::store::StoreOpaque;
use crate::runtime::vm::{
Unwind, VMStoreContext,
traphandlers::{CallThreadState, tls},
};
use core::ops::ControlFlow;
#[derive(Debug)]
pub struct Backtrace(Vec<Frame>);
#[derive(Debug)]
pub struct Frame {
pc: usize,
#[cfg_attr(
not(feature = "gc"),
expect(dead_code, reason = "not worth #[cfg] annotations to remove")
)]
fp: usize,
}
impl Frame {
pub fn pc(&self) -> usize {
self.pc
}
#[cfg(feature = "gc")]
pub fn fp(&self) -> usize {
self.fp
}
}
impl Backtrace {
pub fn empty() -> Backtrace {
Backtrace(Vec::new())
}
pub fn new(store: &StoreOpaque) -> Backtrace {
let vm_store_context = store.vm_store_context();
let unwind = store.unwinder();
tls::with(|state| match state {
Some(state) => unsafe {
Self::new_with_trap_state(vm_store_context, unwind, state, None)
},
None => Backtrace(vec![]),
})
}
pub(crate) unsafe fn new_with_trap_state(
vm_store_context: *const VMStoreContext,
unwind: &dyn Unwind,
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Backtrace {
let mut frames = vec![];
Self::trace_with_trap_state(vm_store_context, unwind, state, trap_pc_and_fp, |frame| {
frames.push(frame);
ControlFlow::Continue(())
});
Backtrace(frames)
}
#[cfg(feature = "gc")]
pub fn trace(store: &StoreOpaque, f: impl FnMut(Frame) -> ControlFlow<()>) {
let vm_store_context = store.vm_store_context();
let unwind = store.unwinder();
tls::with(|state| match state {
Some(state) => unsafe {
Self::trace_with_trap_state(vm_store_context, unwind, state, None, f)
},
None => {}
});
}
pub(crate) unsafe fn trace_with_trap_state(
vm_store_context: *const VMStoreContext,
unwind: &dyn Unwind,
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) {
log::trace!("====== Capturing Backtrace ======");
let (last_wasm_exit_pc, last_wasm_exit_fp) = match trap_pc_and_fp {
Some((pc, fp)) => {
assert!(core::ptr::eq(
vm_store_context,
state.vm_store_context.as_ptr()
));
(pc, fp)
}
None => {
let pc = *(*vm_store_context).last_wasm_exit_pc.get();
let fp = *(*vm_store_context).last_wasm_exit_fp.get();
(pc, fp)
}
};
let activations = core::iter::once((
last_wasm_exit_pc,
last_wasm_exit_fp,
*(*vm_store_context).last_wasm_entry_fp.get(),
))
.chain(
state
.iter()
.filter(|state| core::ptr::eq(vm_store_context, state.vm_store_context.as_ptr()))
.map(|state| {
(
state.old_last_wasm_exit_pc(),
state.old_last_wasm_exit_fp(),
state.old_last_wasm_entry_fp(),
)
}),
)
.take_while(|&(pc, fp, sp)| {
if pc == 0 {
debug_assert_eq!(fp, 0);
debug_assert_eq!(sp, 0);
}
pc != 0
});
for (pc, fp, sp) in activations {
if let ControlFlow::Break(()) = Self::trace_through_wasm(unwind, pc, fp, sp, &mut f) {
log::trace!("====== Done Capturing Backtrace (closure break) ======");
return;
}
}
log::trace!("====== Done Capturing Backtrace (reached end of activations) ======");
}
unsafe fn trace_through_wasm(
unwind: &dyn Unwind,
mut pc: usize,
mut fp: usize,
trampoline_fp: usize,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) -> ControlFlow<()> {
log::trace!("=== Tracing through contiguous sequence of Wasm frames ===");
log::trace!("trampoline_fp = 0x{:016x}", trampoline_fp);
log::trace!(" initial pc = 0x{:016x}", pc);
log::trace!(" initial fp = 0x{:016x}", fp);
assert_ne!(pc, 0);
assert_ne!(fp, 0);
assert_ne!(trampoline_fp, 0);
while fp != trampoline_fp {
assert!(trampoline_fp > fp, "{trampoline_fp:#x} > {fp:#x}");
unwind.assert_fp_is_aligned(fp);
log::trace!("--- Tracing through one Wasm frame ---");
log::trace!("pc = {:p}", pc as *const ());
log::trace!("fp = {:p}", fp as *const ());
f(Frame { pc, fp })?;
pc = unwind.get_next_older_pc_from_fp(fp);
assert_eq!(unwind.next_older_fp_from_fp_offset(), 0);
let next_older_fp = *(fp as *mut usize).add(unwind.next_older_fp_from_fp_offset());
assert!(next_older_fp > fp, "{next_older_fp:#x} > {fp:#x}");
fp = next_older_fp;
}
log::trace!("=== Done tracing contiguous sequence of Wasm frames ===");
ControlFlow::Continue(())
}
pub fn frames<'a>(
&'a self,
) -> impl ExactSizeIterator<Item = &'a Frame> + DoubleEndedIterator + 'a {
self.0.iter()
}
}