Remove $void CPS expression type
[bpt/guile.git] / module / language / cps / closure-conversion.scm
dissimilarity index 79%
index 4221cb8..90e6bdc 100644 (file)
-;;; 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:
-;;;
-;;; This pass converts a CPS term in such a way that no function has any
-;;; free variables.  Instead, closures are built explicitly with
-;;; make-closure primcalls, and free variables are referenced through
-;;; the closure.
-;;;
-;;; Closure conversion also removes any $letrec forms that contification
-;;; did not handle.  See (language cps) for a further discussion of
-;;; $letrec.
-;;;
-;;; Code:
-
-(define-module (language cps closure-conversion)
-  #:use-module (ice-9 match)
-  #:use-module ((srfi srfi-1) #:select (fold
-                                        lset-union lset-difference
-                                        list-index))
-  #:use-module (ice-9 receive)
-  #:use-module (srfi srfi-26)
-  #:use-module (language cps)
-  #:export (convert-closures))
-
-(define (union s1 s2)
-  (lset-union eq? s1 s2))
-
-(define (difference s1 s2)
-  (lset-difference eq? s1 s2))
-
-;; bound := sym ...
-;; free := sym ...
-
-(define (convert-free-var sym self bound k)
-  "Convert one possibly free variable reference to a bound reference.
-
-If @var{sym} is free (i.e., not present in @var{bound},), it is replaced
-by a closure reference via a @code{free-ref} primcall, and @var{k} is
-called with the new var.  Otherwise @var{sym} is bound, so @var{k} is
-called with @var{sym}.
-
-@var{k} should return two values: a term and a list of additional free
-values in the term."
-  (if (memq sym bound)
-      (k sym)
-      (let-gensyms (k* sym*)
-        (receive (exp free) (k sym*)
-          (values (build-cps-term
-                    ($letk ((k* ($kargs (sym*) (sym*) ,exp)))
-                      ($continue k* #f ($primcall 'free-ref (self sym)))))
-                  (cons sym free))))))
-  
-(define (convert-free-vars syms self bound k)
-  "Convert a number of possibly free references to bound references.
-@var{k} is called with the bound references, and should return two
-values: the term and a list of additional free variables in the term."
-  (match syms
-    (() (k '()))
-    ((sym . syms)
-     (convert-free-var sym self bound
-                       (lambda (sym)
-                         (convert-free-vars syms self bound
-                                            (lambda (syms)
-                                              (k (cons sym syms)))))))))
-  
-(define (init-closure src v free outer-self outer-bound body)
-  "Initialize the free variables @var{free} in a closure bound to
-@var{v}, and continue with @var{body}.  @var{outer-self} must be the
-label of the outer procedure, where the initialization will be
-performed, and @var{outer-bound} is the list of bound variables there."
-  (fold (lambda (free idx body)
-          (let-gensyms (k idxsym)
-            (build-cps-term
-              ($letk ((k ($kargs () () ,body)))
-                ,(convert-free-var
-                  free outer-self outer-bound
-                  (lambda (free)
-                    (values (build-cps-term
-                              ($letconst (('idx idxsym idx))
-                                ($continue k src
-                                  ($primcall 'free-set! (v idxsym free)))))
-                            '())))))))
-        body
-        free
-        (iota (length free))))
-
-(define (cc* exps self bound)
-  "Convert all free references in the list of expressions @var{exps} to
-bound references, and convert functions to flat closures.  Returns two
-values: the transformed list, and a cumulative set of free variables."
-  (let lp ((exps exps) (exps* '()) (free '()))
-    (match exps
-      (() (values (reverse exps*) free))
-      ((exp . exps)
-       (receive (exp* free*) (cc exp self bound)
-         (lp exps (cons exp* exps*) (union free free*)))))))
-
-;; Closure conversion.
-(define (cc exp self bound)
-  "Convert all free references in @var{exp} to bound references, and
-convert functions to flat closures."
-  (match exp
-    (($ $letk conts body)
-     (receive (conts free) (cc* conts self bound)
-       (receive (body free*) (cc body self bound)
-         (values (build-cps-term ($letk ,conts ,body))
-                 (union free free*)))))
-
-    (($ $cont sym ($ $kargs names syms body))
-     (receive (body free) (cc body self (append syms bound))
-       (values (build-cps-cont (sym ($kargs names syms ,body)))
-               free)))
-
-    (($ $cont sym ($ $kentry self tail clauses))
-     (receive (clauses free) (cc* clauses self (list self))
-       (values (build-cps-cont (sym ($kentry self ,tail ,clauses)))
-               free)))
-
-    (($ $cont sym ($ $kclause arity body))
-     (receive (body free) (cc body self bound)
-       (values (build-cps-cont (sym ($kclause ,arity ,body)))
-               free)))
-
-    (($ $cont)
-     ;; Other kinds of continuations don't bind values and don't have
-     ;; bodies.
-     (values exp '()))
-
-    ;; Remove letrec.
-    (($ $letrec names syms funs body)
-     (let ((bound (append bound syms)))
-       (receive (body free) (cc body self bound)
-         (let lp ((in (map list names syms funs))
-                  (bindings (lambda (body) body))
-                  (body body)
-                  (free free))
-           (match in
-             (() (values (bindings body) free))
-             (((name sym ($ $fun src meta () fun-body)) . in)
-              (receive (fun-body fun-free) (cc fun-body #f '())
-                (lp in
-                    (lambda (body)
-                      (let-gensyms (k)
-                        (build-cps-term
-                          ($letk ((k ($kargs (name) (sym) ,(bindings body))))
-                            ($continue k src
-                              ($fun src meta fun-free ,fun-body))))))
-                    (init-closure src sym fun-free self bound body)
-                    (union free (difference fun-free bound))))))))))
-
-    (($ $continue k src
-        (or ($ $void)
-            ($ $const)
-            ($ $prim)))
-     (values exp '()))
-
-    (($ $continue k src ($ $fun src* meta () body))
-     (receive (body free) (cc body #f '())
-       (match free
-         (()
-          (values (build-cps-term
-                    ($continue k src ($fun src* meta free ,body)))
-                  free))
-         (_
-          (values
-           (let-gensyms (kinit v)
-             (build-cps-term
-               ($letk ((kinit ($kargs (v) (v)
-                                ,(init-closure
-                                  src v free self bound
-                                  (build-cps-term
-                                    ($continue k src ($values (v))))))))
-                 ($continue kinit src ($fun src* meta free ,body)))))
-           (difference free bound))))))
-
-    (($ $continue k src ($ $call proc args))
-     (convert-free-vars (cons proc args) self bound
-                        (match-lambda
-                         ((proc . args)
-                          (values (build-cps-term
-                                    ($continue k src ($call proc args)))
-                                  '())))))
-
-    (($ $continue k src ($ $primcall name args))
-     (convert-free-vars args self bound
-                        (lambda (args)
-                          (values (build-cps-term
-                                    ($continue k src ($primcall name args)))
-                                  '()))))
-
-    (($ $continue k src ($ $values args))
-     (convert-free-vars args self bound
-                        (lambda (args)
-                          (values (build-cps-term
-                                    ($continue k src ($values args)))
-                                  '()))))
-
-    (($ $continue k src ($ $prompt escape? tag handler))
-     (convert-free-var
-      tag self bound
-      (lambda (tag)
-        (values (build-cps-term
-                  ($continue k src ($prompt escape? tag handler)))
-                '()))))
-
-    (_ (error "what" exp))))
-
-;; Convert the slot arguments of 'free-ref' primcalls from symbols to
-;; indices.
-(define (convert-to-indices body free)
-  (define (free-index sym)
-    (or (list-index (cut eq? <> sym) free)
-        (error "free variable not found!" sym free)))
-  (define (visit-term term)
-    (rewrite-cps-term term
-      (($ $letk conts body)
-       ($letk ,(map visit-cont conts) ,(visit-term body)))
-      (($ $continue k src ($ $primcall 'free-ref (closure sym)))
-       ,(let-gensyms (idx)
-          (build-cps-term
-            ($letconst (('idx idx (free-index sym)))
-              ($continue k src ($primcall 'free-ref (closure idx)))))))
-      (($ $continue k src ($ $fun src* meta free body))
-       ($continue k src
-         ($fun src* meta free ,(convert-to-indices body free))))
-      (($ $continue)
-       ,term)))
-  (define (visit-cont cont)
-    (rewrite-cps-cont cont
-      (($ $cont sym ($ $kargs names syms body))
-       (sym ($kargs names syms ,(visit-term body))))
-      (($ $cont sym ($ $kclause arity body))
-       (sym ($kclause ,arity ,(visit-cont body))))
-      ;; Other kinds of continuations don't bind values and don't have
-      ;; bodies.
-      (($ $cont)
-       ,cont)))
-
-  (rewrite-cps-cont body
-    (($ $cont sym ($ $kentry self tail clauses))
-     (sym ($kentry self ,tail ,(map visit-cont clauses))))))
-
-(define (convert-closures exp)
-  "Convert free reference in @var{exp} to primcalls to @code{free-ref},
-and allocate and initialize flat closures."
-  (match exp
-    (($ $fun src meta () body)
-     (receive (body free) (cc body #f '())
-       (unless (null? free)
-         (error "Expected no free vars in toplevel thunk" exp body free))
-       (build-cps-exp
-         ($fun src meta free ,(convert-to-indices body free)))))))
+;;; 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:
+;;;
+;;; This pass converts a CPS term in such a way that no function has any
+;;; free variables.  Instead, closures are built explicitly with
+;;; make-closure primcalls, and free variables are referenced through
+;;; the closure.
+;;;
+;;; Closure conversion also removes any $letrec forms that contification
+;;; did not handle.  See (language cps) for a further discussion of
+;;; $letrec.
+;;;
+;;; Code:
+
+(define-module (language cps closure-conversion)
+  #:use-module (ice-9 match)
+  #:use-module ((srfi srfi-1) #:select (fold
+                                        lset-union lset-difference
+                                        list-index))
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-26)
+  #:use-module (language cps)
+  #:use-module (language cps dfg)
+  #:export (convert-closures))
+
+;; free := var ...
+
+(define (analyze-closures exp dfg)
+  "Compute the set of free variables for all $fun instances in
+@var{exp}."
+  (let ((bound-vars (make-hash-table))
+        (free-vars (make-hash-table))
+        (named-funs (make-hash-table))
+        (well-known-vars (make-bitvector (var-counter) #t)))
+    (define (add-named-fun! var cont)
+      (hashq-set! named-funs var cont)
+      (match cont
+        (($ $cont label ($ $kfun src meta self))
+         (unless (eq? var self)
+           (hashq-set! bound-vars label var)))))
+    (define (clear-well-known! var)
+      (bitvector-set! well-known-vars var #f))
+    (define (compute-well-known-labels)
+      (let ((bv (make-bitvector (label-counter) #f)))
+        (hash-for-each
+         (lambda (var cont)
+           (match cont
+             (($ $cont label ($ $kfun src meta self))
+              (unless (equal? var self)
+                (bitvector-set! bv label
+                                (and (bitvector-ref well-known-vars var)
+                                     (bitvector-ref well-known-vars self)))))))
+         named-funs)
+        bv))
+    (define (union a b)
+      (lset-union eq? a b))
+    (define (difference a b)
+      (lset-difference eq? a b))
+    (define (visit-cont cont bound)
+      (match cont
+        (($ $cont label ($ $kargs names vars body))
+         (visit-term body (append vars bound)))
+        (($ $cont label ($ $kfun src meta self tail clause))
+         (add-named-fun! self cont)
+         (let ((free (if clause
+                         (visit-cont clause (list self))
+                         '())))
+           (hashq-set! free-vars label free)
+           (difference free bound)))
+        (($ $cont label ($ $kclause arity body alternate))
+         (let ((free (visit-cont body bound)))
+           (if alternate
+               (union (visit-cont alternate bound) free)
+               free)))
+        (($ $cont) '())))
+    (define (visit-term term bound)
+      (match term
+        (($ $letk conts body)
+         (fold (lambda (cont free)
+                 (union (visit-cont cont bound) free))
+               (visit-term body bound)
+               conts))
+        (($ $letrec names vars (($ $fun () cont) ...) body)
+         (let ((bound (append vars bound)))
+           (for-each add-named-fun! vars cont)
+           (fold (lambda (cont free)
+                   (union (visit-cont cont bound) free))
+                 (visit-term body bound)
+                 cont)))
+        (($ $continue k src ($ $fun () body))
+         (match (lookup-predecessors k dfg)
+           ((_) (match (lookup-cont k dfg)
+                  (($ $kargs (name) (var))
+                   (add-named-fun! var body))))
+           (_ #f))
+         (visit-cont body bound))
+        (($ $continue k src exp)
+         (visit-exp exp bound))))
+    (define (visit-exp exp bound)
+      (define (adjoin var free)
+        (if (or (memq var bound) (memq var free))
+            free
+            (cons var free)))
+      (match exp
+        ((or ($ $const) ($ $prim)) '())
+        (($ $call proc args)
+         (for-each clear-well-known! args)
+         (fold adjoin (adjoin proc '()) args))
+        (($ $primcall name args)
+         (for-each clear-well-known! args)
+         (fold adjoin '() args))
+        (($ $branch kt exp)
+         (visit-exp exp bound))
+        (($ $values args)
+         (for-each clear-well-known! args)
+         (fold adjoin '() args))
+        (($ $prompt escape? tag handler)
+         (clear-well-known! tag)
+         (adjoin tag '()))))
+
+    (let ((free (visit-cont exp '())))
+      (unless (null? free)
+        (error "Expected no free vars in toplevel thunk" free exp))
+      (values bound-vars free-vars named-funs (compute-well-known-labels)))))
+
+(define (prune-free-vars free-vars named-funs well-known var-aliases)
+  (define (well-known? label)
+    (bitvector-ref well-known label))
+  (let ((eliminated (make-bitvector (label-counter) #f))
+        (label-aliases (make-vector (label-counter) #f)))
+    (let lp ((label 0))
+      (let ((label (bit-position #t well-known label)))
+        (when label
+          (match (hashq-ref free-vars label)
+            ;; Mark all well-known closures that have no free variables
+            ;; for elimination.
+            (() (bitvector-set! eliminated label #t))
+            ;; Replace well-known closures that have just one free
+            ;; variable by references to that free variable.
+            ((var)
+             (vector-set! label-aliases label var))
+            (_ #f))
+          (lp (1+ label)))))
+    ;; Iterative free variable elimination.
+    (let lp ()
+      (let ((recurse? #f))
+        (define (adjoin elt list)
+          ;; Normally you wouldn't see duplicates in a free variable
+          ;; list, but with aliases that is possible.
+          (if (memq elt list) list (cons elt list)))
+        (define (prune-free closure-label free)
+          (match free
+            (() '())
+            ((var . free)
+             (let lp ((var var) (alias-stack '()))
+               (match (hashq-ref named-funs var)
+                 (($ $cont label)
+                  (cond
+                   ((bitvector-ref eliminated label)
+                    (prune-free closure-label free))
+                   ((vector-ref label-aliases label)
+                    => (lambda (var)
+                         (cond
+                          ((memq label alias-stack)
+                           ;; We have found a set of mutually recursive
+                           ;; well-known procedures, each of which only
+                           ;; closes over one of the others.  Mark them
+                           ;; all for elimination.
+                           (for-each (lambda (label)
+                                       (bitvector-set! eliminated label #t)
+                                       (set! recurse? #t))
+                                     alias-stack)
+                           (prune-free closure-label free))
+                          (else
+                           (lp var (cons label alias-stack))))))
+                   ((eq? closure-label label)
+                    ;; Eliminate self-reference.
+                    (prune-free closure-label free))
+                   (else
+                    (adjoin var (prune-free closure-label free)))))
+                 (_ (adjoin var (prune-free closure-label free))))))))
+        (hash-for-each-handle
+         (lambda (pair)
+           (match pair
+             ((label . ()) #t)
+             ((label . free)
+              (let ((orig-nfree (length free))
+                    (free (prune-free label free)))
+                (set-cdr! pair free)
+                ;; If we managed to eliminate one or more free variables
+                ;; from a well-known function, it could be that we can
+                ;; eliminate or alias this function as well.
+                (when (and (well-known? label)
+                           (< (length free) orig-nfree))
+                  (match free
+                    (()
+                     (bitvector-set! eliminated label #t)
+                     (set! recurse? #t))
+                    ((var)
+                     (vector-set! label-aliases label var)
+                     (set! recurse? #t))
+                    (_ #t)))))))
+         free-vars)
+        ;; Iterate to fixed point.
+        (when recurse? (lp))))
+    ;; Populate var-aliases from label-aliases.
+    (hash-for-each (lambda (var cont)
+                     (match cont
+                       (($ $cont label)
+                        (let ((alias (vector-ref label-aliases label)))
+                          (when alias
+                            (vector-set! var-aliases var alias))))))
+                   named-funs)))
+
+(define (convert-one bound label fun free-vars named-funs well-known aliases)
+  (define (well-known? label)
+    (bitvector-ref well-known label))
+
+  (let ((free (hashq-ref free-vars label))
+        (self-known? (well-known? label))
+        (self (match fun (($ $kfun _ _ self) self))))
+    (define (convert-free-var var k)
+      "Convert one possibly free variable reference to a bound reference.
+
+If @var{var} is free, it is replaced by a closure reference via a
+@code{free-ref} primcall, and @var{k} is called with the new var.
+Otherwise @var{var} is bound, so @var{k} is called with @var{var}."
+      (cond
+       ((list-index (cut eq? <> var) free)
+        => (lambda (free-idx)
+             (match (cons self-known? free)
+               ;; A reference to the one free var of a well-known function.
+               ((#t _) (k self))
+               ;; A reference to one of the two free vars in a well-known
+               ;; function.
+               ((#t _ _)
+                (let-fresh (k*) (var*)
+                  (build-cps-term
+                    ($letk ((k* ($kargs (var*) (var*) ,(k var*))))
+                      ($continue k* #f
+                        ($primcall (match free-idx (0 'car) (1 'cdr)) (self)))))))
+               (_
+                (let-fresh (k* kidx) (idx var*)
+                  (build-cps-term
+                    ($letk ((kidx ($kargs ('idx) (idx)
+                                    ($letk ((k* ($kargs (var*) (var*) ,(k var*))))
+                                      ($continue k* #f
+                                        ($primcall
+                                         (cond
+                                          ((not self-known?) 'free-ref)
+                                          ((<= free-idx #xff) 'vector-ref/immediate)
+                                          (else 'vector-ref))
+                                         (self idx)))))))
+                      ($continue kidx #f ($const free-idx)))))))))
+       ((eq? var bound) (k self))
+       (else (k var))))
+  
+    (define (convert-free-vars vars k)
+      "Convert a number of possibly free references to bound references.
+@var{k} is called with the bound references, and should return the
+term."
+      (match vars
+        (() (k '()))
+        ((var . vars)
+         (convert-free-var var
+                           (lambda (var)
+                             (convert-free-vars vars
+                                                (lambda (vars)
+                                                  (k (cons var vars)))))))))
+  
+    (define (allocate-closure src name var label known? free body)
+      "Allocate a new closure."
+      (match (cons known? free)
+        ((#f . _)
+         (let-fresh (k*) ()
+           (build-cps-term
+             ($letk ((k* ($kargs (name) (var) ,body)))
+               ($continue k* src
+                 ($closure label (length free)))))))
+        ((#t)
+         ;; Well-known closure with no free variables; elide the
+         ;; binding entirely.
+         body)
+        ((#t _)
+         ;; Well-known closure with one free variable; the free var is the
+         ;; closure, and no new binding need be made.
+         body)
+        ((#t _ _)
+         ;; Well-known closure with two free variables; the closure is a
+         ;; pair.
+         (let-fresh (kinit kfalse) (false)
+           (build-cps-term
+             ($letk ((kinit ($kargs (name) (var)
+                              ,body))
+                     (kfalse ($kargs ('false) (false)
+                               ($continue kinit src
+                                 ($primcall 'cons (false false))))))
+               ($continue kfalse src ($const #f))))))
+        ;; Well-known callee with more than two free variables; the closure
+        ;; is a vector.
+        ((#t . _)
+         (let ((nfree (length free)))
+           (let-fresh (kinit klen kfalse) (false len-var)
+             (build-cps-term
+               ($letk ((kinit ($kargs (name) (var) ,body))
+                       (kfalse
+                        ($kargs ('false) (false)
+                          ($letk ((klen
+                                   ($kargs ('len) (len-var)
+                                     ($continue kinit src
+                                       ($primcall (if (<= nfree #xff)
+                                                      'make-vector/immediate
+                                                      'make-vector)
+                                                  (len-var false))))))
+                            ($continue klen src ($const nfree))))))
+                 ($continue kfalse src ($const #f)))))))))
+
+    (define (init-closure src var known? closure-free body)
+      "Initialize the free variables @var{closure-free} in a closure
+bound to @var{var}, and continue with @var{body}."
+      (match (cons known? closure-free)
+        ;; Well-known callee with no free variables; no initialization
+        ;; necessary.
+        ((#t) body)
+        ;; Well-known callee with one free variable; no initialization
+        ;; necessary.
+        ((#t _) body)
+        ;; Well-known callee with two free variables; do a set-car! and
+        ;; set-cdr!.
+        ((#t v0 v1)
+         (let-fresh (kcar kcdr) ()
+           (convert-free-var
+            v0
+            (lambda (v0)
+              (build-cps-term
+                ($letk ((kcar ($kargs () ()
+                                ,(convert-free-var
+                                  v1
+                                  (lambda (v1)
+                                    (build-cps-term
+                                      ($letk ((kcdr ($kargs () () ,body)))
+                                        ($continue kcdr src
+                                          ($primcall 'set-cdr! (var v1))))))))))
+                  ($continue kcar src
+                    ($primcall 'set-car! (var v0)))))))))
+        ;; Otherwise residualize a sequence of vector-set! or free-set!,
+        ;; depending on whether the callee is well-known or not.
+        (_
+         (fold (lambda (free idx body)
+                 (let-fresh (k) (idxvar)
+                   (build-cps-term
+                     ($letk ((k ($kargs () () ,body)))
+                       ,(convert-free-var
+                         free
+                         (lambda (free)
+                           (build-cps-term
+                             ($letconst (('idx idxvar idx))
+                               ($continue k src
+                                 ($primcall (cond
+                                             ((not known?) 'free-set!)
+                                             ((<= idx #xff) 'vector-set!/immediate)
+                                             (else 'vector-set!))
+                                            (var idxvar free)))))))))))
+               body
+               closure-free
+               (iota (length closure-free))))))
+
+    ;; Load the closure for a known call.  The callee may or may not be
+    ;; known at all call sites.
+    (define (convert-known-proc-call var label self self-known? free k)
+      ;; Well-known closures with one free variable are replaced at their
+      ;; use sites by uses of the one free variable.  The use sites of a
+      ;; well-known closures are only in well-known proc calls, and in
+      ;; free lists of other closures.  Here we handle the call case; the
+      ;; free list case is handled by prune-free-vars.
+      (define (rename var)
+        (let ((var* (vector-ref aliases var)))
+          (if var*
+              (rename var*)
+              var)))
+      (match (cons (well-known? label)
+                   (hashq-ref free-vars label))
+        ((#t)
+         ;; Calling a well-known procedure with no free variables; pass #f
+         ;; as the closure.
+         (let-fresh (k*) (v*)
+           (build-cps-term
+             ($letk ((k* ($kargs (v*) (v*) ,(k v*))))
+               ($continue k* #f ($const #f))))))
+        ((#t _)
+         ;; Calling a well-known procedure with one free variable; pass
+         ;; the free variable as the closure.
+         (convert-free-var (rename var) k))
+        (_
+         (convert-free-var var k))))
+
+    (define (visit-cont cont)
+      (rewrite-cps-cont cont
+        (($ $cont label ($ $kargs names vars body))
+         (label ($kargs names vars ,(visit-term body))))
+        (($ $cont label ($ $kfun src meta self tail clause))
+         (label ($kfun src meta self ,tail
+                  ,(and clause (visit-cont clause)))))
+        (($ $cont label ($ $kclause arity body alternate))
+         (label ($kclause ,arity ,(visit-cont body)
+                          ,(and alternate (visit-cont alternate)))))
+        (($ $cont) ,cont)))
+    (define (visit-term term)
+      (match term
+        (($ $letk conts body)
+         (build-cps-term
+           ($letk ,(map visit-cont conts) ,(visit-term body))))
+
+        ;; Remove letrec.
+        (($ $letrec names vars funs body)
+         (let lp ((in (map list names vars funs))
+                  (bindings (lambda (body) body))
+                  (body (visit-term body)))
+           (match in
+             (() (bindings body))
+             (((name var ($ $fun ()
+                            (and fun-body
+                                 ($ $cont kfun ($ $kfun src))))) . in)
+              (let ((fun-free (hashq-ref free-vars kfun)))
+                (lp in
+                    (lambda (body)
+                      (allocate-closure
+                       src name var kfun (well-known? kfun) fun-free
+                       (bindings body)))
+                    (init-closure
+                     src var (well-known? kfun) fun-free
+                     body)))))))
+
+        (($ $continue k src (or ($ $const) ($ $prim)))
+         term)
+
+        (($ $continue k src ($ $fun () ($ $cont kfun)))
+         (let ((fun-free (hashq-ref free-vars kfun)))
+           (match (cons (well-known? kfun) fun-free)
+             ((known?)
+              (build-cps-term
+                ($continue k src ,(if known?
+                                      (build-cps-exp ($const #f))
+                                      (build-cps-exp ($closure kfun 0))))))
+             ((#t _)
+              ;; A well-known closure of one free variable is replaced
+              ;; at each use with the free variable itself, so we don't
+              ;; need a binding at all; and yet, the continuation
+              ;; expects one value, so give it something.  DCE should
+              ;; clean up later.
+              (build-cps-term
+                ($continue k src ,(build-cps-exp ($const #f)))))
+             (_
+              (let-fresh () (var)
+                (allocate-closure
+                 src #f var kfun (well-known? kfun) fun-free
+                 (init-closure
+                  src var (well-known? kfun) fun-free
+                  (build-cps-term ($continue k src ($values (var)))))))))))
+
+        (($ $continue k src ($ $call proc args))
+         (match (hashq-ref named-funs proc)
+           (($ $cont kfun)
+            (convert-known-proc-call
+             proc kfun self self-known? free
+             (lambda (proc)
+               (convert-free-vars args
+                                  (lambda (args)
+                                    (build-cps-term
+                                      ($continue k src
+                                        ($callk kfun proc args))))))))
+           (#f
+            (convert-free-vars (cons proc args)
+                               (match-lambda
+                                ((proc . args)
+                                 (build-cps-term
+                                   ($continue k src
+                                     ($call proc args)))))))))
+
+        (($ $continue k src ($ $primcall name args))
+         (convert-free-vars args
+                            (lambda (args)
+                              (build-cps-term
+                                ($continue k src ($primcall name args))))))
+
+        (($ $continue k src ($ $branch kt ($ $primcall name args)))
+         (convert-free-vars args
+                            (lambda (args)
+                              (build-cps-term
+                                ($continue k src
+                                  ($branch kt ($primcall name args)))))))
+
+        (($ $continue k src ($ $branch kt ($ $values (arg))))
+         (convert-free-var arg
+                           (lambda (arg)
+                             (build-cps-term
+                               ($continue k src
+                                 ($branch kt ($values (arg))))))))
+
+        (($ $continue k src ($ $values args))
+         (convert-free-vars args
+                            (lambda (args)
+                              (build-cps-term
+                                ($continue k src ($values args))))))
+
+        (($ $continue k src ($ $prompt escape? tag handler))
+         (convert-free-var tag
+                           (lambda (tag)
+                             (build-cps-term
+                               ($continue k src
+                                 ($prompt escape? tag handler))))))))
+    (visit-cont (build-cps-cont (label ,fun)))))
+
+(define (convert-closures fun)
+  "Convert free reference in @var{exp} to primcalls to @code{free-ref},
+and allocate and initialize flat closures."
+  (let ((dfg (compute-dfg fun)))
+    (with-fresh-name-state-from-dfg dfg
+      (call-with-values (lambda () (analyze-closures fun dfg))
+        (lambda (bound-vars free-vars named-funs well-known)
+          (let ((labels (sort (hash-map->list (lambda (k v) k) free-vars) <))
+                (aliases (make-vector (var-counter) #f)))
+            (prune-free-vars free-vars named-funs well-known aliases)
+            (build-cps-term
+              ($program
+               ,(map (lambda (label)
+                       (convert-one (hashq-ref bound-vars label) label
+                                    (lookup-cont label dfg)
+                                    free-vars named-funs well-known aliases))
+                     labels)))))))))