;;;; This file contains the lifetime analysis phase in the compiler.

;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.

(in-package "SB!C")

;;;; utilities

;;; Link in a GLOBAL-CONFLICTS structure for TN in BLOCK with NUMBER
;;; as the LTN number. The conflict is inserted in the per-TN
;;; GLOBAL-CONFLICTS thread after the TN's CURRENT-CONFLICT. We change
;;; the CURRENT-CONFLICT to point to the new conflict. Since we scan
;;; the blocks in reverse DFO, this list is automatically built in
;;; order. We have to actually scan the current GLOBAL-TNs for the
;;; block in order to keep that thread sorted.
(defun add-global-conflict (kind tn block number)
  (declare (type (member :read :write :read-only :live) kind)
           (type tn tn) (type ir2-block block)
           (type (or local-tn-number null) number))
  (let ((new (make-global-conflicts kind tn block number)))
    (let ((last (tn-current-conflict tn)))
      (if last
          (shiftf (global-conflicts-next-tnwise new)
                  (global-conflicts-next-tnwise last)
                  new)
          (shiftf (global-conflicts-next-tnwise new)
                  (tn-global-conflicts tn)
                  new)))
    (setf (tn-current-conflict tn) new)

    (insert-block-global-conflict new block))
  (values))

;;; Do the actual insertion of the conflict NEW into BLOCK's global
;;; conflicts.
(defun insert-block-global-conflict (new block)
  (let ((global-num (tn-number (global-conflicts-tn new))))
    (do ((prev nil conf)
         (conf (ir2-block-global-tns block)
               (global-conflicts-next-blockwise conf)))
        ((or (null conf)
             (> (tn-number (global-conflicts-tn conf)) global-num))
         (if prev
             (setf (global-conflicts-next-blockwise prev) new)
             (setf (ir2-block-global-tns block) new))
         (setf (global-conflicts-next-blockwise new) conf))))
  (values))

;;; Reset the CURRENT-CONFLICT slot in all packed TNs to point to the
;;; head of the GLOBAL-CONFLICTS thread.
(defun reset-current-conflict (component)
  (do-packed-tns (tn component)
    (setf (tn-current-conflict tn) (tn-global-conflicts tn))))

;;; Cache the results of BLOCK-PHYSENV during lifetime analysis.
;;;
;;; Fetching the home-lambda of a block (needed in block-physenv) can
;;; be an expensive operation under some circumstances, and it needs
;;; to be done a lot during lifetime analysis when compiling with high
;;; DEBUG (e.g. 30% of the total compilation time for CL-PPCRE with
;;; DEBUG 3 just for that).
(defun cached-block-physenv (block)
  (let ((physenv (block-physenv-cache block)))
    (if (eq physenv :none)
        (setf (block-physenv-cache block)
              (block-physenv block))
        physenv)))

;;;; pre-pass

;;; Convert TN (currently local) to be a global TN, since we
;;; discovered that it is referenced in more than one block. We just
;;; add a global-conflicts structure with a kind derived from the KILL
;;; and LIVE sets.
(defun convert-to-global (tn)
  (declare (type tn tn))
  (let ((block (tn-local tn))
        (num (tn-local-number tn)))
    (add-global-conflict
     (if (zerop (sbit (ir2-block-written block) num))
         :read-only
         (if (zerop (sbit (ir2-block-live-out block) num))
             :write
             :read))
     tn block num))
  (values))

;;; Scan all references to packed TNs in block. We assign LTN numbers
;;; to each referenced TN, and also build the Kill and Live sets that
;;; summarize the references to each TN for purposes of lifetime
;;; analysis.
;;;
;;; It is possible that we will run out of LTN numbers. If this
;;; happens, then we return the VOP that we were processing at the
;;; time we ran out, otherwise we return NIL.
;;;
;;; If a TN is referenced in more than one block, then we must
;;; represent references using GLOBAL-CONFLICTS structures. When we
;;; first see a TN, we assume it will be local. If we see a reference
;;; later on in a different block, then we go back and fix the TN to
;;; global.
;;;
;;; We must globalize TNs that have a block other than the current one
;;; in their LOCAL slot and have no GLOBAL-CONFLICTS. The latter
;;; condition is necessary because we always set Local and
;;; LOCAL-NUMBER when we process a reference to a TN, even when the TN
;;; is already known to be global.
;;;
;;; When we see reference to global TNs during the scan, we add the
;;; global-conflict as :READ-ONLY, since we don't know the correct
;;; kind until we are done scanning the block.
(defun find-local-references (block)
  (declare (type ir2-block block))
  (let ((kill (ir2-block-written block))
        (live (ir2-block-live-out block))
        (tns (ir2-block-local-tns block)))
    (let ((ltn-num (ir2-block-local-tn-count block)))
      (do ((vop (ir2-block-last-vop block)
                (vop-prev vop)))
          ((null vop))
        (do ((ref (vop-refs vop) (tn-ref-next-ref ref)))
            ((null ref))
          (let* ((tn (tn-ref-tn ref))
                 (local (tn-local tn))
                 (kind (tn-kind tn)))
            (unless (member kind '(:component :environment :constant))
              (unless (eq local block)
                (when (= ltn-num local-tn-limit)
                  (return-from find-local-references vop))
                (when local
                  (unless (tn-global-conflicts tn)
                    (convert-to-global tn))
                  (add-global-conflict :read-only tn block ltn-num))

                (setf (tn-local tn) block)
                (setf (tn-local-number tn) ltn-num)
                (setf (svref tns ltn-num) tn)
                (incf ltn-num))

              (let ((num (tn-local-number tn)))
                (if (tn-ref-write-p ref)
                    (setf (sbit kill num) 1  (sbit live num) 0)
                    (setf (sbit live num) 1)))))))

      (setf (ir2-block-local-tn-count block) ltn-num)))
  nil)

;;; Finish up the global conflicts for TNs referenced in BLOCK
;;; according to the local Kill and Live sets.
;;;
;;; We set the kind for TNs already in the global-TNs. If not written
;;; at all, then is :READ-ONLY, the default. Must have been referenced
;;; somehow, or we wouldn't have conflicts for it.
;;;
;;; We also iterate over all the local TNs, looking for TNs local to
;;; this block that are still live at the block beginning, and thus
;;; must be global. This case is only important when a TN is read in a
;;; block but not written in any other, since otherwise the write
;;; would promote the TN to global. But this does happen with various
;;; passing-location TNs that are magically written. This also serves
;;; to propagate the lives of erroneously uninitialized TNs so that
;;; consistency checks can detect them.
(defun init-global-conflict-kind (block)
  (declare (type ir2-block block))
  (let ((live (ir2-block-live-out block)))
    (let ((kill (ir2-block-written block)))
      (do ((conf (ir2-block-global-tns block)
                 (global-conflicts-next-blockwise conf)))
          ((null conf))
        (let ((num (global-conflicts-number conf)))
          (unless (zerop (sbit kill num))
            (setf (global-conflicts-kind conf)
                  (if (zerop (sbit live num))
                      :write
                      :read))))))

    (let ((ltns (ir2-block-local-tns block)))
      (dotimes (i (ir2-block-local-tn-count block))
        (let ((tn (svref ltns i)))
          (unless (or (eq tn :more)
                      (tn-global-conflicts tn)
                      (zerop (sbit live i)))
            (convert-to-global tn))))))

  (values))

(defevent split-ir2-block "Split an IR2 block to meet LOCAL-TN-LIMIT.")

;;; Move the code after the VOP LOSE in 2BLOCK into its own block. The
;;; block is linked into the emit order following 2BLOCK. NUMBER is
;;; the block number assigned to the new block. We return the new
;;; block.
(defun split-ir2-blocks (2block lose number)
  (declare (type ir2-block 2block) (type vop lose)
           (type unsigned-byte number))
  (event split-ir2-block (vop-node lose))
  (let ((new (make-ir2-block (ir2-block-block 2block)))
        (new-start (vop-next lose)))
    (setf (ir2-block-number new) number)
    (add-to-emit-order new 2block)

    (do ((vop new-start (vop-next vop)))
        ((null vop))
      (setf (vop-block vop) new))

    (setf (ir2-block-start-vop new) new-start)
    (shiftf (ir2-block-last-vop new) (ir2-block-last-vop 2block) lose)

    (setf (vop-next lose) nil)
    (setf (vop-prev new-start) nil)

    new))

;;; Clear the global and local conflict info in BLOCK so that we can
;;; recompute it without any old cruft being retained. It is assumed
;;; that all LTN numbers are in use.
;;;
;;; First we delete all the global conflicts. The conflict we are
;;; deleting must be the last in the TN's GLOBAL-CONFLICTS, but we
;;; must scan for it in order to find the previous conflict.
;;;
;;; Next, we scan the local TNs, nulling out the LOCAL slot in all TNs
;;; with no global conflicts. This allows these TNs to be treated as
;;; local when we scan the block again.
;;;
;;; If there are conflicts, then we set LOCAL to one of the
;;; conflicting blocks. This ensures that LOCAL doesn't hold over
;;; BLOCK as its value, causing the subsequent reanalysis to think
;;; that the TN has already been seen in that block.
;;;
;;; This function must not be called on blocks that have :MORE TNs.
(defun clear-lifetime-info (block)
  (declare (type ir2-block block))
  (setf (ir2-block-local-tn-count block) 0)

  (do ((conf (ir2-block-global-tns block)
             (global-conflicts-next-blockwise conf)))
      ((null conf)
       (setf (ir2-block-global-tns block) nil))
    (let ((tn (global-conflicts-tn conf)))
      (aver (eq (tn-current-conflict tn) conf))
      (aver (null (global-conflicts-next-tnwise conf)))
      (do ((current (tn-global-conflicts tn)
                    (global-conflicts-next-tnwise current))
           (prev nil current))
          ((eq current conf)
           (if prev
               (setf (global-conflicts-next-tnwise prev) nil)
               (setf (tn-global-conflicts tn) nil))
           (setf (tn-current-conflict tn) prev)))))

  (fill (ir2-block-written block) 0)
  (let ((ltns (ir2-block-local-tns block)))
    (dotimes (i local-tn-limit)
      (let ((tn (svref ltns i)))
        (aver (not (eq tn :more)))
        (let ((conf (tn-global-conflicts tn)))
          (setf (tn-local tn)
                (if conf
                    (global-conflicts-block conf)
                    nil))))))

  (values))

;;; This provides a panic mode for assigning LTN numbers when there is
;;; a VOP with so many more operands that they can't all be assigned
;;; distinct numbers. When this happens, we recover by assigning all
;;; the &MORE operands the same LTN number. We can get away with this,
;;; since all &MORE args (and results) are referenced simultaneously
;;; as far as conflict analysis is concerned.
;;;
;;; BLOCK is the IR2-BLOCK that the MORE VOP is at the end of. OPS is
;;; the full argument or result TN-REF list. Fixed is the types of the
;;; fixed operands (used only to skip those operands.)
;;;
;;; What we do is grab a LTN number, then make a :READ-ONLY global
;;; conflict for each more operand TN. We require that there be no
;;; existing global conflict in BLOCK for any of the operands. Since
;;; conflicts must be cleared before the first call, this only
;;; prohibits the same TN being used both as a more operand and as any
;;; other operand to the same VOP.
;;;
;;; We don't have to worry about getting the correct conflict kind,
;;; since INIT-GLOBAL-CONFLICT-KIND will fix things up. Similarly,
;;; FIND-LOCAL-REFERENCES will set the local conflict bit
;;; corresponding to this call.
;;;
;;; We also set the LOCAL and LOCAL-NUMBER slots in each TN. It is
;;; possible that there are no operands in any given call to this
;;; function, but there had better be either some more args or more
;;; results.
(defun coalesce-more-ltn-numbers (block ops fixed)
  (declare (type ir2-block block) (type (or tn-ref null) ops) (list fixed))
  (let ((num (ir2-block-local-tn-count block)))
    (aver (< num local-tn-limit))
    (incf (ir2-block-local-tn-count block))
    (setf (svref (ir2-block-local-tns block) num) :more)

    (do ((op (do ((op ops (tn-ref-across op))
                  (i 0 (1+ i)))
                 ((= i (length fixed)) op)
               (declare (type index i)))
             (tn-ref-across op)))
        ((null op))
      (let ((tn (tn-ref-tn op)))
        (assert
          (flet ((frob (refs)
                   (do ((ref refs (tn-ref-next ref)))
                       ((null ref) t)
                     (when (and (eq (vop-block (tn-ref-vop ref)) block)
                                (not (eq ref op)))
                       (return nil)))))
            (and (frob (tn-reads tn)) (frob (tn-writes tn))))
          () "More operand ~S used more than once in its VOP." op)
        (aver (not (find-in #'global-conflicts-next-blockwise tn
                            (ir2-block-global-tns block)
                            :key #'global-conflicts-tn)))

        (add-global-conflict :read-only tn block num)
        (setf (tn-local tn) block)
        (setf (tn-local-number tn) num))))
  (values))

(defevent coalesce-more-ltn-numbers
  "Coalesced LTN numbers for a more operand to meet LOCAL-TN-LIMIT.")

;;; Loop over the blocks in COMPONENT, assigning LTN numbers and
;;; recording TN birth and death. The only interesting action is when
;;; we run out of local TN numbers while finding local references.
;;;
;;; If we run out of LTN numbers while processing a VOP within the
;;; block, then we just split off the VOPs we have successfully
;;; processed into their own block.
;;;
;;; If we run out of LTN numbers while processing the our first VOP
;;; (the last in the block), then it must be the case that this VOP
;;; has large more operands. We split the VOP into its own block, and
;;; then call COALESCE-MORE-LTN-NUMBERS to assign all the more
;;; args/results the same LTN number(s).
;;;
;;; In either case, we clear the lifetime information that we computed
;;; so far, recomputing it after taking corrective action.
;;;
;;; Whenever we split a block, we finish the pre-pass on the split-off
;;; block by doing FIND-LOCAL-REFERENCES and
;;; INIT-GLOBAL-CONFLICT-KIND. This can't run out of LTN numbers.
(defun lifetime-pre-pass (component)
  (declare (type component component))
  (let ((counter -1))
    (declare (type fixnum counter))
    (do-blocks-backwards (block component)
      (let ((2block (block-info block)))
        (do ((lose (find-local-references 2block)
                   (find-local-references 2block))
             (last-lose nil lose)
             (coalesced nil))
            ((not lose)
             (init-global-conflict-kind 2block)
             (setf (ir2-block-number 2block) (incf counter)))

          (clear-lifetime-info 2block)

          (cond
           ((vop-next lose)
            (aver (not (eq last-lose lose)))
            (let ((new (split-ir2-blocks 2block lose (incf counter))))
              (aver (not (find-local-references new)))
              (init-global-conflict-kind new)))
           (t
            (aver (not (eq lose coalesced)))
            (setq coalesced lose)
            (event coalesce-more-ltn-numbers (vop-node lose))
            (let ((info (vop-info lose))
                  (new (if (vop-prev lose)
                           (split-ir2-blocks 2block (vop-prev lose)
                                             (incf counter))
                           2block)))
              (coalesce-more-ltn-numbers new (vop-args lose)
                                         (vop-info-arg-types info))
              (coalesce-more-ltn-numbers new (vop-results lose)
                                         (vop-info-result-types info))
              (let ((lose (find-local-references new)))
                (aver (not lose)))
              (init-global-conflict-kind new))))))))

  (values))

;;;; environment TN stuff

;;; Add a :LIVE global conflict for TN in 2BLOCK if there is none
;;; present. If DEBUG-P is false (a :ENVIRONMENT TN), then modify any
;;; existing conflict to be :LIVE.
(defun setup-environment-tn-conflict (tn 2block debug-p)
  (declare (type tn tn) (type ir2-block 2block))
  (let ((block-num (ir2-block-number 2block)))
    (do ((conf (tn-current-conflict tn) (global-conflicts-next-tnwise conf))
         (prev nil conf))
        ((or (null conf)
             (> (ir2-block-number (global-conflicts-block conf)) block-num))
         (setf (tn-current-conflict tn) prev)
         (add-global-conflict :live tn 2block nil))
      (when (eq (global-conflicts-block conf) 2block)
        (unless (or debug-p
                    (eq (global-conflicts-kind conf) :live))
          (setf (global-conflicts-kind conf) :live)
          (setf (svref (ir2-block-local-tns 2block)
                       (global-conflicts-number conf))
                nil)
          (setf (global-conflicts-number conf) nil))
        (setf (tn-current-conflict tn) conf)
        (return))))
  (values))

;;; Return true if TN represents a closed-over variable with an
;;; "implicit" value-cell.
(defun implicit-value-cell-tn-p (tn)
  (let ((leaf (tn-leaf tn)))
    (and (lambda-var-p leaf)
         (lambda-var-indirect leaf)
         (not (lambda-var-explicit-value-cell leaf)))))

;;; If BLOCK ends with a TAIL LOCAL COMBINATION, the function called.
;;; Otherwise, NIL.
(defun block-tail-local-call-fun (block)
  (let ((node (block-last block)))
    (when (and (combination-p node)
               (eq :local (combination-kind node))
               (combination-tail-p node))
      (ref-leaf (lvar-uses (combination-fun node))))))

;;; Iterate over all the blocks in ENV, setting up :LIVE conflicts for
;;; TN. We make the TN global if it isn't already. The TN must have at
;;; least one reference.
(defun setup-environment-tn-conflicts (component tn env debug-p &optional parent-envs)
  (declare (type component component) (type tn tn) (type physenv env) (type list parent-envs))
  (when (member env parent-envs)
    ;; Prevent infinite recursion due to recursive tail calls.
    (return-from setup-environment-tn-conflicts (values)))
  (when (and debug-p
             (not (tn-global-conflicts tn))
             (tn-local tn))
    (convert-to-global tn))
  (setf (tn-current-conflict tn) (tn-global-conflicts tn))
  (do-blocks-backwards (block component)
    (when (eq (cached-block-physenv block) env)
      (let* ((2block (block-info block))
             (last (do ((b (ir2-block-next 2block) (ir2-block-next b))
                        (prev 2block b))
                       ((not (eq (ir2-block-block b) block))
                        prev))))
        (do ((b last (ir2-block-prev b)))
            ((not (eq (ir2-block-block b) block)))
          (setup-environment-tn-conflict tn b debug-p)))
      ;; If BLOCK ends with a TAIL LOCAL COMBINATION and TN is an
      ;; "implicit value cell" then setup conflicts for the callee
      ;; function as well.
      (let ((fun (and (implicit-value-cell-tn-p tn)
                      (block-tail-local-call-fun block))))
        (when fun
          (setup-environment-tn-conflicts component tn (lambda-physenv fun) debug-p
                                          (list* env parent-envs))))))
  (values))

;;; Iterate over all the environment TNs, adding always-live conflicts
;;; as appropriate.
(defun setup-environment-live-conflicts (component)
  (declare (type component component))
  (dolist (fun (component-lambdas component))
    (let* ((env (lambda-physenv fun))
           (2env (physenv-info env)))
      (dolist (tn (ir2-physenv-live-tns 2env))
        (setup-environment-tn-conflicts component tn env nil))
      (dolist (tn (ir2-physenv-debug-live-tns 2env))
        (setup-environment-tn-conflicts component tn env t))))
  (values))

;;; Convert a :NORMAL or :DEBUG-ENVIRONMENT TN to an :ENVIRONMENT TN.
;;; This requires adding :LIVE conflicts to all blocks in TN-PHYSENV.
(defun convert-to-environment-tn (tn tn-physenv)
  (declare (type tn tn) (type physenv tn-physenv))
  (aver (member (tn-kind tn) '(:normal :debug-environment)))
  (ecase (tn-kind tn)
    (:debug-environment
     (setq tn-physenv (tn-physenv tn))
     (let* ((2env (physenv-info tn-physenv)))
       (setf (ir2-physenv-debug-live-tns 2env)
             (delete tn (ir2-physenv-debug-live-tns 2env)))))
    (:normal
     (setf (tn-local tn) nil)
     (setf (tn-local-number tn) nil)))
  (setup-environment-tn-conflicts *component-being-compiled* tn tn-physenv nil)
  (setf (tn-kind tn) :environment)
  (setf (tn-physenv tn) tn-physenv)
  (push tn (ir2-physenv-live-tns (physenv-info tn-physenv)))
  (values))

;;;; flow analysis

;;; For each GLOBAL-TN in BLOCK2 that is :LIVE, :READ or :READ-ONLY,
;;; ensure that there is a corresponding GLOBAL-CONFLICT in BLOCK1. If
;;; there is none, make a :LIVE GLOBAL-CONFLICT. If there is a
;;; :READ-ONLY conflict, promote it to :LIVE.
;;;
;;; If we did add a new conflict, return true, otherwise false. We
;;; don't need to return true when we promote a :READ-ONLY conflict,
;;; since it doesn't reveal any new information to predecessors of
;;; BLOCK1.
;;;
;;; We use the TN-CURRENT-CONFLICT to walk through the global
;;; conflicts. Since the global conflicts for a TN are ordered by
;;; block, we can be sure that the CURRENT-CONFLICT always points at
;;; or before the block that we are looking at. This allows us to
;;; quickly determine if there is a global conflict for a given TN in
;;; BLOCK1.
;;;
;;; When we scan down the conflicts, we know that there must be at
;;; least one conflict for TN, since we got our hands on TN by picking
;;; it out of a conflict in BLOCK2.
;;;
;;; We leave the CURRENT-CONFLICT pointing to the conflict for BLOCK1.
;;; The CURRENT-CONFLICT must be initialized to the head of the
;;; GLOBAL-CONFLICTS for the TN between each flow analysis iteration.

;;; FASTP is a KLUDGE: SBCL used to update the current-conflict only
;;; for the read-only case, but switched at one point to always
;;; updating it. This generally speeds up the compiler nicely, but
;;; sometimes it causes an infinite loop in the updating machinery,
;;; We cheat by switching of the fast path if it seems we're looping
;;; longer then expected.
(defun propagate-live-tns (block1 block2 fastp)
  (declare (type ir2-block block1 block2))
  (let ((live-in (ir2-block-live-in block1))
        (did-something nil))
    (do ((conf2 (ir2-block-global-tns block2)
                (global-conflicts-next-blockwise conf2)))
        ((null conf2))
      (ecase (global-conflicts-kind conf2)
        ((:live :read :read-only)
         (let* ((tn (global-conflicts-tn conf2))
                (tn-conflicts (tn-current-conflict tn))
                (number1 (ir2-block-number block1)))
           (aver tn-conflicts)
           (do ((current tn-conflicts (global-conflicts-next-tnwise current))
                (prev nil current))
               ((or (null current)
                    (> (ir2-block-number (global-conflicts-block current))
                       number1))
                (setf (tn-current-conflict tn) prev)
                (add-global-conflict :live tn block1 nil)
                (setq did-something t))
             (when (eq (global-conflicts-block current) block1)
               (case (global-conflicts-kind current)
                 (:live)
                 (:read-only
                  (setf (global-conflicts-kind current) :live)
                  (setf (svref (ir2-block-local-tns block1)
                               (global-conflicts-number current))
                        nil)
                  (setf (global-conflicts-number current) nil)
                  (unless fastp
                    (setf (tn-current-conflict tn) current)))
                 (t
                  (setf (sbit live-in (global-conflicts-number current)) 1)))
               (when fastp
                 (setf (tn-current-conflict tn) current))
               (return)))))
        (:write)))
    did-something))

;;; Do backward global flow analysis to find all TNs live at each
;;; block boundary.
(defun lifetime-flow-analysis (component)
  ;; KLUDGE: This is the second part of the FASTP kludge in
  ;; propagate-live-tns: we pass fastp for ten first attempts,
  ;; and then switch to the works-for-sure version.
  ;;
  ;; The upstream uses the fast version always, but sometimes
  ;; that gets stuck in a loop...
  (loop for i = 0 then (1+ i)
        do
    (reset-current-conflict component)
    (let ((did-something nil))
      (do-blocks-backwards (block component)
        (let* ((2block (block-info block))
               (last (do ((b (ir2-block-next 2block) (ir2-block-next b))
                          (prev 2block b))
                         ((not (eq (ir2-block-block b) block))
                          prev))))

          (dolist (b (block-succ block))
            (when (and (block-start b)
                       (propagate-live-tns last (block-info b) (< i 10)))
              (setq did-something t)))

          (do ((b (ir2-block-prev last) (ir2-block-prev b))
               (prev last b))
              ((not (eq (ir2-block-block b) block)))
            (when (propagate-live-tns b prev (< i 10))
              (setq did-something t)))))

      (unless did-something (return))))

  (values))

;;;; post-pass

;;; Note that TN conflicts with all current live TNs. NUM is TN's LTN
;;; number. We bit-ior LIVE-BITS with TN's LOCAL-CONFLICTS, and set TN's
;;; number in the conflicts of all TNs in LIVE-LIST.
(defun note-conflicts (live-bits live-list tn num)
  (declare (type tn tn) (type (or tn null) live-list)
           (type local-tn-bit-vector live-bits)
           (type local-tn-number num))
  (let ((lconf (tn-local-conflicts tn)))
    (bit-ior live-bits lconf lconf))
  (do ((live live-list (tn-next* live)))
      ((null live))
    (setf (sbit (tn-local-conflicts live) num) 1))
  (values))

;;; Compute a bit vector of the TNs live after VOP that aren't results.
(defun compute-save-set (vop live-bits)
  (declare (type vop vop) (type local-tn-bit-vector live-bits))
  (let ((live (bit-vector-copy live-bits)))
    (do ((r (vop-results vop) (tn-ref-across r)))
        ((null r))
      (let ((tn (tn-ref-tn r)))
        (ecase (tn-kind tn)
          ((:normal :debug-environment)
           (setf (sbit live (tn-local-number tn)) 0))
          (:environment :component))))
    live))

;;; This is used to determine whether a :DEBUG-ENVIRONMENT TN should
;;; be considered live at block end. We return true if a VOP with
;;; non-null SAVE-P appears before the first read of TN (hence is seen
;;; first in our backward scan.)
(defun saved-after-read (tn block)
  (do ((vop (ir2-block-last-vop block) (vop-prev vop)))
      ((null vop) t)
    (when (vop-info-save-p (vop-info vop)) (return t))
    (when (find-in #'tn-ref-across tn (vop-args vop) :key #'tn-ref-tn)
      (return nil))))

;;; If the block has no successors, or its successor is the component
;;; tail, then all :DEBUG-ENVIRONMENT TNs are always added, regardless
;;; of whether they appeared to be live. This ensures that these TNs
;;; are considered to be live throughout blocks that read them, but
;;; don't have any interesting successors (such as a return or tail
;;; call.) In this case, we set the corresponding bit in LIVE-IN as
;;; well.
(defun make-debug-environment-tns-live (block live-bits live-list)
  (let* ((1block (ir2-block-block block))
         (live-in (ir2-block-live-in block))
         (succ (block-succ 1block))
         (next (ir2-block-next block)))
    (when (and next
               (not (eq (ir2-block-block next) 1block))
               (or (null succ)
                   (eq (first succ)
                       (component-tail (block-component 1block)))))
      (do ((conf (ir2-block-global-tns block)
                 (global-conflicts-next-blockwise conf)))
          ((null conf))
        (let* ((tn (global-conflicts-tn conf))
               (num (global-conflicts-number conf)))
          (when (and num (zerop (sbit live-bits num))
                     (eq (tn-kind tn) :debug-environment)
                     (eq (tn-physenv tn) (cached-block-physenv 1block))
                     (saved-after-read tn block))
            (note-conflicts live-bits live-list tn num)
            (setf (sbit live-bits num) 1)
            (push-in tn-next* tn live-list)
            (setf (sbit live-in num) 1))))))

  (values live-bits live-list))

;;; Return as values, a LTN bit-vector and a list (threaded by
;;; TN-NEXT*) representing the TNs live at the end of BLOCK (exclusive
;;; of :LIVE TNs).
;;;
;;; We iterate over the TNs in the global conflicts that are live at
;;; the block end, setting up the TN-LOCAL-CONFLICTS and
;;; TN-LOCAL-NUMBER, and adding the TN to the live list.
;;;
;;; If a :MORE result is not live, we effectively fake a read to it.
;;; This is part of the action described in ENSURE-RESULTS-LIVE.
;;;
;;; At the end, we call MAKE-DEBUG-ENVIRONEMNT-TNS-LIVE to make debug
;;; environment TNs appear live when appropriate, even when they
;;; aren't.
;;;
;;; ### Note: we alias the global-conflicts-conflicts here as the
;;; tn-local-conflicts.
(defun compute-initial-conflicts (block)
  (declare (type ir2-block block))
  (let* ((live-in (ir2-block-live-in block))
         (ltns (ir2-block-local-tns block))
         (live-bits (bit-vector-copy live-in))
         (live-list nil))

    (do ((conf (ir2-block-global-tns block)
               (global-conflicts-next-blockwise conf)))
        ((null conf))
      (let ((bits (global-conflicts-conflicts conf))
            (tn (global-conflicts-tn conf))
            (num (global-conflicts-number conf))
            (kind (global-conflicts-kind conf)))
        (setf (tn-local-number tn) num)
        (unless (eq kind :live)
          (cond ((not (zerop (sbit live-bits num)))
                 (bit-vector-replace bits live-bits)
                 (setf (sbit bits num) 0)
                 (push-in tn-next* tn live-list))
                ((and (eq (svref ltns num) :more)
                      (eq kind :write))
                 (note-conflicts live-bits live-list tn num)
                 (setf (sbit live-bits num) 1)
                 (push-in tn-next* tn live-list)
                 (setf (sbit live-in num) 1)))

          (setf (tn-local-conflicts tn) bits))))

    (make-debug-environment-tns-live block live-bits live-list)))

;;; A function called in CONFLICT-ANALYZE-1-BLOCK when we have a VOP
;;; with SAVE-P true. We compute the save-set, and if :FORCE-TO-STACK,
;;; force all the live TNs to be stack environment TNs.
(defun conflictize-save-p-vop (vop block live-bits)
  (declare (type vop vop) (type ir2-block block)
           (type local-tn-bit-vector live-bits))
  (let ((ss (compute-save-set vop live-bits)))
    (setf (vop-save-set vop) ss)
    (when (eq (vop-info-save-p (vop-info vop)) :force-to-stack)
      (do-live-tns (tn ss block)
        (unless (eq (tn-kind tn) :component)
          (force-tn-to-stack tn)
          (unless (eq (tn-kind tn) :environment)
            (convert-to-environment-tn
             tn
             (cached-block-physenv (ir2-block-block block))))))))
  (values))

;;; FIXME: The next 3 macros aren't needed in the target runtime.
;;; Figure out some way to make them only at build time. (Just
;;; (EVAL-WHEN (:COMPILE-TOPLEVEL :EXECUTE) (DEFMACRO ..)) isn't good enough,
;;; since we need CL:DEFMACRO at build-the-cross-compiler time and
;;; SB!XC:DEFMACRO at run-the-cross-compiler time.)

;;; This is used in SCAN-VOP-REFS to simultaneously do something to
;;; all of the TNs referenced by a big more arg. We have to treat
;;; these TNs specially, since when we set or clear the bit in the
;;; live TNs, the represents a change in the liveness of all the more
;;; TNs. If we iterated as normal, the next more ref would be thought
;;; to be not live when it was, etc. We update Ref to be the last
;;; :more ref we scanned, so that the main loop will step to the next
;;; non-more ref.
(defmacro frob-more-tns (action)
  `(when (eq (svref ltns num) :more)
     (let ((prev ref))
       (do ((mref (tn-ref-next-ref ref) (tn-ref-next-ref mref)))
           ((null mref))
         (let ((mtn (tn-ref-tn mref)))
           (unless (eql (tn-local-number mtn) num)
             (return))
           ,action)
         (setq prev mref))
       (setq ref prev))))

;;; Handle the part of CONFLICT-ANALYZE-1-BLOCK that scans the REFs
;;; for the current VOP. This macro shamelessly references free
;;; variables in C-A-1-B.
(defmacro scan-vop-refs ()
  '(do ((ref (vop-refs vop) (tn-ref-next-ref ref)))
       ((null ref))
     (let* ((tn (tn-ref-tn ref))
            (num (tn-local-number tn)))
       (cond
        ((not num))
        ((not (zerop (sbit live-bits num)))
         (when (tn-ref-write-p ref)
           (setf (sbit live-bits num) 0)
           (deletef-in tn-next* live-list tn)
           (frob-more-tns (deletef-in tn-next* live-list mtn))))
        (t
         (aver (not (tn-ref-write-p ref)))
         (note-conflicts live-bits live-list tn num)
         (frob-more-tns (note-conflicts live-bits live-list mtn num))
         (setf (sbit live-bits num) 1)
         (push-in tn-next* tn live-list)
         (frob-more-tns (push-in tn-next* mtn live-list)))))))

;;; This macro is called by CONFLICT-ANALYZE-1-BLOCK to scan the
;;; current VOP's results, and make any dead ones live. This is
;;; necessary, since even though a result is dead after the VOP, it
;;; may be in use for an extended period within the VOP (especially if
;;; it has :FROM specified.) During this interval, temporaries must be
;;; noted to conflict with the result. More results are finessed in
;;; COMPUTE-INITIAL-CONFLICTS, so we ignore them here.
(defmacro ensure-results-live ()
  '(do ((res (vop-results vop) (tn-ref-across res)))
       ((null res))
     (let* ((tn (tn-ref-tn res))
            (num (tn-local-number tn)))
       (when (and num (zerop (sbit live-bits num)))
         (unless (eq (svref ltns num) :more)
           (note-conflicts live-bits live-list tn num)
           (setf (sbit live-bits num) 1)
           (push-in tn-next* tn live-list))))))

;;; Compute the block-local conflict information for BLOCK. We iterate
;;; over all the TN-REFs in a block in reference order, maintaining
;;; the set of live TNs in both a list and a bit-vector
;;; representation.
(defun conflict-analyze-1-block (block)
  (declare (type ir2-block block))
  (multiple-value-bind (live-bits live-list)
      (compute-initial-conflicts block)
    (let ((ltns (ir2-block-local-tns block)))
      (do ((vop (ir2-block-last-vop block)
                (vop-prev vop)))
          ((null vop))
        (when (vop-info-save-p (vop-info vop))
          (conflictize-save-p-vop vop block live-bits))
        (ensure-results-live)
        (scan-vop-refs)))))

;;; Conflict analyze each block, and also add it.
(defun lifetime-post-pass (component)
  (declare (type component component))
  (do-ir2-blocks (block component)
    (conflict-analyze-1-block block)))

;;;; alias TN stuff

;;; Destructively modify OCONF to include the conflict information in CONF.
(defun merge-alias-block-conflicts (conf oconf)
  (declare (type global-conflicts conf oconf))
  (let* ((kind (global-conflicts-kind conf))
         (num (global-conflicts-number conf))
         (okind (global-conflicts-kind oconf))
         (onum (global-conflicts-number oconf))
         (block (global-conflicts-block oconf))
         (ltns (ir2-block-local-tns block)))
    (cond
     ((eq okind :live))
     ((eq kind :live)
      (setf (global-conflicts-kind oconf) :live)
      (setf (svref ltns onum) nil)
      (setf (global-conflicts-number oconf) nil))
     (t
      (unless (eq kind okind)
        (setf (global-conflicts-kind oconf) :read))
      ;; Make original conflict with all the local TNs the alias
      ;; conflicted with.
      (bit-ior (global-conflicts-conflicts oconf)
               (global-conflicts-conflicts conf)
               t)
      (flet ((frob (x)
               (unless (zerop (sbit x num))
                 (setf (sbit x onum) 1))))
        ;; Make all the local TNs that conflicted with the alias
        ;; conflict with the original.
        (dotimes (i (ir2-block-local-tn-count block))
          (let ((tn (svref ltns i)))
            (when (and tn (not (eq tn :more))
                       (null (tn-global-conflicts tn)))
              (frob (tn-local-conflicts tn)))))
        ;; Same for global TNs...
        (do ((current (ir2-block-global-tns block)
                      (global-conflicts-next-blockwise current)))
            ((null current))
          (unless (eq (global-conflicts-kind current) :live)
            (frob (global-conflicts-conflicts current))))
        ;; Make the original TN live everywhere that the alias was live.
        (frob (ir2-block-written block))
        (frob (ir2-block-live-in block))
        (frob (ir2-block-live-out block))
        (do ((vop (ir2-block-start-vop block)
                  (vop-next vop)))
            ((null vop))
          (let ((sset (vop-save-set vop)))
            (when sset (frob sset)))))))
    ;; Delete the alias's conflict info.
    (when num
      (setf (svref ltns num) nil))
    (deletef-in global-conflicts-next-blockwise
                (ir2-block-global-tns block)
                conf))

  (values))

;;; Co-opt CONF to be a conflict for TN.
(defun change-global-conflicts-tn (conf new)
  (declare (type global-conflicts conf) (type tn new))
  (setf (global-conflicts-tn conf) new)
  (let ((ltn-num (global-conflicts-number conf))
        (block (global-conflicts-block conf)))
    (deletef-in global-conflicts-next-blockwise
                (ir2-block-global-tns block)
                conf)
    (setf (global-conflicts-next-blockwise conf) nil)
    (insert-block-global-conflict conf block)
    (when ltn-num
      (setf (svref (ir2-block-local-tns block) ltn-num) new)))
  (values))

;;; Do CONVERT-TO-GLOBAL on TN if it has no global conflicts. Copy the
;;; local conflicts into the global bit vector.
(defun ensure-global-tn (tn)
  (declare (type tn tn))
  (cond ((tn-global-conflicts tn))
        ((tn-local tn)
         (convert-to-global tn)
         (bit-ior (global-conflicts-conflicts (tn-global-conflicts tn))
                  (tn-local-conflicts tn)
                  t))
        (t
         (aver (and (null (tn-reads tn)) (null (tn-writes tn))))))
  (values))

;;; For each :ALIAS TN, destructively merge the conflict info into the
;;; original TN and replace the uses of the alias.
;;;
;;; For any block that uses only the alias TN, just insert that
;;; conflict into the conflicts for the original TN, changing the LTN
;;; map to refer to the original TN. This gives a result
;;; indistinguishable from the what there would have been if the
;;; original TN had always been referenced. This leaves no sign that
;;; an alias TN was ever involved.
;;;
;;; If a block has references to both the alias and the original TN,
;;; then we call MERGE-ALIAS-BLOCK-CONFLICTS to combine the conflicts
;;; into the original conflict.
(defun merge-alias-conflicts (component)
  (declare (type component component))
  (do ((tn (ir2-component-alias-tns (component-info component))
           (tn-next tn)))
      ((null tn))
    (let ((original (tn-save-tn tn)))
      (ensure-global-tn tn)
      (ensure-global-tn original)
      (let ((conf (tn-global-conflicts tn))
            (oconf (tn-global-conflicts original))
            (oprev nil))
        (loop
          (unless oconf
            (if oprev
                (setf (global-conflicts-next-tnwise oprev) conf)
                (setf (tn-global-conflicts original) conf))
            (do ((current conf (global-conflicts-next-tnwise current)))
                ((null current))
              (change-global-conflicts-tn current original))
            (return))
          (let* ((block (global-conflicts-block conf))
                 (num (ir2-block-number block))
                 (onum (ir2-block-number (global-conflicts-block oconf))))

            (cond ((< onum num)
                   (shiftf oprev oconf (global-conflicts-next-tnwise oconf)))
                  ((> onum num)
                   (if oprev
                       (setf (global-conflicts-next-tnwise oprev) conf)
                       (setf (tn-global-conflicts original) conf))
                   (change-global-conflicts-tn conf original)
                   (shiftf oprev
                           conf
                           (global-conflicts-next-tnwise conf)
                           oconf))
                  (t
                   (merge-alias-block-conflicts conf oconf)
                   (shiftf oprev oconf (global-conflicts-next-tnwise oconf))
                   (setf conf (global-conflicts-next-tnwise conf)))))
          (unless conf (return))))

      (flet ((frob (refs)
               (let ((ref refs)
                     (next nil))
                 (loop
                   (unless ref (return))
                   (setq next (tn-ref-next ref))
                   (change-tn-ref-tn ref original)
                   (setq ref next)))))
        (frob (tn-reads tn))
        (frob (tn-writes tn)))
      (setf (tn-global-conflicts tn) nil)))

  (values))

;;; On high debug levels, for all variables that a lambda closes over
;;; convert the TNs to :ENVIRONMENT TNs (in the physical environment
;;; of that lambda). This way the debugger can display the variables.
(defun maybe-environmentalize-closure-tns (component)
  (dolist (lambda (component-lambdas component))
    (when (policy lambda (>= debug 2))
      (let ((physenv (lambda-physenv lambda)))
        (dolist (closure-var (physenv-closure physenv))
          (let ((tn (find-in-physenv closure-var physenv)))
            (when (member (tn-kind tn) '(:normal :debug-environment))
              (convert-to-environment-tn tn physenv))))))))


(defun lifetime-analyze (component)
  (lifetime-pre-pass component)
  (maybe-environmentalize-closure-tns component)
  (setup-environment-live-conflicts component)
  (lifetime-flow-analysis component)
  (lifetime-post-pass component)
  (merge-alias-conflicts component))

;;;; conflict testing

;;; Test for a conflict between the local TN X and the global TN Y. We
;;; just look for a global conflict of Y in X's block, and then test
;;; for conflict in that block.
;;;
;;; [### Might be more efficient to scan Y's global conflicts. This
;;; depends on whether there are more global TNs than blocks.]
(defun tns-conflict-local-global (x y)
  (let ((block (tn-local x)))
    (do ((conf (ir2-block-global-tns block)
               (global-conflicts-next-blockwise conf)))
        ((null conf) nil)
      (when (eq (global-conflicts-tn conf) y)
        (let ((num (global-conflicts-number conf)))
          (return (or (not num)
                      (not (zerop (sbit (tn-local-conflicts x)
                                        num))))))))))

;;; Test for conflict between two global TNs X and Y.
(defun tns-conflict-global-global (x y)
  (declare (type tn x y))
  (let* ((x-conf (tn-global-conflicts x))
         (x-num (ir2-block-number (global-conflicts-block x-conf)))
         (y-conf (tn-global-conflicts y))
         (y-num (ir2-block-number (global-conflicts-block y-conf))))

    (macrolet ((advance (n c)
                 `(progn
                    (setq ,c (global-conflicts-next-tnwise ,c))
                    (unless ,c (return-from tns-conflict-global-global nil))
                    (setq ,n (ir2-block-number (global-conflicts-block ,c)))))
               (scan (g l lc)
                 `(do ()
                      ((>= ,g ,l))
                    (advance ,l ,lc))))

      (loop
        ;; x-conf, y-conf true, x-num, y-num corresponding block numbers.
        (scan x-num y-num y-conf)
        (scan y-num x-num x-conf)
        (when (= x-num y-num)
          (let ((ltn-num-x (global-conflicts-number x-conf)))
            (unless (and ltn-num-x
                         (global-conflicts-number y-conf)
                         (zerop (sbit (global-conflicts-conflicts y-conf)
                                      ltn-num-x)))
              (return t))
            (advance x-num x-conf)
            (advance y-num y-conf)))))))

;;; Return true if X and Y are distinct and the lifetimes of X and Y
;;; overlap at any point.
(defun tns-conflict (x y)
  (declare (type tn x y))
  (let ((x-kind (tn-kind x))
        (y-kind (tn-kind y)))
    (cond ((eq x y) nil)
          ((or (eq x-kind :component) (eq y-kind :component)) t)
          ((tn-global-conflicts x)
           (if (tn-global-conflicts y)
               (tns-conflict-global-global x y)
               (tns-conflict-local-global y x)))
          ((tn-global-conflicts y)
           (tns-conflict-local-global x y))
          (t
           (and (eq (tn-local x) (tn-local y))
                (not (zerop (sbit (tn-local-conflicts x)
                                  (tn-local-number y)))))))))
