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.
82 lines
4.2 KiB
Plaintext
82 lines
4.2 KiB
Plaintext
;;;; Arithmetic with no answer, unhandled. One program, one case per argument,
|
|
;;;; the same shape bounds.flan has and for the same reason: a death is
|
|
;;;; observable only as an exit status and a sentence on stderr, so each case
|
|
;;;; needs its own run.
|
|
;;;;
|
|
;;;; What is asserted is the *reason* — the location, and which operation
|
|
;;;; against which operands. Before this change none of these cases had a
|
|
;;;; reason to assert on: the first four were a raw SIGFPE, which prints
|
|
;;;; nothing at all, and the last was undefined and would have printed whatever
|
|
;;;; the optimiser decided the answer was.
|
|
;;;;
|
|
;;;; Everything comes through a global rather than a literal, which keeps the
|
|
;;;; operands dynamic. A literal divisor is exactly the case the guard is
|
|
;;;; allowed to elide, and folding these away would leave the test asserting on
|
|
;;;; a program that does not contain the check.
|
|
|
|
(defvar zero i64)
|
|
(defvar neg1 i64 -1)
|
|
(defvar big i64 9223372036854775807)
|
|
(defvar ten i64 10)
|
|
(defvar uz u32)
|
|
(defvar huge f64 1e300)
|
|
;; The narrow versions of the same two failures. They are here because they are
|
|
;; the ones the two backends reach by different routes: the overflow test
|
|
;; compares against the *narrow* type's most negative value inside a 64-bit
|
|
;; register, and the f32 range test is compared in f32 on one backend and in a
|
|
;; double on the other. Both routes are supposed to give the same answer and
|
|
;; the survey is what says so.
|
|
(defvar i32big i32 2147483647)
|
|
(defvar m1-32 i32 -1)
|
|
(defvar wide f32 1e30)
|
|
|
|
(defn main [args [string]] i32
|
|
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))
|
|
;; The most negative i64. No literal spells it — the reader parses the
|
|
;; digits and then negates, and the positive half does not fit — so it
|
|
;; is built, which also keeps it out of the constant folder's reach.
|
|
min (- (- (i64 0) big) 1)]
|
|
(cond
|
|
;; None of these may die. Division that is fine has to stay fine, and
|
|
;; these are the four shapes the guard has an opinion about: an ordinary
|
|
;; dynamic divisor, a literal one the guard drops entirely, unsigned
|
|
;; division, which has no overflow case because it has no most-negative
|
|
;; value, and a float division by zero, which is an infinity and is a
|
|
;; defined answer this language is not in the business of refusing.
|
|
(= n 0) (do (print (/ ten (+ zero 3))) (print " ")
|
|
(print (/ ten 2)) (print " ")
|
|
(print (/ (u32 100) (+ uz 7))) (print " ")
|
|
(print (/ (f64 1.0) (f64 0.0)))
|
|
(println ""))
|
|
|
|
(= n 1) (print (/ ten zero)) ; divide by zero
|
|
(= n 2) (print (% ten zero)) ; remainder by zero
|
|
;; The one division that overflows. Nothing is wrong with either
|
|
;; operand on its own; it is the pair, and it is the only pair.
|
|
(= n 3) (print (/ min neg1))
|
|
;; `srem` overflows on exactly the operands `sdiv` does, because the
|
|
;; quotient is what does not fit and a remainder computes one too.
|
|
(= n 4) (print (% min neg1))
|
|
;; A float too large for the destination, and the same value against a
|
|
;; narrower destination, which reports its own range.
|
|
(= n 5) (print (i64 huge))
|
|
(= n 6) (print (i8 (/ huge 1e290)))
|
|
;; NaN, which fails the range test at both ends rather than passing it at
|
|
;; neither: the comparisons are ordered, deliberately.
|
|
(= n 7) (print (i64 (/ (f64 0.0) (f64 0.0))))
|
|
;; The same overflow one width down, which is the case the two backends
|
|
;; used to disagree about *silently* rather than both dying: this one
|
|
;; loaded sign-extended into a 64-bit register, divided there and
|
|
;; truncated on the store, answering -2147483648, where the other backend
|
|
;; emitted poison. Neither was wrong about anything; they just were not
|
|
;; the same program.
|
|
(= n 8) (print (/ (- (- (i32 0) i32big) 1) m1-32))
|
|
;; And an f32 source, whose range test the two backends reach by
|
|
;; different routes on purpose — compared in f32 here and in a widened
|
|
;; double there, which agree because every bound is a power of two and is
|
|
;; exact in both.
|
|
(= n 9) (print (i32 wide))
|
|
|
|
:else (println "?"))
|
|
0))
|