v0.7: event-listener system (addEventListener/step/breakpoint/signal/exit)

- Native step/cont moved to __step_native/__cont_native; public step()/cont()
  defined in prelude.js wrap them and dispatch JS listeners on each stop.
- addEventListener/removeEventListener/clearListeners/listeners() registry on
  globalThis, persistent for the daemon lifetime (independent of ctx.reset()).
- Event object carries {state, prev, stop_reason} plus breakpoint_id/signal/
  exit_code as appropriate. Implements the SPEC 'Event Listeners' + 'Core Loop'.
- No-listener fast path keeps existing scripts zero-cost.
- examples/smarm_ctx_switch.js: worked session inspecting smarm's naked-asm
  context switch; README updated (status table, listener API, deferred list).
This commit is contained in:
llmdbg-dev
2026-05-28 05:50:09 +00:00
parent 98e3347b4c
commit ce96a98829
4 changed files with 276 additions and 10 deletions
+15 -6
View File
@@ -3,7 +3,8 @@
//!
//! Native globals installed here:
//! spawn(path, ...args) -> { pid, status }
//! step(n=1) / cont() -> stop-reason string
//! __step_native(n=1) / __cont_native() -> stop-reason string
//! (public step()/cont() are defined in prelude.js and fire listeners)
//! print(...args), ctx (persistent object)
//! state.pc, state.sp -> BigInt (live ptrace)
//! state.registers -> object of BigInts
@@ -181,7 +182,12 @@ fn install_globals<'js>(
globals.set("spawn", f)?;
}
// ---------- step(n=1) ----------
// ---------- __step_native(n=1) ----------
// Raw single-stepper. Steps up to n instructions, stopping early if the
// target dies. Returns the last stop-reason string. The public `step(n)`
// is defined in prelude.js: it loops one instruction at a time so it can
// fire 'step'/'breakpoint' listeners after each stop. We keep the native
// n-loop too, for the fast path when no listeners are registered.
{
let target = target.clone();
let f = Function::new(c.clone(), move |c: Ctx<'js>, n: rquickjs::function::Opt<i64>| -> rquickjs::Result<String> {
@@ -204,10 +210,13 @@ fn install_globals<'js>(
}
Ok(last.map(|r| r.tag()).unwrap_or_else(|| "step".into()))
})?;
globals.set("step", f)?;
globals.set("__step_native", f)?;
}
// ---------- continue() / cont() ----------
// ---------- __cont_native() ----------
// Raw continue. Resumes until the next stop (breakpoint, signal, exit).
// The public `cont()` in prelude.js wraps this to fire listeners on the
// resulting stop.
{
let target = target.clone();
let f = Function::new(c.clone(), move |c: Ctx<'js>| -> rquickjs::Result<String> {
@@ -219,8 +228,8 @@ fn install_globals<'js>(
}
})?;
// NOTE: `continue` is a reserved keyword in JS — `continue()` as an
// expression is a syntax error. We expose only `cont()` for v0.1.
globals.set("cont", f)?;
// expression is a syntax error. We expose only `cont()`.
globals.set("__cont_native", f)?;
}
// ---------- state ----------