guile-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Guile-commits] 05/05: Add tailify pass


From: Andy Wingo
Subject: [Guile-commits] 05/05: Add tailify pass
Date: Wed, 9 Jun 2021 10:47:05 -0400 (EDT)

wingo pushed a commit to branch wip-tailify
in repository guile.

commit 5bb0ffb5584d385e2fae0f1808e173f1e3bb12a6
Author: Andy Wingo <wingo@pobox.com>
AuthorDate: Thu Jun 3 21:37:59 2021 +0200

    Add tailify pass
    
    * module/Makefile.am (SOURCES): Add tailify.scm
    * module/language/cps/tailify.scm: New file.
---
 module/Makefile.am              |   1 +
 module/language/cps/tailify.scm | 636 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 637 insertions(+)

diff --git a/module/Makefile.am b/module/Makefile.am
index 37786ed..a43640e 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -73,6 +73,7 @@ SOURCES =                                     \
   language/cps/specialize-numbers.scm          \
   language/cps/split-rec.scm                   \
   language/cps/switch.scm                      \
+  language/cps/tailify.scm                     \
   language/cps/type-checks.scm                 \
   language/cps/type-fold.scm                   \
   language/cps/types.scm                       \
diff --git a/module/language/cps/tailify.scm b/module/language/cps/tailify.scm
new file mode 100644
index 0000000..b5a7477
--- /dev/null
+++ b/module/language/cps/tailify.scm
@@ -0,0 +1,636 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2021 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:
+;;;
+;;; Tailification converts a program so that all calls are tail calls.
+;;; It is a minimal form of global CPS conversion that stack-allocates
+;;; "return continuations" -- minimal in the sense that the only
+;;; additionally residualized continuations are the ones necessary to
+;;; preserve the all-tail-calls property.  Notably, loops, conditionals,
+;;; and similar features in the source program are left as is unless
+;;; it's necessary to split them.
+;;;
+;;; The first step of tailification computes the set of "tails" in a
+;;; function.  The function entry starts a tail, as does each return
+;;; point from non-tail calls.  Join points between different tails
+;;; also start tails.
+;;;
+;;; In the residual program, there are four ways that a continuation
+;;; exits:
+;;;
+;;;   - Tail calls in the source program are tail calls in the residual
+;;;     program; no change.
+;;;
+;;;   - For non-tail calls in the source program, the caller saves the
+;;;     state of the continuation (the live variables flowing into the
+;;;     continuation) on an explicit stack, and saves the label of the
+;;;     continuation.  The return continuation will be converted into a
+;;;     arity-checking function entry, to handle multi-value returns;
+;;;     when it is invoked, it will pop its incoming live variables from
+;;;     the continuation stack.
+;;;
+;;;   - Terms that continue to a join continuation are converted to
+;;;     label calls in tail position, passing the state of the
+;;;     continuation as arguments.
+;;;
+;;;   - Returning values from a continuation pops the return label from
+;;;     the stack and does an indirect tail label call on that label,
+;;;     with the given return values.
+;;;
+;;; Additionally, the abort-to-prompt run-time routine may unwind the
+;;; explicit stack and tail-call a handler continuation.  If the
+;;; continuation is not escape-only, then the slice of the continuation
+;;; that would be popped off is captured before unwinding.  Resuming a
+;;; continuation splats the saved continuation back on the stack and
+;;; returns to the top continuation, just as in the tail return case
+;;; above.
+;;;
+;;; We expect that a tailified program will probably be slower than a
+;;; non-tailified program.  However a tailified program has a few
+;;; interesting properties: the stack is packed and only contains live
+;;; data; the stack can be traversed in a portable way, allowing for
+;;; implementation of prompts on systems that don't support them
+;;; natively; and as all calls are tail calls, the whole system can be
+;;; implemented naturally with a driver trampoline on targets that don't
+;;; support tail calls (e.g. JavaScript and WebAssembly).
+;;;
+;;; Code:
+
+(define-module (language cps tailify)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
+  #:use-module (language cps)
+  #:use-module (language cps intmap)
+  #:use-module (language cps intset)
+  #:use-module (language cps graphs)
+  #:use-module (language cps utils)
+  #:use-module (language cps renumber)
+  #:use-module (language cps with-cps)
+  #:export (tailify))
+
+(define (trivial-intmap x)
+  (let ((next (intmap-next x)))
+    (and (eqv? next (intmap-prev x))
+         next)))
+
+(define (live-constants live-in constants head)
+  (intmap-select constants
+                 (intmap-intersect (intmap-ref live-in head)
+                                   (intmap-keys constants))))
+(define (live-vars live-in constants head)
+  (intset-subtract (intmap-ref live-in head)
+                   (intmap-keys constants)))
+
+(define (rename-var* fresh-names var)
+  (intmap-ref fresh-names var (lambda (var) var)))
+(define (rename-vars* fresh-names vars)
+  (match vars
+    (() '())
+    ((var . vars)
+     (cons (rename-var* fresh-names var)
+           (rename-vars* fresh-names vars)))))
+
+(define (compute-saved-vars* fresh-names live-in constants reprs k)
+  (intset-fold-right
+   (lambda (var reprs vars)
+     (values (cons (intmap-ref reprs var) reprs)
+             (cons (rename-var* fresh-names var) vars)))
+   (live-vars live-in constants k) '() '()))
+
+(define (tailify-tail cps head body fresh-names winds live-in constants
+                      reprs entries original-ktail)
+  "Rewrite the conts with labels in the intset BODY, forming the body of
+the tail which begins at HEAD in the source program.  The entry to the
+tail was already rewritten, with ENTRIES containing an intmap of tail
+heads to $kfun labels.  WINDS associates 'unwind primcalls with the
+corresponding conts that pushes on the dynamic stack.  LIVE-IN indicates
+the variables that are live at tail heads, and CONSTANTS is an intmap
+associating vars known to be constant with their values.  REPRS holds
+the representation of each var.  ORIGINAL-KTAIL is the tail cont of the
+source function; terms in the tail that continue to ORIGINAL-KTAIL will
+be rewritten to continue to the tail's ktail."
+
+  ;; HEAD will have been given a corresponding entry $kfun by
+  ;; tailify-tails.  Here we find the tail-label for the current tail.
+  (define local-ktail
+    (match (intmap-ref cps (intmap-ref entries head))
+      (($ $kfun src meta self ktail kentry)
+       ktail)))
+
+  (define (rename-var var)   (rename-var* fresh-names var))
+  (define (rename-vars vars) (rename-vars* fresh-names vars))
+  (define (rename-exp exp)
+    (rewrite-exp exp
+      ((or ($ $const) ($ $prim) ($ $const-fun) ($ $code)) ,exp)
+      (($ $call proc args)
+       ($call (rename-var proc) ,(rename-vars args)))
+      (($ $callk k proc args)
+       ($callk k (and proc (rename-var proc)) ,(rename-vars args)))
+      (($ $primcall name param args)
+       ($primcall name param ,(rename-vars args)))
+      (($ $values args)
+       ($values ,(rename-vars args)))))
+
+  (define (compute-saved-vars fresh-names k)
+    (compute-saved-vars* fresh-names live-in constants reprs k))
+
+  ;; Return a $callk to the join tail with head K.  To allow for
+  ;; tail-local names for values bound by K, JOIN-VARS is an alist of
+  ;; mappings to add to FRESH-NAMES.
+  (define (compute-join-call join-vars k)
+    (let ((fresh-names (fold (lambda (pair fresh-names)
+                               (match pair
+                                 ((old . new)
+                                  (intmap-add fresh-names old new))))
+                             fresh-names join-vars)))
+      (call-with-values (lambda () (compute-saved-vars fresh-names k))
+        (lambda (reprs vars)
+          (build-exp
+            ($callk (intmap-ref entries k) #f vars))))))
+
+  ;; A branch target can either be in the current tail, or it starts a
+  ;; join continuation.  It can't be $ktail, it can't be $kreceive, and
+  ;; it takes no values, hence we pass () to compute-join-call.
+  (define (rewrite-branch-target cps src k)
+    (cond
+     ((intset-ref body k)
+      (with-cps cps k))
+     (else
+      (when (eqv? k original-ktail) (error "what!!"))
+      (with-cps cps
+        (letk kcall
+              ($kargs () ()
+                ($continue local-ktail src ,(compute-join-call '() k))))
+        kcall))))
+  (define (rewrite-branch-targets cps src k*)
+    (match k*
+      (()
+       (with-cps cps '()))
+      ((k . k*)
+       (with-cps cps
+         (let$ k* (rewrite-branch-targets src k*))
+         (let$ k (rewrite-branch-target src k))
+         (cons k k*)))))
+
+  ;; Rewrite TERM.  Generally speaking we just rename variable uses.
+  ;; However if TERM continues to another tail, we have to generate the
+  ;; appropriate call for the continuation tail kind.
+  (define (rewrite-term cps term)
+    (match term
+      (($ $continue k src exp)
+       (let ((exp (rename-exp exp)))
+         (cond
+          ((eqv? k original-ktail)
+           (match exp
+             (($ $values args)
+              ;; The original term is a $values in tail position.
+              ;; Transform to pop the continuation stack and tail call
+              ;; it.
+              (with-cps cps
+                (letv ret)
+                (letk kcall ($kargs ('ret) (ret)
+                              ($continue local-ktail src
+                                ($calli args ret))))
+                (build-term ($continue kcall src
+                              ($primcall 'restore1 'ptr ())))))
+             ((or ($ $call) ($ $callk) ($ $calli))
+              ;; Otherwise the original term was a tail call.
+              (with-cps cps
+                (build-term ($continue local-ktail src ,exp))))))
+          ((intset-ref body k)
+           ;; Continuation within current tail.
+           (with-cps cps
+             (build-term ($continue k src ,exp))))
+          (else
+           (match (intmap-ref cps k)
+             (($ $kreceive)
+              ;; A non-tail-call: push the pending continuation and tail
+              ;; call instead.
+              (match exp
+                ((or ($ $call) ($ $callk) ($ $calli))
+                 (call-with-values (lambda ()
+                                     (compute-saved-vars fresh-names k))
+                   (lambda (reprs vars)
+                     (with-cps cps
+                       (letk kexp ($kargs () ()
+                                    ($continue local-ktail src ,exp)))
+                       (letv cont)
+                       (letk kcont ($kargs ('cont) (cont)
+                                     ($continue kexp src
+                                       ($primcall 'save
+                                                  (append reprs (list 'ptr))
+                                                  ,(append vars (list 
cont))))))
+                       (build-term ($continue kcont src
+                                     ($code (intmap-ref entries k))))))))))
+             (($ $kargs names vars)
+              ;; Calling a join continuation.  This is one of those
+              ;; cases where it might be nice in CPS to have names for
+              ;; phi predecessor values.  Ah well.
+              (let ((vars' (map (lambda (_) (fresh-var)) vars)))
+                (with-cps cps
+                  (letk kvals
+                        ($kargs names vars'
+                          ($continue local-ktail src
+                            ,(compute-join-call (map cons vars vars') k))))
+                  (build-term
+                    ($continue kvals src ,exp))))))))))
+      (($ $branch kf kt src op param args)
+       (with-cps cps
+         (let$ kf (rewrite-branch-target src kf))
+         (let$ kt (rewrite-branch-target src kf))
+         (build-term
+           ($branch kf kt src op param ,(rename-vars args)))))
+      (($ $switch kf kt* src arg)
+       (with-cps cps
+         (let$ kf (rewrite-branch-target src kf))
+         (let$ kt* (rewrite-branch-targets src kt*))
+         (build-term ($switch kf kt* src (rename-var arg)))))
+      (($ $prompt k kh src escape? tag)
+       (call-with-values (lambda () (compute-saved-vars fresh-names kh))
+         (lambda (reprs vars)
+           (with-cps cps
+             (letv handler)
+             (let$ k (rewrite-branch-target src k))
+             (letk kpush ($kargs ('handler) (handler)
+                           ($continue k src
+                             ($primcall 'push-prompt escape?
+                                        ((rename-var tag) handler)))))
+             (letk kcode ($kargs () ()
+                           ($continue kpush src ($code (intmap-ref entries 
kh)))))
+             (build-term ($continue kpush src
+                           ($primcall 'save reprs vars)))))))
+      (($ $throw src op param args)
+       (with-cps cps
+         (build-term ($throw src op param ,(rename-vars args)))))))
+
+  ;; A prompt body begins with a $prompt, may contain nested prompt
+  ;; bodies, and continues until a corresponding 'unwind primcall.
+  ;; Leaving a prompt body may or may not correspond to leaving the
+  ;; current tail.  Leaving the prompt body must remove the handler from
+  ;; the stack.  Removing the handler must happen before leaving the
+  ;; tail, and notably must happen before pushing saved state for a
+  ;; non-tail-call continuation.
+  (define (maybe-unwind-prompt cps label term)
+    (define (not-a-prompt-unwind) (with-cps cps term))
+    (define (pop-prompt kh)
+      (call-with-values (lambda () (compute-saved-vars fresh-names kh))
+        (lambda (reprs vars)
+          (with-cps cps
+            (letk kterm ($kargs () () ,term))
+            (build-term ($continue kterm #f
+                          ($primcall 'drop reprs ())))))))
+    (cond
+     ((intmap-ref winds label (lambda (_) #f))
+      => (lambda (wind)
+           (match (intmap-ref cps wind)
+             (($ $prompt k kh) (pop-prompt kh))
+             (_ (not-a-prompt-unwind)))))
+     (else (not-a-prompt-unwind))))
+
+  ;; The entry for the current tail has already been rewritten, so here
+  ;; we just rewrite all the body conts.
+  (intset-fold
+   (lambda (label cps)
+     (match (intmap-ref cps label)
+       ((or ($ $kfun) ($ $kclause) ($ $ktail)) cps) ;; Unchanged.
+       (($ $kreceive) cps)                          ;; Dead.
+       (($ $kargs names vals term)
+        (with-cps cps
+          (let$ term (rewrite-term term))
+          (let$ term (maybe-unwind-prompt label term))
+          (setk label ($kargs names vals ,term))))))
+   body cps))
+
+(define (tailify-tails cps winds live-in constants reprs tails)
+  "Given that the conts in a function were partitioned into tails in the
+intmap TAILS, mapping tail entries to tail bodies, return a new CPS
+program in which the tails have been split to separate functions in
+which all calls are tail calls.
+
+WINDS associates 'unwind primcalls with the corresponding conts that
+pushes on the dynamic stack.
+
+LIVE-IN indicates the variables that are live at tail heads.
+
+CONSTANTS is an intmap associating vars known to be constant with their
+values.
+
+REPRS holds the representation of each var."
+
+  (define (cont-source label)
+    (match (intmap-ref cps label)
+      (($ $kargs _ _ term)
+       (match term
+         (($ $continue k src) src)
+         (($ $branch k kt src) src)
+         (($ $switch k kt* src) src)
+         (($ $prompt k kh src) src)
+         (($ $throw src) src)))))
+
+  ;; For live values that flow into a tail, each tail will need to give
+  ;; them unique names.
+  (define fresh-names-per-tail
+    (intmap-map (lambda (head body)
+                  (intset-fold (lambda (var fresh)
+                                 (intmap-add fresh var (fresh-var)))
+                               (intmap-ref live-in head)
+                               empty-intmap))
+                tails))
+
+  (define (compute-saved-vars head)
+    (compute-saved-vars* (intmap-ref fresh-names-per-tail head)
+                         live-in constants reprs head))
+
+  ;; For a tail whose head in the source program is HEAD, rewrite to be
+  ;; a $kfun.  For the "main" tail, no change needed.  For join tails,
+  ;; we make an unchecked $kfun-to-$kargs function to which live
+  ;; variables are received directly as arguments.  For return tails,
+  ;; the live vars are restored from the stack.  In all cases, adjoin a
+  ;; HEAD->ENTRY mapping to ENTRIES, where ENTRY is the $kfun label for
+  ;; the tail.
+  (define (add-entry head cps entries)
+    (define fresh-names (intmap-ref fresh-names-per-tail head))
+    ;; Constants don't need to be passed from tail to tail; rather they
+    ;; are rebound locally.
+    (define (restore-constants cps term)
+      (intmap-fold (lambda (var exp cps term)
+                     (define var' (intmap-ref fresh-names var))
+                     (with-cps cps
+                       (letk k ($kargs ('const) (var') ,term))
+                       (build-term ($continue k #f ,exp))))
+                   (live-constants live-in constants head)
+                   cps term))
+    (define (restore-saved cps term)
+      (call-with-values (lambda () (compute-saved-vars head))
+        (lambda (reprs vars)
+          (define names (map (lambda (_) 'restored) vars))
+          (if (null? names)
+              (with-cps cps term)
+              (with-cps cps
+                (letk krestore ($kargs names vars ,term))
+                (build-term ($continue krestore #f
+                              ($primcall 'restore reprs ()))))))))
+    (match (intmap-ref cps head)
+      (($ $kfun)
+       ;; The main entry.
+       (values cps (intmap-add entries head head)))
+      (($ $kreceive ($ $arity req () rest () #f) kargs)
+       ;; The continuation of a non-tail call, or a prompt handler.
+       (match (intmap-ref cps kargs)
+         (($ $kargs names vars)
+          (let ((vars' (map (lambda (_) (fresh-var)) vars))
+                (src (cont-source kargs)))
+            (with-cps cps
+              (letk ktail ($ktail))
+              (let$ term (restore-constants
+                          (build-term
+                            ($continue kargs src ($values vars')))))
+              (let$ term (restore-saved term))
+              (letk krestore ($kargs names vars' ,term))
+              (letk kclause ($kclause (req '() rest '() #f) krestore #f))
+              (letk kfun ($kfun src #f #f ktail kclause))
+              (intmap-add entries head kfun))))))
+      (($ $kargs names vars term)
+       ;; A join point.
+       (call-with-values (lambda () (compute-saved-vars head))
+         (lambda (reprs vars')
+           (define names'
+             (let ((names (map acons vars names)))
+               (map (lambda (var) (assq-ref names var))
+                    vars')))
+           (define meta `((arg-representations . ,reprs)))
+           (with-cps cps
+             (letk ktail ($ktail))
+             (let$ term (restore-constants term))
+             (letk kargs ($kargs names' vars' ,term))
+             (letk kfun ($kfun (cont-source head) meta #f ktail kargs))
+             (intmap-add entries head kfun)))))))
+
+  (define original-ktail
+    (match (intmap-ref cps (intmap-next tails))
+      (($ $kfun src meta self ktail kentry)
+       ktail)))
+  (call-with-values (lambda ()
+                      (intmap-fold (lambda (head body cps entries)
+                                     (add-entry head cps entries))
+                                   tails cps empty-intmap))
+    (lambda (cps entries)
+      (intmap-fold
+       (lambda (head body cps)
+         (define fresh-names (intmap-ref fresh-names-per-tail head))
+         (tailify-tail cps head body fresh-names winds live-in constants
+                       reprs entries original-ktail))
+       tails cps))))
+
+(define (compute-tails kfun body preds cps)
+  "Compute the set of tails in the function with entry KFUN and body
+BODY. Return as an intset mapping the head label for each tail to its
+body, as an intset."
+  ;; Initially, we start with the requirement that kfun and kreceive
+  ;; labels are split heads.
+  (define (initial-split label splits)
+    (match (intmap-ref cps label)
+      ((or ($ $kfun) ($ $kreceive))
+       (intmap-add splits label label))
+      (_
+       splits)))
+  ;; Then we build tails by propagating splits forward in the CFG,
+  ;; possibly creating new split heads at the dominance frontier.
+  (define (compute-split label splits)
+    (define (split-head? label)
+      (eqv? label (intmap-ref splits label (lambda (_) #f))))
+    (define (ktail? label)
+      (match (intmap-ref cps label)
+        (($ $ktail) #t)
+        (_ #f)))
+    (cond
+     ((split-head? label)
+      ;; Once a label is a split head, it stays a split head.
+      splits)
+     ((ktail? label)
+      ;; ktail always part of root tail.
+      (intmap-add splits label kfun))
+     (else
+      (match (intset-fold
+              (lambda (pred pred-splits)
+                (define pred-split
+                  (intmap-ref splits pred (lambda (_) #f)))
+                (match pred-split
+                  (#f pred-splits)
+                  (split (cons split pred-splits))))
+              (intmap-ref preds label) '())
+        ((split)
+         ;; If all predecessors in same split, label is too.
+         (intmap-add splits label split (lambda (old new) new)))
+        ((_ _ . _)
+         ;; Otherwise this is a new split.
+         (intmap-add splits label label (lambda (old new) new)))))))
+  ;; label -> split head
+  (define initial-splits
+    (intset-fold initial-split body empty-intmap))
+  (cond
+   ((trivial-intmap initial-splits)
+    ;; There's only one split head, so only one tail.
+    (intmap-add empty-intmap kfun body))
+   (else
+    ;; Otherwise, assign each label to a tail, identified by the split
+    ;; head, then collect the tails by split head.
+    (let ((splits (fixpoint
+                   (lambda (splits)
+                     (intset-fold compute-split body splits))
+                   initial-splits)))
+      (intmap-fold
+       (lambda (label head split-bodies)
+         (intmap-add split-bodies head (intset label) intset-union))
+       splits
+       empty-intmap)))))
+
+(define (intset-pop set)
+  "Return two values: all values in intset SET except the first one, and
+first value in SET, or #f if SET was empty."
+  (match (intset-next set)
+    (#f (values set #f))
+    (i (values (intset-remove set i) i))))
+
+(define (identify-winds cps kfun body succs)
+  "For each unwind primcall in BODY, adjoin an entry mapping it to the
+corresponding wind expression."
+  (define (visit-label label exits bodies)
+    (define wind (intmap-ref bodies label))
+    (match (intmap-ref cps label)
+      (($ $kargs _ _ ($ $prompt k kh))
+       (let* ((bodies (intmap-add bodies k label))
+              (bodies (intmap-add bodies kh wind)))
+         (values exits bodies)))
+      (($ $kargs _ _ ($ $continue k _ ($ $primcall 'wind)))
+       (let ((bodies (intmap-add bodies k wind)))
+         (values exits bodies)))
+      (($ $kargs _ _ ($ $continue k _ ($ $primcall 'unwind)))
+       (let* ((exits (intmap-add exits label wind))
+              (bodies (intmap-add bodies k (intmap-ref bodies wind))))
+         (values exits bodies)))
+      (else
+       (let ((bodies (intset-fold (lambda (succ bodies)
+                                    (intmap-add bodies succ wind))
+                                  (intmap-ref succs label)
+                                  bodies)))
+         (values exits bodies)))))
+  (values
+   (worklist-fold
+    (lambda (to-visit exits bodies)
+      (call-with-values (lambda () (intset-pop to-visit))
+        (lambda (to-visit label)
+          (call-with-values (lambda () (visit-label label exits bodies))
+            (lambda (exits* bodies*)
+              (if (and (eq? exits exits*) (eq? bodies bodies*))
+                  (values to-visit exits bodies)
+                  (values (intset-union to-visit (intmap-ref succs label))
+                          exits* bodies*)))))))
+    (intset kfun)
+    empty-intmap
+    (intmap-add empty-intmap kfun #f))))
+
+(define (compute-live-in cps body preds)
+  "Return an intmap associating each label in BODY with an intset of
+live variables flowing into the label."
+  (let ((function (intmap-select cps body)))
+    (call-with-values
+        (lambda ()
+          (call-with-values (lambda () (compute-defs-and-uses function))
+            (lambda (defs uses)
+              ;; Unlike the use of compute-live-variables in
+              ;; slot-allocation.scm, we don't need to add prompt
+              ;; control-flow edges, as the prompt handler is in its own
+              ;; tail and therefore $prompt will push the handler
+              ;; continuation (including its needed live vars) before
+              ;; entering the prompt body.
+              (compute-live-variables preds defs uses))))
+      (lambda (live-in live-out)
+        live-in))))
+
+(define (compute-constants cps preds)
+  "Return an intmap associating each variables BODY to their defining
+expression, for all variables binding constant expressions."
+  (define (constant? exp)
+    (match exp
+      ((or ($ $const) ($ $prim) ($ $const-fun) ($ $code)) #t)
+      (_ #f)))
+  (intmap-fold
+   (lambda (label preds constants)
+     (cond
+      ((trivial-intset preds)
+       => (lambda (pred)
+            (match (intmap-ref cps pred)
+              (($ $continue _ _ (? constant? exp))
+               (match (intmap-ref cps label)
+                 (($ $kargs (_) (var) _)
+                  (intmap-add constants var exp))))
+              (_
+               constants))))
+      (else constants)))
+   preds empty-intmap))
+
+(define (tailify-trivial-tail body cps)
+  "For the function with body BODY and only one tail, rewrite any return
+to tail-call the saved continuation."
+  (define (ktail? k)
+    (match (intmap-ref cps k)
+      (($ $ktail) #t)
+      (_ #f)))
+  (define (rewrite-return-to-pop-and-calli label cps)
+    (match (intmap-ref cps label)
+      (($ $kargs names vars
+          ($ $continue (? ktail? k) src ($ $values args)))
+       ;; The original term is a $values in tail position.
+       ;; Transform to pop the continuation stack and tail
+       ;; call it.
+       (with-cps cps
+         (letv ret)
+         (letk kcall ($kargs ('ret) (ret)
+                       ($continue k src ($calli args ret))))
+         (setk label ($kargs names vars
+                       ($continue kcall src
+                         ($primcall 'restore1 'ptr ()))))))))
+  (intset-fold rewrite-return-to-pop-and-calli body cps))
+
+(define (tailify-function kfun body cps)
+  "Partition the function with entry of KFUN into tails.  Rewrite all
+tails in such a way that they enter via a $kfun and leave only via tail
+calls."
+  (define succs (compute-successors cps kfun))
+  (define preds (invert-graph succs))
+  (define tails (compute-tails kfun body preds cps))
+  (cond
+   ((trivial-intmap tails)
+    (tailify-trivial-tail body cps))
+   (else
+    ;; Otherwise we apply tailification.
+    (let ((winds (identify-winds cps kfun body succs))
+          (live-in (compute-live-in cps body preds))
+          (constants (compute-constants cps preds))
+          (reprs (compute-var-representations cps)))
+      (tailify-tails cps winds live-in constants reprs tails)))))
+
+(define (tailify cps)
+  ;; Renumber so that label order is topological order.
+  (let ((cps (renumber cps)))
+    (with-fresh-name-state cps
+      (intmap-fold tailify-function
+                   (compute-reachable-functions cps)
+                   cps))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]