diff --git a/plan.org b/plan.org index 51d0994..49ef49d 100644 --- a/plan.org +++ b/plan.org @@ -266,8 +266,9 @@ Compile-time overload resolution on argument types. No precedence lists, no meth combination, no MOP, no runtime dispatch table — see Types. ** Macros -- Needs a ~&env~ equivalent: macros must see lexical environment (names of - locals in scope). Required by the step debugger. Decide now, painful to retrofit. +- ~&env~ equivalent: macros seeing the lexical environment (names of locals in + scope). No longer urgent — its "decide now" status came from the + instrumentation-based step debugger, which is cut (see Tooling). Milestone 5. - Hygiene model: open decision. * Host language @@ -542,10 +543,66 @@ Clojure-coupled) — reference it. ~clojure-mode~-derived major mode, overlay rendering, hydra for stepping bindings (~transient~ is the maintained alternative). -** Step debugger -Instrumentation-based, like CIDER's — macroexpansion wraps subforms with a -breakpoint that messages the editor and blocks. No native debug info needed. -Limit: only instrumented code. +** Debugging: two layers, two protocols +Do not extend nREPL with registers or memory. The protocol is the easy part — +adding an op is trivial; /implementing/ it means becoming a debugger (ptrace, +trap handling, frame unwinding), duplicating an enormous existing project. + +| Capability | DAP (lldb-dap + dape) | nREPL | +|------------------------------------------+-----------------------+-------| +| Registers, memory, native stack | yes | never | +| Breakpoints, watchpoints, stepping | yes | — | +| Eval Flan expressions with real semantics | no | yes | +| Redefine a function and continue | no | yes | +| Invoke a restart | no | yes | +| Pause /before/ unwinding | no | yes | + +Neither covers the other's column, so this is not a choice between them. + +*DAP is nearly free.* No debug adapter is written: emit DWARF from the LLVM +backend and point ~lldb-dap~ at the binary; dape speaks to that. lldb and gdb +both ship DAP interfaces already. + +This is where /no object headers/ pays off a second time. Flan structs *are* C +structs — real machine types, no tag words, no boxing — so DWARF describes them +with no impedance mismatch and lldb shows a ~Cursor~ correctly with no plugin +and no formatters. + +*** Reload and DAP +Mostly automatic. lldb watches the loader rendezvous structure, so a ~dlopen~'d +generation is noticed and its DWARF read; and lldb keeps breakpoints as +unresolved /specs/, re-resolving them against newly loaded modules, so a +breakpoint on ~sand.flan:85~ binds to the new function by itself. + +*The gotcha is our own never-unload rule.* After N redefinitions there are N +copies of a function, each with DWARF claiming ~sand.flan:85~, so one breakpoint +resolves to N locations and stops in stale generations. Needs glue, and it is +small: give each generation a distinct source identity in its DWARF +(~sand.flan#7~), and have the reload agent disable breakpoint locations in +superseded modules on each reload. This is the one piece that cannot just be +wired up and left alone. + +Not available at any price: if execution is paused /inside/ the old function, +redefinition does not move it — register allocation belongs to the old +compilation (see "What redefinition cannot do"). The current frame finishes in +the old code; the next call enters the new one. Also, dape reads source from +disk, so editing further after a generation was compiled drifts the highlighted +line from what is executing. + +*** Cut: the instrumentation-based step debugger +Previously planned as CIDER's model — macroexpansion wrapping subforms with +breakpoints. Dropped. It is the most expensive piece of milestone 8 +(instrumentation, an overlay protocol, ~&env~, a bespoke Emacs client) and +DAP+DWARF covers most of its use. What DAP cannot cover — pausing before the +stack unwinds with restarts available — comes from ~handler-bind~ over nREPL. + +Consequences: *~&env~ loses its urgency* (its "decide now" status came from the +step debugger requiring it — now an ordinary milestone-5 decision), and *the +shadow stack shrinks* to serving only nREPL backtraces and restart enumeration. + +Forcing the live-programming workflow into DAP would repeat the CIDER mistake in +the other direction: DAP's model is stop/step/inspect, with no vocabulary for +"invoke ~use-placeholder~ and resume". That stays on nREPL. ** Pause on exception Better than CIDER's, because ~handler-bind~ has not unwound the stack. Dev-mode