X-Git-Url: http://repo.macrolet.net/gitweb/?a=blobdiff_plain;f=src%2Fcompiler%2Fir1opt.lisp;h=8604960432f67cddfc92d7959b0a2eef6d105244;hb=e840f481796d191997a47421d60cd039cd260613;hp=f2152d36ad3eedd6bbcebdb9d059aa364d17149e;hpb=151d241aa79f2346ae18d179255fc6b5a2013229;p=sbcl.git diff --git a/src/compiler/ir1opt.lisp b/src/compiler/ir1opt.lisp index f2152d3..8604960 100644 --- a/src/compiler/ir1opt.lisp +++ b/src/compiler/ir1opt.lisp @@ -42,6 +42,22 @@ ;;; 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)) @@ -49,17 +65,7 @@ (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))) - ((null current) res))) - (t - (node-derived-type (lvar-uses lvar)))))) + (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. @@ -67,6 +73,112 @@ (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)) @@ -181,7 +293,7 @@ (lambda-var-p (ref-leaf node))) (let ((type (single-value-type int))) (when (and (member-type-p type) - (null (rest (member-type-members type)))) + (eql 1 (member-type-size type))) (change-ref-leaf node (find-constant (first (member-type-members type))))))) (reoptimize-lvar lvar))))) @@ -305,6 +417,9 @@ (when value (derive-node-type node (lvar-derived-type value))))) (cset + ;; PROPAGATE-FROM-SETS can do a better job if NODE-REOPTIMIZE + ;; is accurate till the node actually has been reoptimized. + (setf (node-reoptimize node) t) (ir1-optimize-set node)) (cast (ir1-optimize-cast node))))) @@ -451,7 +566,7 @@ ;;; appropriate.) ;;; ;;; We call MAYBE-CONVERT-TAIL-LOCAL-CALL on each local non-MV -;;; combination, which may change the succesor of the call to be the +;;; combination, which may change the successor of the call to be the ;;; called function, and if so, checks if the call can become an ;;; assignment. If we convert to an assignment, we abort, since the ;;; RETURN has been deleted. @@ -693,7 +808,10 @@ (setf (lvar-reoptimize arg) nil))) (check-important-result node info) (let ((fun (fun-info-destroyed-constant-args info))) - (when fun + (when (and fun + ;; If somebody is really sure that they want to modify + ;; constants, let them. + (policy node (> check-constant-modification 0))) (let ((destroyed-constant-args (funcall fun args))) (when destroyed-constant-args (let ((*compiler-error-context* node)) @@ -724,20 +842,41 @@ (let ((fun (fun-info-optimizer info))) (unless (and fun (funcall fun node)) - (dolist (x (fun-info-transforms info)) - #!+sb-show - (when *show-transforms-p* - (let* ((lvar (basic-combination-fun node)) - (fname (lvar-fun-name lvar t))) - (/show "trying transform" x (transform-function x) "for" fname))) - (unless (ir1-transform node x) - #!+sb-show - (when *show-transforms-p* - (/show "quitting because IR1-TRANSFORM result was NIL")) - (return)))))))) + ;; First give the VM a peek at the call + (multiple-value-bind (style transform) + (combination-implementation-style node) + (ecase style + (:direct + ;; The VM knows how to handle this. + ) + (:transform + ;; The VM mostly knows how to handle this. We need + ;; to massage the call slightly, though. + (transform-call node transform (combination-fun-source-name node))) + (:default + ;; Let transforms have a crack at it. + (dolist (x (fun-info-transforms info)) + #!+sb-show + (when *show-transforms-p* + (let* ((lvar (basic-combination-fun node)) + (fname (lvar-fun-name lvar t))) + (/show "trying transform" x (transform-function x) "for" fname))) + (unless (ir1-transform node x) + #!+sb-show + (when *show-transforms-p* + (/show "quitting because IR1-TRANSFORM result was NIL")) + (return))))))))))) (values)) +(defun xep-tail-combination-p (node) + (and (combination-p node) + (let* ((lvar (combination-lvar node)) + (dest (when (lvar-p lvar) (lvar-dest lvar))) + (lambda (when (return-p dest) (return-lambda dest)))) + (and (lambda-p lambda) + (eq :external (lambda-kind lambda)))))) + ;;; If NODE doesn't return (i.e. return type is NIL), then terminate ;;; the block there, and link it to the component tail. ;;; @@ -763,7 +902,10 @@ (declare (ignore lvar)) (unless (or (and (eq node (block-last block)) (eq succ tail)) (block-delete-p block)) - (when (eq (node-derived-type node) *empty-type*) + ;; Even if the combination will never return, don't terminate if this + ;; is the tail call of a XEP: doing that would inhibit TCO. + (when (and (eq (node-derived-type node) *empty-type*) + (not (xep-tail-combination-p node))) (cond (ir1-converting-not-optimizing-p (cond ((block-last block) @@ -829,29 +971,42 @@ ((nil :maybe-inline) (policy call (zerop space)))) (defined-fun-p leaf) (defined-fun-inline-expansion leaf) - (let ((fun (defined-fun-functional leaf))) - (or (not fun) - (and (eq inlinep :inline) (functional-kind fun)))) (inline-expansion-ok call)) - (flet (;; FIXME: Is this what the old CMU CL internal documentation - ;; called semi-inlining? A more descriptive name would - ;; be nice. -- WHN 2002-01-07 - (frob () - (let ((res (let ((*allow-instrumenting* t)) - (ir1-convert-lambda-for-defun - (defined-fun-inline-expansion leaf) - leaf t - #'ir1-convert-inline-lambda)))) - (setf (defined-fun-functional leaf) res) + ;; Inline: if the function has already been converted at another call + ;; site in this component, we point this REF to the functional. If not, + ;; we convert the expansion. + ;; + ;; For :INLINE case local call analysis will copy the expansion later, + ;; but for :MAYBE-INLINE and NIL cases we only get one copy of the + ;; expansion per component. + ;; + ;; FIXME: We also convert in :INLINE & FUNCTIONAL-KIND case below. What + ;; is it for? + (flet ((frob () + (let* ((name (leaf-source-name leaf)) + (res (ir1-convert-inline-expansion + name + (defined-fun-inline-expansion leaf) + leaf + inlinep + (info :function :info name)))) + ;; Allow backward references to this function from following + ;; forms. (Reused only if policy matches.) + (push res (defined-fun-functionals leaf)) (change-ref-leaf ref res)))) - (if ir1-converting-not-optimizing-p - (frob) - (with-ir1-environment-from-node call - (frob) - (locall-analyze-component *current-component*)))) - - (values (ref-leaf (lvar-uses (basic-combination-fun call))) - nil)) + (let ((fun (defined-fun-functional leaf))) + (if (or (not fun) + (and (eq inlinep :inline) (functional-kind fun))) + ;; Convert. + (if ir1-converting-not-optimizing-p + (frob) + (with-ir1-environment-from-node call + (frob) + (locall-analyze-component *current-component*))) + ;; If we've already converted, change ref to the converted + ;; functional. + (change-ref-leaf ref fun)))) + (values (ref-leaf ref) nil)) (t (let ((info (info :function :info (leaf-source-name leaf)))) (if info @@ -868,12 +1023,22 @@ ;;; syntax check, arg/result type processing, but still call ;;; RECOGNIZE-KNOWN-CALL, since the call might be to a known lambda, ;;; and that checking is done by local call analysis. -(defun validate-call-type (call type ir1-converting-not-optimizing-p) +(defun validate-call-type (call type defined-type ir1-converting-not-optimizing-p) (declare (type combination call) (type ctype type)) (cond ((not (fun-type-p type)) (aver (multiple-value-bind (val win) (csubtypep type (specifier-type 'function)) (or val (not win)))) + ;; In the commonish case where the function has been defined + ;; in another file, we only get FUNCTION for the type; but we + ;; can check whether the current call is valid for the + ;; existing definition, even if only to STYLE-WARN about it. + (when defined-type + (valid-fun-use call defined-type + :argument-test #'always-subtypep + :result-test nil + :lossage-fun #'compiler-style-warn + :unwinnage-fun #'compiler-notify)) (recognize-known-call call ir1-converting-not-optimizing-p)) ((valid-fun-use call type :argument-test #'always-subtypep @@ -923,7 +1088,7 @@ (derive-node-type call (tail-set-type (lambda-tail-set fun)))))) (:full (multiple-value-bind (leaf info) - (validate-call-type call (lvar-type fun-lvar) nil) + (validate-call-type call (lvar-type fun-lvar) nil nil) (cond ((functional-p leaf) (convert-call-if-possible (lvar-uses (basic-combination-fun call)) @@ -1099,15 +1264,32 @@ (aver (and (legal-fun-name-p source-name) (not (eql source-name '.anonymous.)))) (node-ends-block call) + ;; The internal variables of a transform are not going to be + ;; interesting to the debugger, so there's no sense in + ;; suppressing the substitution of variables with only one use + ;; (the extra variables can slow down constraint propagation). + ;; + ;; This needs to be done before the WITH-IR1-ENVIRONMENT-FROM-NODE, + ;; so that it will bind *LEXENV* to the right environment. + (setf (combination-lexenv call) + (make-lexenv :default (combination-lexenv call) + :policy (process-optimize-decl + '(optimize + (preserve-single-use-debug-variables 0)) + (lexenv-policy + (combination-lexenv call))))) (with-ir1-environment-from-node call (with-component-last-block (*current-component* (block-next (node-block call))) + (let ((new-fun (ir1-convert-inline-lambda res - :debug-name (debug-name 'lambda-inlined source-name))) + :debug-name (debug-name 'lambda-inlined source-name) + :system-lambda t)) (ref (lvar-use (combination-fun call)))) (change-ref-leaf ref new-fun) (setf (combination-kind call) :full) + (maybe-propagate-dynamic-extent call new-fun) (locall-analyze-component *current-component*)))) (values)) @@ -1192,23 +1374,32 @@ ;;;; 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) - (dolist (ref (leaf-refs leaf)) - (derive-node-type ref (make-single-value-type int)) - ;; KLUDGE: LET var substitution - (let* ((lvar (node-lvar ref))) - (when (and lvar (combination-p (lvar-dest lvar))) - (reoptimize-lvar lvar)))))) + (let ((s-int (make-single-value-type int))) + (dolist (ref refs) + (derive-node-type ref s-int) + ;; KLUDGE: LET var substitution + (let* ((lvar (node-lvar ref))) + (when (and lvar (combination-p (lvar-dest lvar))) + (reoptimize-lvar lvar))))))) (values)))) ;;; Iteration variable: exactly one SETQ of the form: @@ -1226,20 +1417,28 @@ (eq (combination-kind set-use) :known) (fun-info-p (combination-fun-info set-use)) (not (node-to-be-deleted-p set-use)) - (eq (combination-fun-source-name set-use) '+)) - :exit-if-null) + (or (eq (combination-fun-source-name set-use) '+) + (eq (combination-fun-source-name set-use) '-))) + :exit-if-null) + (minusp (eq (combination-fun-source-name set-use) '-)) (+-args (basic-combination-args set-use)) (() (and (proper-list-of-length-p +-args 2 2) (let ((first (principal-lvar-use (first +-args)))) (and (ref-p first) (eq (ref-leaf first) var)))) - :exit-if-null) + :exit-if-null) (step-type (lvar-type (second +-args))) (set-type (lvar-type (set-value set)))) (when (and (numeric-type-p initial-type) (numeric-type-p step-type) - (numeric-type-equal initial-type step-type)) + (or (numeric-type-equal initial-type step-type) + ;; Detect cases like (LOOP FOR 1.0 to 5.0 ...), where + ;; the initial and the step are of different types, + ;; and the step is less contagious. + (numeric-type-equal initial-type + (numeric-contagion initial-type + step-type)))) (labels ((leftmost (x y cmp cmp=) (cond ((eq x nil) nil) ((eq y nil) nil) @@ -1256,22 +1455,27 @@ (t (if (funcall cmp x y) x y)))) (max* (x y) (leftmost x y #'> #'>=)) (min* (x y) (leftmost x y #'< #'<=))) - (declare (inline compare)) (multiple-value-bind (low high) - (cond ((csubtypep step-type (specifier-type '(real 0 *))) - (values (numeric-type-low initial-type) - (when (and (numeric-type-p set-type) - (numeric-type-equal set-type initial-type)) - (max* (numeric-type-high initial-type) - (numeric-type-high set-type))))) - ((csubtypep step-type (specifier-type '(real * 0))) - (values (when (and (numeric-type-p set-type) - (numeric-type-equal set-type initial-type)) - (min* (numeric-type-low initial-type) - (numeric-type-low set-type))) - (numeric-type-high initial-type))) - (t - (values nil nil))) + (let ((step-type-non-negative (csubtypep step-type (specifier-type + '(real 0 *)))) + (step-type-non-positive (csubtypep step-type (specifier-type + '(real * 0))))) + (cond ((or (and step-type-non-negative (not minusp)) + (and step-type-non-positive minusp)) + (values (numeric-type-low initial-type) + (when (and (numeric-type-p set-type) + (numeric-type-equal set-type initial-type)) + (max* (numeric-type-high initial-type) + (numeric-type-high set-type))))) + ((or (and step-type-non-positive (not minusp)) + (and step-type-non-negative minusp)) + (values (when (and (numeric-type-p set-type) + (numeric-type-equal set-type initial-type)) + (min* (numeric-type-low initial-type) + (numeric-type-low set-type))) + (numeric-type-high initial-type))) + (t + (values nil nil)))) (modified-numeric-type initial-type :low low :high high @@ -1291,17 +1495,22 @@ ;;; the union of the INITIAL-TYPE and the types of all the set ;;; values and to a PROPAGATE-TO-REFS with this type. (defun propagate-from-sets (var initial-type) - (collect ((res initial-type type-union)) - (dolist (set (basic-var-sets var)) + (let ((changes (not (csubtypep (lambda-var-last-initial-type var) initial-type))) + (types nil)) + (dolist (set (lambda-var-sets var)) (let ((type (lvar-type (set-value set)))) - (res type) + (push type types) (when (node-reoptimize set) - (derive-node-type set (make-single-value-type type)) + (let ((old-type (node-derived-type set))) + (unless (values-subtypep old-type type) + (derive-node-type set (make-single-value-type type)) + (setf changes t))) (setf (node-reoptimize set) nil)))) - (let ((res (res))) - (awhen (maybe-infer-iteration-var-type var initial-type) - (setq res it)) - (propagate-to-refs var res))) + (when changes + (setf (lambda-var-last-initial-type var) initial-type) + (let ((res-type (or (maybe-infer-iteration-var-type var initial-type) + (apply #'type-union initial-type types)))) + (propagate-to-refs var res-type)))) (values)) ;;; If a LET variable, find the initial value's type and do @@ -1317,9 +1526,9 @@ (initial-type (lvar-type initial-value))) (setf (lvar-reoptimize initial-value) nil) (propagate-from-sets var initial-type)))))) - (derive-node-type node (make-single-value-type (lvar-type (set-value node)))) + (setf (node-reoptimize node) nil) (values)) ;;; Return true if the value of REF will always be the same (and is @@ -1394,8 +1603,8 @@ *policy*))) (setf (cast-type-to-check cast) *wild-type*) (substitute-lvar-uses value arg - ;; FIXME - t) + ;; FIXME + t) (%delete-lvar-use ref) (add-lvar-use cast lvar))))) (setf (node-derived-type ref) *wild-type*) @@ -1474,6 +1683,9 @@ leaf var))) t))))) ((and (null (rest (leaf-refs var))) + ;; Don't substitute single-ref variables on high-debug / + ;; low speed, to improve the debugging experience. + (policy call (< preserve-single-use-debug-variables 3)) (substitute-single-use-lvar arg var))) (t (propagate-to-refs var (lvar-type arg)))))) @@ -1497,7 +1709,6 @@ ;;; right here. (defun propagate-local-call-args (call fun) (declare (type combination call) (type clambda fun)) - (unless (or (functional-entry-fun fun) (lambda-optional-dispatch fun)) (let* ((vars (lambda-vars fun)) @@ -1665,10 +1876,13 @@ (with-ir1-environment-from-node node (let* ((dums (make-gensym-list count)) (ignore (gensym)) + (leaf (ref-leaf ref)) (fun (ir1-convert-lambda `(lambda (&optional ,@dums &rest ,ignore) (declare (ignore ,ignore)) - (funcall ,(ref-leaf ref) ,@dums))))) + (%funcall ,leaf ,@dums)) + :source-name (leaf-%source-name leaf) + :debug-name (leaf-%debug-name leaf)))) (change-ref-leaf ref fun) (aver (eq (basic-combination-kind node) :full)) (locall-analyze-component *current-component*) @@ -1785,6 +1999,17 @@ ;;; TODO: ;;; - CAST chains; +(defun delete-cast (cast) + (declare (type cast cast)) + (let ((value (cast-value cast)) + (lvar (node-lvar cast))) + (delete-filter cast lvar value) + (when lvar + (reoptimize-lvar lvar) + (when (lvar-single-value-p lvar) + (note-single-valuified-lvar lvar))) + (values))) + (defun ir1-optimize-cast (cast &optional do-not-optimize) (declare (type cast cast)) (let ((value (cast-value cast)) @@ -1793,11 +2018,7 @@ (let ((lvar (node-lvar cast))) (when (values-subtypep (lvar-derived-type value) (cast-asserted-type cast)) - (delete-filter cast lvar value) - (when lvar - (reoptimize-lvar lvar) - (when (lvar-single-value-p lvar) - (note-single-valuified-lvar lvar))) + (delete-cast cast) (return-from ir1-optimize-cast t)) (when (and (listp (lvar-uses value)) @@ -1866,3 +2087,6 @@ (unless do-not-optimize (setf (node-reoptimize cast) nil))) + +(deftransform make-symbol ((string) (simple-string)) + `(%make-symbol string))