1.0.30.25: deftransform for ARRAY-IN-BOUNDS-P
[sbcl.git] / tests / compiler.pure.lisp
index afe9767..1c49450 100644 (file)
                          `(lambda (x y z)
                             (make-array '3 :initial-contents `(,x ,y ,z))))))))
 
+;;; optimizing array-in-bounds-p
+(with-test (:name :optimize-array-in-bounds-p)
+  (locally
+    (macrolet ((find-callees (&body body)
+                 `(ctu:find-named-callees
+                    (compile nil
+                             '(lambda ()
+                                ,@body))
+                    :name 'array-in-bounds-p))
+               (must-optimize (&body exprs)
+                 `(progn
+                    ,@(loop for expr in exprs
+                            collect `(assert (not (find-callees
+                                                   ,expr))))))
+               (must-not-optimize (&body exprs)
+                 `(progn
+                    ,@(loop for expr in exprs
+                            collect `(assert (find-callees
+                                              ,expr))))))
+      (must-optimize
+        ;; in bounds
+        (let ((a (make-array '(1))))
+          (array-in-bounds-p a 0))
+        ;; exceeds upper bound (constant)
+        (let ((a (make-array '(1))))
+          (array-in-bounds-p a 1))
+        ;; exceeds upper bound (interval)
+        (let ((a (make-array '(1))))
+          (array-in-bounds-p a (+ 1 (random 2))))
+        ;; negative lower bound (constant)
+        (let ((a (make-array '(1))))
+          (array-in-bounds-p a -1))
+        ;; negative lower bound (interval)
+        (let ((a (make-array 3))
+              (i (- (random 1) 20)))
+          (array-in-bounds-p a i))
+        ;; multiple known dimensions
+        (let ((a (make-array '(1 1))))
+          (array-in-bounds-p a 0 0))
+        ;; union types
+        (let ((s (the (simple-string 10) (eval "0123456789"))))
+          (array-in-bounds-p s 9)))
+      (must-not-optimize
+       ;; don't trust non-simple array length in safety=1
+       (let ((a (the (array * (10)) (make-array 10 :adjustable t))))
+         (eval `(adjust-array ,a 0))
+         (array-in-bounds-p a 9))
+       ;; same for a union type
+       (let ((s (the (string 10) (make-array 10
+                                             :element-type 'character
+                                             :adjustable t))))
+         (eval `(adjust-array ,s 0))
+         (array-in-bounds-p s 9))
+       ;; single unknown dimension
+       (let ((a (make-array (random 20))))
+         (array-in-bounds-p a 10))
+       ;; multiple unknown dimensions
+       (let ((a (make-array (list (random 20) (random 5)))))
+         (array-in-bounds-p a 5 2))
+       ;; some other known dimensions
+       (let ((a (make-array (list 1 (random 5)))))
+         (array-in-bounds-p a 0 2))
+       ;; subscript might be negative
+       (let ((a (make-array 5)))
+         (array-in-bounds-p a (- (random 3) 2)))
+       ;; subscript might be too large
+       (let ((a (make-array 5)))
+         (array-in-bounds-p a (random 6)))
+       ;; unknown upper bound
+       (let ((a (make-array 5)))
+         (array-in-bounds-p a (get-universal-time)))
+       ;; unknown lower bound
+       (let ((a (make-array 5)))
+         (array-in-bounds-p a (- (get-universal-time))))
+       ;; in theory we should be able to optimize
+       ;; the following but the current implementation
+       ;; doesn't cut it because the array type's
+       ;; dimensions get reported as (* *).
+       (let ((a (make-array (list (random 20) 1))))
+         (array-in-bounds-p a 5 2))))))
+
 ;;; optimizing (EXPT -1 INTEGER)
 (test-util:with-test (:name (expt minus-one integer))
   (dolist (x '(-1 -1.0 -1.0d0))
       (test '(integer 11 11) '(+ * 1) nil))
     (let ((* "fooo"))
       (test '(integer 4 4) '(length *) t))))
+
+(with-test (:name :float-division-using-exact-reciprocal)
+  (flet ((test (lambda-form arg res &key (check-insts t))
+           (let* ((fun (compile nil lambda-form))
+                  (disassembly (with-output-to-string (s)
+                                  (disassemble fun :stream s))))
+             ;; Let's make sure there is no division at runtime: for x86 and
+             ;; x86-64 that implies an FDIV, DIVSS, or DIVSD instruction, so
+             ;; look for DIV in the disassembly. It's a terrible KLUDGE, but
+             ;; it works.
+             #+(or x86 x86-64)
+             (when check-insts
+               (assert (not (search "DIV" disassembly))))
+             ;; No generic arithmetic!
+             (assert (not (search "GENERIC" disassembly)))
+             (assert (eql res (funcall fun arg))))))
+    (dolist (c '(128 64 32 16 8 4 2 1 1/2 1/4 1/8 1/16 1/32 1/64))
+      (dolist (type '(single-float double-float))
+        (let* ((cf (coerce c type))
+               (arg (- (random (* 2 cf)) cf))
+               (r1 (eval `(/ ,arg ,cf)))
+               (r2 (eval `(/ ,arg ,(- cf)))))
+          (test `(lambda (x) (declare (,type x)) (/ x ,cf)) arg r1)
+          (test `(lambda (x) (declare (,type x)) (/ x ,(- cf))) arg r2)
+          ;; rational args should get optimized as well
+          (test `(lambda (x) (declare (,type x)) (/ x ,c)) arg r1)
+          (test `(lambda (x) (declare (,type x)) (/ x ,(- c))) arg r2))))
+    ;; Also check that inexact reciprocals (1) are not used by default (2) are
+    ;; used with FLOAT-ACCURACY=0.
+    (dolist (type '(single-float double-float))
+      (let ((trey (coerce 3 type))
+            (one (coerce 1 type)))
+        (test `(lambda (x) (declare (,type x)) (/ x 3)) trey one
+              :check-insts nil)
+        (test `(lambda (x)
+                 (declare (,type x)
+                          (optimize (sb-c::float-accuracy 0)))
+                 (/ x 3))
+              trey (eval `(* ,trey (/ ,trey))))))))
+
+(with-test (:name :float-multiplication-by-one)
+  (flet ((test (lambda-form arg &optional (result arg))
+           (let* ((fun1 (compile nil lambda-form))
+                  (fun2 (funcall (compile nil `(lambda ()
+                                                 (declare (optimize (sb-c::float-accuracy 0)))
+                                                 ,lambda-form))))
+                  (disassembly1 (with-output-to-string (s)
+                                  (disassemble fun1 :stream s)))
+                  (disassembly2 (with-output-to-string (s)
+                                  (disassemble fun2 :stream s))))
+             ;; Multiplication at runtime should be eliminated only with
+             ;; FLOAT-ACCURACY=0. (To catch SNaNs.)
+             #+(or x86 x86-64)
+             (assert (and (search "MUL" disassembly1)
+                          (not (search "MUL" disassembly2))))
+             ;; Not generic arithmetic, please!
+             (assert (and (not (search "GENERIC" disassembly1))
+                          (not (search "GENERIC" disassembly2))))
+             (assert (eql result (funcall fun1 arg)))
+             (assert (eql result (funcall fun2 arg))))))
+    (dolist (type '(single-float double-float))
+      (let* ((one (coerce 1 type))
+             (arg (random (* 2 one)))
+             (-r (- arg)))
+        (test `(lambda (x) (declare (,type x)) (* x 1)) arg)
+        (test `(lambda (x) (declare (,type x)) (* x -1)) arg -r)
+        (test `(lambda (x) (declare (,type x)) (* x ,one)) arg)
+        (test `(lambda (x) (declare (,type x)) (* x ,(- one))) arg -r)))))
+
+(with-test (:name :float-addition-of-zero)
+  (flet ((test (lambda-form arg &optional (result arg))
+           (let* ((fun1 (compile nil lambda-form))
+                  (fun2 (funcall (compile nil `(lambda ()
+                                                 (declare (optimize (sb-c::float-accuracy 0)))
+                                                 ,lambda-form))))
+                  (disassembly1 (with-output-to-string (s)
+                                  (disassemble fun1 :stream s)))
+                  (disassembly2 (with-output-to-string (s)
+                                  (disassemble fun2 :stream s))))
+             ;; Let's make sure there is no addition at runtime: for x86 and
+             ;; x86-64 that implies an FADD, ADDSS, or ADDSD instruction, so
+             ;; look for the ADDs in the disassembly. It's a terrible KLUDGE,
+             ;; but it works. Unless FLOAT-ACCURACY is zero, we leave the
+             ;; addition in to catch SNaNs.
+             #+x86
+             (assert (and (search "FADD" disassembly1)
+                          (not (search "FADD" disassembly2))))
+             #+x86-64
+             (let ((inst (if (typep result 'double-float)
+                             "ADDSD" "ADDSS")))
+               (assert (and (search inst disassembly1)
+                            (not (search inst disassembly2)))))
+             (assert (eql result (funcall fun1 arg)))
+             (assert (eql result (funcall fun2 arg))))))
+    (test `(lambda (x) (declare (single-float x)) (+ x 0)) 123.45)
+    (test `(lambda (x) (declare (single-float x)) (+ x 0.0)) 543.21)
+    (test `(lambda (x) (declare (single-float x)) (+ x 0.0d0)) 42.00 42.d0)
+    (test `(lambda (x) (declare (double-float x)) (+ x 0)) 123.45d0)
+    (test `(lambda (x) (declare (double-float x)) (+ x 0.0)) 543.21d0)
+    (test `(lambda (x) (declare (double-float x)) (+ x 0.0d0)) 42.d0)))
+
+(with-test (:name :float-substraction-of-zero)
+  (flet ((test (lambda-form arg &optional (result arg))
+           (let* ((fun1 (compile nil lambda-form))
+                  (fun2 (funcall (compile nil `(lambda ()
+                                                 (declare (optimize (sb-c::float-accuracy 0)))
+                                                 ,lambda-form))))
+                  (disassembly1 (with-output-to-string (s)
+                                  (disassemble fun1 :stream s)))
+                  (disassembly2 (with-output-to-string (s)
+                                  (disassemble fun2 :stream s))))
+             ;; Let's make sure there is no substraction at runtime: for x86
+             ;; and x86-64 that implies an FSUB, SUBSS, or SUBSD instruction,
+             ;; so look for SUB in the disassembly. It's a terrible KLUDGE,
+             ;; but it works. Unless FLOAT-ACCURACY is zero, we leave the
+             ;; substraction in in to catch SNaNs.
+             #+x86
+             (assert (and (search "FSUB" disassembly1)
+                          (not (search "FSUB" disassembly2))))
+             #+x86-64
+             (let ((inst (if (typep result 'double-float)
+                             "SUBSD" "SUBSS")))
+               (assert (and (search inst disassembly1)
+                            (not (search inst disassembly2)))))
+             (assert (eql result (funcall fun1 arg)))
+             (assert (eql result (funcall fun2 arg))))))
+    (test `(lambda (x) (declare (single-float x)) (- x 0)) 123.45)
+    (test `(lambda (x) (declare (single-float x)) (- x 0.0)) 543.21)
+    (test `(lambda (x) (declare (single-float x)) (- x 0.0d0)) 42.00 42.d0)
+    (test `(lambda (x) (declare (double-float x)) (- x 0)) 123.45d0)
+    (test `(lambda (x) (declare (double-float x)) (- x 0.0)) 543.21d0)
+    (test `(lambda (x) (declare (double-float x)) (- x 0.0d0)) 42.d0)))
+
+(with-test (:name :float-multiplication-by-two)
+  (flet ((test (lambda-form arg &optional (result arg))
+           (let* ((fun1 (compile nil lambda-form))
+                  (fun2 (funcall (compile nil `(lambda ()
+                                                 (declare (optimize (sb-c::float-accuracy 0)))
+                                                 ,lambda-form))))
+                  (disassembly1 (with-output-to-string (s)
+                                  (disassemble fun1 :stream s)))
+                  (disassembly2 (with-output-to-string (s)
+                                  (disassemble fun2 :stream s))))
+             ;; Let's make sure there is no multiplication at runtime: for x86
+             ;; and x86-64 that implies an FMUL, MULSS, or MULSD instruction,
+             ;; so look for MUL in the disassembly. It's a terrible KLUDGE,
+             ;; but it works.
+             #+(or x86 x86-64)
+             (assert (and (not (search "MUL" disassembly1))
+                          (not (search "MUL" disassembly2))))
+             (assert (eql result (funcall fun1 arg)))
+             (assert (eql result (funcall fun2 arg))))))
+    (test `(lambda (x) (declare (single-float x)) (* x 2)) 123.45 246.9)
+    (test `(lambda (x) (declare (single-float x)) (* x 2.0)) 543.21 1086.42)
+    (test `(lambda (x) (declare (single-float x)) (* x 2.0d0)) 42.00 84.d0)
+    (test `(lambda (x) (declare (double-float x)) (* x 2)) 123.45d0 246.9d0)
+    (test `(lambda (x) (declare (double-float x)) (* x 2.0)) 543.21d0 1086.42d0)
+    (test `(lambda (x) (declare (double-float x)) (* x 2.0d0)) 42.0d0 84.0d0)))