;;;; 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 (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))