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.
3.9 KiB
3.9 KiB
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 zeronm | grepfor 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()vsstep(). The distinction the docs emphasise is exactly the axis this task needed: stepping into the accessors (164 insns of debug-buildLocalKeyceremony) vs over them is the difference between readable and unreadable, andstep()through the stack-swappingretis 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.pcare treacherous when helpers sit adjacent to the function under test.set_actor_splived atsched+0x50, inside my+0x60window, 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 flatstep()stream to count by eye. The missing primitive is symbol extent (start ≤ pc < start+size), not symbol address. - The
push rbxconsumed-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
objdumpto confirm the compiler spills live XMM around the switch call. For a tool whose reason for existing is x86-64 context-switch correctness, watchingxmm0..15across 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)
- Symbol-extent queries —
symStart(pc)/symSize(pc), or aninFn(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 nativesymlayer exposes extents. countTo(stop, {over})— step until an address/predicate and return just the instruction count, instead of hand-rollinglet 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.- XMM/SSE register reads —
state.xmm[i]orreg("xmm0"). Needs Rust-side support (ptraceGETFPREGS/xsave). In scope for this tool's purpose. - Prologue-aware breakpoints — a mode (or
bp.setAfterPrologue()) that removes the "first instruction eaten" caveat rather than documenting around it. hexdump(addr, n)— peek at raw memory, e.g. a freshly-built initial actor stack frominit_actor_stack. Pure-JS over areadMemprimitive. 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.