1 ;;; Continuation-passing style (CPS) intermediate language (IL)
3 ;; Copyright (C) 2013, 2014 Free Software Foundation, Inc.
5 ;;;; This library is free software; you can redistribute it and/or
6 ;;;; modify it under the terms of the GNU Lesser General Public
7 ;;;; License as published by the Free Software Foundation; either
8 ;;;; version 3 of the License, or (at your option) any later version.
10 ;;;; This library is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;;;; Lesser General Public License for more details.
15 ;;;; You should have received a copy of the GNU Lesser General Public
16 ;;;; License along with this library; if not, write to the Free Software
17 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 ;;; This pass converts a CPS term in such a way that no function has any
22 ;;; free variables. Instead, closures are built explicitly with
23 ;;; make-closure primcalls, and free variables are referenced through
26 ;;; Closure conversion also removes any $letrec forms that contification
27 ;;; did not handle. See (language cps) for a further discussion of
32 (define-module (language cps closure-conversion)
33 #:use-module (ice-9 match)
34 #:use-module ((srfi srfi-1) #:select (fold
35 lset-union lset-difference
37 #:use-module (srfi srfi-26)
38 #:use-module (language cps)
39 #:export (convert-closures))
43 (define (convert-free-var var self free k)
44 "Convert one possibly free variable reference to a bound reference.
46 If @var{var} is free (i.e., present in @var{free},), it is replaced
47 by a closure reference via a @code{free-ref} primcall, and @var{k} is
48 called with the new var. Otherwise @var{var} is bound, so @var{k} is
49 called with @var{var}."
51 ((list-index (cut eq? <> var) free)
53 (let-fresh (k* kidx) (idx var*)
55 ($letk ((kidx ($kargs ('idx) (idx)
56 ($letk ((k* ($kargs (var*) (var*) ,(k var*))))
58 ($primcall 'free-ref (self idx)))))))
59 ($continue kidx #f ($const free-idx)))))))
62 (define (convert-free-vars vars self free k)
63 "Convert a number of possibly free references to bound references.
64 @var{k} is called with the bound references, and should return the
69 (convert-free-var var self free
71 (convert-free-vars vars self free
73 (k (cons var vars)))))))))
75 (define (init-closure src v free outer-self outer-free body)
76 "Initialize the free variables @var{free} in a closure bound to
77 @var{v}, and continue with @var{body}. @var{outer-self} must be the
78 label of the outer procedure, where the initialization will be
79 performed, and @var{outer-free} is the list of free variables there."
80 (fold (lambda (free idx body)
81 (let-fresh (k) (idxvar)
83 ($letk ((k ($kargs () () ,body)))
85 free outer-self outer-free
87 (values (build-cps-term
88 ($letconst (('idx idxvar idx))
90 ($primcall 'free-set! (v idxvar free)))))
94 (iota (length free))))
96 (define (compute-free-vars exp)
97 "Compute the set of free variables for all $fun instances in
99 (let ((table (make-hash-table)))
101 (lset-union eq? a b))
102 (define (difference a b)
103 (lset-difference eq? a b))
104 (define (visit-cont cont bound)
106 (($ $cont label ($ $kargs names vars body))
107 (visit-term body (append vars bound)))
108 (($ $cont label ($ $kfun src meta self tail clause))
109 (let ((free (if clause
110 (visit-cont clause (list self))
112 (hashq-set! table label (cons free cont))
113 (difference free bound)))
114 (($ $cont label ($ $kclause arity body alternate))
115 (let ((free (visit-cont body bound)))
117 (union (visit-cont alternate bound) free)
120 (define (visit-term term bound)
122 (($ $letk conts body)
123 (fold (lambda (cont free)
124 (union (visit-cont cont bound) free))
125 (visit-term body bound)
127 (($ $letrec names vars (($ $fun () cont) ...) body)
128 (let ((bound (append vars bound)))
129 (fold (lambda (cont free)
130 (union (visit-cont cont bound) free))
131 (visit-term body bound)
133 (($ $continue k src ($ $fun () body))
134 (visit-cont body bound))
135 (($ $continue k src exp)
136 (visit-exp exp bound))))
137 (define (visit-exp exp bound)
138 (define (adjoin var free)
139 (if (or (memq var bound) (memq var free))
143 ((or ($ $void) ($ $const) ($ $prim)) '())
145 (fold adjoin (adjoin proc '()) args))
146 (($ $callk k* proc args)
147 (fold adjoin (adjoin proc '()) args))
148 (($ $primcall name args)
149 (fold adjoin '() args))
151 (fold adjoin '() args))
152 (($ $prompt escape? tag handler)
155 (let ((free (visit-cont exp '())))
157 (error "Expected no free vars in toplevel thunk" free exp))
160 (define (convert-one label table)
161 (match (hashq-ref table label)
162 ((free . (and fun ($ $cont _ ($ $kfun _ _ self))))
163 (define (visit-cont cont)
164 (rewrite-cps-cont cont
165 (($ $cont label ($ $kargs names vars body))
166 (label ($kargs names vars ,(visit-term body))))
167 (($ $cont label ($ $kfun src meta self tail clause))
168 (label ($kfun src meta self ,tail ,(and clause (visit-cont clause)))))
169 (($ $cont label ($ $kclause arity body alternate))
170 (label ($kclause ,arity ,(visit-cont body)
171 ,(and alternate (visit-cont alternate)))))
173 (define (visit-term term)
175 (($ $letk conts body)
177 ($letk ,(map visit-cont conts) ,(visit-term body))))
180 (($ $letrec names vars funs body)
181 (let lp ((in (map list names vars funs))
182 (bindings (lambda (body) body))
183 (body (visit-term body)))
186 (((name var ($ $fun ()
188 ($ $cont kfun ($ $kfun src))))) . in)
189 (match (hashq-ref table kfun)
195 ($letk ((k ($kargs (name) (var) ,(bindings body))))
197 ($closure kfun (length fun-free)))))))
198 (init-closure src var fun-free self free body))))))))
200 (($ $continue k src (or ($ $void) ($ $const) ($ $prim)))
203 (($ $continue k src ($ $fun () ($ $cont kfun)))
204 (match (hashq-ref table kfun)
206 (build-cps-term ($continue k src ($closure kfun 0))))
208 (let-fresh (kinit) (v)
210 ($letk ((kinit ($kargs (v) (v)
212 src v fun-free self free
214 ($continue k src ($values (v))))))))
216 ($closure kfun (length fun-free)))))))))
218 (($ $continue k src ($ $call proc args))
219 (convert-free-vars (cons proc args) self free
223 ($continue k src ($call proc args)))))))
225 (($ $continue k src ($ $callk k* proc args))
226 (convert-free-vars (cons proc args) self free
230 ($continue k src ($callk k* proc args)))))))
232 (($ $continue k src ($ $primcall name args))
233 (convert-free-vars args self free
236 ($continue k src ($primcall name args))))))
238 (($ $continue k src ($ $values args))
239 (convert-free-vars args self free
242 ($continue k src ($values args))))))
244 (($ $continue k src ($ $prompt escape? tag handler))
245 (convert-free-var tag self free
249 ($prompt escape? tag handler))))))))
252 (define (convert-closures fun)
253 "Convert free reference in @var{exp} to primcalls to @code{free-ref},
254 and allocate and initialize flat closures."
255 (with-fresh-name-state fun
256 (let* ((table (compute-free-vars fun))
257 (labels (sort (hash-map->list (lambda (k v) k) table) <)))
259 ($program ,(map (cut convert-one <> table) labels))))))