There is no SSE remainder instruction, and LLVM does not invent one: at -O0 it lowers frem to fmod or fmodf. The backend now calls those two symbols rather than refusing the operator, which is agreement by construction rather than a second hand-written identity that would have to get every rounding, every signed zero and every infinity right on its own. Rem was the only gap. emit.ml's float surface is Add, Sub, Mul, Div, Rem and the six comparisons; x86 had everything but Rem, and its comparisons already build LLVM's ordered predicates out of ucomis, setcc and setnp. math3.flan grows the operator spelling beside the fmod-f32/fmod-f64 calls it already had, through globals so the pair is not folded before either backend sees an operator. FIX.org records the ruling the fix came from.
136 lines
5.6 KiB
Plaintext
136 lines
5.6 KiB
Plaintext
;;;; The rest of libm, at both widths — what math.flan and math2.flan left out.
|
|
;;;;
|
|
;;;; The rule those two set holds here unchanged and is the only reason this
|
|
;;;; file looks the way it does: none of these is correctly rounded under
|
|
;;;; IEEE-754 except sqrt, fabs, the rounding three and fmod, so every value
|
|
;;;; below is one whose answer is exact in binary — zero, one, a power of two,
|
|
;;;; a perfect square, a perfect cube. A case that pinned glibc's last bit
|
|
;;;; would pass here and fail on wasi-libc.
|
|
;;;;
|
|
;;;; The acceptance table builds this at -O0 as well, and that run is the one
|
|
;;;; that matters: at -O2 LLVM folds a libm call over two literals and leaves
|
|
;;;; no symbol to resolve, which is how a missing -lm hid the first time.
|
|
|
|
(defn show [x f32] ()
|
|
(print x)
|
|
(print " "))
|
|
|
|
(defn show64 [x f64] ()
|
|
(print x)
|
|
(print " "))
|
|
|
|
;;; The operands of the (% ...) block at the end. See the note there for why
|
|
;;; they are globals.
|
|
(defvar rem-a f64 7.5)
|
|
(defvar rem-na f64 -7.5)
|
|
(defvar rem-b f64 2.0)
|
|
(defvar rem-nb f64 -2.0)
|
|
(defvar rem-z f64 0.0)
|
|
(defvar rem-huge f64 1e300)
|
|
(defvar rem-a32 f32 7.5)
|
|
(defvar rem-na32 f32 -7.5)
|
|
(defvar rem-b32 f32 2.0)
|
|
(defvar rem-z32 f32 0.0)
|
|
|
|
(defn main [] i32
|
|
;; The f32 half. tan, the three inverses, the three logarithms and exp.
|
|
(show (tan-f32 0.0)) ; 0
|
|
(show (asin-f32 0.0)) ; 0
|
|
(show (acos-f32 1.0)) ; 0
|
|
(show (atan-f32 0.0)) ; 0
|
|
(show (log-f32 1.0)) ; 0
|
|
(show (log2-f32 8.0)) ; 3
|
|
(show (log10-f32 1000.0)) ; 3
|
|
(show (exp-f32 0.0)) ; 1
|
|
(println "")
|
|
|
|
(show (fmod-f32 7.0 4.0)) ; 3
|
|
;; The sign follows the dividend and not the divisor, which is the line a
|
|
;; caller reaching for a modulo gets wrong. Written down as a case.
|
|
(show (fmod-f32 -1.0 3.0)) ; -1
|
|
(show (hypot-f32 3.0 4.0)) ; 5
|
|
(show (cbrt-f32 27.0)) ; 3
|
|
;; The negative root, where (pow-f32 x 0.33333334) would be NaN: pow goes
|
|
;; through a logarithm and cbrt does not.
|
|
(show (cbrt-f32 -8.0)) ; -2
|
|
(show (abs-f32 -2.5)) ; 2.5
|
|
(println "")
|
|
|
|
;; The f64 half, at the same exact values. This block is the whole point of
|
|
;; the f64 face existing: before it, every one of these was a cast down to
|
|
;; f32 and back, and the cast down is where the precision went.
|
|
(show64 (sqrt-f64 16.0)) ; 4
|
|
(show64 (sin-f64 0.0)) ; 0
|
|
(show64 (cos-f64 0.0)) ; 1
|
|
(show64 (tan-f64 0.0)) ; 0
|
|
(show64 (asin-f64 0.0)) ; 0
|
|
(show64 (acos-f64 1.0)) ; 0
|
|
(show64 (atan-f64 0.0)) ; 0
|
|
(show64 (atan2-f64 0.0 1.0)) ; 0
|
|
(println "")
|
|
|
|
(show64 (log-f64 1.0)) ; 0
|
|
(show64 (log2-f64 1024.0)) ; 10
|
|
(show64 (log10-f64 100.0)) ; 2
|
|
(show64 (exp-f64 0.0)) ; 1
|
|
(show64 (pow-f64 2.0 10.0)) ; 1024
|
|
(show64 (fmod-f64 7.0 4.0)) ; 3
|
|
(show64 (hypot-f64 3.0 4.0)) ; 5
|
|
(show64 (cbrt-f64 8.0)) ; 2
|
|
(show64 (abs-f64 -1.5)) ; 1.5
|
|
(println "")
|
|
|
|
;; The f64 rounding family, which is libm's where the f32 one is Flan's —
|
|
;; and it agrees with the Flan one where they overlap: half away from zero,
|
|
;; so -2.5 goes to -3 and not to -2.
|
|
(show64 (floor-f64 -2.5)) ; -3
|
|
(show64 (ceil-f64 -2.5)) ; -2
|
|
(show64 (round-f64 -2.5)) ; -3
|
|
(show64 (round-f64 2.5)) ; 3
|
|
(println "")
|
|
|
|
;; Integer magnitude, one per width.
|
|
(print (abs-i32 -7)) (print " ") ; 7
|
|
(print (abs-i64 (i64 -7))) (print " ") ; 7
|
|
(print (abs-i32 7)) (print " ") ; 7
|
|
;; tau is 2pi at both widths. Pinning the relation rather than the digits is
|
|
;; what catches a constant written to too few of them.
|
|
(print (= tau-f32 (* 2.0 pi-f32))) (print " ")
|
|
(print (= tau-f64 (* 2.0 pi-f64)))
|
|
(println "")
|
|
|
|
;; pi is the one value here that can be pinned without pinning a libm: it is
|
|
;; a literal the compiler rounds, so it is the same on every target.
|
|
(println (and (> pi-f64 3.14159265) (< pi-f64 3.14159266)))
|
|
|
|
;; The operator spelling of the same function. A typed float (% x y) is
|
|
;; fmod: LLVM lowers it to `frem`, which at -O0 becomes a call to fmod or
|
|
;; fmodf, and the x86 backend calls the same two symbols. Everything here
|
|
;; comes through a global rather than a literal for the reason arith.flan
|
|
;; gives — a literal pair is folded before either backend sees an operator,
|
|
;; and the folded answer would be evidence about the constant folder and
|
|
;; not about the lowering.
|
|
;;
|
|
;; The four signs are the first line, because that is where a modulo
|
|
;; written in place of a remainder disagrees: the sign follows the
|
|
;; dividend. The second line is the two answers IEEE defines where an
|
|
;; integer % would have died — a zero divisor and a NaN dividend are both
|
|
;; NaN, not a signal.
|
|
(show64 (% rem-a rem-b)) ; 1.5
|
|
(show64 (% rem-na rem-b)) ; -1.5
|
|
(show64 (% rem-a rem-nb)) ; 1.5
|
|
(show64 (% rem-na rem-nb)) ; -1.5
|
|
(show (% rem-a32 rem-b32)) ; 1.5
|
|
(show (% rem-na32 rem-b32)) ; -1.5
|
|
(println "")
|
|
|
|
(show64 (% rem-a rem-z)) ; nan
|
|
(show64 (% (/ rem-z rem-z) rem-b)) ; nan
|
|
(show (% rem-a32 rem-z32)) ; nan
|
|
;; A quotient past anything an integer could name, which is where a
|
|
;; remainder written as x - trunc(x/y)*y has nothing left to truncate.
|
|
(show64 (% rem-huge rem-b)) ; 0
|
|
(show64 (% rem-b rem-huge)) ; 2
|
|
(println "")
|
|
0)
|