From f531bf122e416b8512fb2cc1994e96be223f4631 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Wed, 25 Feb 2026 00:13:07 -0800 Subject: [PATCH] Debugging: set vmctx slot before top-of-function epoch yield point. Epoch yields emit debug events, and the debug event handler can walk the stack and look at the instance associated with each frame, which requires `vmctx`. We weren't setting the `vmctx` slot until after the epoch check in the function preamble, exposing a null or uninitialized slot to the accessor. This PR fixes that by hoisting the initialization to the very top of the preamble. --- crates/cranelift/src/func_environ.rs | 4 +-- tests/all/debug.rs | 48 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index bfb30c1d5320..96bc9126c3f7 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -3865,6 +3865,8 @@ impl FuncEnvironment<'_> { self.conditionally_trap(builder, overflow, ir::TrapCode::STACK_OVERFLOW); } + self.update_state_slot_vmctx(builder); + // Additionally we initialize `fuel_var` if it will get used. if self.tunables.consume_fuel { self.fuel_function_entry(builder); @@ -3885,8 +3887,6 @@ impl FuncEnvironment<'_> { } } - self.update_state_slot_vmctx(builder); - Ok(()) } diff --git a/tests/all/debug.rs b/tests/all/debug.rs index f52373073e36..e8f5cacdcedb 100644 --- a/tests/all/debug.rs +++ b/tests/all/debug.rs @@ -1372,3 +1372,51 @@ async fn single_step_before_instantiation() -> wasmtime::Result<()> { Ok(()) } + +#[tokio::test] +#[cfg_attr(miri, ignore)] +async fn early_epoch_yield_still_has_vmctx() -> wasmtime::Result<()> { + let _ = env_logger::try_init(); + + let mut config = Config::default(); + config.guest_debug(true); + config.epoch_interruption(true); + let engine = Engine::new(&config)?; + let module = Module::new( + &engine, + r#" + (module + (func (export "main") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add)) + "#, + )?; + let mut store = Store::new(&engine, ()); + store.set_epoch_deadline(1); + store.epoch_deadline_async_yield_and_update(1); + engine.increment_epoch(); + + #[derive(Clone)] + struct H; + impl DebugHandler for H { + type Data = (); + async fn handle(&self, mut store: StoreContextMut<'_, ()>, _event: DebugEvent<'_>) { + // Ensure we can access the instance (which accesses the + // vmctx slot in the frame's debug info). + let frame = store.debug_exit_frames().next().unwrap(); + let _instance = frame.instance(&mut store); + } + } + + store.set_debug_handler(H); + + let instance = Instance::new_async(&mut store, &module, &[]).await?; + let func = instance.get_func(&mut store, "main").unwrap(); + let mut results = [Val::I32(0)]; + func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results) + .await?; + assert_eq!(results[0].unwrap_i32(), 3); + + Ok(()) +}