Skip to content

Commit ac16810

Browse files
committed
Convert to iterator form.
1 parent 7884978 commit ac16810

File tree

4 files changed

+201
-98
lines changed

4 files changed

+201
-98
lines changed

crates/wasmtime/src/runtime/debug.rs

Lines changed: 108 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
use crate::{
44
AnyRef, ExnRef, ExternRef, Func, Instance, Module, Val, ValType,
55
store::{AutoAssertNoGc, StoreOpaque},
6-
vm::{Backtrace, VMContext},
6+
vm::{CurrentActivationBacktrace, VMContext},
77
};
88
use alloc::vec::Vec;
9-
use core::{ffi::c_void, ops::ControlFlow, ptr::NonNull};
9+
use core::{ffi::c_void, ptr::NonNull};
1010
use wasmtime_environ::{
1111
DefinedFuncIndex, FrameInstPos, FrameStackShape, FrameStateSlot, FrameStateSlotOffset,
1212
FrameTableDescriptorIndex, FrameValType, FuncKey,
@@ -33,18 +33,11 @@ impl StoreOpaque {
3333
return None;
3434
}
3535

36-
let mut frames = vec![];
37-
Backtrace::trace(self, |frame| {
38-
// `is_trapping_frame == false`: for now, we do not yet
39-
// support capturing stack values after a trap, so the PC
40-
// we use to look up metadata is always a "post-position"
41-
// PC, i.e., a call's return address.
42-
frames.extend(VirtualFrame::decode(self, frame, false));
43-
ControlFlow::Continue(())
44-
});
36+
let iter = unsafe { CurrentActivationBacktrace::new(self) };
4537
Some(StackView {
46-
store: self,
47-
frames,
38+
iter,
39+
is_trapping_frame: false,
40+
frames: vec![],
4841
})
4942
}
5043
}
@@ -54,23 +47,53 @@ impl StoreOpaque {
5447
/// See the documentation on `Store::stack_value` for more information
5548
/// about which frames this view will show.
5649
pub struct StackView<'a> {
57-
/// Mutable borrow held to the store.
50+
/// Iterator over frames.
5851
///
59-
/// This both ensures that the stack does not mutate while we're
60-
/// observing it (any borrow would do), and lets us create
61-
/// host-API GC references as values that are references are read
62-
/// off of the stack (a mutable borrow is needed for this).
63-
store: &'a mut StoreOpaque,
52+
/// This iterator owns the store while the view exists (accessible
53+
/// as `iter.store`).
54+
iter: CurrentActivationBacktrace<'a>,
6455

65-
/// Pre-enumerated frames. We precompute this rather than walking
66-
/// a true iterator finger up the stack (e.g., current FP and
67-
/// current `CallThreadState`) because our existing unwinder logic
68-
/// is written in a visit-with-closure style; and users of this
69-
/// API are likely to visit every frame anyway, so
70-
/// sparseness/efficiency is not a main concern here.
56+
/// Is the next frame to be visited by the iterator a trapping
57+
/// frame?
58+
///
59+
/// This alters how we interpret `pc`: for a trap, we look at the
60+
/// instruction that *starts* at `pc`, while for all frames
61+
/// further up the stack (i.e., at a callsite), we look at teh
62+
/// instruction that *ends* at `pc`.
63+
is_trapping_frame: bool,
64+
65+
/// Virtual frame queue: decoded from `iter`, not yet
66+
/// yielded. Innermost frame on top (last).
67+
///
68+
/// This is only non-empty when there is more than one virtual
69+
/// frame in a physical frame (i.e., for inlining); thus, its size
70+
/// is bounded by our inlining depth.
7171
frames: Vec<VirtualFrame>,
7272
}
7373

74+
impl<'a> Iterator for StackView<'a> {
75+
type Item = FrameView;
76+
fn next(&mut self) -> Option<Self::Item> {
77+
// If there are no virtual frames to yield, take and decode
78+
// the next physical frame.
79+
//
80+
// Note that `if` rather than `while` here, and the assert
81+
// that we get some virtual frames back, enforce the invariant
82+
// that each physical frame decodes to at least one virtual
83+
// frame (i.e., there are no physical frames for interstitial
84+
// functions or other things that we completely ignore). If
85+
// this ever changes, we can remove the assert and convert
86+
// this to a loop that polls until it finds virtual frames.
87+
if self.frames.is_empty() {
88+
let next_frame = self.iter.next()?;
89+
self.frames = VirtualFrame::decode(self.iter.store, next_frame, self.is_trapping_frame);
90+
self.is_trapping_frame = false;
91+
}
92+
93+
self.frames.pop().map(move |vf| FrameView::new(vf))
94+
}
95+
}
96+
7497
/// Internal data pre-computed for one stack frame.
7598
///
7699
/// This combines physical frame info (pc, fp) with the module this PC
@@ -93,32 +116,9 @@ struct VirtualFrame {
93116
stack_shape: FrameStackShape,
94117
}
95118

96-
/// A view of a frame that can decode values in that frame.
97-
pub struct FrameView<'a> {
98-
frame_state_slot: FrameStateSlot<'a>,
99-
store: &'a mut StoreOpaque,
100-
slot_addr: usize,
101-
wasm_pc: u32,
102-
stack: Vec<(FrameStateSlotOffset, FrameValType)>,
103-
}
104-
105-
impl<'a> StackView<'a> {
106-
/// Get a handle to a specific frame.
107-
///
108-
/// # Panics
109-
///
110-
/// Panics if the index is out of range.
111-
pub fn frame(&mut self, index: usize) -> FrameView<'_> {
112-
FrameView::new(self.store, &self.frames[index])
113-
}
114-
115-
/// Get the number of frames viewable on this stack.
116-
pub fn len(&self) -> usize {
117-
self.frames.len()
118-
}
119-
}
120-
121119
impl VirtualFrame {
120+
/// Return virtual frames corresponding to a physical frame, from
121+
/// outermost to innermost.
122122
fn decode(store: &StoreOpaque, frame: Frame, is_trapping_frame: bool) -> Vec<VirtualFrame> {
123123
let module = store
124124
.modules()
@@ -137,25 +137,43 @@ impl VirtualFrame {
137137
return vec![];
138138
};
139139

140-
let mut frames: Vec<_> = program_points
140+
program_points
141141
.map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
142142
fp: frame.fp(),
143143
module: module.clone(),
144144
wasm_pc,
145145
frame_descriptor,
146146
stack_shape,
147147
})
148-
.collect();
149-
150-
// Reverse the frames so we return them inside-out, matching
151-
// the bottom-up stack traversal order.
152-
frames.reverse();
153-
frames
148+
.collect()
154149
}
155150
}
156151

157-
impl<'a> FrameView<'a> {
158-
fn new(store: &'a mut StoreOpaque, frame: &'a VirtualFrame) -> Self {
152+
/// A view of a frame that can decode values in that frame.
153+
pub struct FrameView {
154+
slot_addr: usize,
155+
func_key: FuncKey,
156+
wasm_pc: u32,
157+
/// Shape of locals in this frame.
158+
///
159+
/// We need to store this locally because `FrameView` cannot
160+
/// borrow the store: it needs a mut borrow, and an iterator
161+
/// cannot yield the same mut borrow multiple times because it
162+
/// cannot control the lifetime of the values it yields (the
163+
/// signature of `next()` does not bound the return value to the
164+
/// `&mut self` arg).
165+
locals: Vec<(FrameStateSlotOffset, FrameValType)>,
166+
/// Shape of the stack slots at this program point in this frame.
167+
///
168+
/// In addition to the borrowing-related reason above, we also
169+
/// materialize this because we want to provide O(1) access to the
170+
/// stack by depth, and the frame slot descriptor stores info in a
171+
/// linked-list (actually DAG, with dedup'ing) way.
172+
stack: Vec<(FrameStateSlotOffset, FrameValType)>,
173+
}
174+
175+
impl FrameView {
176+
fn new(frame: VirtualFrame) -> Self {
159177
let frame_table = frame.module.frame_table();
160178
// Parse the frame descriptor.
161179
let (data, slot_to_fp_offset) = frame_table
@@ -165,56 +183,68 @@ impl<'a> FrameView<'a> {
165183
let slot_addr = frame
166184
.fp
167185
.wrapping_sub(usize::try_from(slot_to_fp_offset).unwrap());
168-
// Materialize the stack shape so we have O(1) access to its elements.
186+
187+
// Materialize the stack shape so we have O(1) access to its
188+
// elements, and so we don't need to keep the borrow to the
189+
// module alive.
169190
let mut stack = frame_state_slot
170191
.stack(frame.stack_shape)
171192
.collect::<Vec<_>>();
172193
stack.reverse(); // Put top-of-stack last.
194+
195+
// Materialize the local offsets/types so we don't need to
196+
// keep the borrow to the module alive.
197+
let locals = frame_state_slot.locals().collect::<Vec<_>>();
198+
173199
FrameView {
174-
store,
175-
frame_state_slot,
176200
slot_addr,
201+
func_key: frame_state_slot.func_key(),
177202
wasm_pc: frame.wasm_pc,
178203
stack,
204+
locals,
179205
}
180206
}
181207

182-
fn raw_instance(&mut self) -> &'a crate::vm::Instance {
208+
fn raw_instance<'a>(&self, _store: &'a mut StoreOpaque) -> &'a crate::vm::Instance {
183209
// Read out the vmctx slot.
184210
// SAFETY: vmctx is always at offset 0 in the slot.
185211
let vmctx: *mut VMContext = unsafe { *(self.slot_addr as *mut _) };
186212
let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
187213
// SAFETY: the stored vmctx value is a valid instance in this
188-
// store; we only visit frames from this store in teh backtrace.
214+
// store; we only visit frames from this store in the
215+
// backtrace.
189216
let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
190217
// SAFETY: the instance pointer read above is valid.
191218
unsafe { instance.as_ref() }
192219
}
193220

194221
/// Get the instance associated with this frame.
195-
pub fn instance(&mut self) -> Instance {
196-
let instance = self.raw_instance();
197-
Instance::from_wasmtime(instance.id(), self.store)
222+
pub fn instance(&self, view: &mut StackView<'_>) -> Instance {
223+
let instance = self.raw_instance(view.iter.store);
224+
Instance::from_wasmtime(instance.id(), view.iter.store)
198225
}
199226

200227
/// Get the module associated with this frame, if any (i.e., not a
201228
/// container instance for a host-created entity).
202-
pub fn module(&mut self) -> Option<&Module> {
203-
let instance = self.raw_instance();
229+
pub fn module<'a>(&self, view: &'a mut StackView<'_>) -> Option<&'a Module> {
230+
let instance = self.raw_instance(view.iter.store);
204231
instance.runtime_module()
205232
}
206233

207234
/// Get the raw function index associated with this frame, and the
208235
/// PC as an offset within its code section, if it is a Wasm
209236
/// function directly from the given `Module` (rather than a
210237
/// trampoline).
211-
pub fn wasm_function_index_and_pc(&mut self) -> Option<(DefinedFuncIndex, u32)> {
212-
let FuncKey::DefinedWasmFunction(module, func) = self.frame_state_slot.func_key() else {
238+
pub fn wasm_function_index_and_pc(
239+
&self,
240+
view: &mut StackView<'_>,
241+
) -> Option<(DefinedFuncIndex, u32)> {
242+
let FuncKey::DefinedWasmFunction(module, func) = self.func_key else {
213243
return None;
214244
};
215245
debug_assert_eq!(
216246
module,
217-
self.module()
247+
self.module(view)
218248
.expect("module should be defined if this is a defined function")
219249
.env_module()
220250
.module_index
@@ -224,7 +254,7 @@ impl<'a> FrameView<'a> {
224254

225255
/// Get the number of locals in this frame.
226256
pub fn num_locals(&self) -> usize {
227-
self.frame_state_slot.num_locals()
257+
self.locals.len()
228258
}
229259

230260
/// Get the depth of the operand stack in this frame.
@@ -238,11 +268,11 @@ impl<'a> FrameView<'a> {
238268
///
239269
/// Panics if the index is out-of-range (greater than
240270
/// `num_locals()`).
241-
pub fn local(&mut self, index: usize) -> (ValType, Val) {
242-
let (offset, ty) = self.frame_state_slot.local(index).unwrap();
271+
pub fn local(&self, view: &mut StackView<'_>, index: usize) -> (ValType, Val) {
272+
let (offset, ty) = self.locals[index];
243273
// SAFETY: compiler produced metadata to describe this local
244274
// slot and stored a value of the correct type into it.
245-
unsafe { read_value(self.store, self.slot_addr, offset, ty) }
275+
unsafe { read_value(view.iter.store, self.slot_addr, offset, ty) }
246276
}
247277

248278
/// Get the type and value of the given operand-stack value in
@@ -252,12 +282,12 @@ impl<'a> FrameView<'a> {
252282
/// from there are more recently pushed values. In other words,
253283
/// index order reads the Wasm virtual machine's abstract stack
254284
/// state left-to-right.
255-
pub fn stack(&mut self, index: usize) -> (ValType, Val) {
285+
pub fn stack(&self, view: &mut StackView<'_>, index: usize) -> (ValType, Val) {
256286
let (offset, ty) = self.stack[index];
257287
// SAFETY: compiler produced metadata to describe this
258288
// operand-stack slot and stored a value of the correct type
259289
// into it.
260-
unsafe { read_value(self.store, self.slot_addr, offset, ty) }
290+
unsafe { read_value(view.iter.store, self.slot_addr, offset, ty) }
261291
}
262292
}
263293

crates/wasmtime/src/runtime/vm/traphandlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use core::ptr::{self, NonNull};
2929
use wasmtime_unwinder::Handler;
3030

3131
pub use self::backtrace::Backtrace;
32+
#[cfg(feature = "debug")]
33+
pub(crate) use self::backtrace::CurrentActivationBacktrace;
3234
#[cfg(feature = "gc")]
3335
pub use wasmtime_unwinder::Frame;
3436

crates/wasmtime/src/runtime/vm/traphandlers/backtrace.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl Backtrace {
7777
}
7878

7979
/// Walk the current Wasm stack, calling `f` for each frame we walk.
80-
#[cfg(any(feature = "gc", feature = "debug"))]
80+
#[cfg(feature = "gc")]
8181
pub fn trace(store: &StoreOpaque, f: impl FnMut(Frame) -> ControlFlow<()>) {
8282
let vm_store_context = store.vm_store_context();
8383
let unwind = store.unwinder();
@@ -325,3 +325,59 @@ impl Backtrace {
325325
self.0.iter()
326326
}
327327
}
328+
329+
/// An iterator over one Wasm activation.
330+
#[cfg(feature = "debug")]
331+
pub(crate) struct CurrentActivationBacktrace<'a> {
332+
pub(crate) store: &'a mut StoreOpaque,
333+
inner: Box<dyn Iterator<Item = Frame>>,
334+
}
335+
336+
#[cfg(feature = "debug")]
337+
impl<'a> CurrentActivationBacktrace<'a> {
338+
/// Return an iterator over the most recent Wasm activation.
339+
///
340+
/// The iterator captures the store with a mutable borrow, and
341+
/// then yields it back at each frame. This ensures that the stack
342+
/// remains live while still providing a mutable store that may be
343+
/// needed to access items in the frame (e.g., to create new roots
344+
/// when reading out GC refs).
345+
///
346+
/// This serves as an alternative to `Backtrace::trace()` and
347+
/// friends: it allows external iteration (and e.g. lazily walking
348+
/// through frames in a stack) rather than visiting via a closure.
349+
///
350+
/// # Safety
351+
///
352+
/// Although the iterator yields a mutable store back at each
353+
/// iteration, this *must not* be used to mutate the stack
354+
/// activation itself that this iterator is visiting. While the
355+
/// `store` technically owns the stack in question, the only way
356+
/// to do this with the current API would be to return back into
357+
/// the Wasm activation. As long as this iterator is held and used
358+
/// while within host code called from that activation (which will
359+
/// ordinarily be ensured if the `store`'s lifetime came from the
360+
/// host entry point) then everything will be sound.
361+
pub(crate) unsafe fn new(store: &'a mut StoreOpaque) -> CurrentActivationBacktrace<'a> {
362+
// Get the initial exit FP, exit PC, and entry FP.
363+
let vm_store_context = store.vm_store_context();
364+
let exit_pc = unsafe { *(*vm_store_context).last_wasm_exit_pc.get() };
365+
let exit_fp = unsafe { (*vm_store_context).last_wasm_exit_fp() };
366+
let trampoline_fp = unsafe { *(*vm_store_context).last_wasm_entry_fp.get() };
367+
let unwind = store.unwinder();
368+
// Establish the iterator.
369+
let inner = Box::new(unsafe {
370+
wasmtime_unwinder::frame_iterator(unwind, exit_pc, exit_fp, trampoline_fp)
371+
});
372+
373+
CurrentActivationBacktrace { store, inner }
374+
}
375+
}
376+
377+
#[cfg(feature = "debug")]
378+
impl<'a> Iterator for CurrentActivationBacktrace<'a> {
379+
type Item = Frame;
380+
fn next(&mut self) -> Option<Self::Item> {
381+
self.inner.next()
382+
}
383+
}

0 commit comments

Comments
 (0)