(defun constant-lvar-p (thing)
(declare (type (or lvar null) thing))
(and (lvar-p thing)
- (let ((use (principal-lvar-use thing)))
- (and (ref-p use) (constant-p (ref-leaf use))))))
+ (or (let ((use (principal-lvar-use thing)))
+ (and (ref-p use) (constant-p (ref-leaf use))))
+ ;; check for EQL types (but not singleton numeric types)
+ (let ((type (lvar-type thing)))
+ (and (member-type-p type)
+ (eql 1 (member-type-size type)))))))
;;; Return the constant value for an LVAR whose only use is a constant
;;; node.
(declaim (ftype (function (lvar) t) lvar-value))
(defun lvar-value (lvar)
- (let ((use (principal-lvar-use lvar)))
- (constant-value (ref-leaf use))))
+ (let ((use (principal-lvar-use lvar))
+ (type (lvar-type lvar))
+ leaf)
+ (cond ((and (ref-p use)
+ (constant-p (setf leaf (ref-leaf use))))
+ (constant-value leaf))
+ ((and (member-type-p type)
+ (eql 1 (member-type-size type)))
+ (first (member-type-members type)))
+ (t
+ (error "~S used on non-constant LVAR ~S" 'lvar-value lvar)))))
\f
;;;; interface for obtaining results of type inference
;;; 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)))
- ((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.
(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))
(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)))))
(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)))))
(delete-ref node)
(unlink-node node))
(combination
- (let ((kind (combination-kind node))
- (info (combination-fun-info node)))
- (when (and (eq kind :known) (fun-info-p info))
- (let ((attr (fun-info-attributes info)))
- (when (and (not (ir1-attributep attr call))
- ;; ### For now, don't delete potentially
- ;; flushable calls when they have the CALL
- ;; attribute. Someday we should look at the
- ;; functional args to determine if they have
- ;; any side effects.
- (if (policy node (= safety 3))
- (ir1-attributep attr flushable)
- (ir1-attributep attr unsafely-flushable)))
- (flush-combination node))))))
+ (when (flushable-combination-p node)
+ (flush-combination node)))
(mv-combination
(when (eq (basic-combination-kind node) :local)
(let ((fun (combination-lambda node)))
;;; 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.
(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))
(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.
;;;
(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)
((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
;;; 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
(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))
(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)
\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)
- (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:
;;; 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
(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
(declare (type lvar arg) (type lambda-var var))
(binding* ((ref (first (leaf-refs var)))
(lvar (node-lvar ref) :exit-if-null)
- (dest (lvar-dest lvar)))
+ (dest (lvar-dest lvar))
+ (dest-lvar (when (valued-node-p dest) (node-lvar dest))))
(when (and
;; Think about (LET ((A ...)) (IF ... A ...)): two
;; LVAR-USEs should not be met on one path. Another problem
;; is with dynamic-extent.
(eq (lvar-uses lvar) ref)
(not (block-delete-p (node-block ref)))
+ ;; If the destinatation is dynamic extent, don't substitute unless
+ ;; the source is as well.
+ (or (not dest-lvar)
+ (not (lvar-dynamic-extent dest-lvar))
+ (lvar-dynamic-extent lvar))
(typecase dest
;; we should not change lifetime of unknown values lvars
(cast
*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*)
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))))))
;;; 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))
(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*)
;;; 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))
(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))