Why a handler frame holds a body address
plan.org now says a top-level function value is a stable trampoline over the indirection cell and never the address of a particular body, so that a stored callback observes a redefinition. A pushed handler frame breaks that rule and should: it is not a Fn value, nothing in the language can name it, and it is live only for the duration of the handler-bind body - so a reload landing while it is on the stack finds the clause it pushed still valid, which is the whole of old code is never unloaded. The consequence worth knowing is that a handler already on the stack does not pick up a redefinition of its own clause; the next entry to the handler-bind pushes the new one. Recorded at the store in emit.ml and in NEXT.md, because when Fn values arrive this is the one place that stores a body address on purpose and must not be swept up with the rest.
This commit is contained in:
parent
c322ef60bf
commit
f55ec0a0b8
12
NEXT.md
12
NEXT.md
@ -919,6 +919,18 @@ Three decisions worth keeping:
|
||||
- **A clause is lifted into a function of its own.** A handler runs from
|
||||
wherever the signal was, so it cannot be a branch in the function that wrote
|
||||
it.
|
||||
- **A pushed handler frame holds the clause's body address, not a cell.** This
|
||||
is a deliberate divergence from plan.org's rule that a top-level function
|
||||
value is a stable trampoline over the cell and never the address of a
|
||||
particular body. A handler frame is not a `Fn` value — nothing in the
|
||||
language can name it — and it is live only for the duration of the
|
||||
`handler-bind` body, so a reload landing while it is on the stack finds the
|
||||
clause it pushed still valid, which is exactly the "old code is never
|
||||
unloaded" guarantee. The consequence to know: a handler already on the stack
|
||||
does *not* observe a redefinition of its own clause; the next entry to the
|
||||
`handler-bind` pushes the new one. When `Fn` values arrive, this is the one
|
||||
place that stores a body address on purpose and must not be swept up with
|
||||
them.
|
||||
|
||||
Which gives the two refusals, both by the house rule rather than by accident:
|
||||
|
||||
|
||||
@ -574,6 +574,12 @@ and emit_handled f frames body =
|
||||
let fp = fresh f in
|
||||
ins f "%s = getelementptr inbounds %%handler, ptr %s, i32 0, i32 2"
|
||||
fp slot;
|
||||
(* The clause's body address, deliberately, and not a cell load:
|
||||
plan.org makes a top-level function value a stable trampoline over
|
||||
its cell, but a handler frame is not one — nothing can name it, and
|
||||
it lives only for this body. A reload landing while it is on the
|
||||
stack finds what it pushed still valid, which is what "old code is
|
||||
never unloaded" means. See NEXT.md, conditions step 1. *)
|
||||
ins f "store ptr %s, ptr %s" (fname h.Tast.hfn) fp;
|
||||
ins f "call void @flan_handler_push(ptr %s)" slot;
|
||||
slot)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user