The snapshot was about two threads, and there are still two
Third on the list to delete, and nothing goes. "The compiler can read the stopped frame's memory directly, so copying it is ceremony" is the right instinct and the wrong diagnosis: the snapshot was never about two address spaces. A stopped program is not holding still. The break loop polls, flan_agent_poll runs whatever was delivered, and a C-x C-e thunk is arbitrary Flan that pushes and pops the one global restart list and the shadow stack while it runs. The compiler is a thread beside it either way. Restart names are copied because serving them off the live list hands the reader a pointer into a frame the loop's own poll may already have popped; a pointer is meaningful to the compiler now, and the frame it points into is no more alive for that. The fingerprints have a sharper reason still, already written down: the module a frame's description lives in can be unloaded before the comparison happens. The generation stamp is about nested breaks. A thunk this loop runs can error, push a break of its own, and reach chosen_ready first, claiming an index someone chose from the outer list. Depth cannot tell those apart because a resumed outer break and a new one reuse the number. One process changes none of that. So of the three things the merge was expected to make deletable, one was. The socket was transport. The result cap and the snapshot are concurrency, and were only mistaken for transport because the socket was in front of them. What the merge does unlock here is item 4, deliberately not touched: flan_agent_frame_slot already hands back an address, and in one process the compiler could read the value there instead of compiling a thunk to print it.
This commit is contained in:
parent
f63974142f
commit
a3baa0b25a
34
BUILT.md
34
BUILT.md
@ -1110,6 +1110,40 @@ So it is a render budget, not a wire size, and it was only ever mistaken for one
|
||||
Removing the bound is a redesign of the *read* — probe the length, allocate, re-read, validate the generation, retry —
|
||||
and it belongs with moving the read to a frame boundary, which is the seqlock's own decision and its own lane.
|
||||
|
||||
#### The break snapshot is not marshalling either, and all of it stays
|
||||
|
||||
Third on the list, and the answer is no, with nothing left over. "The compiler can read the stopped frame's memory
|
||||
directly, so copying it is now ceremony" is the right instinct and the wrong diagnosis: **the snapshot was never about
|
||||
two address spaces. It is about two threads, and there are still two.**
|
||||
|
||||
A stopped program is not holding still. The break loop polls, `flan_agent_poll` runs whatever the compiler delivered,
|
||||
and a `C-x C-e` thunk is arbitrary Flan — it pushes and pops the one global restart list and the shadow stack while it
|
||||
runs. The compiler is a thread beside it either way. So every copy in `snap_push` has the same justification it had
|
||||
before:
|
||||
|
||||
- **Restart names** are copied because serving them off the live list hands the reader a pointer into a frame the
|
||||
break loop's own poll may already have popped. A pointer is meaningful to the compiler now; the frame it points into
|
||||
is no more alive for that.
|
||||
- **Frame names and locations** are copied from the held-still stack for the same reason, and the *fingerprints* have a
|
||||
sharper one already written down: the module a frame's description lives in can be unloaded once a replacement is
|
||||
installed, and the comparison happens after that.
|
||||
- **The generation stamp** (`snap_gen`, `chosen_gen`) is about nested breaks, not about processes. A thunk this loop
|
||||
runs can error, push a break of its own, and reach `chosen_ready` first — claiming an index someone chose from the
|
||||
outer list. Depth cannot tell those apart, because an outer break resuming and a new one starting reuse the number. A
|
||||
generation can. One process changes nothing about that.
|
||||
|
||||
The fixed caps go with it: the snapshot is taken **on the game thread**, so it cannot allocate, which is why
|
||||
`SNAP_MAX`, `FRAME_MAX` and `FRAME_TEXT` are literals and why truncation is reported rather than avoided.
|
||||
|
||||
What the merge *does* unlock here is one thing and it is the next item: `flan_agent_frame_slot` already hands back the
|
||||
address of a slot, and in one process the compiler could read the value at that address instead of compiling a render
|
||||
thunk to print it. That is the render-thunk-per-inspection redesign — a different mechanism rather than a deletion, and
|
||||
what makes "the inspector can retain a value" reachable. It is deliberately not done here.
|
||||
|
||||
**So of the three things the merge was expected to make deletable, one was.** The socket was transport and is gone; the
|
||||
result cap and the snapshot are both concurrency, and they were only ever mistaken for transport because the socket was
|
||||
the thing in front of them.
|
||||
|
||||
### The Emacs client
|
||||
|
||||
`emacs/flan-mode.el` derives from `prog-mode` with `lisp-mode`'s syntax table, which is most of the work: Flan is
|
||||
|
||||
10
NEXT.md
10
NEXT.md
@ -531,6 +531,9 @@ socket did not move. See "One process" in [`BUILT.md`](BUILT.md).
|
||||
|
||||
What it reopened is now being deleted one piece at a time, each with its own green run:
|
||||
|
||||
**One of the three was transport. The other two were concurrency, and were only mistaken for transport because the
|
||||
socket was in front of them.**
|
||||
|
||||
1. ~~**The internal socket, the line protocol, and `Dev.deliver`/`result`/`ask`.**~~ **Done** — a delivery is a direct
|
||||
call into the agent's verb table. Measured: the transport was ~50µs of a 23ms redefinition, so the end-to-end
|
||||
number did not move. Code generation is 19 of the 22 milliseconds, which is the number any backend argument has to
|
||||
@ -541,8 +544,11 @@ What it reopened is now being deleted one piece at a time, each with its own gre
|
||||
calling `realloc`, and it would break the seqlock — which is a protocol about torn contents and assumes the address
|
||||
it copies from does not move. Removing it is a redesign of the read, and belongs with moving the read to a frame
|
||||
boundary.
|
||||
3. **`flan_agent.c`'s snapshot copying and generation stamping.** The compiler can read the stopped frame's memory
|
||||
directly.
|
||||
3. ~~**`flan_agent.c`'s snapshot copying and generation stamping.**~~ **Refused, in full** — it was never about two
|
||||
address spaces, it is about two threads, and there are still two. The break loop polls, a thunk it runs is
|
||||
arbitrary Flan that pushes and pops the live restart list and the shadow stack, and the generation stamp is what
|
||||
keeps a nested break from claiming a choice made against the outer one. A pointer is meaningful to the compiler
|
||||
now; the frame it points into is no more alive for that.
|
||||
4. **The render-thunk-per-inspection design for locals and globals.** A redesign rather than a deletion, and its own
|
||||
lane: it is what unblocks "the inspector can retain a value".
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user