1.0.28.19: faster ARRAY-DIMENSION for non-vectors
[sbcl.git] / src / compiler / array-tran.lisp
index ffc9dda..1aeb26e 100644 (file)
          "upgraded array element type not known at compile time")
         element-type-specifier)))
 
-;;; Array access functions return an object from the array, hence its
-;;; type is going to be the array upgraded element type.
+;;; Array access functions return an object from the array, hence its type is
+;;; going to be the array upgraded element type. Secondary return value is the
+;;; known supertype of the upgraded-array-element-type, if if the exact
+;;; U-A-E-T is not known. (If it is NIL, the primary return value is as good
+;;; as it gets.)
 (defun extract-upgraded-element-type (array)
   (let ((type (lvar-type array)))
     (cond
       ;; we can have values declared e.g. (AND SIMPLE-VECTOR UNKNOWN-TYPE),
       ;; which are represented in the compiler as INTERSECTION-TYPE, not
       ;; array type.
-      ((array-type-p type) (array-type-specialized-element-type type))
-      ;; fix for bug #396.  This type logic corresponds to the special
-      ;; case for strings in HAIRY-DATA-VECTOR-REF
-      ;; (generic/vm-tran.lisp)
-      ((csubtypep type (specifier-type 'simple-string))
+      ((array-type-p type)
+       (values (array-type-specialized-element-type type) nil))
+      ;; fix for bug #396. This type logic corresponds to the special case for
+      ;; strings in HAIRY-DATA-VECTOR-REF (generic/vm-tran.lisp)
+      ((csubtypep type (specifier-type 'string))
        (cond
-         ((csubtypep type (specifier-type '(simple-array character (*))))
-          (specifier-type 'character))
+         ((csubtypep type (specifier-type '(array character (*))))
+          (values (specifier-type 'character) nil))
          #!+sb-unicode
-         ((csubtypep type (specifier-type '(simple-array base-char (*))))
-          (specifier-type 'base-char))
-         ((csubtypep type (specifier-type '(simple-array nil (*))))
-          *empty-type*)
-         ;; see KLUDGE below.
-         (t *wild-type*)))
+         ((csubtypep type (specifier-type '(array base-char (*))))
+          (values (specifier-type 'base-char) nil))
+         ((csubtypep type (specifier-type '(array nil (*))))
+          (values *empty-type* nil))
+         (t
+          ;; See KLUDGE below.
+          (values *wild-type* (specifier-type 'character)))))
       (t
        ;; KLUDGE: there is no good answer here, but at least
        ;; *wild-type* won't cause HAIRY-DATA-VECTOR-{REF,SET} to be
        ;; erroneously optimized (see generic/vm-tran.lisp) -- CSR,
        ;; 2002-08-21
-       *wild-type*))))
+       (values *wild-type* nil)))))
 
 (defun extract-declared-element-type (array)
   (let ((type (lvar-type array)))
    (specifier-type `(array * ,(make-list rank :initial-element '*)))
    (lexenv-policy (node-lexenv (lvar-dest array)))))
 
+(defun derive-aref-type (array)
+  (multiple-value-bind (uaet other) (extract-upgraded-element-type array)
+    (or other uaet)))
+
 (defoptimizer (array-in-bounds-p derive-type) ((array &rest indices))
   (assert-array-rank array (length indices))
   *universal-type*)
 
 (defoptimizer (aref derive-type) ((array &rest indices) node)
   (assert-array-rank array (length indices))
-  (extract-upgraded-element-type array))
+  (derive-aref-type array))
 
 (defoptimizer (%aset derive-type) ((array &rest stuff))
   (assert-array-rank array (1- (length stuff)))
   (assert-new-value-type (car (last stuff)) array))
 
-(defoptimizer (hairy-data-vector-ref derive-type) ((array index))
-  (extract-upgraded-element-type array))
-(defoptimizer (data-vector-ref derive-type) ((array index))
-  (extract-upgraded-element-type array))
-#!+x86
+(macrolet ((define (name)
+             `(defoptimizer (,name derive-type) ((array index))
+                (derive-aref-type array))))
+  (define hairy-data-vector-ref)
+  (define hairy-data-vector-ref/check-bounds)
+  (define data-vector-ref))
+
+#!+(or x86 x86-64)
 (defoptimizer (data-vector-ref-with-offset derive-type) ((array index offset))
-  (extract-upgraded-element-type array))
+  (derive-aref-type array))
 
-(defoptimizer (data-vector-set derive-type) ((array index new-value))
-  (assert-new-value-type new-value array))
-#!+x86
+(macrolet ((define (name)
+             `(defoptimizer (,name derive-type) ((array index new-value))
+                (assert-new-value-type new-value array))))
+  (define hairy-data-vector-set)
+  (define hairy-data-vector-set/check-bounds)
+  (define data-vector-set))
+
+#!+(or x86 x86-64)
 (defoptimizer (data-vector-set-with-offset derive-type) ((array index offset new-value))
   (assert-new-value-type new-value array))
-(defoptimizer (hairy-data-vector-set derive-type) ((array index new-value))
-  (assert-new-value-type new-value array))
 
 ;;; Figure out the type of the data vector if we know the argument
 ;;; element type.
-(defoptimizer (%with-array-data derive-type) ((array start end))
+(defun derive-%with-array-data/mumble-type (array)
   (let ((atype (lvar-type array)))
     (when (array-type-p atype)
       (specifier-type
        `(simple-array ,(type-specifier
-                       (array-type-specialized-element-type atype))
-                     (*))))))
+                        (array-type-specialized-element-type atype))
+                      (*))))))
+(defoptimizer (%with-array-data derive-type) ((array start end))
+  (derive-%with-array-data/mumble-type array))
+(defoptimizer (%with-array-data/fp derive-type) ((array start end))
+  (derive-%with-array-data/mumble-type array))
 
 (defoptimizer (array-row-major-index derive-type) ((array &rest indices))
   (assert-array-rank array (length indices))
   *universal-type*)
 
 (defoptimizer (row-major-aref derive-type) ((array index))
-  (extract-upgraded-element-type array))
+  (derive-aref-type array))
 
 (defoptimizer (%set-row-major-aref derive-type) ((array index new-value))
   (assert-new-value-type new-value array))
                                ,@(when element-type
                                    '(:element-type element-type))))
              (setf (%array-displaced-p header) nil)
+             (setf (%array-displaced-from header) nil)
              ,@(let ((axis -1))
                  (mapcar (lambda (dim)
                            `(setf (%array-dimension header ,(incf axis))
 (deftransform array-rank ((array))
   (let ((array-type (lvar-type array)))
     (let ((dims (array-type-dimensions-or-give-up array-type)))
-      (if (not (listp dims))
-          (give-up-ir1-transform
-           "The array rank is not known at compile time: ~S"
-           dims)
-          (length dims)))))
+      (cond ((listp dims)
+             (length dims))
+            ((eq t (array-type-complexp array-type))
+             '(%array-rank array))
+            (t
+             `(if (array-header-p array)
+                  (%array-rank array)
+                  1))))))
 
 ;;; If we know the dimensions at compile time, just use it. Otherwise,
 ;;; if we can tell that the axis is in bounds, convert to
                                (array index))
   (unless (constant-lvar-p axis)
     (give-up-ir1-transform "The axis is not constant."))
-  (let ((array-type (lvar-type array))
+  ;; Dimensions may change thanks to ADJUST-ARRAY, so we need the
+  ;; conservative type.
+  (let ((array-type (lvar-conservative-type array))
         (axis (lvar-value axis)))
     (let ((dims (array-type-dimensions-or-give-up array-type)))
       (unless (listp dims)
                  ((t)
                   '(%array-dimension array 0))
                  ((nil)
-                  '(length array))
+                  '(vector-length array))
                  ((:maybe)
-                  (give-up-ir1-transform
-                   "can't tell whether array is simple"))))
+                  `(if (array-header-p array)
+                       (%array-dimension array axis)
+                       (vector-length array)))))
               (t
                '(%array-dimension array axis)))))))
 
          (give-up-ir1-transform))
         (t
          (let ((dim (lvar-value dimension)))
+           ;; FIXME: Can SPEED > SAFETY weaken this check to INTEGER?
            `(the (integer 0 (,dim)) index)))))
 \f
 ;;;; WITH-ARRAY-DATA
 (def!macro with-array-data (((data-var array &key offset-var)
                              (start-var &optional (svalue 0))
                              (end-var &optional (evalue nil))
-                             &key force-inline)
-                            &body forms)
+                             &key force-inline check-fill-pointer)
+                            &body forms
+                            &environment env)
   (once-only ((n-array array)
               (n-svalue `(the index ,svalue))
               (n-evalue `(the (or index null) ,evalue)))
-    `(multiple-value-bind (,data-var
-                           ,start-var
-                           ,end-var
-                           ,@(when offset-var `(,offset-var)))
-         (if (not (array-header-p ,n-array))
-             (let ((,n-array ,n-array))
-               (declare (type (simple-array * (*)) ,n-array))
-               ,(once-only ((n-len `(length ,n-array))
-                            (n-end `(or ,n-evalue ,n-len)))
-                  `(if (<= ,n-svalue ,n-end ,n-len)
-                       ;; success
-                       (values ,n-array ,n-svalue ,n-end 0)
-                       (failed-%with-array-data ,n-array
-                                                ,n-svalue
-                                                ,n-evalue))))
-             (,(if force-inline '%with-array-data-macro '%with-array-data)
-              ,n-array ,n-svalue ,n-evalue))
-       ,@forms)))
+    (let ((check-bounds (policy env (plusp insert-array-bounds-checks))))
+      `(multiple-value-bind (,data-var
+                             ,start-var
+                             ,end-var
+                             ,@(when offset-var `(,offset-var)))
+           (if (not (array-header-p ,n-array))
+               (let ((,n-array ,n-array))
+                 (declare (type (simple-array * (*)) ,n-array))
+                 ,(once-only ((n-len (if check-fill-pointer
+                                         `(length ,n-array)
+                                         `(array-total-size ,n-array)))
+                              (n-end `(or ,n-evalue ,n-len)))
+                             (if check-bounds
+                                 `(if (<= 0 ,n-svalue ,n-end ,n-len)
+                                      (values ,n-array ,n-svalue ,n-end 0)
+                                      ,(if check-fill-pointer
+                                           `(sequence-bounding-indices-bad-error ,n-array ,n-svalue ,n-evalue)
+                                           `(array-bounding-indices-bad-error ,n-array ,n-svalue ,n-evalue)))
+                                 `(values ,n-array ,n-svalue ,n-end 0))))
+               ,(if force-inline
+                    `(%with-array-data-macro ,n-array ,n-svalue ,n-evalue
+                                             :check-bounds ,check-bounds
+                                             :check-fill-pointer ,check-fill-pointer)
+                    (if check-fill-pointer
+                        `(%with-array-data/fp ,n-array ,n-svalue ,n-evalue)
+                        `(%with-array-data ,n-array ,n-svalue ,n-evalue))))
+         ,@forms))))
 
 ;;; This is the fundamental definition of %WITH-ARRAY-DATA, for use in
 ;;; DEFTRANSFORMs and DEFUNs.
                                    end
                                    &key
                                    (element-type '*)
-                                   unsafe?
-                                   fail-inline?)
+                                   check-bounds
+                                   check-fill-pointer)
   (with-unique-names (size defaulted-end data cumulative-offset)
-    `(let* ((,size (array-total-size ,array))
-            (,defaulted-end
-              (cond (,end
-                     (unless (or ,unsafe? (<= ,end ,size))
-                       ,(if fail-inline?
-                            `(error 'bounding-indices-bad-error
-                              :datum (cons ,start ,end)
-                              :expected-type `(cons (integer 0 ,',size)
-                                                    (integer ,',start ,',size))
-                              :object ,array)
-                            `(failed-%with-array-data ,array ,start ,end)))
-                     ,end)
-                    (t ,size))))
-       (unless (or ,unsafe? (<= ,start ,defaulted-end))
-         ,(if fail-inline?
-              `(error 'bounding-indices-bad-error
-                :datum (cons ,start ,end)
-                :expected-type `(cons (integer 0 ,',size)
-                                      (integer ,',start ,',size))
-                :object ,array)
-              `(failed-%with-array-data ,array ,start ,end)))
+    `(let* ((,size ,(if check-fill-pointer
+                        `(length ,array)
+                        `(array-total-size ,array)))
+            (,defaulted-end (or ,end ,size)))
+       ,@(when check-bounds
+               `((unless (<= ,start ,defaulted-end ,size)
+                   ,(if check-fill-pointer
+                        `(sequence-bounding-indices-bad-error ,array ,start ,end)
+                        `(array-bounding-indices-bad-error ,array ,start ,end)))))
        (do ((,data ,array (%array-data-vector ,data))
             (,cumulative-offset 0
                                 (+ ,cumulative-offset
                     (the index ,cumulative-offset)))
          (declare (type index ,cumulative-offset))))))
 
+(defun transform-%with-array-data/muble (array node check-fill-pointer)
+  (let ((element-type (upgraded-element-type-specifier-or-give-up array))
+        (type (lvar-type array))
+        (check-bounds (policy node (plusp insert-array-bounds-checks))))
+    (if (and (array-type-p type)
+             (not (array-type-complexp type))
+             (listp (array-type-dimensions type))
+             (not (null (cdr (array-type-dimensions type)))))
+        ;; If it's a simple multidimensional array, then just return
+        ;; its data vector directly rather than going through
+        ;; %WITH-ARRAY-DATA-MACRO. SBCL doesn't generally generate
+        ;; code that would use this currently, but we have encouraged
+        ;; users to use WITH-ARRAY-DATA and we may use it ourselves at
+        ;; some point in the future for optimized libraries or
+        ;; similar.
+        (if check-bounds
+            `(let* ((data (truly-the (simple-array ,element-type (*))
+                                     (%array-data-vector array)))
+                    (len (length data))
+                    (real-end (or end len)))
+               (unless (<= 0 start data-end lend)
+                 (sequence-bounding-indices-bad-error array start end))
+               (values data 0 real-end 0))
+            `(let ((data (truly-the (simple-array ,element-type (*))
+                                    (%array-data-vector array))))
+               (values data 0 (or end (length data)) 0)))
+        `(%with-array-data-macro array start end
+                                 :check-fill-pointer ,check-fill-pointer
+                                 :check-bounds ,check-bounds
+                                 :element-type ,element-type))))
+
+;; It might very well be reasonable to allow general ARRAY here, I
+;; just haven't tried to understand the performance issues involved.
+;; -- WHN, and also CSR 2002-05-26
 (deftransform %with-array-data ((array start end)
-                                ;; It might very well be reasonable to
-                                ;; allow general ARRAY here, I just
-                                ;; haven't tried to understand the
-                                ;; performance issues involved. --
-                                ;; WHN, and also CSR 2002-05-26
-                                ((or vector simple-array) index (or index null))
+                                ((or vector simple-array) index (or index null) t)
                                 *
                                 :node node
                                 :policy (> speed space))
   "inline non-SIMPLE-vector-handling logic"
-  (let ((element-type (upgraded-element-type-specifier-or-give-up array)))
-    `(%with-array-data-macro array start end
-                             :unsafe? ,(policy node (= safety 0))
-                             :element-type ,element-type)))
+  (transform-%with-array-data/muble array node nil))
+(deftransform %with-array-data/fp ((array start end)
+                                ((or vector simple-array) index (or index null) t)
+                                *
+                                :node node
+                                :policy (> speed space))
+  "inline non-SIMPLE-vector-handling logic"
+  (transform-%with-array-data/muble array node t))
 \f
 ;;;; array accessors
 
       (with-row-major-index (array indices index new-value)
         (hairy-data-vector-set array index new-value)))))
 
-(deftransform aref ((array index) ((or simple-vector
-                                       simple-unboxed-array)
-                                   index))
-  (let ((type (lvar-type array)))
-    (unless (array-type-p type)
-      ;; Not an exactly specified one-dimensional simple array -> punt
-      ;; to the complex version.
-      (give-up-ir1-transform)))
-  `(data-vector-ref array (%check-bound array
-                                        (array-dimension array 0)
-                                        index)))
+;; For AREF of vectors we do the bounds checking in the callee. This
+;; lets us do a significantly more efficient check for simple-arrays
+;; without bloating the code. If we already know the type of the array
+;; with sufficient precision, skip directly to DATA-VECTOR-REF.
+(deftransform aref ((array index) (t t) * :node node)
+  (let* ((type (lvar-type array))
+         (element-ctype (extract-upgraded-element-type array)))
+    (cond
+      ((and (array-type-p type)
+            (null (array-type-complexp type))
+            (not (eql element-ctype *wild-type*))
+            (eql (length (array-type-dimensions type)) 1))
+       (let* ((declared-element-ctype (extract-declared-element-type array))
+              (bare-form
+               `(data-vector-ref array
+                 (%check-bound array (array-dimension array 0) index))))
+         (if (type= declared-element-ctype element-ctype)
+             bare-form
+             `(the ,(type-specifier declared-element-ctype) ,bare-form))))
+      ((policy node (zerop insert-array-bounds-checks))
+       `(hairy-data-vector-ref array index))
+      (t `(hairy-data-vector-ref/check-bounds array index)))))
+
+(deftransform %aset ((array index new-value) (t t t) * :node node)
+  (if (policy node (zerop insert-array-bounds-checks))
+      `(hairy-data-vector-set array index new-value)
+      `(hairy-data-vector-set/check-bounds array index new-value)))
+
+;;; But if we find out later that there's some useful type information
+;;; available, switch back to the normal one to give other transforms
+;;; a stab at it.
+(macrolet ((define (name transform-to extra extra-type)
+             (declare (ignore extra-type))
+             `(deftransform ,name ((array index ,@extra))
+                (let ((type (lvar-type array))
+                      (element-type (extract-upgraded-element-type array)))
+                  ;; If an element type has been declared, we want to
+                  ;; use that information it for type checking (even
+                  ;; if the access can't be optimized due to the array
+                  ;; not being simple).
+                  (when (and (eql element-type *wild-type*)
+                             ;; This type logic corresponds to the special
+                             ;; case for strings in HAIRY-DATA-VECTOR-REF
+                             ;; (generic/vm-tran.lisp)
+                             (not (csubtypep type (specifier-type 'simple-string))))
+                    (when (or (not (array-type-p type))
+                              ;; If it's a simple array, we might be able
+                              ;; to inline the access completely.
+                              (not (null (array-type-complexp type))))
+                      (give-up-ir1-transform
+                       "Upgraded element type of array is not known at compile time."))))
+                `(,',transform-to array
+                                  (%check-bound array
+                                                (array-dimension array 0)
+                                                index)
+                                  ,@',extra))))
+  (define hairy-data-vector-ref/check-bounds
+      hairy-data-vector-ref nil nil)
+  (define hairy-data-vector-set/check-bounds
+      hairy-data-vector-set (new-value) (*)))
 
 ;;; Just convert into a HAIRY-DATA-VECTOR-REF (or
 ;;; HAIRY-DATA-VECTOR-SET) after checking that the index is inside the