The trio the author decided on 2026-09-20 is now all built: def is CL's defparameter — its initialiser runs on every daemon re-run, unguarded, so an edited initialiser repaints the same storage on C-c C-c plus re-run — defonce (Clojure's name for CL's defvar, per the author) initialises once behind the .init~once. flag, and defconst stays the image. One parse arm reads both forms; the difference is Ast.reinit, carried to Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and Check.check_global lifts every def initialiser — zero and literal included — into global/<n>, so the host's startup reaches it through the function cell and a re-evaluated def swaps it (Session's def_inits; Emit.redefinition declares the cell for a non-sibling target). The old defvar spelling is refused with the rename and both compiling spellings, and every program, test, doc and editor list is swept — except sand.flan, the author's live WIP, whose seven defvar lines are flagged in FIX.org and keep its three dependent tests red on this branch.
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.
|
|
|
|
(defonce zero i64)
|
|
(defonce neg1 i64 -1)
|
|
(defonce big i64 9223372036854775807)
|
|
(defonce ten i64 10)
|
|
(defonce uz u32)
|
|
(defonce 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.
|
|
(defonce i32big i32 2147483647)
|
|
(defonce m1-32 i32 -1)
|
|
(defonce 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))
|