Compute-contification also visits body
[bpt/guile.git] / module / language / cps / contification.scm
1 ;;; Continuation-passing style (CPS) intermediate language (IL)
2
3 ;; Copyright (C) 2013 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 ;;; Contification is a pass that turns $fun instances into $cont
22 ;;; instances if all calls to the $fun return to the same continuation.
23 ;;; This is a more rigorous variant of our old "fixpoint labels
24 ;;; allocation" optimization.
25 ;;;
26 ;;; See Kennedy's "Compiling with Continuations, Continued", and Fluet
27 ;;; and Weeks's "Contification using Dominators".
28 ;;;
29 ;;; Code:
30
31 (define-module (language cps contification)
32 #:use-module (ice-9 match)
33 #:use-module ((srfi srfi-1) #:select (concatenate filter-map))
34 #:use-module (srfi srfi-26)
35 #:use-module (language cps)
36 #:use-module (language cps dfg)
37 #:use-module (language cps primitives)
38 #:use-module (language rtl)
39 #:export (contify))
40
41 (define (compute-contification fun)
42 (let* ((dfg (compute-dfg fun))
43 (cont-table (dfg-cont-table dfg))
44 (call-substs '())
45 (cont-substs '())
46 (fun-elisions '())
47 (cont-splices (make-hash-table)))
48 (define (subst-call! sym arities body-ks)
49 (set! call-substs (acons sym (map cons arities body-ks) call-substs)))
50 (define (subst-return! old-tail new-tail)
51 (set! cont-substs (acons old-tail new-tail cont-substs)))
52 (define (elide-function! k cont)
53 (set! fun-elisions (acons k cont fun-elisions)))
54 (define (splice-conts! scope conts)
55 (hashq-set! cont-splices scope
56 (append conts (hashq-ref cont-splices scope '()))))
57
58 ;; If K is a continuation that binds one variable, and it has only
59 ;; one predecessor, return that variable.
60 (define (bound-symbol k)
61 (match (lookup-cont k cont-table)
62 (($ $kargs (_) (sym))
63 (match (lookup-predecessors k dfg)
64 ((_)
65 ;; K has one predecessor, the one that defined SYM.
66 sym)
67 (_ #f)))
68 (_ #f)))
69
70 (define (contify-fun term-k sym self tail arities bodies)
71 (contify-funs term-k
72 (list sym) (list self) (list tail)
73 (list arities) (list bodies)))
74
75 ;; Given a set of mutually recursive functions bound to local
76 ;; variables SYMS, with self symbols SELFS, tail continuations
77 ;; TAILS, arities ARITIES, and bodies BODIES, all bound in TERM-K,
78 ;; contify them if we can prove that they all return to the same
79 ;; continuation. Returns a true value on success, and false
80 ;; otherwise.
81 (define (contify-funs term-k syms selfs tails arities bodies)
82 (define (unused? sym)
83 (null? (lookup-uses sym dfg)))
84
85 ;; Are the given args compatible with any of the arities?
86 (define (applicable? proc args)
87 (let lp ((arities (assq-ref (map cons syms arities) proc)))
88 (match arities
89 ((($ $arity req () #f () #f) . arities)
90 (or (= (length args) (length req))
91 (lp arities)))
92 ;; If we reached the end of the arities, fail. Also fail if
93 ;; the next arity in the list has optional, keyword, or rest
94 ;; arguments.
95 (_ #f))))
96
97 ;; If the use of PROC in continuation USE is a call to PROC that
98 ;; is compatible with one of the procedure's arities, return the
99 ;; target continuation. Otherwise return #f.
100 (define (call-target use proc)
101 (match (find-call (lookup-cont use cont-table))
102 (($ $continue k src ($ $call proc* args))
103 (and (eq? proc proc*) (not (memq proc args)) (applicable? proc args)
104 k))
105 (_ #f)))
106
107 ;; If this set of functions is always called with one
108 ;; continuation, not counting tail calls between the functions,
109 ;; return that continuation.
110 (define (find-common-continuation)
111 (let visit-syms ((syms syms) (k #f))
112 (match syms
113 (() k)
114 ((sym . syms)
115 (let visit-uses ((uses (lookup-uses sym dfg)) (k k))
116 (match uses
117 (() (visit-syms syms k))
118 ((use . uses)
119 (and=> (call-target use sym)
120 (lambda (k*)
121 (cond
122 ((memq k* tails) (visit-uses uses k))
123 ((not k) (visit-uses uses k*))
124 ((eq? k k*) (visit-uses uses k))
125 (else #f)))))))))))
126
127 ;; Given that the functions are called with the common
128 ;; continuation K, determine the scope at which to contify the
129 ;; functions. If K is in scope in the term, we go ahead and
130 ;; contify them there. Otherwise the scope is inside the letrec
131 ;; body, and so choose the scope in which the continuation is
132 ;; defined, whose free variables are a superset of the free
133 ;; variables of the functions.
134 ;;
135 ;; FIXME: Does this choose the right scope for contified let-bound
136 ;; functions?
137 (define (find-contification-scope k)
138 (if (continuation-bound-in? k term-k dfg)
139 term-k
140 (let ((scope (lookup-block-scope k dfg)))
141 (match (lookup-cont scope cont-table)
142 ;; The common continuation was the tail of some function
143 ;; inside the letrec body. If that function has just
144 ;; one clause, contify into that clause. Otherwise
145 ;; bail.
146 (($ $kentry self tail clauses)
147 (match clauses
148 ((($ $cont _ ($ $kclause arity ($ $cont kargs))))
149 kargs)
150 (_ #f)))
151 (_ scope)))))
152
153 ;; We are going to contify. Mark all SYMs for replacement in
154 ;; calls, and mark the tail continuations for replacement by K.
155 ;; Arrange for the continuations to be spliced into SCOPE.
156 (define (enqueue-contification! k scope)
157 (for-each (lambda (sym tail arities bodies)
158 (match bodies
159 ((($ $cont body-k) ...)
160 (subst-call! sym arities body-k)))
161 (subst-return! tail k))
162 syms tails arities bodies)
163 (splice-conts! scope (concatenate bodies))
164 #t)
165
166 ;; "Call me maybe"
167 (and (and-map unused? selfs)
168 (and=> (find-common-continuation)
169 (lambda (k)
170 (and=> (find-contification-scope k)
171 (cut enqueue-contification! k <>))))))
172
173 (define (visit-fun term)
174 (match term
175 (($ $fun src meta free body)
176 (visit-cont body))))
177 (define (visit-cont cont)
178 (match cont
179 (($ $cont sym ($ $kargs _ _ body))
180 (visit-term body sym))
181 (($ $cont sym ($ $kentry self tail clauses))
182 (for-each visit-cont clauses))
183 (($ $cont sym ($ $kclause arity body))
184 (visit-cont body))
185 (($ $cont)
186 #t)))
187 (define (visit-term term term-k)
188 (match term
189 (($ $letk conts body)
190 (for-each visit-cont conts)
191 (visit-term body term-k))
192 (($ $letrec names syms funs body)
193 (define (split-components nsf)
194 ;; FIXME: Compute strongly-connected components. Currently
195 ;; we just put non-recursive functions in their own
196 ;; components, and lump everything else in the remaining
197 ;; component.
198 (define (recursive? k)
199 (or-map (cut variable-free-in? <> k dfg) syms))
200 (let lp ((nsf nsf) (rec '()))
201 (match nsf
202 (()
203 (if (null? rec)
204 '()
205 (list rec)))
206 (((and elt (n s ($ $fun src meta free ($ $cont kentry))))
207 . nsf)
208 (if (recursive? kentry)
209 (lp nsf (cons elt rec))
210 (cons (list elt) (lp nsf rec)))))))
211 (define (visit-component component)
212 (match component
213 (((name sym fun) ...)
214 (match fun
215 ((($ $fun src meta free
216 ($ $cont fun-k
217 ($ $kentry self
218 ($ $cont tail-k ($ $ktail))
219 (($ $cont _ ($ $kclause arity body))
220 ...))))
221 ...)
222 (if (contify-funs term-k sym self tail-k arity body)
223 (for-each (cut for-each visit-cont <>) body)
224 (for-each visit-fun fun)))))))
225 (visit-term body term-k)
226 (for-each visit-component
227 (split-components (map list names syms funs))))
228 (($ $continue k src exp)
229 (match exp
230 (($ $fun src meta free
231 ($ $cont fun-k
232 ($ $kentry self
233 ($ $cont tail-k ($ $ktail))
234 (($ $cont _ ($ $kclause arity body)) ...))))
235 (if (and=> (bound-symbol k)
236 (lambda (sym)
237 (contify-fun term-k sym self tail-k arity body)))
238 (begin
239 (elide-function! k (lookup-cont k cont-table))
240 (for-each visit-cont body))
241 (visit-fun exp)))
242 (_ #t)))))
243
244 (visit-fun fun)
245 (values call-substs cont-substs fun-elisions cont-splices)))
246
247 (define (apply-contification fun call-substs cont-substs fun-elisions cont-splices)
248 (define (contify-call src proc args)
249 (and=> (assq-ref call-substs proc)
250 (lambda (clauses)
251 (let lp ((clauses clauses))
252 (match clauses
253 (() (error "invalid contification"))
254 (((($ $arity req () #f () #f) . k) . clauses)
255 (if (= (length req) (length args))
256 (build-cps-term
257 ($continue k src
258 ($values args)))
259 (lp clauses)))
260 ((_ . clauses) (lp clauses)))))))
261 (define (continue k src exp)
262 (define (lookup-return-cont k)
263 (match (assq-ref cont-substs k)
264 (#f k)
265 (k (lookup-return-cont k))))
266 (let ((k* (lookup-return-cont k)))
267 ;; We are contifying this return. It must be a call or a
268 ;; primcall to values, return, or return-values.
269 (if (eq? k k*)
270 (build-cps-term ($continue k src ,exp))
271 (rewrite-cps-term exp
272 (($ $primcall 'return (val))
273 ($continue k* src ($primcall 'values (val))))
274 (($ $values vals)
275 ($continue k* src ($primcall 'values vals)))
276 (_ ($continue k* src ,exp))))))
277 (define (splice-continuations term-k term)
278 (match (hashq-ref cont-splices term-k)
279 (#f term)
280 ((cont ...)
281 (let lp ((term term))
282 (rewrite-cps-term term
283 (($ $letrec names syms funs body)
284 ($letrec names syms funs ,(lp body)))
285 (($ $letk conts* body)
286 ($letk ,(append conts* (filter-map visit-cont cont))
287 ,body))
288 (body
289 ($letk ,(filter-map visit-cont cont)
290 ,body)))))))
291 (define (visit-fun term)
292 (rewrite-cps-exp term
293 (($ $fun src meta free body)
294 ($fun src meta free ,(visit-cont body)))))
295 (define (visit-cont cont)
296 (rewrite-cps-cont cont
297 (($ $cont (? (cut assq <> fun-elisions)))
298 ;; This cont gets inlined in place of the $fun.
299 ,#f)
300 (($ $cont sym ($ $kargs names syms body))
301 (sym ($kargs names syms ,(visit-term body sym))))
302 (($ $cont sym ($ $kentry self tail clauses))
303 (sym ($kentry self ,tail ,(map visit-cont clauses))))
304 (($ $cont sym ($ $kclause arity body))
305 (sym ($kclause ,arity ,(visit-cont body))))
306 (($ $cont)
307 ,cont)))
308 (define (visit-term term term-k)
309 (match term
310 (($ $letk conts body)
311 ;; Visit the body first, so we rewrite depth-first.
312 (let lp ((body (visit-term body term-k)))
313 ;; Because we attach contified functions on a particular
314 ;; term-k, and one term-k can correspond to an arbitrarily
315 ;; nested sequence of $letrec and $letk instances, normalize
316 ;; so that all continuations are bound by one $letk --
317 ;; guaranteeing that they are in the same scope.
318 (rewrite-cps-term body
319 (($ $letrec names syms funs body)
320 ($letrec names syms funs ,(lp body)))
321 (($ $letk conts* body)
322 ($letk ,(append conts* (filter-map visit-cont conts))
323 ,body))
324 (body
325 ($letk ,(filter-map visit-cont conts)
326 ,body)))))
327 (($ $letrec names syms funs body)
328 (rewrite-cps-term (filter (match-lambda
329 ((n s f) (not (assq s call-substs))))
330 (map list names syms funs))
331 (((names syms funs) ...)
332 ($letrec names syms (map visit-fun funs)
333 ,(visit-term body term-k)))))
334 (($ $continue k src exp)
335 (splice-continuations
336 term-k
337 (match exp
338 (($ $fun)
339 (cond
340 ((assq-ref fun-elisions k)
341 => (match-lambda
342 (($ $kargs (_) (_) body)
343 (visit-term body k))))
344 (else
345 (continue k src (visit-fun exp)))))
346 (($ $call proc args)
347 (or (contify-call src proc args)
348 (continue k src exp)))
349 (_ (continue k src exp)))))))
350 (visit-fun fun))
351
352 (define (contify fun)
353 (call-with-values (lambda () (compute-contification fun))
354 (lambda (call-substs cont-substs fun-elisions cont-splices)
355 (if (null? call-substs)
356 fun
357 ;; Iterate to fixed point.
358 (contify
359 (apply-contification fun call-substs cont-substs fun-elisions cont-splices))))))