flan/test/programs/arith.flan
Joseph Ferano a431cddd3b A divide by zero names the file and the line, and is answerable
Three arithmetic situations had no defined behaviour and the two backends
disagreed about all three: a divide or remainder by zero, which was a raw
SIGFPE with no message and no location; (/ min -1), whose quotient is one past
the top of the type; and a float to integer cast whose value does not fit,
which LLVM called undefined and would fold to anything.

They now signal ArithError with `error`, exactly as a bad index signals
BoundsError, and die with a sentence naming the file, the line and the operands
only if nothing answered. The guards ride the same --checks flag as the bounds
check and are elided with it.

No restart is established at the failing operation. The sketch this started
from asked for use-value, and the implementation ruled it out: a restart frame
is allocated by the restart-case that offers it, on its own stack, so the
runtime cannot hold one on a program's behalf and use-value here would mean an
alloca and a restart frame at every division in every checked build. That is
the cost already refused for indexing, buying a silently different answer.

The x86 backend is unchanged and is the next commit.
2026-09-13 22:55:26 +07:00

61 lines
3.0 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)
(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))))
:else (println "?"))
0))