The INSERTIONSORT crash, all three rulings (FIX.org 2026-09-20): - (bytes s) allocates a writable copy through the allocator surface — context or (bytes s a), StorageExhausted with retry, a registry note in dev builds (flan_bytes_dup, lowered like vec-new). (bytes-view s) is the old zero-cost reinterpret, renamed, read-only by convention; every in-repo reader swept over to it. (string b) unchanged. - String constants were already read-only on both backends at -O0; now pinned — bytes-copy.flan rows on LLVM/-O0/--x86, and dies_segv rows asserting the write-through-view trap on both backends. - A dev build installs a SIGSEGV/SIGBUS handler by the same dev-only constructor slot that arms the registry: one line naming the address and the innermost frame, then the trap-hook park — stopped, not dead, the daemon serving. No agent: message and re-raise. Release builds untouched. Pinned by trap_park over dev-segv.flan.
104 lines
4.5 KiB
Plaintext
104 lines
4.5 KiB
Plaintext
;;;; slurp and barf — NEXT.md decisions 2 and 5.
|
|
;;;;
|
|
;;;; slurp reads a whole file and answers a (Vec u8). It allocates, which is
|
|
;;;; why it waited for Vec, and it follows spec-memory.md's rule to the letter:
|
|
;;;; no allocating operation returns an error, so there is no Result here and
|
|
;;;; no out-parameter anywhere.
|
|
;;;;
|
|
;;;; Failure signals a condition under a restart, which is the pattern
|
|
;;;; StorageExhausted set this session. Two restarts, and they are the pair
|
|
;;;; Common Lisp establishes for a file-error:
|
|
;;;;
|
|
;;;; retry the file may be there now
|
|
;;;; use-value [p string] try this other path instead
|
|
;;;;
|
|
;;;; use-value is a *typed* restart — the second thing this session bought —
|
|
;;;; and it is the first one the compiler itself emits. Its parameter is the
|
|
;;;; path slot the attempt reads, so the clause body is empty: the invoker's
|
|
;;;; argument lands in the slot, the clause falls through, and the loop
|
|
;;;; re-attempts against the new path.
|
|
|
|
;; Handlers cannot see the locals of the function that established them, so the
|
|
;; observations are globals — the same shape exhausted.flan uses.
|
|
(defvar seen i64)
|
|
(defvar last-reason i32)
|
|
(defvar last-op i32)
|
|
(defvar last-path string)
|
|
|
|
(defn main [] i32
|
|
;; ── The happy path ────────────────────────────────────────────────
|
|
(let [v (slurp "programs/assets/a.txt")]
|
|
(println (len v)) ; 13
|
|
(print (string (as-slice v))) ; hello from a
|
|
(free v))
|
|
|
|
;; Byte-exact, the same as an embed: nothing here decodes anything.
|
|
(let [v (slurp "programs/assets/raw.bin")]
|
|
(println (len v)) ; 4
|
|
(println (at v 0)) ; 0
|
|
(println (at v 2)) ; 255
|
|
(free v))
|
|
|
|
;; ── use-value: a missing file, answered with another path ─────────
|
|
;; The textbook case. The handler does not know what slurp was going to do
|
|
;; with the bytes and does not have to: it names a file that is there and
|
|
;; the read resumes as if that had been asked for all along.
|
|
(handler-bind
|
|
[(FileError [c]
|
|
(set seen (+ seen 1))
|
|
(set last-reason (.reason c))
|
|
(set last-op (.op c))
|
|
(set last-path (.path c))
|
|
(invoke-restart 'use-value "programs/assets/b.bin"))]
|
|
(let [v (slurp "programs/assets/does-not-exist")]
|
|
(println (len v)) ; 3
|
|
(println (string (as-slice v))) ; BBB
|
|
(free v)))
|
|
(println seen) ; 1
|
|
(println (= last-reason file-missing)) ; true
|
|
(println (= last-op file-op-read)) ; true
|
|
;; The condition carries the path that actually failed, not the one that
|
|
;; eventually worked — the handler is told what it is answering about.
|
|
(println last-path) ; programs/assets/does-not-exist
|
|
|
|
;; ── barf, and reading back what it wrote ──────────────────────────
|
|
(barf "slurp-out.txt" (bytes-view "round trip\n"))
|
|
(let [v (slurp "slurp-out.txt")]
|
|
(println (len v)) ; 11
|
|
(print (string (as-slice v))) ; round trip
|
|
(free v))
|
|
|
|
;; barf's own failure signals the same condition with op = write. A directory
|
|
;; that does not exist is the reachable case on every platform.
|
|
(set seen 0)
|
|
(handler-bind
|
|
[(FileError [c]
|
|
(set seen (+ seen 1))
|
|
(set last-op (.op c))
|
|
(invoke-restart 'use-value "slurp-out.txt"))]
|
|
(barf "no-such-dir/x.txt" (bytes-view "second\n")))
|
|
(println seen) ; 1
|
|
(println (= last-op file-op-write)) ; true
|
|
(let [v (slurp "slurp-out.txt")]
|
|
(print (string (as-slice v))) ; second
|
|
(free v))
|
|
|
|
;; ── retry: the file was not there, so the handler makes it ────────
|
|
;; The other restart, and the one use-value cannot stand in for: here the
|
|
;; path is right and the world is wrong. The handler fixes the world and
|
|
;; re-attempts the *same* request, which is exactly what retry means for a
|
|
;; failed allocation too.
|
|
(set seen 0)
|
|
(handler-bind
|
|
[(FileError [c]
|
|
(set seen (+ seen 1))
|
|
(set last-reason (.reason c))
|
|
(barf "slurp-made.txt" (bytes-view "made by the handler\n"))
|
|
(invoke-restart 'retry))]
|
|
(let [v (slurp "slurp-made.txt")]
|
|
(print (string (as-slice v))) ; made by the handler
|
|
(free v)))
|
|
(println seen) ; 1
|
|
(println (= last-reason file-missing)) ; true
|
|
0)
|