An i32 min / -1 and an f32 cast out of range. The first is where the two backends disagreed silently rather than both dying -- x86 divided in 64 bits and truncated on the store, answering -2147483648, where LLVM emitted poison -- and it is the only case that exercises the widening on the way into the condition, so a bug there would have left every other row passing. The second is the one place the two backends reach the same answer by deliberately different routes, f32 bounds here and widened doubles there, and the survey is what says the routes agree.
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 (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))
|