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