Skip to content

Commit 7e0331c

Browse files
authored
Debugging: refactor stack frame cursor into frame handle abstraction. (#12566)
* Debugging: refactor stack frame cursor into frame handle abstraction. This addresses some of the issues described #12486: we need the ability to keep a handle to a stack frame as long as execution is frozen, and keep multiple of these handles around, alongside the `Store`, without any handle directly holding a borrow of the store. The frame handles work by means of an "execution version" scheme: the idea is that whenever any execution resumes in a given store, all handles to existing frames could be invalidated, but if no such execution occurs, all handles should still be valid. A tuple of (globally unique for process lifetime) store ID, and execution version within that store, should be sufficient to uniquely identify any frozen-stack period during execution. This accomplishes cheap handle invalidation without the need to track existing handles. This PR also implements a cache of parsed frame-table data. Previously this was lazily parsed by the cursor as it walked up a stack, but with multiple handles hanging around, and with handles meant to be cheap to hold and clone, and with handles being invalidated eagerly, it makes much more sense to persist this parsed metadata at the `Store` level. (It cannot persist at the `Engine` level because PCs are local per store.) * Re-bless disas tests (offsets in VMStoreContext changed). * Handle invalidation tests. * Review comments, and make API return `Result`s rather than panic'ing on stale handles. * Review feedback. * Doc-comment link fix. * Review feedback. * cfg-gate Activation method to `debug` feature only. * Fix unused-import warning in no-debug cfg. * Fix doc link (again, after rename from latest feedback).
1 parent 3623501 commit 7e0331c

1,857 files changed

Lines changed: 3658 additions & 3369 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/cranelift/src/compiler.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,36 @@ impl wasmtime_environ::Compiler for Compiler {
434434
callee,
435435
&[callee_vmctx, caller_vmctx, args_base, args_len],
436436
);
437+
438+
// Increment the "execution version" on the VMStoreContext if
439+
// guest debugging is enabled.
440+
if self.tunables.debug_guest {
441+
let vmstore_ctx_ptr = builder.ins().load(
442+
pointer_type,
443+
MemFlags::trusted().with_readonly(),
444+
caller_vmctx,
445+
i32::from(ptr_size.vmctx_store_context()),
446+
);
447+
let old_version = builder.ins().load(
448+
ir::types::I64,
449+
MemFlags::trusted(),
450+
vmstore_ctx_ptr,
451+
i32::from(ptr_size.vmstore_context_execution_version()),
452+
);
453+
let new_version = builder.ins().iadd_imm(old_version, 1);
454+
builder.ins().store(
455+
MemFlags::trusted(),
456+
new_version,
457+
vmstore_ctx_ptr,
458+
i32::from(ptr_size.vmstore_context_execution_version()),
459+
);
460+
}
461+
462+
// Invoke `raise` if the callee (host) returned an error.
437463
let succeeded = builder.func.dfg.inst_results(call)[0];
438464
self.raise_if_host_trapped(&mut builder, caller_vmctx, succeeded);
465+
466+
// Return results from the array as native return values.
439467
let results =
440468
self.load_values_from_array(wasm_func_ty.returns(), &mut builder, args_base, args_len);
441469
builder.ins().return_(&results);

crates/debugger/src/lib.rs

Lines changed: 104 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -498,75 +498,131 @@ mod test {
498498
assert!(matches!(event, DebugRunResult::Breakpoint));
499499
// At (before executing) first `local.get`.
500500
debugger
501-
.with_store(|store| {
502-
let mut frame = store.debug_frames().unwrap();
503-
assert!(!frame.done());
504-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
505-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 36);
506-
assert_eq!(frame.num_locals(), 2);
507-
assert_eq!(frame.num_stacks(), 0);
508-
assert_eq!(frame.local(0).unwrap_i32(), 1);
509-
assert_eq!(frame.local(1).unwrap_i32(), 2);
510-
assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
511-
assert!(frame.done());
501+
.with_store(|mut store| {
502+
let frame = store.debug_exit_frames().next().unwrap();
503+
assert_eq!(
504+
frame
505+
.wasm_function_index_and_pc(&mut store)
506+
.unwrap()
507+
.unwrap()
508+
.0
509+
.as_u32(),
510+
0
511+
);
512+
assert_eq!(
513+
frame
514+
.wasm_function_index_and_pc(&mut store)
515+
.unwrap()
516+
.unwrap()
517+
.1,
518+
36
519+
);
520+
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
521+
assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
522+
assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
523+
assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
524+
let frame = frame.parent(&mut store).unwrap();
525+
assert!(frame.is_none());
512526
})
513527
.await?;
514528

515529
let event = debugger.run().await?;
516530
// At second `local.get`.
517531
assert!(matches!(event, DebugRunResult::Breakpoint));
518532
debugger
519-
.with_store(|store| {
520-
let mut frame = store.debug_frames().unwrap();
521-
assert!(!frame.done());
522-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
523-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 38);
524-
assert_eq!(frame.num_locals(), 2);
525-
assert_eq!(frame.num_stacks(), 1);
526-
assert_eq!(frame.local(0).unwrap_i32(), 1);
527-
assert_eq!(frame.local(1).unwrap_i32(), 2);
528-
assert_eq!(frame.stack(0).unwrap_i32(), 1);
529-
assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
530-
assert!(frame.done());
533+
.with_store(|mut store| {
534+
let frame = store.debug_exit_frames().next().unwrap();
535+
assert_eq!(
536+
frame
537+
.wasm_function_index_and_pc(&mut store)
538+
.unwrap()
539+
.unwrap()
540+
.0
541+
.as_u32(),
542+
0
543+
);
544+
assert_eq!(
545+
frame
546+
.wasm_function_index_and_pc(&mut store)
547+
.unwrap()
548+
.unwrap()
549+
.1,
550+
38
551+
);
552+
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
553+
assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
554+
assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
555+
assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
556+
assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
557+
let frame = frame.parent(&mut store).unwrap();
558+
assert!(frame.is_none());
531559
})
532560
.await?;
533561

534562
let event = debugger.run().await?;
535563
// At `i32.add`.
536564
assert!(matches!(event, DebugRunResult::Breakpoint));
537565
debugger
538-
.with_store(|store| {
539-
let mut frame = store.debug_frames().unwrap();
540-
assert!(!frame.done());
541-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
542-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 40);
543-
assert_eq!(frame.num_locals(), 2);
544-
assert_eq!(frame.num_stacks(), 2);
545-
assert_eq!(frame.local(0).unwrap_i32(), 1);
546-
assert_eq!(frame.local(1).unwrap_i32(), 2);
547-
assert_eq!(frame.stack(0).unwrap_i32(), 1);
548-
assert_eq!(frame.stack(1).unwrap_i32(), 2);
549-
assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
550-
assert!(frame.done());
566+
.with_store(|mut store| {
567+
let frame = store.debug_exit_frames().next().unwrap();
568+
assert_eq!(
569+
frame
570+
.wasm_function_index_and_pc(&mut store)
571+
.unwrap()
572+
.unwrap()
573+
.0
574+
.as_u32(),
575+
0
576+
);
577+
assert_eq!(
578+
frame
579+
.wasm_function_index_and_pc(&mut store)
580+
.unwrap()
581+
.unwrap()
582+
.1,
583+
40
584+
);
585+
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
586+
assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
587+
assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
588+
assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
589+
assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
590+
assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
591+
let frame = frame.parent(&mut store).unwrap();
592+
assert!(frame.is_none());
551593
})
552594
.await?;
553595

554596
let event = debugger.run().await?;
555597
// At return point.
556598
assert!(matches!(event, DebugRunResult::Breakpoint));
557599
debugger
558-
.with_store(|store| {
559-
let mut frame = store.debug_frames().unwrap();
560-
assert!(!frame.done());
561-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().0.as_u32(), 0);
562-
assert_eq!(frame.wasm_function_index_and_pc().unwrap().1, 41);
563-
assert_eq!(frame.num_locals(), 2);
564-
assert_eq!(frame.num_stacks(), 1);
565-
assert_eq!(frame.local(0).unwrap_i32(), 1);
566-
assert_eq!(frame.local(1).unwrap_i32(), 2);
567-
assert_eq!(frame.stack(0).unwrap_i32(), 3);
568-
assert_eq!(frame.move_to_parent(), FrameParentResult::SameActivation);
569-
assert!(frame.done());
600+
.with_store(|mut store| {
601+
let frame = store.debug_exit_frames().next().unwrap();
602+
assert_eq!(
603+
frame
604+
.wasm_function_index_and_pc(&mut store)
605+
.unwrap()
606+
.unwrap()
607+
.0
608+
.as_u32(),
609+
0
610+
);
611+
assert_eq!(
612+
frame
613+
.wasm_function_index_and_pc(&mut store)
614+
.unwrap()
615+
.unwrap()
616+
.1,
617+
41
618+
);
619+
assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
620+
assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
621+
assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
622+
assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
623+
assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
624+
let frame = frame.parent(&mut store).unwrap();
625+
assert!(frame.is_none());
570626
})
571627
.await?;
572628

crates/environ/src/vmoffsets.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,17 @@ pub trait PtrSize {
181181
self.vmstore_context_fuel_consumed() + 8
182182
}
183183

184+
/// Return the offset of the `execution_version` field of
185+
/// `VMStoreContext`
186+
#[inline]
187+
fn vmstore_context_execution_version(&self) -> u8 {
188+
self.vmstore_context_epoch_deadline() + 8
189+
}
190+
184191
/// Return the offset of the `stack_limit` field of `VMStoreContext`
185192
#[inline]
186193
fn vmstore_context_stack_limit(&self) -> u8 {
187-
self.vmstore_context_epoch_deadline() + 8
194+
self.vmstore_context_execution_version() + 8
188195
}
189196

190197
/// Return the offset of the `gc_heap` field of `VMStoreContext`.

0 commit comments

Comments
 (0)