diff --git a/spec-conditions.md b/spec-conditions.md index 9c6902e..d0bcc3a 100644 --- a/spec-conditions.md +++ b/spec-conditions.md @@ -91,10 +91,37 @@ Invoking a restart transfers control outward past zero or more frames. ## 6. Crossing compiler-generated frames Transfer is lowered **explicitly** — result propagation plus branch targets — not -via platform unwinding, so that native and wasm32 behave identically. That means +via platform unwinding. Three reasons, none of them about dev builds: + +- **wasm32 cannot unwind** without the exceptions proposal, so a release export + would not work at all. +- **Native unwinding is not cheaper and is much less legible.** Every call + becomes an `invoke` with a landing pad, plus a personality function and an + exception table; a `cmp`/`jne` after a call reads like ordinary code and that + matters once there is a disassembler. +- **One mechanism is one thing to get right.** The acceptance table runs the + same programs on both targets and compares a hash, and that hash is the only + tripwire two implementations would have. + +That means every function on the path between the invoke and the target must be -transfer-aware: it returns a discriminated "normal value / transferring to frame -N" result, checks it after each call, and forwards. +transfer-aware: it carries a "normal / transferring to frame N" channel, checks +it after each call, and forwards. + +**The channel is an out-parameter**, a `ptr` appended to the signature, and not +a discriminated return value. The return type then stays what the source says, +which keeps a function's disassembly readable as the release one plus a guard; +a discriminated return would repack every `ret`, turn an aggregate return into +an `sret` call, and nest awkwardly inside the discriminated return `(Option T)` +already is. One pointer threads down the whole chain, so a callee writes the +target into its caller's own slot and each frame only has to check and return +early — which reuses the existing `return` path, and therefore §5's defers, for +free. + +A single global slot would be more legible still — no signature change at all — +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