Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit f09fd1c

Browse files
committed
fix save/restore of PC/FP/SP/limit on fiber switch
This fixes stack backtrace panics/SIGSEGVs due to broken `CallThreadState`/`VMStoreContext` PC/FP/SP/limit info which wasn't been updated correctly for nested fiber invocations. Thanks to Alex for walking me through the code and sketching the solution. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 3231c1d commit f09fd1c

2 files changed

Lines changed: 42 additions & 16 deletions

File tree

crates/wasmtime/src/runtime/component/concurrent.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,7 +1398,7 @@ impl ComponentInstance {
13981398
break;
13991399
} else {
14001400
unsafe {
1401-
suspend_fiber(fiber.suspend, fiber.stack_limit, None)?;
1401+
suspend_fiber(fiber.suspend, None)?;
14021402
};
14031403
}
14041404
}
@@ -3310,7 +3310,6 @@ pub(crate) struct AsyncCx {
33103310
Option<*mut dyn VMStore>,
33113311
(Option<*mut dyn VMStore>, Result<()>),
33123312
>,
3313-
current_stack_limit: *mut usize,
33143313
current_poll_cx: *mut PollContext,
33153314
track_pkey_context_switch: bool,
33163315
}
@@ -3327,7 +3326,6 @@ impl AsyncCx {
33273326
} else {
33283327
Some(Self {
33293328
current_suspend: store.concurrent_async_state().current_suspend.get(),
3330-
current_stack_limit: store.vm_store_context().stack_limit.get(),
33313329
current_poll_cx,
33323330
track_pkey_context_switch: store.has_pkey(),
33333331
})
@@ -3365,7 +3363,7 @@ impl AsyncCx {
33653363
} else {
33663364
ProtectionMask::all()
33673365
};
3368-
let store = suspend_fiber(self.current_suspend, self.current_stack_limit, store);
3366+
let store = suspend_fiber(self.current_suspend, store);
33693367
if self.track_pkey_context_switch {
33703368
mpk::allow(previous_mask);
33713369
}
@@ -3550,7 +3548,6 @@ struct StoreFiber<'a> {
35503548
Option<*mut dyn VMStore>,
35513549
(Option<*mut dyn VMStore>, Result<()>),
35523550
>,
3553-
stack_limit: *mut usize,
35543551
instance: Option<RuntimeComponentInstanceIndex>,
35553552
}
35563553

@@ -3607,7 +3604,6 @@ unsafe fn make_fiber<'a>(
36073604
.concurrent_async_state()
36083605
.current_suspend
36093606
.get(),
3610-
stack_limit: (*store).vm_store_context().stack_limit.get(),
36113607
instance,
36123608
})
36133609
}
@@ -3631,7 +3627,6 @@ unsafe fn resume_fiber_raw<'a>(
36313627
}
36323628

36333629
let _reset_suspend = Reset((*fiber).suspend, *(*fiber).suspend);
3634-
let _reset_stack_limit = Reset((*fiber).stack_limit, *(*fiber).stack_limit);
36353630
let state = Some((*fiber).state.take().unwrap().push());
36363631
let restore = Restore { fiber, state };
36373632
(*restore.fiber)
@@ -3667,11 +3662,9 @@ unsafe fn suspend_fiber(
36673662
Option<*mut dyn VMStore>,
36683663
(Option<*mut dyn VMStore>, Result<()>),
36693664
>,
3670-
stack_limit: *mut usize,
36713665
store: Option<*mut dyn VMStore>,
36723666
) -> Result<Option<*mut dyn VMStore>> {
36733667
let _reset_suspend = Reset(suspend, *suspend);
3674-
let _reset_stack_limit = Reset(stack_limit, *stack_limit);
36753668
assert!(!(*suspend).is_null());
36763669
let (store, result) = (**suspend).suspend(store);
36773670
result?;

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -439,17 +439,19 @@ mod call_thread_state {
439439
#[cfg(all(has_native_signals, unix))]
440440
pub(crate) async_guard_range: Range<*mut u8>,
441441

442-
// The values of `VMStoreContext::last_wasm_{exit_{pc,fp},entry_sp}` for
442+
// The values of
443+
// `VMStoreContext::{stack_limit,last_wasm_{exit_{pc,fp},entry_sp}}` for
443444
// the *previous* `CallThreadState` for this same store/limits. Our
444445
// *current* last wasm PC/FP/SP are saved in `self.vm_store_context`. We
445446
// save a copy of the old registers here because the `VMStoreContext`
446447
// typically doesn't change across nested calls into Wasm (i.e. they are
447448
// typically calls back into the same store and `self.vm_store_context
448449
// == self.prev.vm_store_context`) and we must to maintain the list of
449450
// contiguous-Wasm-frames stack regions for backtracing purposes.
450-
old_last_wasm_exit_fp: Cell<usize>,
451-
old_last_wasm_exit_pc: Cell<usize>,
452-
old_last_wasm_entry_fp: Cell<usize>,
451+
pub(super) old_stack_limit: Cell<usize>,
452+
pub(super) old_last_wasm_exit_fp: Cell<usize>,
453+
pub(super) old_last_wasm_exit_pc: Cell<usize>,
454+
pub(super) old_last_wasm_entry_fp: Cell<usize>,
453455
}
454456

455457
impl Drop for CallThreadState {
@@ -460,6 +462,7 @@ mod call_thread_state {
460462

461463
unsafe {
462464
let cx = self.vm_store_context.as_ref();
465+
*cx.stack_limit.get() = self.old_stack_limit.get();
463466
*cx.last_wasm_exit_fp.get() = self.old_last_wasm_exit_fp.get();
464467
*cx.last_wasm_exit_pc.get() = self.old_last_wasm_exit_pc.get();
465468
*cx.last_wasm_entry_fp.get() = self.old_last_wasm_entry_fp.get();
@@ -500,6 +503,7 @@ mod call_thread_state {
500503
#[cfg(all(has_native_signals, unix))]
501504
async_guard_range,
502505
prev: Cell::new(ptr::null()),
506+
old_stack_limit: Cell::new(unsafe { *vm_store_context.as_ref().stack_limit.get() }),
503507
old_last_wasm_exit_fp: Cell::new(unsafe {
504508
*vm_store_context.as_ref().last_wasm_exit_fp.get()
505509
}),
@@ -825,6 +829,20 @@ pub(crate) mod tls {
825829

826830
pub use raw::initialize as tls_eager_initialize;
827831

832+
impl CallThreadState {
833+
unsafe fn swap(&self) {
834+
unsafe fn swap(a: &core::cell::UnsafeCell<usize>, b: &core::cell::Cell<usize>) {
835+
*a.get() = b.replace(*a.get());
836+
}
837+
838+
let cx = self.vm_store_context.as_ref();
839+
swap(&cx.stack_limit, &self.old_stack_limit);
840+
swap(&cx.last_wasm_exit_fp, &self.old_last_wasm_exit_fp);
841+
swap(&cx.last_wasm_exit_pc, &self.old_last_wasm_exit_pc);
842+
swap(&cx.last_wasm_entry_fp, &self.old_last_wasm_entry_fp);
843+
}
844+
}
845+
828846
/// Opaque state used to persist the state of the `CallThreadState`
829847
/// activations associated with a fiber stack that's used as part of an
830848
/// async wasm call.
@@ -873,6 +891,13 @@ pub(crate) mod tls {
873891
// list as stored in the state of this current thread.
874892
let ret = PreviousAsyncWasmCallState { state: raw::get() };
875893
let mut ptr = self.state;
894+
895+
if let Some(state) = ptr.as_ref() {
896+
// Swap the current PC/FP/SP/etc. with those in the oldest
897+
// activation so we can restore them later in
898+
// `PreviousAsyncWasmCallState::restore`
899+
state.swap();
900+
}
876901
while let Some(state) = ptr.as_ref() {
877902
ptr = state.prev.replace(core::ptr::null_mut());
878903
state.push();
@@ -932,14 +957,22 @@ pub(crate) mod tls {
932957
// this loop is finished.
933958
let ptr = raw::get();
934959
if ptr == thread_head {
960+
if let Some(state) = ret.state.as_ref() {
961+
// Swap the current PC/FP/SP/etc. with those in the
962+
// oldest activation, which reverses what we did in
963+
// `AsyncWasmCallState::push`.
964+
state.swap();
965+
}
966+
935967
break ret;
936968
}
937969

938970
// Pop this activation from the current thread's TLS state, and
939971
// then afterwards push it onto our own linked list within this
940-
// `AsyncWasmCallState`. Note that the linked list in `AsyncWasmCallState` is stored
941-
// in reverse order so a subsequent `push` later on pushes
942-
// everything in the right order.
972+
// `AsyncWasmCallState`. Note that the linked list in
973+
// `AsyncWasmCallState` is stored in reverse order so a
974+
// subsequent `push` later on pushes everything in the right
975+
// order.
943976
(*ptr).pop();
944977
if let Some(state) = ret.state.as_ref() {
945978
(*ptr).prev.set(state);

0 commit comments

Comments
 (0)