The two cases the tests did not cover, and one leak of a permission
A type-changing (map f) is the case into's single shadowed element name would break if the shadowing were a trick rather than the language's rule; it is not, because each stage is a fresh slot at its own type, and into.flan now runs an i32 source into a (Vec f32) to say so. A move-only accumulator carried round by recur is the shape BUILT.md pitches the form on and was untested. It works, and recur.flan now carries a Vec three times round and answers with it. block's empty-body arm returned before the loop that distributes the tail, so (do) in a tail position left ctx.tail set for whatever was checked next. Latent rather than live — every consumer sets it immediately before use, and the leaking form is always Unit-typed — but it is one line to close and the invariant is easier to state closed. Also the PORTING.md line listing loop/recur among the things with no customer: it was built, and the half of that finding that still stands is tail calls, which were not.
This commit is contained in:
parent
ca14394e0f
commit
0405666b1f
7
BUILT.md
7
BUILT.md
@ -3058,7 +3058,9 @@ is. So `into` collects, and a reducing macro of the same shape is a separate for
|
|||||||
in that position should borrow — `into.flan`'s does — and the day `drop` exists this stops being a question.
|
in that position should borrow — `into.flan`'s does — and the day `drop` exists this stops being a question.
|
||||||
- **One element name throughout**, shadowed by each `(map f)` stage: `(let [x (f x)] ...)`. A `let` binding's value is
|
- **One element name throughout**, shadowed by each `(map f)` stage: `(let [x (f x)] ...)`. A `let` binding's value is
|
||||||
checked before its name is bound, so the initialiser reads the outer `x` — that is the language's rule, not an
|
checked before its name is bound, so the initialiser reads the outer `x` — that is the language's rule, not an
|
||||||
accident, and `bind`'s `x~2` debug suffix exists precisely so a debugger does not lie about which is which.
|
accident, and `bind`'s `x~2` debug suffix exists precisely so a debugger does not lie about which is which. A
|
||||||
|
**type-changing** `map` is the case this most plausibly breaks and it does not: each stage is a fresh slot at the
|
||||||
|
stage's own type, and `into.flan` runs an `i32` source into a `(Vec f32)` to say so.
|
||||||
|
|
||||||
### What the prelude's macro limits cost, exactly
|
### What the prelude's macro limits cost, exactly
|
||||||
|
|
||||||
@ -3111,6 +3113,9 @@ it: the machinery was already there, and the question `recur` asks — *may this
|
|||||||
- **`recur` rebinds every name at once.** The new values go into temporaries and are written afterwards, so
|
- **`recur` rebinds every name at once.** The new values go into temporaries and are written afterwards, so
|
||||||
`(recur y x)` swaps. Interleaved writes would give `y y`, and `recur.flan` asserts the swap for exactly that
|
`(recur y x)` swaps. Interleaved writes would give `y y`, and `recur.flan` asserts the swap for exactly that
|
||||||
reason.
|
reason.
|
||||||
|
- **A move-only accumulator goes round.** `(loop [acc (vec-new i32) i 0] ... (recur acc (+ i 1)))` is the shape the
|
||||||
|
form exists for, and it is the one the move tracker had to be taught about (below). `recur.flan` carries a `Vec`
|
||||||
|
round three iterations and answers with it.
|
||||||
|
|
||||||
`tast.ml` said a `While` the checker *invents* contains no jumps, because the depths would be minted against a stack
|
`tast.ml` said a `While` the checker *invents* contains no jumps, because the depths would be minted against a stack
|
||||||
it is not on. `check_loop`'s `While` is the exception, and it is the exception because it is pushed on `ctx.loops`
|
it is not on. `check_loop`'s `While` is the exception, and it is the exception because it is pushed on `ctx.loops`
|
||||||
|
|||||||
4
NEXT.md
4
NEXT.md
@ -115,7 +115,9 @@ abandoned frame leaving half-written state behind; rollback is what finishes tha
|
|||||||
**What `PORTING.md` says NOT to build, with evidence:** escaping closures (one capture site, fixed by one parameter),
|
**What `PORTING.md` says NOT to build, with evidence:** escaping closures (one capture site, fixed by one parameter),
|
||||||
`Handle`/pools, `Result`/`try`, `handler-case`, `loop`/`recur` and tail calls, user allocators, structural typing —
|
`Handle`/pools, `Result`/`try`, `handler-case`, `loop`/`recur` and tail calls, user allocators, structural typing —
|
||||||
**none has a customer in that code**. (`Handle` and the pool were built anyway, and on the other reason: they are the
|
**none has a customer in that code**. (`Handle` and the pool were built anyway, and on the other reason: they are the
|
||||||
gate on classes. The finding stands and is why they were built small — see [`BUILT.md`](BUILT.md).) And **generics is not the blocker** there either: the element-changing maps are
|
gate on classes. The finding stands and is why they were built small — see [`BUILT.md`](BUILT.md). `loop`/`recur` was
|
||||||
|
built too, and the finding stands there as well: what it is not is **tail calls**, which are still not built and still
|
||||||
|
have no customer.) And **generics is not the blocker** there either: the element-changing maps are
|
||||||
five-line load-time loops. That last one hangs on a design decision the report states flatly — whether the game's
|
five-line load-time loops. That last one hangs on a design decision the report states flatly — whether the game's
|
||||||
state holds fixed arrays or `Vec`s.
|
state holds fixed arrays or `Vec`s.
|
||||||
|
|
||||||
|
|||||||
@ -1367,7 +1367,9 @@ and borrowed ctx (a : Ast.expr) f =
|
|||||||
[let] is the case the relaxation exists for. *)
|
[let] is the case the relaxation exists for. *)
|
||||||
and block ctx ?want ?(defer_ok = false) loc body =
|
and block ctx ?want ?(defer_ok = false) loc body =
|
||||||
match body with
|
match body with
|
||||||
| [] -> expect loc ~want (unit_at loc)
|
(* Withdrawn here too. An empty body has no last form to be the tail, so
|
||||||
|
leaving the permission set would hand it to whatever is checked next. *)
|
||||||
|
| [] -> ctx.tail <- false; expect loc ~want (unit_at loc)
|
||||||
| _ ->
|
| _ ->
|
||||||
(* A block's tail is its last form and nothing else. Callers that must not
|
(* A block's tail is its last form and nothing else. Callers that must not
|
||||||
pass one on need do nothing: [check] withdrew it before they were
|
pass one on need do nothing: [check] withdrew it before they were
|
||||||
|
|||||||
@ -35,6 +35,9 @@
|
|||||||
(set builds (+ builds 1))
|
(set builds (+ builds 1))
|
||||||
(slice xs 0 (len xs)))
|
(slice xs 0 (len xs)))
|
||||||
|
|
||||||
|
(defn wide [x i32] f32 (f32 x))
|
||||||
|
(defn bigf? [x f32] bool (> x 2.5))
|
||||||
|
|
||||||
(defn show [v [i32]] ()
|
(defn show [v [i32]] ()
|
||||||
(dotimes [i (len v)] (print (at v i)) (print " "))
|
(dotimes [i (len v)] (print (at v i)) (print " "))
|
||||||
(println ""))
|
(println ""))
|
||||||
@ -71,6 +74,16 @@
|
|||||||
(free v)
|
(free v)
|
||||||
(free src))
|
(free src))
|
||||||
|
|
||||||
|
;; A type-changing map: the chain's element name is rebound at the new type
|
||||||
|
;; by each stage, and the push sees the destination's element type. One name,
|
||||||
|
;; shadowed — a let binding's value is checked before its name is bound, so
|
||||||
|
;; each stage reads the stage before it.
|
||||||
|
(let [xs [1 2 3 4]
|
||||||
|
v (into xs (vec-new f32) (map wide) (filter bigf?))]
|
||||||
|
(dotimes [i (len v)] (print (at v i)) (print " "))
|
||||||
|
(println "") ; 3 4
|
||||||
|
(free v))
|
||||||
|
|
||||||
;; A source that is a call is bound once, so it is made once however many
|
;; A source that is a call is bound once, so it is made once however many
|
||||||
;; elements come out of it.
|
;; elements come out of it.
|
||||||
(let [xs [1 2 3 4]
|
(let [xs [1 2 3 4]
|
||||||
|
|||||||
@ -81,6 +81,18 @@
|
|||||||
(recur (+ i 1) (+ acc hit))))))
|
(recur (+ i 1) (+ acc hit))))))
|
||||||
(println "") ; 4
|
(println "") ; 4
|
||||||
|
|
||||||
|
;; A move-only accumulator, carried round by recur and answered with. This
|
||||||
|
;; is the shape the form exists for: no mutable local, no sentinel flag, and
|
||||||
|
;; the Vec is the loop's value. recur writes every name on the way round, so
|
||||||
|
;; the "moves a value bound outside the loop" rule is not about acc.
|
||||||
|
(let [v (loop [acc (vec-new i32) i 0]
|
||||||
|
(if (= i 3)
|
||||||
|
acc
|
||||||
|
(do (push acc i) (recur acc (+ i 1)))))]
|
||||||
|
(dotimes [i (len v)] (print (at v i)))
|
||||||
|
(println "") ; 012
|
||||||
|
(free v))
|
||||||
|
|
||||||
;; A match arm is a tail too.
|
;; A match arm is a tail too.
|
||||||
(print (loop [i 0 acc 0]
|
(print (loop [i 0 acc 0]
|
||||||
(match (step i)
|
(match (step i)
|
||||||
|
|||||||
@ -131,13 +131,13 @@ let () =
|
|||||||
rather than running out of stack. The swap line is the other — recur
|
rather than running out of stack. The swap line is the other — recur
|
||||||
rebinds every name at once, and interleaved writes would print 1. *)
|
rebinds every name at once, and interleaved writes would print 1. *)
|
||||||
outputs "loop and recur" "programs/recur.flan"
|
outputs "loop and recur" "programs/recur.flan"
|
||||||
"10\n2\n21\n8\n10000000\n64\n012\n0\n4\n6\n";
|
"10\n2\n21\n8\n10000000\n64\n012\n0\n4\n012\n6\n";
|
||||||
(* into. The count of pulls is the assertion a unit test cannot make: one
|
(* into. The count of pulls is the assertion a unit test cannot make: one
|
||||||
pass, one call per element per stage it reaches, and no intermediate
|
pass, one call per element per stage it reaches, and no intermediate
|
||||||
collection anywhere. The two show lines either side of it are the same
|
collection anywhere. The two show lines either side of it are the same
|
||||||
source transformed in two orders, which have to differ. *)
|
source transformed in two orders, which have to differ. *)
|
||||||
outputs "into" "programs/into.flan"
|
outputs "into" "programs/into.flan"
|
||||||
"7 8 9 \n2 4 6 8 10 12 \n4 8 12 \n21\n6 2 4 \n3 1 2 \n2 4 \n1\n";
|
"7 8 9 \n2 4 6 8 10 12 \n4 8 12 \n21\n6 2 4 \n3 1 2 \n3 4 \n2 4 \n1\n";
|
||||||
(* The prelude's slice algorithms. Every assertion here is over an input a
|
(* The prelude's slice algorithms. Every assertion here is over an input a
|
||||||
wrong implementation fails: unsorted with duplicates, negatives and an
|
wrong implementation fails: unsorted with duplicates, negatives and an
|
||||||
odd length; a reverse-sorted slice; and a sort of a subslice whose
|
odd length; a reverse-sorted slice; and a sort of a subslice whose
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user