page: say when a trap unwinds a provider's guest calls, and which resumes the Worker set before a wild branch

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-08 18:59:32 -04:00
1 parent 7e23c82a56
commit f823644fce
2 files changed
+36

No files matched your search

+19
View File
@@ -2790,6 +2790,8 @@ export class Gex {
* exception, so code the provider publishes at run time (its JIT, which imports it)
* can catch it with `try_table`, and `provider_try` takes it when nothing did. */
#provTag = null;
/** Traps seen unwinding provider guest calls (reported up to 16). */
#provLeaks = 0;
#providerTag() {
this.#provTag ??= new WebAssembly.Tag({ parameters: ['i32'] });
return this.#provTag;
@@ -2810,8 +2812,16 @@ export class Gex {
ftab.get(f)(arg);
return 0;
} catch (err) {
const open = e.gex_libprov_guest_calls();
e.gex_libprov_guest_calls_set(calls);
if (err instanceof WebAssembly.Exception && err.is(tag)) return err.getArg(tag, 0);
// Anything else that unwinds a provider's guest calls abandons
// frames the guest will return into (their return addresses stay
// on its stack): say which trap, where it resumes, and from where.
if (open > calls && (this.#provLeaks = (this.#provLeaks ?? 0) + 1) <= 16) {
console.error(`[gex] a trap left through ${open - calls} provider guest call(s): ${err?.message ?? err}; ` +
`resume ${BigInt.asUintN(64, BigInt(e.gex_resume_pc())).toString(16)}\n${err?.stack ?? ''}`);
}
throw err;
}
},
@@ -3644,6 +3654,15 @@ export class Gex {
// alone it slid into the guest's stack and overwrote saved registers.
this.#shim.exports.__stack_pointer.value = addrVal(this.#workerStack());
const e = this.#shim.exports;
// A trap that reached the run loop with a provider's guest call
// still open abandoned the provider's frames (see provider_try).
if ((e.gex_libprov_guest_calls?.() ?? 0) > 0) {
if ((this.#provLeaks = (this.#provLeaks ?? 0) + 1) <= 16) {
console.error(`[gex] a trap reached the run loop with ${e.gex_libprov_guest_calls()} provider guest call(s) open: ${err?.message ?? err}; ` +
`resume ${BigInt.asUintN(64, BigInt(e.gex_resume_pc())).toString(16)}\n${err?.stack ?? ''}`);
}
e.gex_libprov_guest_calls_set(0);
}
// A slot the shim wants written before the guest goes back to it: its
// module is instantiated with passive segments, so the code is present
// and simply not in the table. Filling here costs one segment; at
+17
View File
@@ -7188,9 +7188,22 @@ fn resume() -> Option<u64> {
RESUME.with(|c| c.get())
}
thread_local! {
/// This Worker's last resumes set (the shim's line that set it, pc), for
/// the wild-branch report: who sent the guest where it went.
static RESUME_RING: std::cell::RefCell<Vec<(u32, u64)>> = const { std::cell::RefCell::new(Vec::new()) };
}
#[track_caller]
fn set_resume(v: u64) {
RESUME.with(|c| c.set(Some(v)));
RESUME_RING.with(|r| {
let mut r = r.borrow_mut();
if r.len() == 16 {
r.remove(0);
}
r.push((std::panic::Location::caller().line(), v));
});
// GEX_NT_RESUME_LOG: each (call site, pc) a resume was set from, once:
// where the guest leaves wasm and comes back (an AOT-only run needs a
// unit or region entry at every such pc).
@@ -11001,6 +11014,10 @@ pub extern "C" fn gex_dispatch_miss(_ctx: i32, pc: i64) -> i32 {
cgen::callouts_recent(),
cgen::libprov::guest_call_open()
));
warn_line(&format!(
"[gex] last resumes set (lib.rs line, pc): {:x?}\n",
RESUME_RING.with(|r| r.borrow().iter().map(|&(l, v)| (l, v)).collect::<Vec<_>>())
));
}
set_exit(h, 128 + 11);
});