Review found it, and it is a silent miscompile on both backends rather than a refusal anywhere. mangle_ty flattens a whole signature into one hyphen-joined string, so (CFn [(Ptr i32)] i32) and (CFn [ptr i32] i32) -- the second over a struct someone called ptr -- flatten alike; keyed on that, the second widening reused the first's thunk at the wrong arity. The key is the types now, compared with Types.equal, and the symbol is a counter over the thunks already minted, so nothing is derived from a spelling. fn-thunk-share.flan is the pair, and it prints 5 and 17. And a regression beside it: a generic whose function parameter binds the type variable. (defn apply2 [f (Fn [$t] $t) x $t] ...) called as (apply2 bump 1) compiled before this lane and stopped, because bind_ty had no arm admitting a CFn argument at an Fn pattern -- and once it had one, the call still handed one word to an instance declaring two, because a parameter that still mentions a variable is checked with no expectation and expect never sees the pair. Both halves: the arm, and the widening in generic_call's catch-up pass beside the numeric one. The same gap hid new functionality -- a CFn argument at a (CFn [$t] $t) parameter had no arm either -- and mentions had no CFn case, so bound_exactly answered wrong for a variable living only inside one. The corpus missed all of it because the prelude binds $t from an earlier argument, so the parameter is concrete before bind_ty sees it. While here: escaping's enumeration is the *clean* set now rather than the suspect set. It had a hole where a list like that cannot -- an (at s 0) over a slice of Fn read as clean while the Vec, struct and pointer spellings were refused. Unreachable today, and the header claims the list is closed. The same inversion fixes which refusal message an index read gets. Three minors: Anull was defined and never constructed; the capture-dyn message substituted a descriptor into a noun slot; and session.ml rendered a defn's changed signature as (Fn [...] ...), which is now a real type and not the same as (CFn [...] ...) -- it writes the parameters and the return the way a defn writes them. And one rounding corrected in the docs: handler-bind is not free for a program that captures nothing. %handler grew from 24 bytes to 32, every push writes a null into the new field, every clause gains ptr %env with an alloca and a store, and flan_signal passes one more argument per dispatch -- twenty changed x86 lines on loops.flan. Small, real, and paid by every conditions program.
26 lines
1.1 KiB
Plaintext
26 lines
1.1 KiB
Plaintext
;; The widening thunk is memoised per signature, and the key is the types.
|
|
;;
|
|
;; It used to be a *name*, derived from mangle_ty, which flattens a whole
|
|
;; signature into one hyphen-joined string and loses arity and every type
|
|
;; boundary with it: (CFn [(Ptr i32)] i32) and (CFn [ptr i32] i32) — the
|
|
;; second over a struct someone called ptr — both flatten to the same thing.
|
|
;; Keyed on that, the second widening reuses the first's thunk and calls it
|
|
;; with the wrong arity, which both backends compile without a word and
|
|
;; neither runs. This program is that pair, and it prints 5 and 17.
|
|
;;
|
|
;; A struct named ptr is legal and ordinary; nothing about the collision
|
|
;; needed a program written to provoke it, only two signatures that happened
|
|
;; to flatten alike.
|
|
(defstruct ptr [a i32 b i32])
|
|
|
|
(defn f1 [p (Ptr i32)] i32 (deref p))
|
|
(defn f2 [a ptr b i32] i32 (+ (.a a) b))
|
|
|
|
(defn use1 [f (Fn [(Ptr i32)] i32)] i32 (let [x 5] (f (addr x))))
|
|
(defn use2 [f (Fn [ptr i32] i32)] i32 (f (ptr {.a 10 .b 0}) 7))
|
|
|
|
(defn main [] i32
|
|
(println (use1 f1))
|
|
(println (use2 f2))
|
|
0)
|