docs: field notes from smarm context-switch session (v0.8)
Friction-and-wishlist from one real profiling/verification session, not a
defect report. Highlights: demangling and trace({over,capture}) worked well;
pc-range checks are treacherous when helpers sit adjacent to the function
under test (symbol extent, not address, is the missing primitive); no
float/vector register access forced the XMM verification out to objdump.
Wishlist, JS-expressible first: symStart/symSize (or inFn), countTo, hexdump
— candidates for a v0.9 prelude with no Rust changes. xmm reads and
prologue-aware breakpoints need native support.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Field notes — smarm context-switch session (v0.8)
|
||||
|
||||
Notes from one real session using llmdbg v0.8 to profile and verify smarm's
|
||||
naked-asm context switch (the TLS-accessor perf question and the XMM-not-saved
|
||||
correctness question). Recorded as friction-and-wishlist, not a defect report —
|
||||
nothing here blocked the work; every rough edge had a workaround.
|
||||
|
||||
## What worked
|
||||
|
||||
- **Demangling.** `addr("switch_to_scheduler")` resolving directly meant zero
|
||||
`nm | grep` for mangled names whose hash changes every rebuild. Biggest
|
||||
single quality-of-life win.
|
||||
- **`trace({over, range, capture})`.** The capture closure returning a
|
||||
structured record per step (`{pc, insn, len, is_call}`) and letting the
|
||||
caller format it is the right shape — produced a clean 16-line shim trace in
|
||||
one pass.
|
||||
- **`next()` vs `step()`.** The distinction the docs emphasise is exactly the
|
||||
axis this task needed: stepping *into* the accessors (164 insns of debug-build
|
||||
`LocalKey` ceremony) vs *over* them is the difference between readable and
|
||||
unreadable, and `step()` *through* the stack-swapping `ret` is what let me
|
||||
walk the switch itself.
|
||||
- **The worked example** (`examples/smarm_ctx_switch_next.js`) was close enough
|
||||
to the real task to adapt directly. Good investment.
|
||||
|
||||
## What bit me
|
||||
|
||||
- **Range checks on `state.pc` are treacherous when helpers sit adjacent to the
|
||||
function under test.** `set_actor_sp` lived at `sched+0x50`, *inside* my
|
||||
`+0x60` window, so a "stop when pc returns to the shim" loop terminated the
|
||||
instant it entered the accessor. Cost me two attempts before I abandoned
|
||||
range-based exit detection and dumped a flat `step()` stream to count by eye.
|
||||
The missing primitive is symbol *extent* (start ≤ pc < start+size), not
|
||||
symbol *address*.
|
||||
- **The `push rbx` consumed-by-breakpoint quirk** is correctly documented, but
|
||||
I had to re-apply "the first observed instruction is the second one" mentally
|
||||
at every session. A documented footgun is still a footgun.
|
||||
- **No float/vector register access.** The XMM-not-saved verification had to
|
||||
leave llmdbg entirely and fall back to `objdump` to confirm the compiler
|
||||
spills live XMM around the switch call. For a tool whose reason for existing
|
||||
is x86-64 context-switch correctness, watching `xmm0..15` across a switch
|
||||
feels squarely in scope — it would have turned a static inference into a
|
||||
dynamic proof (XMM dead at switch entry, live again after reload).
|
||||
|
||||
## Wishlist (rough priority order)
|
||||
|
||||
1. **Symbol-extent queries** — `symStart(pc)` / `symSize(pc)`, or an
|
||||
`inFn(pc, "name")` membership test. Fixes the range-check footgun directly.
|
||||
This is the highest-value add for asm work, where "am I still inside this
|
||||
function" is a constant question. Pure-JS-expressible if the native `sym`
|
||||
layer exposes extents.
|
||||
2. **`countTo(stop, {over})`** — step until an address/predicate and return
|
||||
just the instruction count, instead of hand-rolling `let n=0; while(...){
|
||||
step(); n++ }` and getting the boundary wrong. Counting per-call instruction
|
||||
cost was the core measurement of this task and had no first-class support.
|
||||
Pure-JS prelude helper.
|
||||
3. **XMM/SSE register reads** — `state.xmm[i]` or `reg("xmm0")`. Needs
|
||||
Rust-side support (ptrace `GETFPREGS`/`xsave`). In scope for this tool's
|
||||
purpose.
|
||||
4. **Prologue-aware breakpoints** — a mode (or `bp.setAfterPrologue()`) that
|
||||
removes the "first instruction eaten" caveat rather than documenting around
|
||||
it.
|
||||
5. **`hexdump(addr, n)`** — peek at raw memory, e.g. a freshly-built initial
|
||||
actor stack from `init_actor_stack`. Pure-JS over a `readMem` primitive.
|
||||
Nice-to-have; didn't strictly need it this round.
|
||||
|
||||
## Suggested next step
|
||||
|
||||
Items 1, 2, and 5 are pure-JS and fit the SPEC ethos ("everything expressible
|
||||
in JS lives in JS") — they belong in `src/prelude.js` and would be a clean v0.9
|
||||
with no Rust changes (assuming `sym` extents and a `readMem` primitive are
|
||||
reachable from JS). Items 3 and 4 need native support and are larger.
|
||||
Reference in New Issue
Block a user