atoll-dap is the live Debug Adapter Protocol implementation for Atoll. It
compiles a source program with debug safepoints, runs it in the WebAssembly
runtime, and maps source breakpoints and step commands to continuation-aware
invocation state.
Debug support is optional. Production builds can omit the debug-step feature
and its suspension machinery.
Session
The adapter speaks DAP over standard input and output. Its implemented session path includes:
initialize;launchand the current attach-style source session;setBreakpoints;configurationDone;threads;continue;next;stepIn;stepOut;pause;disconnectandterminate;- stack, scope, variable, and evaluate requests for a stopped context.
Launch and attach read an Atoll source file and compile an instrumented Program.
Execution waits for configurationDone, allowing an editor to install
breakpoints before the first invocation runs.
The adapter accepts stopOnEntry, optimization levels O0 through O2, and a
concurrent-invocation setting currently clamped to 1 through 64.
“Attach” here shares the source-compilation/session implementation. It should not be read as a promise that the adapter can inject into any arbitrary remote Atoll process.
Safepoints
With debug instrumentation enabled, generated code emits source-aware safepoints at line and operation boundaries. Internal compiler and builtin safepoints remain breakpoint- and step-inert so an editor does not stop in synthetic runtime work.
The compiler records source byte offsets in custom debug metadata. The runtime does not retain the source text. The adapter’s line map translates an editor’s requested line to source spans and installs byte-offset breakpoints.
This distinction handles Unicode and variable-width line endings without asking the runtime to reinterpret source files.
Breakpoints
A source breakpoint matches when execution crosses a mapped source safepoint. The debugger remembers the last crossing so a resumed invocation does not immediately stop at the same line without progressing. A loop or continuation resume that genuinely crosses the location again can stop again.
Breakpoints belong to a program-level table in PauseGate. Each Store reactor
registers separate per-reactor debug state. The table is shared; the stopped
invocation slot is not.
Stepping
At a stop, the adapter records the reference task depth and arms one of three modes:
| Command | Stop condition |
|---|---|
| step in | next eligible source safepoint |
| step over | next safepoint at depth less than or equal to the reference |
| step out | next safepoint shallower than the reference |
An enabled breakpoint can win before a step condition. Command epochs associate continue and step requests with the stop they intend to release. A late command from an older stop cannot accidentally skip a newer one.
Suspension
Normal runtime calls use synchronous Wasmtime entry. When stepping is armed, the debug runtime can execute the invocation on a suspendable coroutine backed by an 8 MiB green stack and save its Wasmtime async activation state. This allows a stop in the middle of a generated operation without making the Wasmtime Store itself cross-thread or asynchronous.
The coroutine is shelved in its owning Store. While stopped:
- unrelated Unit instances on that Store can continue;
- other Stores continue independently;
- the stopped Unit instance remains busy;
- its arena and frames remain live;
- eviction and migration of that instance are disabled.
For a singleton Unit, the runtime preserves the same logical instance rather than spawning a second state container to route around the stop.
Threads
DAP exposes an invocation as a debugger thread. Its ID includes the Store reactor identity so concurrent stops remain distinguishable across the fleet.
Atoll tasks within that invocation are identified by:
(TCB arena offset, debug stamp)The stamp prevents an old debugger reference from resolving to a newly allocated task that reused the same arena offset.
Commands target a known stopped thread. An unknown thread ID is an error; it is not interpreted as a broadcast continue.
Stacks
Stack capture combines:
- the current task’s last continuation;
- physical caller links in frame headers;
- compiler-emitted function and source metadata;
- the task tree for parent and child relationships.
Optimized builds may coalesce, remove, or lack source frames. The adapter returns the best valid stack supported by available metadata instead of inventing frames. O0 is the most predictable setting for detailed stepping.
Variables
Scopes and variables are decoded from compiler debug metadata and the stopped arena. Compound values receive expandable variable references; later DAP requests enumerate their fields or elements.
Every read is tied to the stopped invocation and current task identity. Resuming invalidates ephemeral expansion references, because continuation execution can move, drop, or reuse the underlying blocks.
Evaluation is metadata-driven and sandboxed in the stopped context. It is not a promise to compile and execute every possible Atoll expression with arbitrary side effects.
Pause
Pause requests arm a stop on the selected running invocation and take effect at the next eligible safepoint. Atoll’s cooperative model cannot guarantee an instantaneous stop while uninstrumented host code or a long non-yielding Wasm region is executing.
Wasmtime epoch preemption and debug pause are related scheduling mechanisms but have different outcomes: preemption yields fairly, while debug pause shelves a specific inspectable continuation.
Termination
Continuing releases the saved activation and resumes generated code. Terminating a suspended invocation can interrupt ownership operations, so the runtime poisons and evicts its Unit instance. It does not claim normal destructor completion.
Disconnect behavior follows the DAP request’s terminate choice. Editors should send an explicit termination intent rather than assuming that closing the transport safely resumes every stopped invocation.
Limits
Current limitations and scope boundaries include:
- debug instrumentation is opt-in and has runtime cost;
- a pause is observed only at an eligible safepoint;
- optimized stacks and variables depend on retained metadata;
- attach is not general remote-process injection;
- expression evaluation is narrower than the complete language;
- a paused instance cannot migrate or be evicted;
- the reusable stateless DAP library surface is less complete than the live adapter/session path.
These limits are part of the current implementation status, not future guarantees hidden behind the word “debugger.”