;;;; This file implements the stack analysis phase in the compiler. We ;;;; do a graph walk to determine which unknown-values continuations ;;;; are on the stack at each point in the program, and then we insert ;;;; cleanup code to pop off unused values. ;;;; 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") ;;; Scan through BLOCK looking for uses of :UNKNOWN continuations that ;;; have their DEST outside of the block. We do some checking to ;;; verify the invariant that all pushes come after the last pop. (defun find-pushed-lvars (block) (let* ((2block (block-info block)) (popped (ir2-block-popped 2block)) (last-pop (if popped (lvar-dest (car (last popped))) nil))) (collect ((pushed)) (let ((saw-last nil)) (do-nodes (node lvar block) (when (eq node last-pop) (setq saw-last t)) (when lvar (let ((dest (lvar-dest lvar)) (2lvar (lvar-info lvar))) (when (and (not (eq (node-block dest) block)) 2lvar (eq (ir2-lvar-kind 2lvar) :unknown)) (aver (or saw-last (not last-pop))) (pushed lvar)))))) (setf (ir2-block-pushed 2block) (pushed)))) (values)) ;;;; annotation graph walk ;;; Do a backward walk in the flow graph simulating the run-time stack ;;; of unknown-values continuations and annotating the blocks with the ;;; result. ;;; ;;; BLOCK is the block that is currently being walked and STACK is the ;;; stack of unknown-values continuations in effect immediately after ;;; block. We simulate the stack by popping off the unknown-values ;;; generated by this block (if any) and pushing the continuations for ;;; values received by this block. (The role of push and pop are ;;; interchanged because we are doing a backward walk.) ;;; ;;; If we run into a values generator whose continuation isn't on ;;; stack top, then the receiver hasn't yet been reached on any walk ;;; to this use. In this case, we ignore the push for now, counting on ;;; Annotate-Dead-Values to clean it up if we discover that it isn't ;;; reachable at all. ;;; ;;; If our final stack isn't empty, then we walk all the predecessor ;;; blocks that don't have all the continuations that we have on our ;;; START-STACK on their END-STACK. This is our termination condition ;;; for the graph walk. We put the test around the recursive call so ;;; that the initial call to this function will do something even ;;; though there isn't initially anything on the stack. ;;; ;;; We can use the tailp test, since the only time we want to bottom ;;; out with a non-empty stack is when we intersect with another path ;;; from the same top level call to this function that has more values ;;; receivers on that path. When we bottom out in this way, we are ;;; counting on DISCARD-UNUSED-VALUES doing its thing. ;;; ;;; When we do recurse, we check that predecessor's END-STACK is a ;;; subsequence of our START-STACK. There may be extra stuff on the ;;; top of our stack because the last path to the predecessor may have ;;; discarded some values that we use. There may be extra stuff on the ;;; bottom of our stack because this walk may be from a values ;;; receiver whose lifetime encloses that of the previous walk. ;;; ;;; If a predecessor block is the component head, then it must be the ;;; case that this is a NLX entry stub. If so, we just stop our walk, ;;; since the stack at the exit point doesn't have anything to do with ;;; our stack. (defun stack-simulation-walk (block stack) (declare (type cblock block) (list stack)) (let ((2block (block-info block))) (setf (ir2-block-end-stack 2block) stack) (let ((new-stack stack)) (dolist (push (reverse (ir2-block-pushed 2block))) (if (eq (car new-stack) push) (pop new-stack) (aver (not (member push new-stack))))) (dolist (pop (reverse (ir2-block-popped 2block))) (push pop new-stack)) (setf (ir2-block-start-stack 2block) new-stack) (when new-stack (dolist (pred (block-pred block)) (if (eq pred (component-head (block-component block))) (aver (find block (physenv-nlx-info (block-physenv block)) :key #'nlx-info-target)) (let ((pred-stack (ir2-block-end-stack (block-info pred)))) (unless (tailp new-stack pred-stack) (aver (search pred-stack new-stack)) (stack-simulation-walk pred new-stack)))))))) (values)) ;;; Do stack annotation for any values generators in Block that were ;;; unreached by all walks (i.e. the continuation isn't live at the point that ;;; it is generated.) This will only happen when the values receiver cannot be ;;; reached from this particular generator (due to an unconditional control ;;; transfer.) ;;; ;;; What we do is push on the End-Stack all continuations in Pushed that ;;; aren't already present in the End-Stack. When we find any pushed ;;; continuation that isn't live, it must be the case that all continuations ;;; pushed after (on top of) it aren't live. ;;; ;;; If we see a pushed continuation that is the CONT of a tail call, then we ;;; ignore it, since the tail call didn't actually push anything. The tail ;;; call must always the last in the block. (defun annotate-dead-values (block) (declare (type cblock block)) (let* ((2block (block-info block)) (stack (ir2-block-end-stack 2block)) (last (block-last block)) (tailp-lvar (if (node-tail-p last) (node-lvar last)))) (do ((pushes (ir2-block-pushed 2block) (rest pushes)) (popping nil)) ((null pushes)) (let ((push (first pushes))) (cond ((member push stack) (aver (not popping))) ((eq push tailp-lvar) (aver (null (rest pushes)))) (t (push push (ir2-block-end-stack 2block)) (setq popping t)))))) (values)) ;;; This is called when we discover that the stack-top unknown-values ;;; continuation at the end of BLOCK1 is different from that at the ;;; start of BLOCK2 (its successor). ;;; ;;; We insert a call to a funny function in a new cleanup block ;;; introduced between BLOCK1 and BLOCK2. Since control analysis and ;;; LTN have already run, we must do make an IR2 block, then do ;;; ADD-TO-EMIT-ORDER and LTN-ANALYZE-BELATED-BLOCK on the new block. ;;; The new block is inserted after BLOCK1 in the emit order. ;;; ;;; If the control transfer between BLOCK1 and BLOCK2 represents a ;;; tail-recursive return (:DELETED IR2-continuation) or a non-local ;;; exit, then the cleanup code will never actually be executed. It ;;; doesn't seem to be worth the risk of trying to optimize this, ;;; since this rarely happens and wastes only space. (defun discard-unused-values (block1 block2) (declare (type cblock block1 block2)) (let* ((block1-stack (ir2-block-end-stack (block-info block1))) (block2-stack (ir2-block-start-stack (block-info block2))) (last-popped (elt block1-stack (- (length block1-stack) (length block2-stack) 1)))) (aver (tailp block2-stack block1-stack)) (let* ((block (insert-cleanup-code block1 block2 (block-start-node block2) `(%pop-values ',last-popped))) (2block (make-ir2-block block))) (setf (block-info block) 2block) (add-to-emit-order 2block (block-info block1)) (ltn-analyze-belated-block block))) (values)) ;;;; stack analysis ;;; Return a list of all the blocks containing genuine uses of one of the ;;; RECEIVERS. Exits are excluded, since they don't drop through to the ;;; receiver. (defun find-values-generators (receivers) (declare (list receivers)) (collect ((res nil adjoin)) (dolist (rec receivers) (dolist (pop (ir2-block-popped (block-info rec))) (do-uses (use pop) (unless (exit-p use) (res (node-block use)))))) (res))) ;;; Analyze the use of unknown-values continuations in COMPONENT, ;;; inserting cleanup code to discard values that are generated but ;;; never received. This phase doesn't need to be run when ;;; Values-Receivers is null, i.e. there are no unknown-values ;;; continuations used across block boundaries. ;;; ;;; Do the backward graph walk, starting at each values receiver. We ;;; ignore receivers that already have a non-null START-STACK. These ;;; are nested values receivers that have already been reached on ;;; another walk. We don't want to clobber that result with our null ;;; initial stack. (defun stack-analyze (component) (declare (type component component)) (let* ((2comp (component-info component)) (receivers (ir2-component-values-receivers 2comp)) (generators (find-values-generators receivers))) (dolist (block generators) (find-pushed-lvars block)) (dolist (block receivers) (unless (ir2-block-start-stack (block-info block)) (stack-simulation-walk block ()))) (dolist (block generators) (annotate-dead-values block)) (do-blocks (block component) (let ((top (car (ir2-block-end-stack (block-info block))))) (dolist (succ (block-succ block)) (when (and (block-start succ) (not (eq (car (ir2-block-start-stack (block-info succ))) top))) (discard-unused-values block succ)))))) (values))