-;;; Continuation-passing style (CPS) intermediate language (IL)
-
-;; Copyright (C) 2013, 2014 Free Software Foundation, Inc.
-
-;;;; This library is free software; you can redistribute it and/or
-;;;; modify it under the terms of the GNU Lesser General Public
-;;;; License as published by the Free Software Foundation; either
-;;;; version 3 of the License, or (at your option) any later version.
-;;;;
-;;;; This library is distributed in the hope that it will be useful,
-;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-;;;; Lesser General Public License for more details.
-;;;;
-;;;; You should have received a copy of the GNU Lesser General Public
-;;;; License along with this library; if not, write to the Free Software
-;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-;;; Commentary:
-;;;
-;;; Many passes rely on a local or global static analysis of a function.
-;;; This module implements a simple data-flow graph (DFG) analysis,
-;;; tracking the definitions and uses of variables and continuations.
-;;; It also builds a table of continuations and scope links, to be able
-;;; to easily determine if one continuation is in the scope of another,
-;;; and to get to the expression inside a continuation.
-;;;
-;;; Note that the data-flow graph of continuation labels is a
-;;; control-flow graph.
-;;;
-;;; We currently don't expose details of the DFG type outside this
-;;; module, preferring to only expose accessors. That may change in the
-;;; future but it seems to work for now.
-;;;
-;;; Code:
-
-(define-module (language cps dfg)
- #:use-module (ice-9 match)
- #:use-module (srfi srfi-1)
- #:use-module (srfi srfi-9)
- #:use-module (srfi srfi-26)
- #:use-module (language cps)
- #:export (build-cont-table
- lookup-cont
-
- compute-dfg
- dfg-cont-table
- lookup-def
- lookup-uses
- lookup-predecessors
- lookup-successors
- lookup-block-scope
- find-call
- call-expression
- find-expression
- find-defining-expression
- find-constant-value
- continuation-bound-in?
- variable-free-in?
- constant-needs-allocation?
- control-point?
- lookup-bound-syms
-
- ;; Control flow analysis.
- analyze-control-flow
- cfa-k-idx cfa-k-count cfa-k-sym cfa-predecessors
-
- ;; Data flow analysis.
- compute-live-variables
- dfa-k-idx dfa-k-sym dfa-k-count dfa-k-in dfa-k-out
- dfa-var-idx dfa-var-name dfa-var-sym dfa-var-count
- print-dfa))
-
-;; These definitions are here because currently we don't do cross-module
-;; inlining. They can be removed once that restriction is gone.
-(define-inlinable (for-each f l)
- (unless (list? l)
- (scm-error 'wrong-type-arg "for-each" "Not a list: ~S" (list l) #f))
- (let for-each1 ((l l))
- (unless (null? l)
- (f (car l))
- (for-each1 (cdr l)))))
-
-(define-inlinable (for-each/2 f l1 l2)
- (unless (= (length l1) (length l2))
- (scm-error 'wrong-type-arg "for-each" "List of wrong length: ~S"
- (list l2) #f))
- (let for-each2 ((l1 l1) (l2 l2))
- (unless (null? l1)
- (f (car l1) (car l2))
- (for-each2 (cdr l1) (cdr l2)))))
-
-(define (build-cont-table fun)
- (let ((max-k (fold-conts (lambda (k cont max-k) (max k max-k))
- -1 fun)))
- (fold-conts (lambda (k cont table)
- (vector-set! table k cont)
- table)
- (make-vector (1+ max-k) #f)
- fun)))
-
-(define (lookup-cont label dfg)
- (match dfg
- (($ $dfg conts blocks use-maps)
- (let ((res (hashq-ref conts label)))
- (unless res
- (error "Unknown continuation!" label conts))
- res))))
-
-;; Data-flow graph for CPS: both for values and continuations.
-(define-record-type $dfg
- (make-dfg conts blocks use-maps)
- dfg?
- ;; hash table of sym -> $kif, $kargs, etc
- (conts dfg-cont-table)
- ;; hash table of sym -> $block
- (blocks dfg-blocks)
- ;; hash table of sym -> $use-map
- (use-maps dfg-use-maps))
-
-(define-record-type $use-map
- (make-use-map name sym def uses)
- use-map?
- (name use-map-name)
- (sym use-map-sym)
- (def use-map-def)
- (uses use-map-uses set-use-map-uses!))
-
-(define-record-type $block
- (%make-block scope scope-level preds succs)
- block?
- (scope block-scope set-block-scope!)
- (scope-level block-scope-level set-block-scope-level!)
- (preds block-preds set-block-preds!)
- (succs block-succs set-block-succs!))
-
-(define (make-block scope scope-level)
- (%make-block scope scope-level '() '()))
-
-;; Some analyses assume that the only relevant set of nodes is the set
-;; that is reachable from some start node. Others need to include nodes
-;; that are reachable from an end node as well, or all nodes in a
-;; function. In that case pass an appropriate implementation of
-;; fold-all-conts, as analyze-control-flow does.
-(define (reverse-post-order k0 get-successors fold-all-conts)
- (let ((order '())
- (visited? (make-hash-table)))
- (let visit ((k k0))
- (hashq-set! visited? k #t)
- (for-each (lambda (k)
- (unless (hashq-ref visited? k)
- (visit k)))
- (get-successors k))
- (set! order (cons k order)))
- (list->vector (fold-all-conts
- (lambda (k seed)
- (if (hashq-ref visited? k)
- seed
- (begin
- (hashq-set! visited? k #t)
- (cons k seed))))
- order))))
-
-(define (make-block-mapping order)
- (let ((mapping (make-hash-table)))
- (let lp ((n 0))
- (when (< n (vector-length order))
- (hashq-set! mapping (vector-ref order n) n)
- (lp (1+ n))))
- mapping))
-
-(define (convert-predecessors order get-predecessors)
- (let ((preds-vec (make-vector (vector-length order) #f)))
- (let lp ((n 0))
- (when (< n (vector-length order))
- (vector-set! preds-vec n (get-predecessors (vector-ref order n)))
- (lp (1+ n))))
- preds-vec))
-
-;; Control-flow analysis.
-(define-record-type $cfa
- (make-cfa k-map order preds)
- cfa?
- ;; Hash table mapping k-sym -> k-idx
- (k-map cfa-k-map)
- ;; Vector of k-idx -> k-sym, in reverse post order
- (order cfa-order)
- ;; Vector of k-idx -> list of k-idx
- (preds cfa-preds))
-
-(define* (cfa-k-idx cfa k
- #:key (default (lambda (k)
- (error "unknown k" k))))
- (or (hashq-ref (cfa-k-map cfa) k)
- (default k)))
-
-(define (cfa-k-count cfa)
- (vector-length (cfa-order cfa)))
-
-(define (cfa-k-sym cfa n)
- (vector-ref (cfa-order cfa) n))
-
-(define (cfa-predecessors cfa n)
- (vector-ref (cfa-preds cfa) n))
-
-(define-inlinable (vector-push! vec idx val)
- (let ((v vec) (i idx))
- (vector-set! v i (cons val (vector-ref v i)))))
-
-(define (compute-reachable cfa dfg)
- "Given the forward control-flow analysis in CFA, compute and return
-the continuations that may be reached if flow reaches a continuation N.
-Returns a vector of bitvectors. The given CFA should be a forward CFA,
-for quickest convergence."
- (let* ((k-count (cfa-k-count cfa))
- ;; Vector of bitvectors, indicating that continuation N can
- ;; reach a set M...
- (reachable (make-vector k-count #f))
- ;; Vector of lists, indicating that continuation N can directly
- ;; reach continuations M...
- (succs (make-vector k-count '())))
-
- ;; All continuations are reachable from themselves.
- (let lp ((n 0))
- (when (< n k-count)
- (let ((bv (make-bitvector k-count #f)))
- (bitvector-set! bv n #t)
- (vector-set! reachable n bv)
- (lp (1+ n)))))
-
- ;; Initialize successor lists.
- (let lp ((n 0))
- (when (< n k-count)
- (for-each (lambda (succ)
- (vector-push! succs n (cfa-k-idx cfa succ)))
- (block-succs (lookup-block (cfa-k-sym cfa n)
- (dfg-blocks dfg))))
- (lp (1+ n))))
-
- ;; Iterate cfa backwards, to converge quickly.
- (let ((tmp (make-bitvector k-count #f)))
- (let lp ((n k-count) (changed? #f))
- (cond
- ((zero? n)
- (if changed?
- (lp 0 #f)
- reachable))
- (else
- (let ((n (1- n)))
- (bitvector-fill! tmp #f)
- (for-each (lambda (succ)
- (bit-set*! tmp (vector-ref reachable succ) #t))
- (vector-ref succs n))
- (bitvector-set! tmp n #t)
- (bit-set*! tmp (vector-ref reachable n) #f)
- (cond
- ((bit-position #t tmp 0)
- (bit-set*! (vector-ref reachable n) tmp #t)
- (lp n #t))
- (else
- (lp n changed?))))))))))
-
-(define (find-prompts cfa dfg)
- "Find the prompts in CFA, and return them as a list of PROMPT-INDEX,
-HANDLER-INDEX pairs."
- (let lp ((n 0) (prompts '()))
- (cond
- ((= n (cfa-k-count cfa))
- (reverse prompts))
- (else
- (match (lookup-cont (cfa-k-sym cfa n) dfg)
- (($ $kargs names syms body)
- (match (find-expression body)
- (($ $prompt escape? tag handler)
- (lp (1+ n) (acons n (cfa-k-idx cfa handler) prompts)))
- (_ (lp (1+ n) prompts))))
- (_ (lp (1+ n) prompts)))))))
-
-(define (compute-interval cfa dfg reachable start end)
- "Compute and return the set of continuations that may be reached from
-START, inclusive, but not reached by END, exclusive. Returns a
-bitvector."
- (let ((body (make-bitvector (cfa-k-count cfa) #f)))
- (bit-set*! body (vector-ref reachable start) #t)
- (bit-set*! body (vector-ref reachable end) #f)
- body))
-
-(define (find-prompt-bodies cfa dfg)
- "Find all the prompts in CFA, and compute the set of continuations
-that is reachable from the prompt bodies but not from the corresponding
-handler. Returns a list of PROMPT, HANDLER, BODY lists, where the BODY
-is a bitvector."
- (match (find-prompts cfa dfg)
- (() '())
- (((prompt . handler) ...)
- (let ((reachable (compute-reachable cfa dfg)))
- (map (lambda (prompt handler)
- ;; FIXME: It isn't correct to use all continuations
- ;; reachable from the prompt, because that includes
- ;; continuations outside the prompt body. This point is
- ;; moot if the handler's control flow joins with the the
- ;; body, as is usually but not always the case.
- ;;
- ;; One counter-example is when the handler contifies an
- ;; infinite loop; in that case we compute a too-large
- ;; prompt body. This error is currently innocuous, but
- ;; we should fix it at some point.
- ;;
- ;; The fix is to end the body at the corresponding "pop"
- ;; primcall, if any.
- (let ((body (compute-interval cfa dfg reachable prompt handler)))
- (list prompt handler body)))
- prompt handler)))))
-
-(define* (visit-prompt-control-flow cfa dfg f #:key complete?)
- "For all prompts in CFA, invoke F with arguments PROMPT, HANDLER, and
-BODY for each body continuation in the prompt."
- (for-each
- (match-lambda
- ((prompt handler body)
- (define (out-or-back-edge? n)
- ;; Most uses of visit-prompt-control-flow don't need every body
- ;; continuation, and would be happy getting called only for
- ;; continuations that postdominate the rest of the body. Unless
- ;; you pass #:complete? #t, we only invoke F on continuations
- ;; that can leave the body, or on back-edges in loops.
- ;;
- ;; You would think that looking for the final "pop" primcall
- ;; would be sufficient, but that is incorrect; it's possible for
- ;; a loop in the prompt body to be contified, and that loop need
- ;; not continue to the pop if it never terminates. The pop could
- ;; even be removed by DCE, in that case.
- (or-map (lambda (succ)
- (let ((succ (cfa-k-idx cfa succ)))
- (or (not (bitvector-ref body succ))
- (<= succ n))))
- (block-succs (lookup-block (cfa-k-sym cfa n)
- (dfg-blocks dfg)))))
- (let lp ((n 0))
- (let ((n (bit-position #t body n)))
- (when n
- (when (or complete? (out-or-back-edge? n))
- (f prompt handler n))
- (lp (1+ n)))))))
- (find-prompt-bodies cfa dfg)))
-
-(define* (analyze-control-flow fun dfg #:key reverse? add-handler-preds?)
- (define (build-cfa kentry block-succs block-preds forward-cfa)
- (define (block-accessor accessor)
- (lambda (k)
- (accessor (lookup-block k (dfg-blocks dfg)))))
- (define (reachable-preds mapping accessor)
- ;; It's possible for a predecessor to not be in the mapping, if
- ;; the predecessor is not reachable from the entry node.
- (lambda (k)
- (filter-map (cut hashq-ref mapping <>)
- ((block-accessor accessor) k))))
- (let* ((order (reverse-post-order
- kentry
- (block-accessor block-succs)
- (if forward-cfa
- (lambda (f seed)
- (let lp ((n (cfa-k-count forward-cfa)) (seed seed))
- (if (zero? n)
- seed
- (lp (1- n)
- (f (cfa-k-sym forward-cfa (1- n)) seed)))))
- (lambda (f seed) seed))))
- (k-map (make-block-mapping order))
- (preds (convert-predecessors order
- (reachable-preds k-map block-preds)))
- (cfa (make-cfa k-map order preds)))
- (when add-handler-preds?
- ;; Any expression in the prompt body could cause an abort to the
- ;; handler. This code adds links from every block in the prompt
- ;; body to the handler. This causes all values used by the
- ;; handler to be seen as live in the prompt body, as indeed they
- ;; are.
- (let ((forward-cfa (or forward-cfa cfa)))
- (visit-prompt-control-flow
- forward-cfa dfg
- (lambda (prompt handler body)
- (define (renumber n)
- (if (eq? forward-cfa cfa)
- n
- (cfa-k-idx cfa (cfa-k-sym forward-cfa n))))
- (let ((handler (renumber handler))
- (body (renumber body)))
- (if reverse?
- (vector-push! preds body handler)
- (vector-push! preds handler body)))))))
- cfa))
- (match fun
- (($ $fun src meta free
- ($ $cont kentry
- (and entry
- ($ $kentry self ($ $cont ktail tail) clauses))))
- (if reverse?
- (build-cfa ktail block-preds block-succs
- (analyze-control-flow fun dfg #:reverse? #f
- #:add-handler-preds? #f))
- (build-cfa kentry block-succs block-preds #f)))))
-
-;; Dominator analysis.
-(define-record-type $dominator-analysis
- (make-dominator-analysis cfa idoms dom-levels loop-header irreducible)
- dominator-analysis?
- ;; The corresponding $cfa
- (cfa dominator-analysis-cfa)
- ;; Vector of k-idx -> k-idx
- (idoms dominator-analysis-idoms)
- ;; Vector of k-idx -> dom-level
- (dom-levels dominator-analysis-dom-levels)
- ;; Vector of k-idx -> k-idx or -1
- (loop-header dominator-analysis-loop-header)
- ;; Vector of k-idx -> true or false value
- (irreducible dominator-analysis-irreducible))
-
-(define (compute-dom-levels idoms)
- (let ((dom-levels (make-vector (vector-length idoms) #f)))
- (define (compute-dom-level n)
- (or (vector-ref dom-levels n)
- (let ((dom-level (1+ (compute-dom-level (vector-ref idoms n)))))
- (vector-set! dom-levels n dom-level)
- dom-level)))
- (vector-set! dom-levels 0 0)
- (let lp ((n 0))
- (when (< n (vector-length idoms))
- (compute-dom-level n)
- (lp (1+ n))))
- dom-levels))
-
-(define (compute-idoms preds)
- (let ((idoms (make-vector (vector-length preds) 0)))
- (define (common-idom d0 d1)
- ;; We exploit the fact that a reverse post-order is a topological
- ;; sort, and so the idom of a node is always numerically less than
- ;; the node itself.
- (cond
- ((= d0 d1) d0)
- ((< d0 d1) (common-idom d0 (vector-ref idoms d1)))
- (else (common-idom (vector-ref idoms d0) d1))))
- (define (compute-idom preds)
- (match preds
- (() 0)
- ((pred . preds)
- (let lp ((idom pred) (preds preds))
- (match preds
- (() idom)
- ((pred . preds)
- (lp (common-idom idom pred) preds)))))))
- ;; This is the iterative O(n^2) fixpoint algorithm, originally from
- ;; Allen and Cocke ("Graph-theoretic constructs for program flow
- ;; analysis", 1972). See the discussion in Cooper, Harvey, and
- ;; Kennedy's "A Simple, Fast Dominance Algorithm", 2001.
- (let iterate ((n 0) (changed? #f))
- (cond
- ((< n (vector-length preds))
- (let ((idom (vector-ref idoms n))
- (idom* (compute-idom (vector-ref preds n))))
- (cond
- ((eqv? idom idom*)
- (iterate (1+ n) changed?))
- (else
- (vector-set! idoms n idom*)
- (iterate (1+ n) #t)))))
- (changed?
- (iterate 0 #f))
- (else idoms)))))
-
-;; Compute a vector containing, for each node, a list of the nodes that
-;; it immediately dominates. These are the "D" edges in the DJ tree.
-(define (compute-dom-edges idoms)
- (let ((doms (make-vector (vector-length idoms) '())))
- (let lp ((n 0))
- (when (< n (vector-length idoms))
- (let ((idom (vector-ref idoms n)))
- (vector-push! doms idom n))
- (lp (1+ n))))
- doms))
-
-;; Compute a vector containing, for each node, a list of the successors
-;; of that node that are not dominated by that node. These are the "J"
-;; edges in the DJ tree.
-(define (compute-join-edges preds idoms)
- (define (dominates? n1 n2)
- (or (= n1 n2)
- (and (< n1 n2)
- (dominates? n1 (vector-ref idoms n2)))))
- (let ((joins (make-vector (vector-length idoms) '())))
- (let lp ((n 0))
- (when (< n (vector-length preds))
- (for-each (lambda (pred)
- (unless (dominates? pred n)
- (vector-push! joins pred n)))
- (vector-ref preds n))
- (lp (1+ n))))
- joins))
-
-;; Compute a vector containing, for each node, a list of the back edges
-;; to that node. If a node is not the entry of a reducible loop, that
-;; list is empty.
-(define (compute-reducible-back-edges joins idoms)
- (define (dominates? n1 n2)
- (or (= n1 n2)
- (and (< n1 n2)
- (dominates? n1 (vector-ref idoms n2)))))
- (let ((back-edges (make-vector (vector-length idoms) '())))
- (let lp ((n 0))
- (when (< n (vector-length joins))
- (for-each (lambda (succ)
- (when (dominates? succ n)
- (vector-push! back-edges succ n)))
- (vector-ref joins n))
- (lp (1+ n))))
- back-edges))
-
-;; Compute the levels in the dominator tree at which there are
-;; irreducible loops, as an integer. If a bit N is set in the integer,
-;; that indicates that at level N in the dominator tree, there is at
-;; least one irreducible loop.
-(define (compute-irreducible-dom-levels doms joins idoms dom-levels)
- (define (dominates? n1 n2)
- (or (= n1 n2)
- (and (< n1 n2)
- (dominates? n1 (vector-ref idoms n2)))))
- (let ((pre-order (make-vector (vector-length doms) #f))
- (last-pre-order (make-vector (vector-length doms) #f))
- (res 0)
- (count 0))
- ;; Is MAYBE-PARENT an ancestor of N on the depth-first spanning tree
- ;; computed from the DJ graph? See Havlak 1997, "Nesting of
- ;; Reducible and Irreducible Loops".
- (define (ancestor? a b)
- (let ((w (vector-ref pre-order a))
- (v (vector-ref pre-order b)))
- (and (<= w v)
- (<= v (vector-ref last-pre-order w)))))
- ;; Compute depth-first spanning tree of DJ graph.
- (define (recurse n)
- (unless (vector-ref pre-order n)
- (visit n)))
- (define (visit n)
- ;; Pre-order visitation index.
- (vector-set! pre-order n count)
- (set! count (1+ count))
- (for-each recurse (vector-ref doms n))
- (for-each recurse (vector-ref joins n))
- ;; Pre-order visitation index of last descendant.
- (vector-set! last-pre-order (vector-ref pre-order n) (1- count)))
-
- (visit 0)
-
- (let lp ((n 0))
- (when (< n (vector-length joins))
- (for-each (lambda (succ)
- ;; If this join edge is not a loop back edge but it
- ;; does go to an ancestor on the DFST of the DJ
- ;; graph, then we have an irreducible loop.
- (when (and (not (dominates? succ n))
- (ancestor? succ n))
- (set! res (logior (ash 1 (vector-ref dom-levels succ))))))
- (vector-ref joins n))
- (lp (1+ n))))
-
- res))
-
-(define (compute-nodes-by-level dom-levels)
- (let* ((max-level (let lp ((n 0) (max-level 0))
- (if (< n (vector-length dom-levels))
- (lp (1+ n) (max (vector-ref dom-levels n) max-level))
- max-level)))
- (nodes-by-level (make-vector (1+ max-level) '())))
- (let lp ((n (1- (vector-length dom-levels))))
- (when (>= n 0)
- (vector-push! nodes-by-level (vector-ref dom-levels n) n)
- (lp (1- n))))
- nodes-by-level))
-
-;; Collect all predecessors to the back-nodes that are strictly
-;; dominated by the loop header, and mark them as belonging to the loop.
-;; If they already have a loop header, that means they are either in a
-;; nested loop, or they have already been visited already.
-(define (mark-loop-body header back-nodes preds idoms loop-headers)
- (define (strictly-dominates? n1 n2)
- (and (< n1 n2)
- (let ((idom (vector-ref idoms n2)))
- (or (= n1 idom)
- (strictly-dominates? n1 idom)))))
- (define (visit node)
- (when (strictly-dominates? header node)
- (cond
- ((vector-ref loop-headers node) => visit)
- (else
- (vector-set! loop-headers node header)
- (for-each visit (vector-ref preds node))))))
- (for-each visit back-nodes))
-
-(define (mark-irreducible-loops level idoms dom-levels loop-headers)
- ;; FIXME: Identify strongly-connected components that are >= LEVEL in
- ;; the dominator tree, and somehow mark them as irreducible.
- (warn 'irreducible-loops-at-level level))
-
-;; "Identifying Loops Using DJ Graphs" by Sreedhar, Gao, and Lee, ACAPS
-;; Technical Memo 98, 1995.
-(define (identify-loops preds idoms dom-levels)
- (let* ((doms (compute-dom-edges idoms))
- (joins (compute-join-edges preds idoms))
- (back-edges (compute-reducible-back-edges joins idoms))
- (irreducible-levels
- (compute-irreducible-dom-levels doms joins idoms dom-levels))
- (loop-headers (make-vector (vector-length preds) #f))
- (nodes-by-level (compute-nodes-by-level dom-levels)))
- (let lp ((level (1- (vector-length nodes-by-level))))
- (when (>= level 0)
- (for-each (lambda (n)
- (let ((edges (vector-ref back-edges n)))
- (unless (null? edges)
- (mark-loop-body n edges preds idoms loop-headers))))
- (vector-ref nodes-by-level level))
- (when (logbit? level irreducible-levels)
- (mark-irreducible-loops level idoms dom-levels loop-headers))
- (lp (1- level))))
- loop-headers))
-
-(define (analyze-dominators cfa)
- (match cfa
- (($ $cfa k-map order preds)
- (let* ((idoms (compute-idoms preds))
- (dom-levels (compute-dom-levels idoms))
- (loop-headers (identify-loops preds idoms dom-levels)))
- (make-dominator-analysis cfa idoms dom-levels loop-headers #f)))))
-
-
-;; Compute the maximum fixed point of the data-flow constraint problem.
-;;
-;; This always completes, as the graph is finite and the in and out sets
-;; are complete semi-lattices. If the graph is reducible and the blocks
-;; are sorted in reverse post-order, this completes in a maximum of LC +
-;; 2 iterations, where LC is the loop connectedness number. See Hecht
-;; and Ullman, "Analysis of a simple algorithm for global flow
-;; problems", POPL 1973, or the recent summary in "Notes on graph
-;; algorithms used in optimizing compilers", Offner 2013.
-(define (compute-maximum-fixed-point preds inv outv killv genv union?)
- (define (bitvector-copy! dst src)
- (bitvector-fill! dst #f)
- (bit-set*! dst src #t))
- (define (bitvector-meet! accum src)
- (bit-set*! accum src union?))
- (let lp ((n 0) (changed? #f))
- (cond
- ((< n (vector-length preds))
- (let ((in (vector-ref inv n))
- (out (vector-ref outv n))
- (kill (vector-ref killv n))
- (gen (vector-ref genv n)))
- (let ((out-count (or changed? (bit-count #t out))))
- (for-each
- (lambda (pred)
- (bitvector-meet! in (vector-ref outv pred)))
- (vector-ref preds n))
- (bitvector-copy! out in)
- (for-each (cut bitvector-set! out <> #f) kill)
- (for-each (cut bitvector-set! out <> #t) gen)
- (lp (1+ n)
- (or changed? (not (eqv? out-count (bit-count #t out))))))))
- (changed?
- (lp 0 #f)))))
-
-;; Data-flow analysis.
-(define-record-type $dfa
- (make-dfa cfa var-map names syms in out)
- dfa?
- ;; CFA, for its reverse-post-order numbering
- (cfa dfa-cfa)
- ;; Hash table mapping var-sym -> var-idx
- (var-map dfa-var-map)
- ;; Vector of var-idx -> name
- (names dfa-names)
- ;; Vector of var-idx -> var-sym
- (syms dfa-syms)
- ;; Vector of k-idx -> bitvector
- (in dfa-in)
- ;; Vector of k-idx -> bitvector
- (out dfa-out))
-
-(define (dfa-k-idx dfa k)
- (cfa-k-idx (dfa-cfa dfa) k))
-
-(define (dfa-k-sym dfa idx)
- (cfa-k-sym (dfa-cfa dfa) idx))
-
-(define (dfa-k-count dfa)
- (cfa-k-count (dfa-cfa dfa)))
-
-(define (dfa-var-idx dfa var)
- (or (hashq-ref (dfa-var-map dfa) var)
- (error "unknown var" var)))
-
-(define (dfa-var-name dfa idx)
- (vector-ref (dfa-names dfa) idx))
-
-(define (dfa-var-sym dfa idx)
- (vector-ref (dfa-syms dfa) idx))
-
-(define (dfa-var-count dfa)
- (vector-length (dfa-syms dfa)))
-
-(define (dfa-k-in dfa idx)
- (vector-ref (dfa-in dfa) idx))
-
-(define (dfa-k-out dfa idx)
- (vector-ref (dfa-out dfa) idx))
-
-(define (compute-live-variables fun dfg)
- (define (make-variable-mapping use-maps)
- (let ((mapping (make-hash-table))
- (n 0))
- (hash-for-each (lambda (sym use-map)
- (hashq-set! mapping sym n)
- (set! n (1+ n)))
- use-maps)
- (values mapping n)))
- (call-with-values (lambda () (make-variable-mapping (dfg-use-maps dfg)))
- (lambda (var-map nvars)
- (let* ((cfa (analyze-control-flow fun dfg #:reverse? #t
- #:add-handler-preds? #t))
- (syms (make-vector nvars #f))
- (names (make-vector nvars #f))
- (usev (make-vector (cfa-k-count cfa) '()))
- (defv (make-vector (cfa-k-count cfa) '()))
- (live-in (make-vector (cfa-k-count cfa) #f))
- (live-out (make-vector (cfa-k-count cfa) #f)))
- ;; Initialize syms, names, defv, and usev.
- (hash-for-each
- (lambda (sym use-map)
- (match use-map
- (($ $use-map name sym def uses)
- (let ((v (or (hashq-ref var-map sym)
- (error "unknown var" sym))))
- (vector-set! syms v sym)
- (vector-set! names v name)
- (for-each (lambda (def)
- (vector-push! defv (cfa-k-idx cfa def) v))
- (block-preds (lookup-block def (dfg-blocks dfg))))
- (for-each (lambda (use)
- (vector-push! usev (cfa-k-idx cfa use) v))
- uses)))))
- (dfg-use-maps dfg))
-
- ;; Initialize live-in and live-out sets.
- (let lp ((n 0))
- (when (< n (vector-length live-out))
- (vector-set! live-in n (make-bitvector nvars #f))
- (vector-set! live-out n (make-bitvector nvars #f))
- (lp (1+ n))))
-
- ;; Liveness is a reverse data-flow problem, so we give
- ;; compute-maximum-fixed-point a reversed graph, swapping in
- ;; for out, and usev for defv. Note that since we are using
- ;; a reverse CFA, cfa-preds are actually successors, and
- ;; continuation 0 is ktail.
- (compute-maximum-fixed-point (cfa-preds cfa)
- live-out live-in defv usev #t)
-
- (make-dfa cfa var-map names syms live-in live-out)))))
-
-(define (print-dfa dfa)
- (match dfa
- (($ $dfa cfa var-map names syms in out)
- (define (print-var-set bv)
- (let lp ((n 0))
- (let ((n (bit-position #t bv n)))
- (when n
- (format #t " ~A" (vector-ref syms n))
- (lp (1+ n))))))
- (let lp ((n 0))
- (when (< n (cfa-k-count cfa))
- (format #t "~A:\n" (cfa-k-sym cfa n))
- (format #t " in:")
- (print-var-set (vector-ref in n))
- (newline)
- (format #t " out:")
- (print-var-set (vector-ref out n))
- (newline)
- (lp (1+ n)))))))
-
-(define (visit-fun fun conts blocks use-maps global?)
- (define (add-def! name sym def-k)
- (unless def-k
- (error "Term outside labelled continuation?"))
- (hashq-set! use-maps sym (make-use-map name sym def-k '())))
-
- (define (add-use! sym use-k)
- (match (hashq-ref use-maps sym)
- (#f (error "Symbol out of scope?" sym))
- ((and use-map ($ $use-map name sym def uses))
- (set-use-map-uses! use-map (cons use-k uses)))))
-
- (define* (declare-block! label cont parent
- #:optional (level
- (1+ (lookup-scope-level parent blocks))))
- (hashq-set! conts label cont)
- (hashq-set! blocks label (make-block parent level)))
-
- (define (link-blocks! pred succ)
- (let ((pred-block (hashq-ref blocks pred))
- (succ-block (hashq-ref blocks succ)))
- (unless (and pred-block succ-block)
- (error "internal error" pred-block succ-block))
- (set-block-succs! pred-block (cons succ (block-succs pred-block)))
- (set-block-preds! succ-block (cons pred (block-preds succ-block)))))
-
- (define (visit exp exp-k)
- (define (def! name sym)
- (add-def! name sym exp-k))
- (define (use! sym)
- (add-use! sym exp-k))
- (define (use-k! k)
- (link-blocks! exp-k k))
- (define (recur exp)
- (visit exp exp-k))
- (match exp
- (($ $letk (($ $cont k cont) ...) body)
- ;; Set up recursive environment before visiting cont bodies.
- (for-each/2 (lambda (cont k)
- (declare-block! k cont exp-k))
- cont k)
- (for-each/2 visit cont k)
- (recur body))
-
- (($ $kargs names syms body)
- (for-each/2 def! names syms)
- (recur body))
-
- (($ $kif kt kf)
- (use-k! kt)
- (use-k! kf))
-
- (($ $kreceive arity k)
- (use-k! k))
-
- (($ $letrec names syms funs body)
- (unless global?
- (error "$letrec should not be present when building a local DFG"))
- (for-each/2 def! names syms)
- (for-each (cut visit-fun <> conts blocks use-maps global?) funs)
- (visit body exp-k))
-
- (($ $continue k src exp)
- (use-k! k)
- (match exp
- (($ $call proc args)
- (use! proc)
- (for-each use! args))
-
- (($ $callk k proc args)
- (use! proc)
- (for-each use! args))
-
- (($ $primcall name args)
- (for-each use! args))
-
- (($ $values args)
- (for-each use! args))
-
- (($ $prompt escape? tag handler)
- (use! tag)
- (use-k! handler))
-
- (($ $fun)
- (when global?
- (visit-fun exp conts blocks use-maps global?)))
-
- (_ #f)))))
-
- (match fun
- (($ $fun src meta free
- ($ $cont kentry
- (and entry
- ($ $kentry self ($ $cont ktail tail) clauses))))
- (declare-block! kentry entry #f 0)
- (add-def! #f self kentry)
-
- (declare-block! ktail tail kentry)
-
- (for-each
- (match-lambda
- (($ $cont kclause
- (and clause ($ $kclause arity ($ $cont kbody body))))
- (declare-block! kclause clause kentry)
- (link-blocks! kentry kclause)
-
- (declare-block! kbody body kclause)
- (link-blocks! kclause kbody)
-
- (visit body kbody)))
- clauses))))
-
-(define* (compute-dfg fun #:key (global? #t))
- (let* ((conts (make-hash-table))
- (blocks (make-hash-table))
- (use-maps (make-hash-table)))
- (visit-fun fun conts blocks use-maps global?)
- (make-dfg conts blocks use-maps)))
-
-(define (lookup-block k blocks)
- (let ((res (hashq-ref blocks k)))
- (unless res
- (error "Unknown continuation!" k (hash-fold acons '() blocks)))
- res))
-
-(define (lookup-scope-level k blocks)
- (match (lookup-block k blocks)
- (($ $block _ scope-level) scope-level)))
-
-(define (lookup-use-map sym use-maps)
- (let ((res (hashq-ref use-maps sym)))
- (unless res
- (error "Unknown lexical!" sym (hash-fold acons '() use-maps)))
- res))
-
-(define (lookup-def sym dfg)
- (match dfg
- (($ $dfg conts blocks use-maps)
- (match (lookup-use-map sym use-maps)
- (($ $use-map name sym def uses)
- def)))))
-
-(define (lookup-uses sym dfg)
- (match dfg
- (($ $dfg conts blocks use-maps)
- (match (lookup-use-map sym use-maps)
- (($ $use-map name sym def uses)
- uses)))))
-
-(define (lookup-block-scope k dfg)
- (block-scope (lookup-block k (dfg-blocks dfg))))
-
-(define (lookup-predecessors k dfg)
- (match (lookup-block k (dfg-blocks dfg))
- (($ $block _ _ preds succs) preds)))
-
-(define (lookup-successors k dfg)
- (match (lookup-block k (dfg-blocks dfg))
- (($ $block _ _ preds succs) succs)))
-
-(define (find-defining-term sym dfg)
- (match (lookup-predecessors (lookup-def sym dfg) dfg)
- ((def-exp-k)
- (lookup-cont def-exp-k dfg))
- (else #f)))
-
-(define (find-call term)
- (match term
- (($ $kargs names syms body) (find-call body))
- (($ $letk conts body) (find-call body))
- (($ $letrec names syms funs body) (find-call body))
- (($ $continue) term)))
-
-(define (call-expression call)
- (match call
- (($ $continue k src exp) exp)))
-
-(define (find-expression term)
- (call-expression (find-call term)))
-
-(define (find-defining-expression sym dfg)
- (match (find-defining-term sym dfg)
- (#f #f)
- (($ $kreceive) #f)
- (($ $kclause) #f)
- (term (find-expression term))))
-
-(define (find-constant-value sym dfg)
- (match (find-defining-expression sym dfg)
- (($ $const val)
- (values #t val))
- (($ $continue k src ($ $void))
- (values #t *unspecified*))
- (else
- (values #f #f))))
-
-(define (constant-needs-allocation? sym val dfg)
- (define (immediate-u8? val)
- (and (integer? val) (exact? val) (<= 0 val 255)))
-
- (define (find-exp term)
- (match term
- (($ $kargs names syms body) (find-exp body))
- (($ $letk conts body) (find-exp body))
- (else term)))
- (match dfg
- (($ $dfg conts blocks use-maps)
- (match (lookup-use-map sym use-maps)
- (($ $use-map _ _ def uses)
- (or-map
- (lambda (use)
- (match (find-expression (lookup-cont use dfg))
- (($ $call) #f)
- (($ $callk) #f)
- (($ $values) #f)
- (($ $primcall 'free-ref (closure slot))
- (not (eq? sym slot)))
- (($ $primcall 'free-set! (closure slot value))
- (not (eq? sym slot)))
- (($ $primcall 'cache-current-module! (mod . _))
- (eq? sym mod))
- (($ $primcall 'cached-toplevel-box _)
- #f)
- (($ $primcall 'cached-module-box _)
- #f)
- (($ $primcall 'resolve (name bound?))
- (eq? sym name))
- (($ $primcall 'make-vector/immediate (len init))
- (not (eq? sym len)))
- (($ $primcall 'vector-ref/immediate (v i))
- (not (eq? sym i)))
- (($ $primcall 'vector-set!/immediate (v i x))
- (not (eq? sym i)))
- (($ $primcall 'allocate-struct/immediate (vtable nfields))
- (not (eq? sym nfields)))
- (($ $primcall 'struct-ref/immediate (s n))
- (not (eq? sym n)))
- (($ $primcall 'struct-set!/immediate (s n x))
- (not (eq? sym n)))
- (($ $primcall 'builtin-ref (idx))
- #f)
- (_ #t)))
- uses))))))
-
-(define (continuation-scope-contains? scope-k k blocks)
- (let ((scope-level (lookup-scope-level scope-k blocks)))
- (let lp ((k k))
- (or (eq? scope-k k)
- (match (lookup-block k blocks)
- (($ $block scope level)
- (and (< scope-level level)
- (lp scope))))))))
-
-(define (continuation-bound-in? k use-k dfg)
- (match dfg
- (($ $dfg conts blocks use-maps)
- (match (lookup-block k blocks)
- (($ $block def-k)
- (continuation-scope-contains? def-k use-k blocks))))))
-
-(define (variable-free-in? var k dfg)
- (match dfg
- (($ $dfg conts blocks use-maps)
- (or-map (lambda (use)
- (continuation-scope-contains? k use blocks))
- (match (lookup-use-map var use-maps)
- (($ $use-map name sym def uses)
- uses))))))
-
-;; A continuation is a control point if it has multiple predecessors, or
-;; if its single predecessor has multiple successors.
-(define (control-point? k dfg)
- (match (lookup-predecessors k dfg)
- ((pred)
- (match (lookup-successors pred dfg)
- ((_) #f)
- (_ #t)))
- (_ #t)))
-
-(define (lookup-bound-syms k dfg)
- (match (lookup-cont k dfg)
- (($ $kargs names syms body)
- syms)))
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013, 2014, 2015 Free Software Foundation, Inc.
+
+;;;; This library is free software; you can redistribute it and/or
+;;;; modify it under the terms of the GNU Lesser General Public
+;;;; License as published by the Free Software Foundation; either
+;;;; version 3 of the License, or (at your option) any later version.
+;;;;
+;;;; This library is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;;; Lesser General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU Lesser General Public
+;;;; License along with this library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+;;;
+;;; Many passes rely on a local or global static analysis of a function.
+;;; This module implements a simple data-flow graph (DFG) analysis,
+;;; tracking the definitions and uses of variables and continuations.
+;;; It also builds a table of continuations and scope links, to be able
+;;; to easily determine if one continuation is in the scope of another,
+;;; and to get to the expression inside a continuation.
+;;;
+;;; Note that the data-flow graph of continuation labels is a
+;;; control-flow graph.
+;;;
+;;; We currently don't expose details of the DFG type outside this
+;;; module, preferring to only expose accessors. That may change in the
+;;; future but it seems to work for now.
+;;;
+;;; Code:
+
+(define-module (language cps dfg)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 format)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:use-module (language cps intset)
+ #:export (build-cont-table
+ lookup-cont
+
+ compute-dfg
+ dfg-cont-table
+ dfg-min-label
+ dfg-label-count
+ dfg-min-var
+ dfg-var-count
+ with-fresh-name-state-from-dfg
+ lookup-def
+ lookup-uses
+ lookup-predecessors
+ lookup-successors
+ lookup-block-scope
+ find-call
+ call-expression
+ find-expression
+ find-defining-expression
+ find-constant-value
+ continuation-bound-in?
+ variable-free-in?
+ constant-needs-allocation?
+ control-point?
+ lookup-bound-syms
+
+ compute-idoms
+ compute-dom-edges
+
+ ;; Data flow analysis.
+ compute-live-variables
+ dfa-k-idx dfa-k-sym dfa-k-count dfa-k-in dfa-k-out
+ dfa-var-idx dfa-var-sym dfa-var-count
+ print-dfa))
+
+;; These definitions are here because currently we don't do cross-module
+;; inlining. They can be removed once that restriction is gone.
+(define-inlinable (for-each f l)
+ (unless (list? l)
+ (scm-error 'wrong-type-arg "for-each" "Not a list: ~S" (list l) #f))
+ (let for-each1 ((l l))
+ (unless (null? l)
+ (f (car l))
+ (for-each1 (cdr l)))))
+
+(define-inlinable (for-each/2 f l1 l2)
+ (unless (= (length l1) (length l2))
+ (scm-error 'wrong-type-arg "for-each" "List of wrong length: ~S"
+ (list l2) #f))
+ (let for-each2 ((l1 l1) (l2 l2))
+ (unless (null? l1)
+ (f (car l1) (car l2))
+ (for-each2 (cdr l1) (cdr l2)))))
+
+(define (build-cont-table fun)
+ (let ((max-k (fold-conts (lambda (k cont max-k) (max k max-k))
+ -1 fun)))
+ (fold-conts (lambda (k cont table)
+ (vector-set! table k cont)
+ table)
+ (make-vector (1+ max-k) #f)
+ fun)))
+
+;; Data-flow graph for CPS: both for values and continuations.
+(define-record-type $dfg
+ (make-dfg conts preds defs uses scopes scope-levels
+ min-label max-label label-count
+ min-var max-var var-count)
+ dfg?
+ ;; vector of label -> $kargs, etc
+ (conts dfg-cont-table)
+ ;; vector of label -> (pred-label ...)
+ (preds dfg-preds)
+ ;; vector of var -> def-label
+ (defs dfg-defs)
+ ;; vector of var -> (use-label ...)
+ (uses dfg-uses)
+ ;; vector of label -> label
+ (scopes dfg-scopes)
+ ;; vector of label -> int
+ (scope-levels dfg-scope-levels)
+
+ (min-label dfg-min-label)
+ (max-label dfg-max-label)
+ (label-count dfg-label-count)
+
+ (min-var dfg-min-var)
+ (max-var dfg-max-var)
+ (var-count dfg-var-count))
+
+(define-inlinable (vector-push! vec idx val)
+ (let ((v vec) (i idx))
+ (vector-set! v i (cons val (vector-ref v i)))))
+
+(define (compute-reachable dfg min-label label-count)
+ "Compute and return the continuations that may be reached if flow
+reaches a continuation N. Returns a vector of intsets, whose first
+index corresponds to MIN-LABEL, and so on."
+ (let (;; Vector of intsets, indicating that continuation N can
+ ;; reach a set M...
+ (reachable (make-vector label-count #f)))
+
+ (define (label->idx label) (- label min-label))
+
+ ;; Iterate labels backwards, to converge quickly.
+ (let lp ((label (+ min-label label-count)) (changed? #f))
+ (cond
+ ((= label min-label)
+ (if changed?
+ (lp (+ min-label label-count) #f)
+ reachable))
+ (else
+ (let* ((label (1- label))
+ (idx (label->idx label))
+ (old (vector-ref reachable idx))
+ (new (fold (lambda (succ set)
+ (cond
+ ((vector-ref reachable (label->idx succ))
+ => (lambda (succ-set)
+ (intset-union set succ-set)))
+ (else set)))
+ (or (vector-ref reachable idx)
+ (intset-add empty-intset label))
+ (visit-cont-successors list
+ (lookup-cont label dfg)))))
+ (cond
+ ((eq? old new)
+ (lp label changed?))
+ (else
+ (vector-set! reachable idx new)
+ (lp label #t)))))))))
+
+(define (find-prompts dfg min-label label-count)
+ "Find the prompts in DFG between MIN-LABEL and MIN-LABEL +
+LABEL-COUNT, and return them as a list of PROMPT-LABEL, HANDLER-LABEL
+pairs."
+ (let lp ((label min-label) (prompts '()))
+ (cond
+ ((= label (+ min-label label-count))
+ (reverse prompts))
+ (else
+ (match (lookup-cont label dfg)
+ (($ $kargs names syms body)
+ (match (find-expression body)
+ (($ $prompt escape? tag handler)
+ (lp (1+ label) (acons label handler prompts)))
+ (_ (lp (1+ label) prompts))))
+ (_ (lp (1+ label) prompts)))))))
+
+(define (compute-interval reachable min-label label-count start end)
+ "Compute and return the set of continuations that may be reached from
+START, inclusive, but not reached by END, exclusive. Returns an
+intset."
+ (intset-subtract (vector-ref reachable (- start min-label))
+ (vector-ref reachable (- end min-label))))
+
+(define (find-prompt-bodies dfg min-label label-count)
+ "Find all the prompts in DFG from the LABEL-COUNT continuations
+starting at MIN-LABEL, and compute the set of continuations that is
+reachable from the prompt bodies but not from the corresponding handler.
+Returns a list of PROMPT, HANDLER, BODY lists, where the BODY is an
+intset."
+ (match (find-prompts dfg min-label label-count)
+ (() '())
+ (((prompt . handler) ...)
+ (let ((reachable (compute-reachable dfg min-label label-count)))
+ (map (lambda (prompt handler)
+ ;; FIXME: It isn't correct to use all continuations
+ ;; reachable from the prompt, because that includes
+ ;; continuations outside the prompt body. This point is
+ ;; moot if the handler's control flow joins with the the
+ ;; body, as is usually but not always the case.
+ ;;
+ ;; One counter-example is when the handler contifies an
+ ;; infinite loop; in that case we compute a too-large
+ ;; prompt body. This error is currently innocuous, but we
+ ;; should fix it at some point.
+ ;;
+ ;; The fix is to end the body at the corresponding "pop"
+ ;; primcall, if any.
+ (let ((body (compute-interval reachable min-label label-count
+ prompt handler)))
+ (list prompt handler body)))
+ prompt handler)))))
+
+(define* (visit-prompt-control-flow dfg min-label label-count f #:key complete?)
+ "For all prompts in DFG in the range [MIN-LABEL, MIN-LABEL +
+LABEL-COUNT), invoke F with arguments PROMPT, HANDLER, and BODY for each
+body continuation in the prompt."
+ (define (label->idx label) (- label min-label))
+ (define (idx->label idx) (+ idx min-label))
+ (for-each
+ (match-lambda
+ ((prompt handler body)
+ (define (out-or-back-edge? label)
+ ;; Most uses of visit-prompt-control-flow don't need every body
+ ;; continuation, and would be happy getting called only for
+ ;; continuations that postdominate the rest of the body. Unless
+ ;; you pass #:complete? #t, we only invoke F on continuations
+ ;; that can leave the body, or on back-edges in loops.
+ ;;
+ ;; You would think that looking for the final "pop" primcall
+ ;; would be sufficient, but that is incorrect; it's possible for
+ ;; a loop in the prompt body to be contified, and that loop need
+ ;; not continue to the pop if it never terminates. The pop could
+ ;; even be removed by DCE, in that case.
+ (or-map (lambda (succ)
+ (or (not (intset-ref body succ))
+ (<= succ label)))
+ (lookup-successors label dfg)))
+ (let lp ((label min-label))
+ (let ((label (intset-next body label)))
+ (when label
+ (when (or complete? (out-or-back-edge? label))
+ (f prompt handler label))
+ (lp (1+ label)))))))
+ (find-prompt-bodies dfg min-label label-count)))
+
+(define (analyze-reverse-control-flow fun dfg min-label label-count)
+ (define (compute-reverse-control-flow-order ktail dfg)
+ (let ((label-map (make-vector label-count #f))
+ (next -1))
+ (define (label->idx label) (- label min-label))
+ (define (idx->label idx) (+ idx min-label))
+
+ (let visit ((k ktail))
+ ;; Mark this label as visited.
+ (vector-set! label-map (label->idx k) #t)
+ (for-each (lambda (k)
+ ;; Visit predecessors unless they are already visited.
+ (unless (vector-ref label-map (label->idx k))
+ (visit k)))
+ (lookup-predecessors k dfg))
+ ;; Add to reverse post-order chain.
+ (vector-set! label-map (label->idx k) next)
+ (set! next k))
+
+ (let lp ((n 0) (head next))
+ (if (< head 0)
+ ;; Add nodes that are not reachable from the tail.
+ (let lp ((n n) (m label-count))
+ (unless (= n label-count)
+ (let find-unvisited ((m (1- m)))
+ (if (vector-ref label-map m)
+ (find-unvisited (1- m))
+ (begin
+ (vector-set! label-map m n)
+ (lp (1+ n) m))))))
+ ;; Pop the head off the chain, give it its
+ ;; reverse-post-order numbering, and continue.
+ (let ((next (vector-ref label-map (label->idx head))))
+ (vector-set! label-map (label->idx head) n)
+ (lp (1+ n) next))))
+
+ label-map))
+
+ (define (convert-successors k-map)
+ (define (idx->label idx) (+ idx min-label))
+ (define (renumber label)
+ (vector-ref k-map (- label min-label)))
+ (let ((succs (make-vector (vector-length k-map) #f)))
+ (let lp ((n 0))
+ (when (< n (vector-length succs))
+ (vector-set! succs (vector-ref k-map n)
+ (map renumber
+ (lookup-successors (idx->label n) dfg)))
+ (lp (1+ n))))
+ succs))
+
+ (match fun
+ (($ $cont kfun ($ $kfun src meta self ($ $cont ktail tail)))
+ (let* ((k-map (compute-reverse-control-flow-order ktail dfg))
+ (succs (convert-successors k-map)))
+ ;; Any expression in the prompt body could cause an abort to
+ ;; the handler. This code adds links from every block in the
+ ;; prompt body to the handler. This causes all values used
+ ;; by the handler to be seen as live in the prompt body, as
+ ;; indeed they are.
+ (visit-prompt-control-flow
+ dfg min-label label-count
+ (lambda (prompt handler body)
+ (define (renumber label)
+ (vector-ref k-map (- label min-label)))
+ (vector-push! succs (renumber body) (renumber handler))))
+
+ (values k-map succs)))))
+
+(define (compute-idoms dfg min-label label-count)
+ (define preds (dfg-preds dfg))
+ (define (label->idx label) (- label min-label))
+ (define (idx->label idx) (+ idx min-label))
+ (define (idx->dfg-idx idx) (- (idx->label idx) (dfg-min-label dfg)))
+ (let ((idoms (make-vector label-count #f)))
+ (define (common-idom d0 d1)
+ ;; We exploit the fact that a reverse post-order is a topological
+ ;; sort, and so the idom of a node is always numerically less than
+ ;; the node itself.
+ (cond
+ ((= d0 d1) d0)
+ ((< d0 d1) (common-idom d0 (vector-ref idoms (label->idx d1))))
+ (else (common-idom (vector-ref idoms (label->idx d0)) d1))))
+ (define (compute-idom preds)
+ (define (has-idom? pred)
+ (vector-ref idoms (label->idx pred)))
+ (match preds
+ (() min-label)
+ ((pred . preds)
+ (if (has-idom? pred)
+ (let lp ((idom pred) (preds preds))
+ (match preds
+ (() idom)
+ ((pred . preds)
+ (lp (if (has-idom? pred)
+ (common-idom idom pred)
+ idom)
+ preds))))
+ (compute-idom preds)))))
+ ;; This is the iterative O(n^2) fixpoint algorithm, originally from
+ ;; Allen and Cocke ("Graph-theoretic constructs for program flow
+ ;; analysis", 1972). See the discussion in Cooper, Harvey, and
+ ;; Kennedy's "A Simple, Fast Dominance Algorithm", 2001.
+ (let iterate ((n 0) (changed? #f))
+ (cond
+ ((< n label-count)
+ (let ((idom (vector-ref idoms n))
+ (idom* (compute-idom (vector-ref preds (idx->dfg-idx n)))))
+ (cond
+ ((eqv? idom idom*)
+ (iterate (1+ n) changed?))
+ (else
+ (vector-set! idoms n idom*)
+ (iterate (1+ n) #t)))))
+ (changed?
+ (iterate 0 #f))
+ (else idoms)))))
+
+;; Compute a vector containing, for each node, a list of the nodes that
+;; it immediately dominates. These are the "D" edges in the DJ tree.
+(define (compute-dom-edges idoms min-label)
+ (define (label->idx label) (- label min-label))
+ (define (idx->label idx) (+ idx min-label))
+ (let ((doms (make-vector (vector-length idoms) '())))
+ (let lp ((n 0))
+ (when (< n (vector-length idoms))
+ (let ((idom (vector-ref idoms n)))
+ (vector-push! doms (label->idx idom) (idx->label n)))
+ (lp (1+ n))))
+ doms))
+
+;; There used to be some loop detection code here, but it bitrotted.
+;; We'll need it again eventually but for now it can be found in the git
+;; history.
+
+;; Data-flow analysis.
+(define-record-type $dfa
+ (make-dfa min-label min-var var-count in out)
+ dfa?
+ ;; Minimum label in this function.
+ (min-label dfa-min-label)
+ ;; Minimum var in this function.
+ (min-var dfa-min-var)
+ ;; Var count in this function.
+ (var-count dfa-var-count)
+ ;; Vector of k-idx -> intset
+ (in dfa-in)
+ ;; Vector of k-idx -> intset
+ (out dfa-out))
+
+(define (dfa-k-idx dfa k)
+ (- k (dfa-min-label dfa)))
+
+(define (dfa-k-sym dfa idx)
+ (+ idx (dfa-min-label dfa)))
+
+(define (dfa-k-count dfa)
+ (vector-length (dfa-in dfa)))
+
+(define (dfa-var-idx dfa var)
+ (let ((idx (- var (dfa-min-var dfa))))
+ (unless (< -1 idx (dfa-var-count dfa))
+ (error "var out of range" var))
+ idx))
+
+(define (dfa-var-sym dfa idx)
+ (unless (< -1 idx (dfa-var-count dfa))
+ (error "idx out of range" idx))
+ (+ idx (dfa-min-var dfa)))
+
+(define (dfa-k-in dfa idx)
+ (vector-ref (dfa-in dfa) idx))
+
+(define (dfa-k-out dfa idx)
+ (vector-ref (dfa-out dfa) idx))
+
+(define (compute-live-variables fun dfg)
+ ;; Compute the maximum fixed point of the data-flow constraint problem.
+ ;;
+ ;; This always completes, as the graph is finite and the in and out sets
+ ;; are complete semi-lattices. If the graph is reducible and the blocks
+ ;; are sorted in reverse post-order, this completes in a maximum of LC +
+ ;; 2 iterations, where LC is the loop connectedness number. See Hecht
+ ;; and Ullman, "Analysis of a simple algorithm for global flow
+ ;; problems", POPL 1973, or the recent summary in "Notes on graph
+ ;; algorithms used in optimizing compilers", Offner 2013.
+ (define (compute-maximum-fixed-point preds inv outv killv genv)
+ (define (fold f seed l)
+ (if (null? l) seed (fold f (f (car l) seed) (cdr l))))
+ (let lp ((n 0) (changed? #f))
+ (cond
+ ((< n (vector-length preds))
+ (let* ((in (vector-ref inv n))
+ (in* (or
+ (fold (lambda (pred set)
+ (cond
+ ((vector-ref outv pred)
+ => (lambda (out)
+ (if set
+ (intset-union set out)
+ out)))
+ (else set)))
+ in
+ (vector-ref preds n))
+ empty-intset)))
+ (if (eq? in in*)
+ (lp (1+ n) changed?)
+ (let ((out* (fold (lambda (gen set)
+ (intset-add set gen))
+ (fold (lambda (kill set)
+ (intset-remove set kill))
+ in*
+ (vector-ref killv n))
+ (vector-ref genv n))))
+ (vector-set! inv n in*)
+ (vector-set! outv n out*)
+ (lp (1+ n) #t)))))
+ (changed?
+ (lp 0 #f)))))
+
+ (unless (and (= (vector-length (dfg-uses dfg)) (dfg-var-count dfg))
+ (= (vector-length (dfg-cont-table dfg)) (dfg-label-count dfg)))
+ (error "function needs renumbering"))
+ (let* ((min-label (dfg-min-label dfg))
+ (nlabels (dfg-label-count dfg))
+ (min-var (dfg-min-var dfg))
+ (nvars (dfg-var-count dfg))
+ (usev (make-vector nlabels '()))
+ (defv (make-vector nlabels '()))
+ (live-in (make-vector nlabels #f))
+ (live-out (make-vector nlabels #f)))
+ (call-with-values
+ (lambda ()
+ (analyze-reverse-control-flow fun dfg min-label nlabels))
+ (lambda (k-map succs)
+ (define (var->idx var) (- var min-var))
+ (define (idx->var idx) (+ idx min-var))
+ (define (label->idx label)
+ (vector-ref k-map (- label min-label)))
+
+ ;; Initialize defv and usev.
+ (let ((defs (dfg-defs dfg))
+ (uses (dfg-uses dfg)))
+ (let lp ((n 0))
+ (when (< n (vector-length defs))
+ (let ((def (vector-ref defs n)))
+ (unless def
+ (error "internal error -- var array not packed"))
+ (for-each (lambda (def)
+ (vector-push! defv (label->idx def) n))
+ (lookup-predecessors def dfg))
+ (for-each (lambda (use)
+ (vector-push! usev (label->idx use) n))
+ (vector-ref uses n))
+ (lp (1+ n))))))
+
+ ;; Liveness is a reverse data-flow problem, so we give
+ ;; compute-maximum-fixed-point a reversed graph, swapping in for
+ ;; out, usev for defv, and using successors instead of
+ ;; predecessors. Continuation 0 is ktail.
+ (compute-maximum-fixed-point succs live-out live-in defv usev)
+
+ ;; Now rewrite the live-in and live-out sets to be indexed by
+ ;; (LABEL - MIN-LABEL).
+ (let ((live-in* (make-vector nlabels #f))
+ (live-out* (make-vector nlabels #f)))
+ (let lp ((idx 0))
+ (when (< idx nlabels)
+ (let ((dfa-idx (vector-ref k-map idx)))
+ (vector-set! live-in* idx (vector-ref live-in dfa-idx))
+ (vector-set! live-out* idx (vector-ref live-out dfa-idx))
+ (lp (1+ idx)))))
+
+ (make-dfa min-label min-var nvars live-in* live-out*))))))
+
+(define (print-dfa dfa)
+ (match dfa
+ (($ $dfa min-label min-var var-count in out)
+ (define (print-var-set bv)
+ (let lp ((n 0))
+ (let ((n (intset-next bv n)))
+ (when n
+ (format #t " ~A" (+ n min-var))
+ (lp (1+ n))))))
+ (let lp ((n 0))
+ (when (< n (vector-length in))
+ (format #t "~A:\n" (+ n min-label))
+ (format #t " in:")
+ (print-var-set (vector-ref in n))
+ (newline)
+ (format #t " out:")
+ (print-var-set (vector-ref out n))
+ (newline)
+ (lp (1+ n)))))))
+
+(define (compute-label-and-var-ranges fun global?)
+ (define (min* a b)
+ (if b (min a b) a))
+ (define-syntax-rule (do-fold make-cont-folder)
+ ((make-cont-folder min-label max-label label-count
+ min-var max-var var-count)
+ (lambda (label cont
+ min-label max-label label-count
+ min-var max-var var-count)
+ (let ((min-label (min* label min-label))
+ (max-label (max label max-label)))
+ (define (visit-letrec body min-var max-var var-count)
+ (match body
+ (($ $letk conts body)
+ (visit-letrec body min-var max-var var-count))
+ (($ $letrec names vars funs body)
+ (visit-letrec body
+ (cond (min-var (fold min min-var vars))
+ ((pair? vars) (fold min (car vars) (cdr vars)))
+ (else min-var))
+ (fold max max-var vars)
+ (+ var-count (length vars))))
+ (($ $continue) (values min-var max-var var-count))))
+ (match cont
+ (($ $kargs names vars body)
+ (call-with-values
+ (lambda ()
+ (if global?
+ (visit-letrec body min-var max-var var-count)
+ (values min-var max-var var-count)))
+ (lambda (min-var max-var var-count)
+ (values min-label max-label (1+ label-count)
+ (cond (min-var (fold min min-var vars))
+ ((pair? vars) (fold min (car vars) (cdr vars)))
+ (else min-var))
+ (fold max max-var vars)
+ (+ var-count (length vars))))))
+ (($ $kfun src meta self)
+ (values min-label max-label (1+ label-count)
+ (min* self min-var) (max self max-var) (1+ var-count)))
+ (_ (values min-label max-label (1+ label-count)
+ min-var max-var var-count)))))
+ fun
+ #f -1 0 #f -1 0))
+ (if global?
+ (do-fold make-global-cont-folder)
+ (do-fold make-local-cont-folder)))
+
+(define* (compute-dfg fun #:key (global? #t))
+ (call-with-values (lambda () (compute-label-and-var-ranges fun global?))
+ (lambda (min-label max-label label-count min-var max-var var-count)
+ (when (or (zero? label-count) (zero? var-count))
+ (error "internal error (no vars or labels for fun?)"))
+ (let* ((nlabels (- (1+ max-label) min-label))
+ (nvars (- (1+ max-var) min-var))
+ (conts (make-vector nlabels #f))
+ (preds (make-vector nlabels '()))
+ (defs (make-vector nvars #f))
+ (uses (make-vector nvars '()))
+ (scopes (make-vector nlabels #f))
+ (scope-levels (make-vector nlabels #f)))
+ (define (var->idx var) (- var min-var))
+ (define (label->idx label) (- label min-label))
+
+ (define (add-def! var def-k)
+ (vector-set! defs (var->idx var) def-k))
+ (define (add-use! var use-k)
+ (vector-push! uses (var->idx var) use-k))
+
+ (define* (declare-block! label cont parent
+ #:optional (level
+ (1+ (vector-ref
+ scope-levels
+ (label->idx parent)))))
+ (vector-set! conts (label->idx label) cont)
+ (vector-set! scopes (label->idx label) parent)
+ (vector-set! scope-levels (label->idx label) level))
+
+ (define (link-blocks! pred succ)
+ (vector-push! preds (label->idx succ) pred))
+
+ (define (visit-cont cont label)
+ (match cont
+ (($ $kargs names syms body)
+ (for-each (cut add-def! <> label) syms)
+ (visit-term body label))
+ (($ $kreceive arity k)
+ (link-blocks! label k))))
+
+ (define (visit-term term label)
+ (match term
+ (($ $letk (($ $cont k cont) ...) body)
+ ;; Set up recursive environment before visiting cont bodies.
+ (for-each/2 (lambda (cont k)
+ (declare-block! k cont label))
+ cont k)
+ (for-each/2 visit-cont cont k)
+ (visit-term body label))
+ (($ $letrec names syms funs body)
+ (unless global?
+ (error "$letrec should not be present when building a local DFG"))
+ (for-each (cut add-def! <> label) syms)
+ (for-each (lambda (fun)
+ (match fun
+ (($ $fun free body)
+ (visit-fun body))))
+ funs)
+ (visit-term body label))
+ (($ $continue k src exp)
+ (link-blocks! label k)
+ (visit-exp exp label))))
+
+ (define (visit-exp exp label)
+ (define (use! sym)
+ (add-use! sym label))
+ (match exp
+ ((or ($ $const) ($ $prim) ($ $closure)) #f)
+ (($ $call proc args)
+ (use! proc)
+ (for-each use! args))
+ (($ $callk k proc args)
+ (use! proc)
+ (for-each use! args))
+ (($ $primcall name args)
+ (for-each use! args))
+ (($ $branch kt exp)
+ (link-blocks! label kt)
+ (visit-exp exp label))
+ (($ $values args)
+ (for-each use! args))
+ (($ $prompt escape? tag handler)
+ (use! tag)
+ (link-blocks! label handler))
+ (($ $fun free body)
+ (when global?
+ (visit-fun body)))))
+
+ (define (visit-clause clause kfun)
+ (match clause
+ (#f #t)
+ (($ $cont kclause
+ (and clause ($ $kclause arity ($ $cont kbody body)
+ alternate)))
+ (declare-block! kclause clause kfun)
+ (link-blocks! kfun kclause)
+
+ (declare-block! kbody body kclause)
+ (link-blocks! kclause kbody)
+
+ (visit-cont body kbody)
+ (visit-clause alternate kfun))))
+
+ (define (visit-fun fun)
+ (match fun
+ (($ $cont kfun
+ (and cont
+ ($ $kfun src meta self ($ $cont ktail tail) clause)))
+ (declare-block! kfun cont #f 0)
+ (add-def! self kfun)
+ (declare-block! ktail tail kfun)
+ (visit-clause clause kfun))))
+
+ (visit-fun fun)
+
+ (make-dfg conts preds defs uses scopes scope-levels
+ min-label max-label label-count
+ min-var max-var var-count)))))
+
+(define* (dump-dfg dfg #:optional (port (current-output-port)))
+ (let ((min-label (dfg-min-label dfg))
+ (min-var (dfg-min-var dfg)))
+ (define (label->idx label) (- label min-label))
+ (define (idx->label idx) (+ idx min-label))
+ (define (var->idx var) (- var min-var))
+ (define (idx->var idx) (+ idx min-var))
+
+ (let lp ((label (dfg-min-label dfg)))
+ (when (<= label (dfg-max-label dfg))
+ (let ((cont (vector-ref (dfg-cont-table dfg) (label->idx label))))
+ (when cont
+ (unless (equal? (lookup-predecessors label dfg) (list (1- label)))
+ (newline port))
+ (format port "k~a:~8t" label)
+ (match cont
+ (($ $kreceive arity k)
+ (format port "$kreceive ~a k~a\n" arity k))
+ (($ $kfun src meta self tail clause)
+ (format port "$kfun ~a ~a v~a\n" src meta self))
+ (($ $ktail)
+ (format port "$ktail\n"))
+ (($ $kclause arity ($ $cont kbody) alternate)
+ (format port "$kclause ~a k~a" arity kbody)
+ (match alternate
+ (#f #f)
+ (($ $cont kalt) (format port " -> k~a" kalt)))
+ (newline port))
+ (($ $kargs names vars term)
+ (unless (null? vars)
+ (format port "v~a[~a]~:{ v~a[~a]~}: "
+ (car vars) (car names) (map list (cdr vars) (cdr names))))
+ (match (find-call term)
+ (($ $continue kf src ($ $branch kt exp))
+ (format port "if ")
+ (match exp
+ (($ $primcall name args)
+ (format port "(~a~{ v~a~})" name args))
+ (($ $values (arg))
+ (format port "v~a" arg)))
+ (format port " k~a k~a\n" kt kf))
+ (($ $continue k src exp)
+ (match exp
+ (($ $const val) (format port "const ~@y" val))
+ (($ $prim name) (format port "prim ~a" name))
+ (($ $fun free ($ $cont kbody)) (format port "fun k~a" kbody))
+ (($ $closure label nfree) (format port "closure k~a (~a free)" label nfree))
+ (($ $call proc args) (format port "call~{ v~a~}" (cons proc args)))
+ (($ $callk k proc args) (format port "callk k~a~{ v~a~}" k (cons proc args)))
+ (($ $primcall name args) (format port "~a~{ v~a~}" name args))
+ (($ $values args) (format port "values~{ v~a~}" args))
+ (($ $prompt escape? tag handler) (format port "prompt ~a v~a k~a" escape? tag handler)))
+ (unless (= k (1+ label))
+ (format port " -> k~a" k))
+ (newline port))))))
+ (lp (1+ label)))))))
+
+(define-syntax-rule (with-fresh-name-state-from-dfg dfg body ...)
+ (parameterize ((label-counter (1+ (dfg-max-label dfg)))
+ (var-counter (1+ (dfg-max-var dfg))))
+ body ...))
+
+(define (lookup-cont label dfg)
+ (let ((res (vector-ref (dfg-cont-table dfg) (- label (dfg-min-label dfg)))))
+ (unless res
+ (error "Unknown continuation!" label))
+ res))
+
+(define (lookup-predecessors k dfg)
+ (vector-ref (dfg-preds dfg) (- k (dfg-min-label dfg))))
+
+(define (lookup-successors k dfg)
+ (let ((cont (vector-ref (dfg-cont-table dfg) (- k (dfg-min-label dfg)))))
+ (visit-cont-successors list cont)))
+
+(define (lookup-def var dfg)
+ (vector-ref (dfg-defs dfg) (- var (dfg-min-var dfg))))
+
+(define (lookup-uses var dfg)
+ (vector-ref (dfg-uses dfg) (- var (dfg-min-var dfg))))
+
+(define (lookup-block-scope k dfg)
+ (vector-ref (dfg-scopes dfg) (- k (dfg-min-label dfg))))
+
+(define (lookup-scope-level k dfg)
+ (vector-ref (dfg-scope-levels dfg) (- k (dfg-min-label dfg))))
+
+(define (find-defining-term sym dfg)
+ (match (lookup-predecessors (lookup-def sym dfg) dfg)
+ ((def-exp-k)
+ (lookup-cont def-exp-k dfg))
+ (else #f)))
+
+(define (find-call term)
+ (match term
+ (($ $kargs names syms body) (find-call body))
+ (($ $letk conts body) (find-call body))
+ (($ $letrec names syms funs body) (find-call body))
+ (($ $continue) term)))
+
+(define (call-expression call)
+ (match call
+ (($ $continue k src exp) exp)))
+
+(define (find-expression term)
+ (call-expression (find-call term)))
+
+(define (find-defining-expression sym dfg)
+ (match (find-defining-term sym dfg)
+ (#f #f)
+ (($ $kreceive) #f)
+ (($ $kclause) #f)
+ (term (find-expression term))))
+
+(define (find-constant-value sym dfg)
+ (match (find-defining-expression sym dfg)
+ (($ $const val)
+ (values #t val))
+ (else
+ (values #f #f))))
+
+(define (constant-needs-allocation? var val dfg)
+ (define (immediate-u8? val)
+ (and (integer? val) (exact? val) (<= 0 val 255)))
+
+ (define (find-exp term)
+ (match term
+ (($ $kargs names vars body) (find-exp body))
+ (($ $letk conts body) (find-exp body))
+ (else term)))
+
+ (or-map
+ (lambda (use)
+ (match (find-expression (lookup-cont use dfg))
+ (($ $call) #f)
+ (($ $callk) #f)
+ (($ $values) #f)
+ (($ $primcall 'free-ref (closure slot))
+ (eq? var closure))
+ (($ $primcall 'free-set! (closure slot value))
+ (or (eq? var closure) (eq? var value)))
+ (($ $primcall 'cache-current-module! (mod . _))
+ (eq? var mod))
+ (($ $primcall 'cached-toplevel-box _)
+ #f)
+ (($ $primcall 'cached-module-box _)
+ #f)
+ (($ $primcall 'resolve (name bound?))
+ (eq? var name))
+ (($ $primcall 'make-vector/immediate (len init))
+ (eq? var init))
+ (($ $primcall 'vector-ref/immediate (v i))
+ (eq? var v))
+ (($ $primcall 'vector-set!/immediate (v i x))
+ (or (eq? var v) (eq? var x)))
+ (($ $primcall 'allocate-struct/immediate (vtable nfields))
+ (eq? var vtable))
+ (($ $primcall 'struct-ref/immediate (s n))
+ (eq? var s))
+ (($ $primcall 'struct-set!/immediate (s n x))
+ (or (eq? var s) (eq? var x)))
+ (($ $primcall 'builtin-ref (idx))
+ #f)
+ (_ #t)))
+ (vector-ref (dfg-uses dfg) (- var (dfg-min-var dfg)))))
+
+(define (continuation-scope-contains? scope-k k dfg)
+ (let ((scope-level (lookup-scope-level scope-k dfg)))
+ (let lp ((k k))
+ (or (eq? scope-k k)
+ (and (< scope-level (lookup-scope-level k dfg))
+ (lp (lookup-block-scope k dfg)))))))
+
+(define (continuation-bound-in? k use-k dfg)
+ (continuation-scope-contains? (lookup-block-scope k dfg) use-k dfg))
+
+(define (variable-free-in? var k dfg)
+ (or-map (lambda (use)
+ (continuation-scope-contains? k use dfg))
+ (lookup-uses var dfg)))
+
+;; A continuation is a control point if it has multiple predecessors, or
+;; if its single predecessor does not have a single successor.
+(define (control-point? k dfg)
+ (match (lookup-predecessors k dfg)
+ ((pred)
+ (let ((cont (vector-ref (dfg-cont-table dfg)
+ (- pred (dfg-min-label dfg)))))
+ (visit-cont-successors (case-lambda
+ (() #t)
+ ((succ0) #f)
+ ((succ1 succ2) #t))
+ cont)))
+ (_ #t)))
+
+(define (lookup-bound-syms k dfg)
+ (match (lookup-cont k dfg)
+ (($ $kargs names syms body)
+ syms)))