The transfer channel is the ABI, not a stopgap

plan.org now says every Flan function carries the transfer channel, that
uniformity is what keeps indirect calls and hot reload ABI-safe, and that a
later optimisation cannot change the ABI. spec-conditions.md §6 still read the
other way round - escape analysis deciding which functions are
transfer-transparent, with the rest paying nothing - which describes a
signature that depends on an analysis, and a cell cannot hold one of those.

So the analysis is demoted to what it can still honestly do: a function that
provably cannot transfer need not check the channel after a call and can pass
the pointer straight through. It may not drop the parameter. NEXT.md said the
same thing as a for-later note and now says it is settled.
This commit is contained in:
Joseph Ferano 2026-09-11 12:23:36 +07:00
parent 943561e765
commit 5980b5b60f
2 changed files with 16 additions and 8 deletions

12
NEXT.md
View File

@ -955,11 +955,13 @@ check and return early, which reuses the existing `return` path and with it
§5's defers. `Emit.signature` was already the one place a signature is spelled,
which is what made this a three-line change rather than a hunt.
**Every function is transfer-transparent**, release included. §6's escape
analysis is an optimisation, and in a dev build a cell can hold anything, so
the honest answer to "what can this call?" is "anything" — the same bargain as
the indirect call. Uniform also means redefinition acquires no new refusal
class.
**Every function is transfer-transparent**, release included — and that is the
ABI, not a stopgap. A cell holds a bare pointer, so the honest answer to "what
can this call?" is "anything"; the same bargain as the indirect call. §6 and
plan.org both now say the later optimisation may stop a function *checking* the
channel, or pass the pointer straight through, but may not drop the parameter —
a signature that depended on an analysis could not be reloaded into. Uniform
also means redefinition acquires no new refusal class.
**The transfer target is the restart frame's own address, not a clause id.**
This is a correction to what the previous note settled. A static id has to be

View File

@ -123,9 +123,15 @@ but it is not re-entrant: §5 runs defers *during* a transfer, so a defer that
signals and invokes a restart would start a second transfer over the first. A
per-frame slot nests correctly with no threads involved.
- The compiler marks a function transfer-transparent if it can call, directly or
indirectly, anything that may invoke a restart. Escape analysis narrows this
set; functions outside it pay nothing.
- **Every function carries the channel, and that is the ABI.** Uniformity is
what keeps an indirect call and a hot-reload cell safe: a cell holds a bare
pointer, so the honest answer to "what can this call?" is "anything", and a
signature that depended on the answer could not be reloaded into. An earlier
draft had escape analysis decide which functions are transfer-transparent;
that is now an *optimisation over what a function does with the channel*
a function that provably cannot transfer need not check it after a call, and
can pass the pointer straight through. It may not drop the parameter. See
plan.org, Hot reload.
- **Foreign frames cannot be crossed.** A restart transfer whose path passes
through a C frame (a raylib callback, an `extern` function calling back into
Flan) is a runtime error, not undefined behaviour. Handlers installed across an