1.0.28.2: fix bug 201, Incautious type inference from compound types
[sbcl.git] / src / compiler / ir1opt.lisp
index d1d9e68..c22c25c 100644 (file)
 ;;; The result value is cached in the LVAR-%DERIVED-TYPE slot. If the
 ;;; slot is true, just return that value, otherwise recompute and
 ;;; stash the value there.
+(eval-when (:compile-toplevel :execute)
+  (#+sb-xc-host cl:defmacro
+   #-sb-xc-host sb!xc:defmacro
+        lvar-type-using (lvar accessor)
+     `(let ((uses (lvar-uses ,lvar)))
+        (cond ((null uses) *empty-type*)
+              ((listp uses)
+               (do ((res (,accessor (first uses))
+                         (values-type-union (,accessor (first current))
+                                            res))
+                    (current (rest uses) (rest current)))
+                   ((or (null current) (eq res *wild-type*))
+                    res)))
+              (t
+               (,accessor uses))))))
+
 #!-sb-fluid (declaim (inline lvar-derived-type))
 (defun lvar-derived-type (lvar)
   (declare (type lvar lvar))
       (setf (lvar-%derived-type lvar)
             (%lvar-derived-type lvar))))
 (defun %lvar-derived-type (lvar)
-  (declare (type lvar lvar))
-  (let ((uses (lvar-uses lvar)))
-    (cond ((null uses) *empty-type*)
-          ((listp uses)
-           (do ((res (node-derived-type (first uses))
-                     (values-type-union (node-derived-type (first current))
-                                        res))
-                (current (rest uses) (rest current)))
-               ((or (null current) (eq res *wild-type*))
-                res)))
-          (t
-           (node-derived-type uses)))))
+  (lvar-type-using lvar node-derived-type))
 
 ;;; Return the derived type for LVAR's first value. This is guaranteed
 ;;; not to be a VALUES or FUNCTION type.
 (defun lvar-type (lvar)
   (single-value-type (lvar-derived-type lvar)))
 
+;;; LVAR-CONSERVATIVE-TYPE
+;;;
+;;; Certain types refer to the contents of an object, which can
+;;; change without type derivation noticing: CONS types and ARRAY
+;;; types suffer from this:
+;;;
+;;;  (let ((x (the (cons fixnum fixnum) (cons a b))))
+;;;     (setf (car x) c)
+;;;     (+ (car x) (cdr x)))
+;;;
+;;; Python doesn't realize that the SETF CAR can change the type of X -- so we
+;;; cannot use LVAR-TYPE which gets the derived results. Worse, still, instead
+;;; of (SETF CAR) we might have a call to a user-defined function FOO which
+;;; does the same -- so there is no way to use the derived information in
+;;; general.
+;;;
+;;; So, the conservative option is to use the derived type if the leaf has
+;;; only a single ref -- in which case there cannot be a prior call that
+;;; mutates it. Otherwise we use the declared type or punt to the most general
+;;; type we know to be correct for sure.
+(defun lvar-conservative-type (lvar)
+  (let ((derived-type (lvar-type lvar))
+        (t-type *universal-type*))
+    ;; Recompute using NODE-CONSERVATIVE-TYPE instead of derived type if
+    ;; necessary -- picking off some easy cases up front.
+    (cond ((or (eq derived-type t-type)
+               ;; Can't use CSUBTYPEP!
+               (type= derived-type (specifier-type 'list))
+               (type= derived-type (specifier-type 'null)))
+           derived-type)
+          ((and (cons-type-p derived-type)
+                (eq t-type (cons-type-car-type derived-type))
+                (eq t-type (cons-type-cdr-type derived-type)))
+           derived-type)
+          ((and (array-type-p derived-type)
+                (or (not (array-type-complexp derived-type))
+                    (let ((dimensions (array-type-dimensions derived-type)))
+                      (or (eq '* dimensions)
+                          (every (lambda (dim) (eq '* dim)) dimensions)))))
+           derived-type)
+          ((type-needs-conservation-p derived-type)
+           (single-value-type (lvar-type-using lvar node-conservative-type)))
+          (t
+           derived-type))))
+
+(defun node-conservative-type (node)
+  (let* ((derived-values-type (node-derived-type node))
+         (derived-type (single-value-type derived-values-type)))
+    (if (ref-p node)
+        (let ((leaf (ref-leaf node)))
+          (if (and (basic-var-p leaf)
+                   (cdr (leaf-refs leaf)))
+              (coerce-to-values
+               (if (eq :declared (leaf-where-from leaf))
+                   (leaf-type leaf)
+                   (conservative-type derived-type)))
+              derived-values-type))
+        derived-values-type)))
+
+(defun conservative-type (type)
+  (cond ((or (eq type *universal-type*)
+             (eq type (specifier-type 'list))
+             (eq type (specifier-type 'null)))
+         type)
+        ((cons-type-p type)
+         (specifier-type 'cons))
+        ((array-type-p type)
+         (if (array-type-complexp type)
+             (make-array-type
+              ;; ADJUST-ARRAY may change dimensions, but rank stays same.
+              :dimensions
+              (let ((old (array-type-dimensions type)))
+                (if (eq '* old)
+                    old
+                    (mapcar (constantly '*) old)))
+              ;; Complexity cannot change.
+              :complexp (array-type-complexp type)
+              ;; Element type cannot change.
+              :element-type (array-type-element-type type)
+              :specialized-element-type (array-type-specialized-element-type type))
+             ;; Simple arrays cannot change at all.
+             type))
+        (t
+         ;; If the type contains some CONS types, the conservative type contains all
+         ;; of them.
+         (when (types-equal-or-intersect type (specifier-type 'cons))
+           (setf type (type-union type (specifier-type 'cons))))
+         ;; Similarly for non-simple arrays -- it should be possible to preserve
+         ;; more information here, but really...
+         (let ((non-simple-arrays (specifier-type '(and array (not simple-array)))))
+           (when (types-equal-or-intersect type non-simple-arrays)
+             (setf type (type-union type non-simple-arrays))))
+         type)))
+
+(defun type-needs-conservation-p (type)
+  (cond ((eq type *universal-type*)
+         ;; Excluding T is necessary, because we do want type derivation to
+         ;; be able to narrow it down in case someone (most like a macro-expansion...)
+         ;; actually declares something as having type T.
+         nil)
+        ((or (cons-type-p type) (and (array-type-p type) (array-type-complexp type)))
+         ;; Covered by the next case as well, but this is a quick test.
+         t)
+        ((types-equal-or-intersect type (specifier-type '(or cons (and array (not simple-array)))))
+         t)))
+
 ;;; If LVAR is an argument of a function, return a type which the
 ;;; function checks LVAR for.
 #!-sb-fluid (declaim (inline lvar-externally-checkable-type))
 \f
 ;;;; local call optimization
 
-;;; Propagate TYPE to LEAF and its REFS, marking things changed. If
-;;; the leaf type is a function type, then just leave it alone, since
-;;; TYPE is never going to be more specific than that (and
-;;; TYPE-INTERSECTION would choke.)
+;;; Propagate TYPE to LEAF and its REFS, marking things changed.
+;;;
+;;; If the leaf type is a function type, then just leave it alone, since TYPE
+;;; is never going to be more specific than that (and TYPE-INTERSECTION would
+;;; choke.)
+;;;
+;;; Also, if the type is one requiring special care don't touch it if the leaf
+;;; has multiple references -- otherwise LVAR-CONSERVATIVE-TYPE is screwed.
 (defun propagate-to-refs (leaf type)
   (declare (type leaf leaf) (type ctype type))
-  (let ((var-type (leaf-type leaf)))
-    (unless (fun-type-p var-type)
+  (let ((var-type (leaf-type leaf))
+        (refs (leaf-refs leaf)))
+    (unless (or (fun-type-p var-type)
+                (and (cdr refs)
+                     (eq :declared (leaf-where-from leaf))
+                     (type-needs-conservation-p var-type)))
       (let ((int (type-approx-intersection2 var-type type)))
         (when (type/= int var-type)
           (setf (leaf-type leaf) int)
           (let ((s-int (make-single-value-type int)))
-            (dolist (ref (leaf-refs leaf))
+            (dolist (ref refs)
               (derive-node-type ref s-int)
               ;; KLUDGE: LET var substitution
               (let* ((lvar (node-lvar ref)))