flan/spike/x86/p6-transfer.flan
Joseph Ferano e0e5c1e645 The whole corpus goes through the hand-written backend
The transfer exit returned whatever the return temporary held where
emit.ml returns zero. Meaningless to a caller -- its guard sees the
channel set and never looks -- but main is a caller with no guard, and
what it finds in rax is the process exit status.

The survey compares stderr as well now, which is where every message
the new machinery produces goes: the bounds and slice errors, the
three restart refusals, the transfer failure. Each carries a location
this backend emits by hand as a .rodata label and a length in a
register, and an exit status of 134 with the wrong text beside it is
exactly the failure that reads as a match. It also walks spike/x86's
own probes.

p6-transfer.flan is the two re-propagation branches the corpus does
not reach. Every transfer in restarts.flan stops at a restart-case
inside the handler-bind's extent, so the handler frames never come off
on the transfer path; and in nested and shadowed the inner frame
offers the name, so a restart-case the transfer is not aimed at never
has to put the target back. allocators.flan already covers the third.

  89 MATCH  0 DIFFER  0 refused, over test/programs and spike/x86,
  comparing stdout, stderr and the exit status.

DISCUSS.md item 17 is the report.
2026-09-13 18:41:13 +07:00

73 lines
2.8 KiB
Plaintext

;;;; The re-propagation branches, which the corpus walks past.
;;;;
;;;; Every landing pad this backend emits has two halves: the one a body
;;;; reaches by finishing, and the one a transfer reaches by passing through.
;;;; `test/programs` exercises the first everywhere and the second only for
;;;; with-allocator (allocators.flan's last case aims an invoke-restart out of
;;;; a with-allocator body at a restart-case outside it). The two below it does
;;;; not reach at all, so they are here:
;;;;
;;;; hxfer a transfer crossing a handler-bind, which has to take the handler
;;;; frames off before it goes any further -- in restarts.flan every
;;;; transfer stops at a restart-case *inside* the handler-bind's
;;;; extent, so that pop never runs on the transfer path
;;;;
;;;; rxfer's last branch
;;;; a restart-case the transfer is not aimed at: it puts the target
;;;; back in the channel and re-propagates. restarts.flan's `nested`
;;;; and `shadowed` both have the *inner* frame offering the name, so
;;;; the inner one always wins and this branch is never taken.
;;;;
;;;; Proved the way everything else here is: built both ways, and compared by
;;;; what it prints.
(defstruct Blip [n i32])
(defvar log i64)
;;; Two frames down, with a defer between, so the transfer crosses a function
;;; boundary and a transfer exit that has work to do.
(defn deep [n i32] i32
(signal (Blip {.n n}))
0)
(defn middle [n i32] i32
(defer (set log (+ log 1)))
(deep n))
;;; The handler frames come off on the transfer path. The restart-case is
;;; outside the handler-bind, so the pad pops and re-propagates rather than
;;; the body's own pop running.
(defn crosses-handler [n i32] i32
(restart-case
(handler-bind [(Blip [c] (invoke-restart 'outer-one))]
(middle n))
(outer-one [] 11)))
;;; An inner restart-case that does not offer the name. Its pad clears the
;;; channel, pops its frames, matches nothing, and puts the target back.
(defn crosses-restart [n i32] i32
(restart-case
(handler-bind [(Blip [c] (invoke-restart 'outer-two))]
(restart-case (middle n)
(inner-only [] 22)))
(outer-two [] 33)))
;;; Both at once, and a parameter as well, so the buffer the outer frame owns
;;; is written by an invoke two pads and one function away from it.
(defn crosses-both [n i32] i32
(restart-case
(handler-bind [(Blip [c] (invoke-restart 'outer-three 7)) ]
(restart-case (middle n)
(inner-only [] 44)))
(outer-three [v i32] (* v 100))))
(defn main [] i32
(print (crosses-handler 1)) (println "") ; 11
(print log) (println "") ; 1 — the defer ran
(print (crosses-restart 2)) (println "") ; 33
(print log) (println "") ; 2
(print (crosses-both 3)) (println "") ; 700
(print log) (println "") ; 3
0)