use crate::prelude::*;
use crate::runtime::store::StoreOpaque;
use crate::runtime::vm::stack_switching::VMStackChain;
use crate::runtime::vm::{
Unwind, VMStoreContext,
traphandlers::{CallThreadState, tls},
};
#[cfg(all(feature = "gc", feature = "stack-switching"))]
use crate::vm::stack_switching::{VMContRef, VMStackState};
#[cfg(feature = "debug")]
use crate::{StoreContext, StoreContextMut};
use core::ops::ControlFlow;
use wasmtime_unwinder::Frame;
#[derive(Debug)]
pub struct Backtrace(Vec<Frame>);
pub(crate) struct Activation {
exit_pc: usize,
exit_fp: usize,
entry_trampoline_fp: usize,
}
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![];
let f = |activation: Activation| unsafe {
wasmtime_unwinder::visit_frames(
unwind,
activation.exit_pc,
activation.exit_fp,
activation.entry_trampoline_fp,
|frame| {
frames.push(frame);
ControlFlow::Continue(())
},
)
};
unsafe {
Self::trace_with_trap_state(vm_store_context, state, trap_pc_and_fp, f);
}
Backtrace(frames)
}
#[cfg(feature = "gc")]
pub fn trace(store: &StoreOpaque, mut 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 {
let f = |activation: Activation| {
wasmtime_unwinder::visit_frames(
unwind,
activation.exit_pc,
activation.exit_fp,
activation.entry_trampoline_fp,
&mut f,
)
};
Self::trace_with_trap_state(vm_store_context, state, None, f)
},
None => {}
});
}
#[cfg(all(feature = "gc", feature = "stack-switching"))]
pub fn trace_suspended_continuation(
store: &StoreOpaque,
continuation: &VMContRef,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) {
log::trace!("====== Capturing Backtrace (suspended continuation) ======");
assert_eq!(
continuation.common_stack_information.state,
VMStackState::Suspended
);
let unwind = store.unwinder();
let pc = continuation.stack.control_context_instruction_pointer();
let fp = continuation.stack.control_context_frame_pointer();
let trampoline_fp = continuation
.common_stack_information
.limits
.last_wasm_entry_fp;
unsafe {
let stack_chain =
VMStackChain::Continuation(continuation as *const VMContRef as *mut VMContRef);
if let ControlFlow::Break(()) = Self::trace_through_continuations(
stack_chain,
pc,
fp,
trampoline_fp,
|activation| {
wasmtime_unwinder::visit_frames(
unwind,
activation.exit_pc,
activation.exit_fp,
activation.entry_trampoline_fp,
&mut f,
)
},
) {
log::trace!("====== Done Capturing Backtrace (closure break) ======");
return;
}
}
log::trace!("====== Done Capturing Backtrace (reached end of stack chain) ======");
}
pub(crate) unsafe fn trace_with_trap_state(
vm_store_context: *const VMStoreContext,
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
mut f: impl FnMut(Activation) -> 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 => unsafe {
let pc = *(*vm_store_context).last_wasm_exit_pc.get();
let fp = (*vm_store_context).last_wasm_exit_fp();
(pc, fp)
},
};
let stack_chain = unsafe { (*(*vm_store_context).stack_chain.get()).clone() };
let activations =
core::iter::once((stack_chain, last_wasm_exit_pc, last_wasm_exit_fp, unsafe {
*(*vm_store_context).last_wasm_entry_fp.get()
}))
.chain(
state
.iter()
.flat_map(|state| state.iter())
.filter(|state| {
core::ptr::eq(vm_store_context, state.vm_store_context.as_ptr())
})
.map(|state| unsafe {
(
state.old_stack_chain(),
state.old_last_wasm_exit_pc(),
state.old_last_wasm_exit_fp(),
state.old_last_wasm_entry_fp(),
)
}),
)
.take_while(|(chain, pc, fp, sp)| {
if *pc == 0 {
debug_assert_eq!(*fp, 0);
debug_assert_eq!(*sp, 0);
} else {
debug_assert_ne!(chain.clone(), VMStackChain::Absent)
}
*pc != 0
});
for (chain, exit_pc, exit_fp, entry_trampoline_fp) in activations {
let res = unsafe {
Self::trace_through_continuations(
chain,
exit_pc,
exit_fp,
entry_trampoline_fp,
&mut f,
)
};
if let ControlFlow::Break(()) = res {
log::trace!("====== Done Capturing Backtrace (closure break) ======");
return;
}
}
log::trace!("====== Done Capturing Backtrace (reached end of activations) ======");
}
unsafe fn trace_through_continuations(
chain: VMStackChain,
exit_pc: usize,
exit_fp: usize,
entry_trampoline_fp: usize,
mut f: impl FnMut(Activation) -> ControlFlow<()>,
) -> ControlFlow<()> {
use crate::runtime::vm::stack_switching::{VMContRef, VMStackLimits};
f(Activation {
exit_pc,
exit_fp,
entry_trampoline_fp,
})?;
assert_ne!(chain, VMStackChain::Absent);
let stack_limits_vec: Vec<*mut VMStackLimits> =
unsafe { chain.clone().into_stack_limits_iter().collect() };
let continuations_vec: Vec<*mut VMContRef> =
unsafe { chain.clone().into_continuation_iter().collect() };
assert_eq!(stack_limits_vec.len(), continuations_vec.len() + 1);
for i in 0..continuations_vec.len() {
let continuation = unsafe { &*continuations_vec[i] };
let parent_limits = unsafe { &*stack_limits_vec[i + 1] };
let parent_continuation = continuations_vec.get(i + 1).map(|&c| unsafe { &*c });
let fiber_stack = continuation.fiber_stack();
let resume_pc = fiber_stack.control_context_instruction_pointer();
let resume_fp = fiber_stack.control_context_frame_pointer();
let parent_stack_range = parent_continuation.and_then(|p| p.fiber_stack().range());
parent_stack_range.inspect(|parent_stack_range| {
debug_assert!(parent_stack_range.contains(&resume_fp));
debug_assert!(parent_stack_range.contains(&parent_limits.last_wasm_entry_fp));
debug_assert!(parent_stack_range.contains(&parent_limits.stack_limit));
});
f(Activation {
exit_pc: resume_pc,
exit_fp: resume_fp,
entry_trampoline_fp: parent_limits.last_wasm_entry_fp,
})?;
}
ControlFlow::Continue(())
}
#[cfg(feature = "debug")]
fn activations(store: &StoreOpaque) -> Vec<Activation> {
let mut activations = vec![];
let vm_store_context = store.vm_store_context();
tls::with(|state| match state {
Some(state) => unsafe {
Self::trace_with_trap_state(vm_store_context, state, None, |act| {
activations.push(act);
ControlFlow::Continue(())
});
},
None => {}
});
activations
}
pub fn frames<'a>(
&'a self,
) -> impl ExactSizeIterator<Item = &'a Frame> + DoubleEndedIterator + 'a {
self.0.iter()
}
}
#[cfg(feature = "debug")]
struct ActivationBacktrace<'a, T: 'static> {
pub(crate) store: StoreContextMut<'a, T>,
inner: Box<dyn Iterator<Item = Frame>>,
}
#[cfg(feature = "debug")]
impl<'a, T: 'static> ActivationBacktrace<'a, T> {
pub(crate) fn new(
store: StoreContextMut<'a, T>,
activation: Activation,
) -> ActivationBacktrace<'a, T> {
let inner: Box<dyn Iterator<Item = Frame>> = if activation.exit_fp == 0 {
Box::new(core::iter::empty())
} else {
let unwind = store.0.unwinder();
Box::new(unsafe {
wasmtime_unwinder::frame_iterator(
unwind,
activation.exit_pc,
activation.exit_fp,
activation.entry_trampoline_fp,
)
})
};
ActivationBacktrace { store, inner }
}
}
#[cfg(feature = "debug")]
impl<'a, T: 'static> Iterator for ActivationBacktrace<'a, T> {
type Item = Frame;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
#[cfg(feature = "debug")]
pub(crate) struct StoreBacktrace<'a, T: 'static> {
current: Option<StoreOrActivationBacktrace<'a, T>>,
activations: Vec<Activation>,
}
#[cfg(feature = "debug")]
enum StoreOrActivationBacktrace<'a, T: 'static> {
Store(StoreContextMut<'a, T>),
Activation(ActivationBacktrace<'a, T>),
}
#[cfg(feature = "debug")]
impl<'a, T: 'static> StoreOrActivationBacktrace<'a, T> {
fn is_activation(&self) -> bool {
match self {
Self::Activation(_) => true,
_ => false,
}
}
}
#[cfg(feature = "debug")]
impl<'a, T> StoreBacktrace<'a, T> {
pub(crate) fn new(store: StoreContextMut<'a, T>) -> StoreBacktrace<'a, T> {
use crate::store::AsStoreOpaque;
let mut activations = Backtrace::activations(store.0.as_store_opaque());
activations.reverse();
let current = match activations.pop() {
Some(innermost) => {
StoreOrActivationBacktrace::Activation(ActivationBacktrace::new(store, innermost))
}
None => StoreOrActivationBacktrace::Store(store),
};
StoreBacktrace {
current: Some(current),
activations,
}
}
pub fn store(&self) -> StoreContext<'_, T> {
match self.current.as_ref().unwrap() {
StoreOrActivationBacktrace::Activation(activation) => StoreContext(activation.store.0),
StoreOrActivationBacktrace::Store(store) => StoreContext(store.0),
}
}
pub fn store_mut(&mut self) -> StoreContextMut<'_, T> {
match self.current.as_mut().unwrap() {
StoreOrActivationBacktrace::Activation(activation) => {
StoreContextMut(activation.store.0)
}
StoreOrActivationBacktrace::Store(store) => StoreContextMut(store.0),
}
}
fn take_store(&mut self) -> StoreContextMut<'a, T> {
match self.current.take().unwrap() {
StoreOrActivationBacktrace::Activation(activation) => activation.store,
StoreOrActivationBacktrace::Store(store) => store,
}
}
fn next_activation(&mut self) {
let activation = self.activations.pop();
let store = self.take_store();
self.current = Some(match activation {
Some(activation) => {
StoreOrActivationBacktrace::Activation(ActivationBacktrace::new(store, activation))
}
None => StoreOrActivationBacktrace::Store(store),
});
}
}
#[cfg(feature = "debug")]
pub enum FrameOrHostCode {
Frame(Frame),
HostCode,
}
#[cfg(feature = "debug")]
impl<'a, T: 'static> Iterator for StoreBacktrace<'a, T> {
type Item = FrameOrHostCode;
fn next(&mut self) -> Option<Self::Item> {
match self.current.as_mut().unwrap() {
StoreOrActivationBacktrace::Store(_) => None,
StoreOrActivationBacktrace::Activation(act) => match act.next() {
Some(frame) => Some(FrameOrHostCode::Frame(frame)),
None => {
self.next_activation();
if self.current.as_ref().unwrap().is_activation() {
Some(FrameOrHostCode::HostCode)
} else {
None
}
}
},
}
}
}