Microoptimise TN-LEXICAL-DEPTH
[sbcl.git] / src / compiler / locall.lisp
index f480fcf..d7b3a63 100644 (file)
 (defun recognize-dynamic-extent-lvars (call fun)
   (declare (type combination call) (type clambda fun))
   (loop for arg in (basic-combination-args call)
-        and var in (lambda-vars fun)
-        when (and arg
-                  (lambda-var-dynamic-extent var)
-                  (not (lvar-dynamic-extent arg)))
-        collect arg into dx-lvars
-        and do (let ((use (lvar-uses arg)))
-                 ;; Stack analysis wants DX value generators to end
-                 ;; their blocks. Uses of mupltiple used LVARs already
-                 ;; end their blocks, so we just need to process
-                 ;; used-once LVARs.
-                 (when (node-p use)
-                   (node-ends-block use)))
+        for var in (lambda-vars fun)
+        for dx = (leaf-dynamic-extent var)
+        when (and dx arg (not (lvar-dynamic-extent arg)))
+        append (handle-nested-dynamic-extent-lvars dx arg) into dx-lvars
         finally (when dx-lvars
-                  (binding* ((before-ctran (node-prev call))
-                             (nil (ensure-block-start before-ctran))
-                             (block (ctran-block before-ctran))
-                             (new-call-ctran (make-ctran :kind :inside-block
-                                                         :next call
-                                                         :block block))
-                             (entry (with-ir1-environment-from-node call
-                                      (make-entry :prev before-ctran
-                                                  :next new-call-ctran)))
-                             (cleanup (make-cleanup :kind :dynamic-extent
-                                                    :mess-up entry
-                                                    :info dx-lvars)))
-                    (setf (node-prev call) new-call-ctran)
-                    (setf (ctran-next before-ctran) entry)
-                    (setf (ctran-use new-call-ctran) entry)
+                  ;; Stack analysis requires that the CALL ends the block, so
+                  ;; that MAP-BLOCK-NLXES sees the cleanup we insert here.
+                  (node-ends-block call)
+                  (let* ((entry (with-ir1-environment-from-node call
+                                  (make-entry)))
+                         (cleanup (make-cleanup :kind :dynamic-extent
+                                                :mess-up entry
+                                                :info dx-lvars)))
                     (setf (entry-cleanup entry) cleanup)
+                    (insert-node-before call entry)
                     (setf (node-lexenv call)
                           (make-lexenv :default (node-lexenv call)
                                        :cleanup cleanup))
                     (push entry (lambda-entries (node-home-lambda entry)))
-                    (dolist (lvar dx-lvars)
-                      (setf (lvar-dynamic-extent lvar) cleanup)))))
+                    (dolist (cell dx-lvars)
+                      (setf (lvar-dynamic-extent (cdr cell)) cleanup)))))
   (values))
 
 ;;; This function handles merging the tail sets if CALL is potentially
     (dolist (arg (basic-combination-args call))
       (when arg
         (flush-lvar-externally-checkable-type arg))))
-  (pushnew fun (lambda-calls-or-closes (node-home-lambda call)))
+  (sset-adjoin fun (lambda-calls-or-closes (node-home-lambda call)))
   (recognize-dynamic-extent-lvars call fun)
   (merge-tail-sets call fun)
   (change-ref-leaf ref fun)
           ,(if (policy *lexenv* (zerop verify-arg-count))
                `(declare (ignore ,n-supplied))
                `(%verify-arg-count ,n-supplied ,nargs))
-          (locally
-            (declare (optimize (merge-tail-calls 3)))
-            (%funcall ,fun ,@temps)))))
+          (%funcall ,fun ,@temps))))
     (optional-dispatch
      (let* ((min (optional-dispatch-min-args fun))
             (max (optional-dispatch-max-args fun))
          (optional-dispatch-entry-point-fun fun 0)
          (loop for ep in (optional-dispatch-entry-points fun)
                and n from min
-               do (entries `((= ,n-supplied ,n)
+               do (entries `((eql ,n-supplied ,n)
                              (%funcall ,(force ep) ,@(subseq temps 0 n)))))
          `(lambda (,n-supplied ,@temps)
-            ;; FIXME: Make sure that INDEX type distinguishes between
-            ;; target and host. (Probably just make the SB!XC:DEFTYPE
-            ;; different from CL:DEFTYPE.)
             (declare (type index ,n-supplied))
             (cond
              ,@(if more (butlast (entries)) (entries))
              ,@(when more
-                 `((,(if (zerop min) t `(>= ,n-supplied ,max))
-                    ,(let ((n-context (gensym))
-                           (n-count (gensym)))
+                 ;; KLUDGE: (NOT (< ...)) instead of >= avoids one round of
+                 ;; deftransforms and lambda-conversion.
+                 `((,(if (zerop min) t `(not (< ,n-supplied ,max)))
+                    ,(with-unique-names (n-context n-count)
                        `(multiple-value-bind (,n-context ,n-count)
                             (%more-arg-context ,n-supplied ,max)
-                          (locally
-                            (declare (optimize (merge-tail-calls 3)))
-                            (%funcall ,more ,@temps ,n-context ,n-count)))))))
+                          (%funcall ,more ,@temps ,n-context ,n-count))))))
              (t
               (%arg-count-error ,n-supplied)))))))))
 
   (declare (type functional fun))
   (aver (null (functional-entry-fun fun)))
   (with-ir1-environment-from-node (lambda-bind (main-entry fun))
-    (let ((res (ir1-convert-lambda (make-xep-lambda-expression fun)
+    (let ((xep (ir1-convert-lambda (make-xep-lambda-expression fun)
                                    :debug-name (debug-name
-                                                'xep (leaf-debug-name fun)))))
-      (setf (functional-kind res) :external
-            (leaf-ever-used res) t
-            (functional-entry-fun res) fun
-            (functional-entry-fun fun) res
+                                                'xep (leaf-debug-name fun))
+                                   :system-lambda t)))
+      (setf (functional-kind xep) :external
+            (leaf-ever-used xep) t
+            (functional-entry-fun xep) fun
+            (functional-entry-fun fun) xep
             (component-reanalyze *current-component*) t)
       (reoptimize-component *current-component* :maybe)
-      (etypecase fun
-        (clambda
-         (locall-analyze-fun-1 fun))
-        (optional-dispatch
-         (dolist (ep (optional-dispatch-entry-points fun))
-           (locall-analyze-fun-1 (force ep)))
-         (when (optional-dispatch-more-entry fun)
-           (locall-analyze-fun-1 (optional-dispatch-more-entry fun)))))
-      res)))
+      (locall-analyze-xep-entry-point fun)
+      xep)))
+
+(defun locall-analyze-xep-entry-point (fun)
+  (declare (type functional fun))
+  (etypecase fun
+    (clambda
+     (locall-analyze-fun-1 fun))
+    (optional-dispatch
+     (dolist (ep (optional-dispatch-entry-points fun))
+       (locall-analyze-fun-1 (force ep)))
+     (when (optional-dispatch-more-entry fun)
+       (locall-analyze-fun-1 (optional-dispatch-more-entry fun))))))
 
 ;;; Notice a REF that is not in a local-call context. If the REF is
 ;;; already to an XEP, then do nothing, otherwise change it to the
                                                 component)))))
                (locall-analyze-fun-1 functional)
                (when (lambda-p functional)
-                 (maybe-let-convert functional)))))))
+                 (maybe-let-convert functional component)))))))
   (values))
 
 (defun locall-analyze-clambdas-until-done (clambdas)
   (loop
    (let ((did-something nil))
      (dolist (clambda clambdas)
-       (let* ((component (lambda-component clambda))
-              (*all-components* (list component)))
+       (let ((component (lambda-component clambda)))
          ;; The original CMU CL code seemed to implicitly assume that
          ;; COMPONENT is the only one here. Let's make that explicit.
          (aver (= 1 (length (functional-components clambda))))
   (declare (type ref ref) (type mv-combination call) (type functional fun))
   (when (and (looks-like-an-mv-bind fun)
              (singleton-p (leaf-refs fun))
-             (singleton-p (basic-combination-args call)))
+             (singleton-p (basic-combination-args call))
+             (not (functional-entry-fun fun)))
     (let* ((*current-component* (node-component ref))
            (ep (optional-dispatch-entry-point-fun
                 fun (optional-dispatch-max-args fun))))
       (when (null (leaf-refs ep))
         (aver (= (optional-dispatch-min-args fun) 0))
-        (aver (not (functional-entry-fun fun)))
         (setf (basic-combination-kind call) :local)
-        (pushnew ep (lambda-calls-or-closes (node-home-lambda call)))
+        (sset-adjoin ep (lambda-calls-or-closes (node-home-lambda call)))
         (merge-tail-sets call ep)
         (change-ref-leaf ref ep)
 
          (lexenv-policy (node-lexenv call))))))
   (values))
 
+;;; Convenience function to mark local calls as known bad.
+(defun transform-call-with-ir1-environment (node lambda default-name)
+  (aver (combination-p node))
+  (with-ir1-environment-from-node node
+    (transform-call node lambda
+                    (or (combination-fun-source-name node nil)
+                        default-name))))
+
+(defun warn-invalid-local-call (node count &rest warn-arguments)
+  (aver (combination-p node))
+  (aver (typep count 'unsigned-byte))
+  (apply 'warn warn-arguments)
+  (transform-call-with-ir1-environment node
+                                       `(lambda (&rest args)
+                                          (declare (ignore args))
+                                          (%arg-count-error ,count))
+                                       '%arg-count-error))
+
 ;;; Attempt to convert a call to a lambda. If the number of args is
 ;;; wrong, we give a warning and mark the call as :ERROR to remove it
 ;;; from future consideration. If the argcount is O.K. then we just
     (cond ((= n-call-args nargs)
            (convert-call ref call fun))
           (t
-           (warn
+           (warn-invalid-local-call call n-call-args
             'local-argument-mismatch
             :format-control
             "function called with ~R argument~:P, but wants exactly ~R"
-            :format-arguments (list n-call-args nargs))
-           (setf (basic-combination-kind call) :error)))))
+            :format-arguments (list n-call-args nargs))))))
 \f
 ;;;; &OPTIONAL, &MORE and &KEYWORD calls
 
         (max-args (optional-dispatch-max-args fun))
         (call-args (length (combination-args call))))
     (cond ((< call-args min-args)
-           (warn
+           (warn-invalid-local-call call call-args
             'local-argument-mismatch
             :format-control
             "function called with ~R argument~:P, but wants at least ~R"
-            :format-arguments (list call-args min-args))
-           (setf (basic-combination-kind call) :error))
+            :format-arguments (list call-args min-args)))
           ((<= call-args max-args)
            (convert-call ref call
                          (let ((*current-component* (node-component ref)))
           ((optional-dispatch-more-entry fun)
            (convert-more-call ref call fun))
           (t
-           (warn
+           (warn-invalid-local-call call call-args
             'local-argument-mismatch
             :format-control
             "function called with ~R argument~:P, but wants at most ~R"
             :format-arguments
-            (list call-args max-args))
-           (setf (basic-combination-kind call) :error))))
+            (list call-args max-args)))))
   (values))
 
 ;;; This function is used to convert a call to an entry point when
 ;;; function that rearranges the arguments and calls the entry point.
 ;;; We analyze the new function and the entry point immediately so
 ;;; that everything gets converted during the single pass.
-(defun convert-hairy-fun-entry (ref call entry vars ignores args)
+(defun convert-hairy-fun-entry (ref call entry vars ignores args indef)
   (declare (list vars ignores args) (type ref ref) (type combination call)
            (type clambda entry))
   (let ((new-fun
          (with-ir1-environment-from-node call
            (ir1-convert-lambda
             `(lambda ,vars
-               (declare (ignorable ,@ignores))
+               (declare (ignorable ,@ignores)
+                        (indefinite-extent ,@indef))
                (%funcall ,entry ,@args))
             :debug-name (debug-name 'hairy-function-entry
-                                    (lvar-fun-name
-                                     (basic-combination-fun call)))))))
+                                    (lvar-fun-debug-name
+                                     (basic-combination-fun call)))
+            :system-lambda t))))
     (convert-call ref call new-fun)
     (dolist (ref (leaf-refs entry))
       (convert-call-if-possible ref (lvar-dest (node-lvar ref))))))
         (when (oddp (length more))
           (compiler-warn "function called with odd number of ~
                           arguments in keyword portion")
-          (setf (basic-combination-kind call) :error)
+          (transform-call-with-ir1-environment
+           call
+           `(lambda (&rest args)
+              (declare (ignore args))
+              (%odd-key-args-error))
+           '%odd-key-args-error)
           (return-from convert-more-call))
 
         (do ((key more (cddr key))
         (when (and loser (not (optional-dispatch-allowp fun)) (not allowp))
           (compiler-warn "function called with unknown argument keyword ~S"
                          (car loser))
-          (setf (basic-combination-kind call) :error)
+          (transform-call-with-ir1-environment
+           call
+           `(lambda (&rest args)
+              (declare (ignore args))
+              (%unknown-key-arg-error ',(car loser)))
+           '%unknown-key-arg-error)
           (return-from convert-more-call)))
 
       (collect ((call-args))
                      (call-args t)))
                   (:rest
                    (call-args `(list ,@more-temps))
+                   ;; &REST arguments may be accompanied by extra
+                   ;; context and count arguments. We know this by
+                   ;; the ARG-INFO-DEFAULT. Supply 0 and 0 or
+                   ;; don't convert at all depending.
+                   (let ((more (arg-info-default info)))
+                     (when more
+                       (unless (eq t more)
+                         (destructuring-bind (context count &optional used) more
+                           (declare (ignore context count))
+                           (when used
+                             ;; We've already converted to use the more context
+                             ;; instead of the rest list.
+                             (return-from convert-more-call))))
+                       (call-args 0)
+                       (call-args 0)
+                       (setf (arg-info-default info) t)))
                    (return))
                   (:keyword
                    (return)))
 
         (convert-hairy-fun-entry ref call (optional-dispatch-main-entry fun)
                                  (append temps more-temps)
-                                 (ignores) (call-args)))))
+                                 (ignores) (call-args)
+                                 (when (optional-rest-p fun)
+                                   more-temps)))))
 
   (values))
 \f
     (setf (lambda-physenv clambda) home-physenv)
 
     (when physenv
+      (unless home-physenv
+        (setf home-physenv (get-lambda-physenv home)))
       (setf (physenv-nlx-info home-physenv)
             (nconc (physenv-nlx-info physenv)
                    (physenv-nlx-info home-physenv))))
 
     ;; HOME no longer calls CLAMBDA, and owns all of CLAMBDA's old
     ;; DFO dependencies.
-    (setf (lambda-calls-or-closes home)
-          (delete clambda
-                  (nunion (lambda-calls-or-closes clambda)
-                          (lambda-calls-or-closes home))))
+    (sset-union (lambda-calls-or-closes home)
+                (lambda-calls-or-closes clambda))
+    (sset-delete clambda (lambda-calls-or-closes home))
     ;; CLAMBDA no longer has an independent existence as an entity
     ;; which calls things or has DFO dependencies.
     (setf (lambda-calls-or-closes clambda) nil)
 ;;; the RETURN-RESULT, because the return might have been deleted (if
 ;;; all calls were TR.)
 (defun unconvert-tail-calls (fun call next-block)
-  (dolist (called (lambda-calls-or-closes fun))
+  (do-sset-elements (called (lambda-calls-or-closes fun))
     (when (lambda-p called)
       (dolist (ref (leaf-refs called))
         (let ((this-call (node-dest ref)))
   ;; with anonymous things, and suppressing inlining
   ;; for such things can easily give Python acute indigestion, so
   ;; we don't.)
-  (when (leaf-has-source-name-p clambda)
+  ;;
+  ;; A functional that is already inline-expanded in this componsne definitely
+  ;; deserves let-conversion -- and in case of main entry points for inline
+  ;; expanded optional dispatch, the main-etry isn't explicitly marked :INLINE
+  ;; even if the function really is.
+  (when (and (leaf-has-source-name-p clambda)
+             (not (functional-inline-expanded clambda)))
     ;; ANSI requires that explicit NOTINLINE be respected.
     (or (eq (lambda-inlinep clambda) :notinline)
         ;; If (= LET-CONVERSION 0) we can guess that inlining
                 (eq (component-kind (lambda-component fun))
                     :initial)))))
 
+;;; ir1opt usually takes care of forwarding let-bound values directly
+;;; to their destination when possible.  However, locall analysis
+;;; greatly benefits from that transformation, and is executed in a
+;;; distinct phase from ir1opt.  After let-conversion, variables
+;;; bound to functional values are immediately substituted away.
+;;;
+;;; When called from locall, component is non-nil, and the functionals
+;;; are marked for reanalysis when appropriate.
+(defun substitute-let-funargs (call fun component)
+  (declare (type combination call) (type clambda fun)
+           (type (or null component) component))
+  (loop for arg in (combination-args call)
+        and var in (lambda-vars fun)
+        ;; only do that in the absence of assignment
+        when (and arg (null (lambda-var-sets var)))
+        do
+     (binding* ((use  (lvar-uses arg))
+                (()   (ref-p use) :exit-if-null)
+                (leaf (ref-leaf use))
+                (done-something nil))
+       ;; unlike propagate-let-args, we're only concerned with
+       ;; functionals.
+       (cond ((not (functional-p leaf)))
+             ;; if the types match, we can mutate refs to point to
+             ;;  the functional instead of var
+             ((csubtypep (single-value-type (node-derived-type use))
+                         (leaf-type var))
+              (let ((use-component (node-component use)))
+                (substitute-leaf-if
+                 (lambda (ref)
+                   (cond ((eq (node-component ref) use-component)
+                          (setf done-something t))
+                         (t
+                          (aver (lambda-toplevelish-p (lambda-home fun)))
+                          nil)))
+                 leaf var)))
+             ;; otherwise, we can still play LVAR-level tricks for single
+             ;;  destination variables.
+             ((and (singleton-p (leaf-refs var))
+                   ;; Don't substitute single-ref variables on high-debug /
+                   ;; low speed, to improve the debugging experience.
+                   (not (preserve-single-use-debug-var-p call var)))
+              (setf done-something t)
+              (substitute-single-use-lvar arg var)))
+       ;; if we've done something, the functional may now be used in
+       ;; more analysis-friendly manners.  Enqueue it if we're in
+       ;; locall.
+       (when (and done-something
+                  component
+                  (member leaf (component-lambdas component)))
+         (pushnew leaf (component-reanalyze-functionals component)))))
+  (values))
+
 ;;; This function is called when there is some reason to believe that
 ;;; CLAMBDA might be converted into a LET. This is done after local
 ;;; call analysis, and also when a reference is deleted. We return
 ;;; true if we converted.
-(defun maybe-let-convert (clambda)
-  (declare (type clambda clambda))
-  (unless (declarations-suppress-let-conversion-p clambda)
+;;;
+;;; COMPONENT is non-nil during local call analysis.  It is used to
+;;; re-enqueue functionals for reanalysis when they have been forwarded
+;;; directly to destination nodes.
+(defun maybe-let-convert (clambda &optional component)
+  (declare (type clambda clambda)
+           (type (or null component) component))
+  (unless (or (declarations-suppress-let-conversion-p clambda)
+              (functional-has-external-references-p clambda))
     ;; We only convert to a LET when the function is a normal local
     ;; function, has no XEP, and is referenced in exactly one local
     ;; call. Conversion is also inhibited if the only reference is in
               (let-convert clambda dest))
             (reoptimize-call dest)
             (setf (functional-kind clambda)
-                  (if (mv-combination-p dest) :mv-let :let))))
+                  (if (mv-combination-p dest) :mv-let :let))
+            (when (combination-p dest)  ;  mv-combinations are too hairy
+                                        ;  for me to handle - PK 2012-05-30
+              (substitute-let-funargs dest clambda component))))
         t))))
 \f
 ;;;; tail local calls and assignments
 (defun maybe-convert-to-assignment (clambda)
   (declare (type clambda clambda))
   (when (and (not (functional-kind clambda))
-             (not (functional-entry-fun clambda)))
+             (not (functional-entry-fun clambda))
+             (not (functional-has-external-references-p clambda)))
     (let ((outside-non-tail-call nil)
           (outside-call nil))
       (when (and (dolist (ref (leaf-refs clambda) t)