Optimize closures with one free variable
[bpt/guile.git] / module / language / cps / closure-conversion.scm
1 ;;; Continuation-passing style (CPS) intermediate language (IL)
2
3 ;; Copyright (C) 2013, 2014 Free Software Foundation, Inc.
4
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.
9 ;;;;
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.
14 ;;;;
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
18
19 ;;; Commentary:
20 ;;;
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
24 ;;; the closure.
25 ;;;
26 ;;; Closure conversion also removes any $letrec forms that contification
27 ;;; did not handle. See (language cps) for a further discussion of
28 ;;; $letrec.
29 ;;;
30 ;;; Code:
31
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
36 list-index))
37 #:use-module (srfi srfi-9)
38 #:use-module (srfi srfi-26)
39 #:use-module (language cps)
40 #:use-module (language cps dfg)
41 #:export (convert-closures))
42
43 ;; free := var ...
44
45 (define (convert-free-var var self self-known? free k)
46 "Convert one possibly free variable reference to a bound reference.
47
48 If @var{var} is free (i.e., present in @var{free},), it is replaced
49 by a closure reference via a @code{free-ref} primcall, and @var{k} is
50 called with the new var. Otherwise @var{var} is bound, so @var{k} is
51 called with @var{var}."
52 (cond
53 ((list-index (cut eq? <> var) free)
54 => (lambda (free-idx)
55 (match (cons self-known? free)
56 ;; A reference to the one free var of a well-known function.
57 ((#t _) (k self))
58 ;; A reference to one of the two free vars in a well-known
59 ;; function.
60 ((#t _ _)
61 (let-fresh (k*) (var*)
62 (build-cps-term
63 ($letk ((k* ($kargs (var*) (var*) ,(k var*))))
64 ($continue k* #f
65 ($primcall (match free-idx (0 'car) (1 'cdr)) (self)))))))
66 (_
67 (let-fresh (k* kidx) (idx var*)
68 (build-cps-term
69 ($letk ((kidx ($kargs ('idx) (idx)
70 ($letk ((k* ($kargs (var*) (var*) ,(k var*))))
71 ($continue k* #f
72 ($primcall
73 (cond
74 ((not self-known?) 'free-ref)
75 ((<= free-idx #xff) 'vector-ref/immediate)
76 (else 'vector-ref))
77 (self idx)))))))
78 ($continue kidx #f ($const free-idx)))))))))
79 (else (k var))))
80
81 (define (convert-free-vars vars self self-known? free k)
82 "Convert a number of possibly free references to bound references.
83 @var{k} is called with the bound references, and should return the
84 term."
85 (match vars
86 (() (k '()))
87 ((var . vars)
88 (convert-free-var var self self-known? free
89 (lambda (var)
90 (convert-free-vars vars self self-known? free
91 (lambda (vars)
92 (k (cons var vars)))))))))
93
94 (define (allocate-closure src name var label known? free body)
95 "Allocate a new closure."
96 (match (cons known? free)
97 ((#f . _)
98 (let-fresh (k*) ()
99 (build-cps-term
100 ($letk ((k* ($kargs (name) (var) ,body)))
101 ($continue k* src
102 ($closure label (length free)))))))
103 ((#t)
104 ;; Well-known closure with no free variables; elide the
105 ;; binding entirely.
106 body)
107 ((#t _)
108 ;; Well-known closure with one free variable; the free var is the
109 ;; closure, and no new binding need be made.
110 body)
111 ((#t _ _)
112 ;; Well-known closure with two free variables; the closure is a
113 ;; pair.
114 (let-fresh (kinit kfalse) (false)
115 (build-cps-term
116 ($letk ((kinit ($kargs (name) (var)
117 ,body))
118 (kfalse ($kargs ('false) (false)
119 ($continue kinit src
120 ($primcall 'cons (false false))))))
121 ($continue kfalse src ($const #f))))))
122 ;; Well-known callee with more than two free variables; the closure
123 ;; is a vector.
124 ((#t . _)
125 (let ((nfree (length free)))
126 (let-fresh (kinit klen kfalse) (false len-var)
127 (build-cps-term
128 ($letk ((kinit ($kargs (name) (var) ,body))
129 (kfalse ($kargs ('false) (false)
130 ($letk ((klen
131 ($kargs ('len) (len-var)
132 ($continue kinit src
133 ($primcall (if (<= nfree #xff)
134 'make-vector/immediate
135 'make-vector)
136 (len-var false))))))
137 ($continue klen src ($const nfree))))))
138 ($continue kfalse src ($const #f)))))))))
139
140 (define (init-closure src var known? free
141 outer-self outer-known? outer-free body)
142 "Initialize the free variables @var{free} in a closure bound to
143 @var{var}, and continue with @var{body}. @var{outer-self} must be the
144 label of the outer procedure, where the initialization will be
145 performed, and @var{outer-free} is the list of free variables there."
146 (match (cons known? free)
147 ;; Well-known callee with no free variables; no initialization
148 ;; necessary.
149 ((#t) body)
150 ;; Well-known callee with one free variable; no initialization
151 ;; necessary.
152 ((#t _) body)
153 ;; Well-known callee with two free variables; do a set-car! and
154 ;; set-cdr!.
155 ((#t v0 v1)
156 (let-fresh (kcar kcdr) ()
157 (convert-free-var
158 v0 outer-self outer-known? outer-free
159 (lambda (v0)
160 (build-cps-term
161 ($letk ((kcar ($kargs () ()
162 ,(convert-free-var
163 v1 outer-self outer-known? outer-free
164 (lambda (v1)
165 (build-cps-term
166 ($letk ((kcdr ($kargs () () ,body)))
167 ($continue kcdr src
168 ($primcall 'set-cdr! (var v1))))))))))
169 ($continue kcar src
170 ($primcall 'set-car! (var v0)))))))))
171 ;; Otherwise residualize a sequence of vector-set! or free-set!,
172 ;; depending on whether the callee is well-known or not.
173 (_
174 (fold (lambda (free idx body)
175 (let-fresh (k) (idxvar)
176 (build-cps-term
177 ($letk ((k ($kargs () () ,body)))
178 ,(convert-free-var
179 free outer-self outer-known? outer-free
180 (lambda (free)
181 (build-cps-term
182 ($letconst (('idx idxvar idx))
183 ($continue k src
184 ($primcall (cond
185 ((not known?) 'free-set!)
186 ((<= idx #xff) 'vector-set!/immediate)
187 (else 'vector-set!))
188 (var idxvar free)))))))))))
189 body
190 free
191 (iota (length free))))))
192
193 (define (analyze-closures exp dfg)
194 "Compute the set of free variables for all $fun instances in
195 @var{exp}."
196 (let ((free-vars (make-hash-table))
197 (named-funs (make-hash-table))
198 (well-known-vars (make-bitvector (var-counter) #t)))
199 (define (add-named-fun! var cont)
200 (hashq-set! named-funs var cont))
201 (define (clear-well-known! var)
202 (bitvector-set! well-known-vars var #f))
203 (define (compute-well-known-labels)
204 (let ((bv (make-bitvector (label-counter) #f)))
205 (hash-for-each
206 (lambda (var cont)
207 (match cont
208 (($ $cont label ($ $kfun src meta self))
209 (unless (equal? var self)
210 (bitvector-set! bv label
211 (and (bitvector-ref well-known-vars var)
212 (bitvector-ref well-known-vars self)))))))
213 named-funs)
214 bv))
215 (define (union a b)
216 (lset-union eq? a b))
217 (define (difference a b)
218 (lset-difference eq? a b))
219 (define (visit-cont cont bound)
220 (match cont
221 (($ $cont label ($ $kargs names vars body))
222 (visit-term body (append vars bound)))
223 (($ $cont label ($ $kfun src meta self tail clause))
224 (add-named-fun! self cont)
225 (let ((free (if clause
226 (visit-cont clause (list self))
227 '())))
228 (hashq-set! free-vars label free)
229 (difference free bound)))
230 (($ $cont label ($ $kclause arity body alternate))
231 (let ((free (visit-cont body bound)))
232 (if alternate
233 (union (visit-cont alternate bound) free)
234 free)))
235 (($ $cont) '())))
236 (define (visit-term term bound)
237 (match term
238 (($ $letk conts body)
239 (fold (lambda (cont free)
240 (union (visit-cont cont bound) free))
241 (visit-term body bound)
242 conts))
243 (($ $letrec names vars (($ $fun () cont) ...) body)
244 (let ((bound (append vars bound)))
245 (for-each add-named-fun! vars cont)
246 (fold (lambda (cont free)
247 (union (visit-cont cont bound) free))
248 (visit-term body bound)
249 cont)))
250 (($ $continue k src ($ $fun () body))
251 (match (lookup-predecessors k dfg)
252 ((_) (match (lookup-cont k dfg)
253 (($ $kargs (name) (var))
254 (add-named-fun! var body))))
255 (_ #f))
256 (visit-cont body bound))
257 (($ $continue k src exp)
258 (visit-exp exp bound))))
259 (define (visit-exp exp bound)
260 (define (adjoin var free)
261 (if (or (memq var bound) (memq var free))
262 free
263 (cons var free)))
264 (match exp
265 ((or ($ $void) ($ $const) ($ $prim)) '())
266 (($ $call proc args)
267 (for-each clear-well-known! args)
268 (fold adjoin (adjoin proc '()) args))
269 (($ $primcall name args)
270 (for-each clear-well-known! args)
271 (fold adjoin '() args))
272 (($ $values args)
273 (for-each clear-well-known! args)
274 (fold adjoin '() args))
275 (($ $prompt escape? tag handler)
276 (clear-well-known! tag)
277 (adjoin tag '()))))
278
279 (let ((free (visit-cont exp '())))
280 (unless (null? free)
281 (error "Expected no free vars in toplevel thunk" free exp))
282 (values free-vars named-funs (compute-well-known-labels)))))
283
284 (define (prune-free-vars free-vars named-funs well-known var-aliases)
285 (define (well-known? label)
286 (bitvector-ref well-known label))
287 (let ((eliminated (make-bitvector (label-counter) #f))
288 (label-aliases (make-vector (label-counter) #f)))
289 (let lp ((label 0))
290 (let ((label (bit-position #t well-known label)))
291 (when label
292 (match (hashq-ref free-vars label)
293 ;; Mark all well-known closures that have no free variables
294 ;; for elimination.
295 (() (bitvector-set! eliminated label #t))
296 ;; Replace well-known closures that have just one free
297 ;; variable by references to that free variable.
298 ((var)
299 (vector-set! label-aliases label var))
300 (_ #f))
301 (lp (1+ label)))))
302 ;; Iterative free variable elimination.
303 (let lp ()
304 (let ((recurse? #f))
305 (define (adjoin elt list)
306 ;; Normally you wouldn't see duplicates in a free variable
307 ;; list, but with aliases that is possible.
308 (if (memq elt list) list (cons elt list)))
309 (define (filter-out-eliminated free)
310 (match free
311 (() '())
312 ((var . free)
313 (let lp ((var var) (alias-stack '()))
314 (match (hashq-ref named-funs var)
315 (($ $cont label)
316 (cond
317 ((bitvector-ref eliminated label)
318 (filter-out-eliminated free))
319 ((vector-ref label-aliases label)
320 => (lambda (var)
321 (cond
322 ((memq label alias-stack)
323 ;; We have found a set of mutually recursive
324 ;; well-known procedures, each of which only
325 ;; closes over one of the others. Mark them
326 ;; all for elimination.
327 (for-each (lambda (label)
328 (bitvector-set! eliminated label #t)
329 (set! recurse? #t))
330 alias-stack)
331 (filter-out-eliminated free))
332 (else
333 (lp var (cons label alias-stack))))))
334 (else
335 (adjoin var (filter-out-eliminated free)))))
336 (_ (adjoin var (filter-out-eliminated free))))))))
337 (hash-for-each-handle
338 (lambda (pair)
339 (match pair
340 ((label . ()) #t)
341 ((label . free)
342 (let ((orig-nfree (length free))
343 (free (filter-out-eliminated free)))
344 (set-cdr! pair free)
345 ;; If we managed to eliminate one or more free variables
346 ;; from a well-known function, it could be that we can
347 ;; eliminate or alias this function as well.
348 (when (and (well-known? label)
349 (< (length free) orig-nfree))
350 (match free
351 (()
352 (bitvector-set! eliminated label #t)
353 (set! recurse? #t))
354 ((var)
355 (vector-set! label-aliases label var)
356 (set! recurse? #t))
357 (_ #t)))))))
358 free-vars)
359 ;; Iterate to fixed point.
360 (when recurse? (lp))))
361 ;; Populate var-aliases from label-aliases.
362 (hash-for-each (lambda (var cont)
363 (match cont
364 (($ $cont label)
365 (let ((alias (vector-ref label-aliases label)))
366 (when alias
367 (vector-set! var-aliases var alias))))))
368 named-funs)))
369
370 (define (convert-one label fun free-vars named-funs well-known aliases)
371 (define (well-known? label)
372 (bitvector-ref well-known label))
373
374 ;; Load the closure for a known call. The callee may or may not be
375 ;; known at all call sites.
376 (define (convert-known-proc-call var label self self-known? free k)
377 ;; Well-known closures with one free variable are replaced at their
378 ;; use sites by uses of the one free variable. The use sites of a
379 ;; well-known closures are only in well-known proc calls, and in
380 ;; free lists of other closures. Here we handle the call case; the
381 ;; free list case is handled by prune-free-vars.
382 (define (rename var)
383 (let ((var* (vector-ref aliases var)))
384 (if var*
385 (rename var*)
386 var)))
387 (match (cons (well-known? label)
388 (hashq-ref free-vars label))
389 ((#t)
390 ;; Calling a well-known procedure with no free variables; pass #f
391 ;; as the closure.
392 (let-fresh (k*) (v*)
393 (build-cps-term
394 ($letk ((k* ($kargs (v*) (v*) ,(k v*))))
395 ($continue k* #f ($const #f))))))
396 ((#t _)
397 ;; Calling a well-known procedure with one free variable; pass
398 ;; the free variable as the closure.
399 (convert-free-var (rename var) self self-known? free k))
400 (_
401 (convert-free-var var self self-known? free k))))
402
403 (let ((free (hashq-ref free-vars label))
404 (self-known? (well-known? label))
405 (self (match fun (($ $kfun _ _ self) self))))
406 (define (visit-cont cont)
407 (rewrite-cps-cont cont
408 (($ $cont label ($ $kargs names vars body))
409 (label ($kargs names vars ,(visit-term body))))
410 (($ $cont label ($ $kfun src meta self tail clause))
411 (label ($kfun src meta self ,tail
412 ,(and clause (visit-cont clause)))))
413 (($ $cont label ($ $kclause arity body alternate))
414 (label ($kclause ,arity ,(visit-cont body)
415 ,(and alternate (visit-cont alternate)))))
416 (($ $cont) ,cont)))
417 (define (visit-term term)
418 (match term
419 (($ $letk conts body)
420 (build-cps-term
421 ($letk ,(map visit-cont conts) ,(visit-term body))))
422
423 ;; Remove letrec.
424 (($ $letrec names vars funs body)
425 (let lp ((in (map list names vars funs))
426 (bindings (lambda (body) body))
427 (body (visit-term body)))
428 (match in
429 (() (bindings body))
430 (((name var ($ $fun ()
431 (and fun-body
432 ($ $cont kfun ($ $kfun src))))) . in)
433 (let ((fun-free (hashq-ref free-vars kfun)))
434 (lp in
435 (lambda (body)
436 (allocate-closure
437 src name var kfun (well-known? kfun) fun-free
438 (bindings body)))
439 (init-closure
440 src var
441 (well-known? kfun) fun-free self self-known? free
442 body)))))))
443
444 (($ $continue k src (or ($ $void) ($ $const) ($ $prim)))
445 term)
446
447 (($ $continue k src ($ $fun () ($ $cont kfun)))
448 (let ((fun-free (hashq-ref free-vars kfun)))
449 (match (cons (well-known? kfun) fun-free)
450 ((known?)
451 (build-cps-term
452 ($continue k src ,(if known?
453 (build-cps-exp ($const #f))
454 (build-cps-exp ($closure kfun 0))))))
455 ((#t _)
456 ;; A well-known closure of one free variable is replaced
457 ;; at each use with the free variable itself, so we don't
458 ;; need a binding at all; and yet, the continuation
459 ;; expects one value, so give it something. DCE should
460 ;; clean up later.
461 (build-cps-term
462 ($continue k src ,(build-cps-exp ($const #f)))))
463 (_
464 (let-fresh () (var)
465 (allocate-closure
466 src #f var kfun (well-known? kfun) fun-free
467 (init-closure
468 src var
469 (well-known? kfun) fun-free self self-known? free
470 (build-cps-term ($continue k src ($values (var)))))))))))
471
472 (($ $continue k src ($ $call proc args))
473 (match (hashq-ref named-funs proc)
474 (($ $cont kfun)
475 (convert-known-proc-call
476 proc kfun self self-known? free
477 (lambda (proc)
478 (convert-free-vars args self self-known? free
479 (lambda (args)
480 (build-cps-term
481 ($continue k src
482 ($callk kfun proc args))))))))
483 (#f
484 (convert-free-vars (cons proc args) self self-known? free
485 (match-lambda
486 ((proc . args)
487 (build-cps-term
488 ($continue k src
489 ($call proc args)))))))))
490
491 (($ $continue k src ($ $primcall name args))
492 (convert-free-vars args self self-known? free
493 (lambda (args)
494 (build-cps-term
495 ($continue k src ($primcall name args))))))
496
497 (($ $continue k src ($ $values args))
498 (convert-free-vars args self self-known? free
499 (lambda (args)
500 (build-cps-term
501 ($continue k src ($values args))))))
502
503 (($ $continue k src ($ $prompt escape? tag handler))
504 (convert-free-var tag self self-known? free
505 (lambda (tag)
506 (build-cps-term
507 ($continue k src
508 ($prompt escape? tag handler))))))))
509 (visit-cont (build-cps-cont (label ,fun)))))
510
511 (define (convert-closures fun)
512 "Convert free reference in @var{exp} to primcalls to @code{free-ref},
513 and allocate and initialize flat closures."
514 (let ((dfg (compute-dfg fun)))
515 (with-fresh-name-state-from-dfg dfg
516 (call-with-values (lambda () (analyze-closures fun dfg))
517 (lambda (free-vars named-funs well-known)
518 (let ((labels (sort (hash-map->list (lambda (k v) k) free-vars) <))
519 (aliases (make-vector (var-counter) #f)))
520 (prune-free-vars free-vars named-funs well-known aliases)
521 (build-cps-term
522 ($program
523 ,(map (lambda (label)
524 (convert-one label (lookup-cont label dfg)
525 free-vars named-funs well-known aliases))
526 labels)))))))))