summaryrefslogtreecommitdiff
path: root/module/language/cps/contification.scm
blob: 1f702310acc6e5c2767f2bba43eb50deaaa1e00a (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
;;; 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:
;;;
;;; Contification is a pass that turns $fun instances into $cont
;;; instances if all calls to the $fun return to the same continuation.
;;; This is a more rigorous variant of our old "fixpoint labels
;;; allocation" optimization.
;;;
;;; See Kennedy's "Compiling with Continuations, Continued", and Fluet
;;; and Weeks's "Contification using Dominators".
;;;
;;; Code:

(define-module (language cps contification)
  #:use-module (ice-9 match)
  #:use-module ((srfi srfi-1) #:select (concatenate filter-map))
  #:use-module (srfi srfi-26)
  #:use-module (language cps)
  #:use-module (language cps dfg)
  #:use-module (language cps primitives)
  #:use-module (language bytecode)
  #:export (contify))

(define (compute-contification fun)
  (let* ((dfg (compute-dfg fun))
         (scope-table (make-hash-table))
         (call-substs '())
         (cont-substs '())
         (cont-splices (make-hash-table)))
    (define (subst-call! sym arities body-ks)
      (set! call-substs (acons sym (map cons arities body-ks) call-substs)))
    (define (subst-return! old-tail new-tail)
      (set! cont-substs (acons old-tail new-tail cont-substs)))
    (define (splice-conts! scope conts)
      (for-each (match-lambda
                 (($ $cont k) (hashq-set! scope-table k scope)))
                conts)
      (hashq-set! cont-splices scope
                  (append conts (hashq-ref cont-splices scope '()))))

    (define (lookup-return-cont k)
      (match (assq-ref cont-substs k)
        (#f k)
        (k (lookup-return-cont k))))

    ;; If K is a continuation that binds one variable, and it has only
    ;; one predecessor, return that variable.
    (define (bound-symbol k)
      (match (lookup-cont k dfg)
        (($ $kargs (_) (sym))
         (match (lookup-predecessors k dfg)
           ((_)
            ;; K has one predecessor, the one that defined SYM.
            sym)
           (_ #f)))
        (_ #f)))

    (define (extract-arities clause)
      (match clause
        (($ $cont _ ($ $kclause arity body alternate))
         (cons arity (extract-arities alternate)))
        (#f '())))
    (define (extract-bodies clause)
      (match clause
        (($ $cont _ ($ $kclause arity body alternate))
         (cons body (extract-bodies alternate)))
        (#f '())))

    (define (contify-fun term-k sym self tail arities bodies)
      (contify-funs term-k
                    (list sym) (list self) (list tail)
                    (list arities) (list bodies)))

    ;; Given a set of mutually recursive functions bound to local
    ;; variables SYMS, with self symbols SELFS, tail continuations
    ;; TAILS, arities ARITIES, and bodies BODIES, all bound in TERM-K,
    ;; contify them if we can prove that they all return to the same
    ;; continuation.  Returns a true value on success, and false
    ;; otherwise.
    (define (contify-funs term-k syms selfs tails arities bodies)
      (define (unused? sym)
        (null? (lookup-uses sym dfg)))

      ;; Are the given args compatible with any of the arities?
      (define (applicable? proc args)
        (let lp ((arities (assq-ref (map cons syms arities) proc)))
          (match arities
            ((($ $arity req () #f () #f) . arities)
             (or (= (length args) (length req))
                 (lp arities)))
            ;; If we reached the end of the arities, fail.  Also fail if
            ;; the next arity in the list has optional, keyword, or rest
            ;; arguments.
            (_ #f))))

      ;; If the use of PROC in continuation USE is a call to PROC that
      ;; is compatible with one of the procedure's arities, return the
      ;; target continuation.  Otherwise return #f.
      (define (call-target use proc)
        (match (find-call (lookup-cont use dfg))
          (($ $continue k src ($ $call proc* args))
           (and (eq? proc proc*) (not (memq proc args)) (applicable? proc args)
                ;; Converge more quickly by resolving already-contified
                ;; call targets.
                (lookup-return-cont k)))
          (_ #f)))

      ;; If this set of functions is always called with one
      ;; continuation, not counting tail calls between the functions,
      ;; return that continuation.
      (define (find-common-continuation)
        (let visit-syms ((syms syms) (k #f))
          (match syms
            (() k)
            ((sym . syms)
             (let visit-uses ((uses (lookup-uses sym dfg)) (k k))
               (match uses
                 (() (visit-syms syms k))
                 ((use . uses)
                  (and=> (call-target use sym)
                         (lambda (k*)
                           (cond
                            ((memq k* tails) (visit-uses uses k))
                            ((not k) (visit-uses uses k*))
                            ((eq? k k*) (visit-uses uses k))
                            (else #f)))))))))))

      ;; Given that the functions are called with the common
      ;; continuation K, determine the scope at which to contify the
      ;; functions.  If K is in scope in the term, we go ahead and
      ;; contify them there.  Otherwise the scope is inside the letrec
      ;; body, and so choose the scope in which the continuation is
      ;; defined, whose free variables are a superset of the free
      ;; variables of the functions.
      ;;
      ;; There is some slight trickiness here.  Call-target already uses
      ;; the information we compute within this pass.  Previous
      ;; contifications may cause functions to be contified not at their
      ;; point of definition but at their point of non-recursive use.
      ;; That will cause the scope nesting to change.  (It may
      ;; effectively push a function deeper down the tree -- the second
      ;; case above, a call within the letrec body.)  What if we contify
      ;; to the tail of a previously contified function?  We have to
      ;; track what the new scope tree will be when asking whether K
      ;; will be bound in TERM-K's scope, not the scope tree that
      ;; existed when we started the pass.
      ;;
      ;; FIXME: Does this choose the right scope for contified let-bound
      ;; functions?
      (define (find-contification-scope k)
        (define (scope-contains? scope k)
          (let ((k-scope (or (hashq-ref scope-table k)
                             (let ((k-scope (lookup-block-scope k dfg)))
                               (hashq-set! scope-table k k-scope)
                               k-scope))))
            (or (eq? scope k-scope)
                (and k-scope (scope-contains? scope k-scope)))))

        ;; Find the scope of K.
        (define (continuation-scope k)
          (or (hashq-ref scope-table k)
              (let ((scope (lookup-block-scope k dfg)))
                (hashq-set! scope-table k scope)
                scope)))

        (let ((k-scope (continuation-scope k)))
          (if (scope-contains? k-scope term-k)
              term-k
              (match (lookup-cont k-scope dfg)
                (($ $kfun src meta self tail clause)
                 ;; K is the tail of some function.  If that function
                 ;; has just one clause, return that clause.  Otherwise
                 ;; bail.
                 (match clause
                   (($ $cont _ ($ $kclause arity ($ $cont kargs) #f))
                    kargs)
                   (_ #f)))
                (_ k-scope)))))

      ;; We are going to contify.  Mark all SYMs for replacement in
      ;; calls, and mark the tail continuations for replacement by K.
      ;; Arrange for the continuations to be spliced into SCOPE.
      (define (enqueue-contification! k scope)
        (for-each (lambda (sym tail arities bodies)
                    (match bodies
                      ((($ $cont body-k) ...)
                       (subst-call! sym arities body-k)))
                    (subst-return! tail k))
                  syms tails arities bodies)
        (splice-conts! scope (concatenate bodies))
        #t)

      ;; "Call me maybe"
      (and (and-map unused? selfs)
           (and=> (find-common-continuation)
                  (lambda (k)
                    (and=> (find-contification-scope k)
                           (cut enqueue-contification! k <>))))))

    (define (visit-fun term)
      (match term
        (($ $fun body)
         (visit-cont body))))
    (define (visit-cont cont)
      (match cont
        (($ $cont sym ($ $kargs _ _ body))
         (visit-term body sym))
        (($ $cont sym ($ $kfun src meta self tail clause))
         (when clause (visit-cont clause)))
        (($ $cont sym ($ $kclause arity body alternate))
         (visit-cont body)
         (when alternate (visit-cont alternate)))
        (($ $cont)
         #t)))
    (define (visit-term term term-k)
      (match term
        (($ $letk conts body)
         (for-each visit-cont conts)
         (visit-term body term-k))
        (($ $continue k src exp)
         (match exp
           (($ $fun
               ($ $cont fun-k
                  ($ $kfun src meta self ($ $cont tail-k ($ $ktail)) clause)))
            (if (and=> (bound-symbol k)
                       (lambda (sym)
                         (contify-fun term-k sym self tail-k
                                      (extract-arities clause)
                                      (extract-bodies clause))))
                (begin
                  (for-each visit-cont (extract-bodies clause)))
                (visit-fun exp)))
           (($ $rec names syms funs)
            (define (split-components nsf)
              ;; FIXME: Compute strongly-connected components.  Currently
              ;; we just put non-recursive functions in their own
              ;; components, and lump everything else in the remaining
              ;; component.
              (define (recursive? k)
                (or-map (cut variable-free-in? <> k dfg) syms))
              (let lp ((nsf nsf) (rec '()))
                (match nsf
                  (()
                   (if (null? rec)
                       '()
                       (list rec)))
                  (((and elt (n s ($ $fun ($ $cont kfun))))
                    . nsf)
                   (if (recursive? kfun)
                       (lp nsf (cons elt rec))
                       (cons (list elt) (lp nsf rec)))))))
            (define (extract-arities+bodies clauses)
              (values (map extract-arities clauses)
                      (map extract-bodies clauses)))
            (define (visit-component component)
              (match component
                (((name sym fun) ...)
                 (match fun
                   ((($ $fun
                        ($ $cont fun-k
                           ($ $kfun src meta self ($ $cont tail-k ($ $ktail))
                              clause)))
                     ...)
                    (call-with-values (lambda () (extract-arities+bodies clause))
                      (lambda (arities bodies)
                        ;; Technically the procedures are created in
                        ;; term-k but bound for use in k.  But, there is
                        ;; a tight link between term-k and k, as they
                        ;; are in the same block.  Mark k as the
                        ;; contification scope, because that's where
                        ;; they'll be used.  Perhaps we can fix this
                        ;; with the new CPS dialect that doesn't have
                        ;; $letk.
                        (if (contify-funs k sym self tail-k arities bodies)
                            (for-each (cut for-each visit-cont <>) bodies)
                            (for-each visit-fun fun)))))))))
            (for-each visit-component
                      (split-components (map list names syms funs))))
           (_ #t)))))

    (visit-cont fun)
    (values call-substs cont-substs cont-splices)))

(define (apply-contification fun call-substs cont-substs cont-splices)
  (define (contify-call src proc args)
    (and=> (assq-ref call-substs proc)
           (lambda (clauses)
             (let lp ((clauses clauses))
               (match clauses
                 (() (error "invalid contification"))
                 (((($ $arity req () #f () #f) . k) . clauses)
                  (if (= (length req) (length args))
                      (build-cps-term
                        ($continue k src
                          ($values args)))
                      (lp clauses)))
                 ((_ . clauses) (lp clauses)))))))
  (define (continue k src exp)
    (define (lookup-return-cont k)
      (match (assq-ref cont-substs k)
        (#f k)
        (k (lookup-return-cont k))))
    (let ((k* (lookup-return-cont k)))
      ;; We are contifying this return.  It must be a call or a
      ;; primcall to values, return, or return-values.
      (if (eq? k k*)
          (build-cps-term ($continue k src ,exp))
          (rewrite-cps-term exp
            (($ $primcall 'return (val))
             ($continue k* src ($primcall 'values (val))))
            (($ $values vals)
             ($continue k* src ($primcall 'values vals)))
            (_ ($continue k* src ,exp))))))
  (define (splice-continuations term-k term)
    (match (hashq-ref cont-splices term-k)
      (#f term)
      ((cont ...)
       (let lp ((term term))
         (rewrite-cps-term term
           (($ $letk conts* body)
            ($letk ,(append conts* (filter-map visit-cont cont))
              ,body))
           (body
            ($letk ,(filter-map visit-cont cont)
              ,body)))))))
  (define (visit-fun term)
    (rewrite-cps-exp term
      (($ $fun body)
       ($fun ,(visit-cont body)))))
  (define (visit-cont cont)
    (rewrite-cps-cont cont
      (($ $cont label ($ $kargs names syms body))
       ;; Remove bindings for functions that have been contified.
       ,(rewrite-cps-cont (filter (match-lambda
                                   ((name sym) (not (assq sym call-substs))))
                                  (map list names syms))
          (((names syms) ...)
           (label ($kargs names syms ,(visit-term body label))))))
      (($ $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 term-k)
    (match term
      (($ $letk conts body)
       ;; Visit the body first, so we rewrite depth-first.
       (let lp ((body (visit-term body term-k)))
         ;; Because we attach contified functions on a particular
         ;; term-k, and one term-k can correspond to an arbitrarily
         ;; nested sequence of $letk instances, normalize so that all
         ;; continuations are bound by one $letk -- guaranteeing that
         ;; they are in the same scope.
         (rewrite-cps-term body
           (($ $letk conts* body)
            ($letk ,(append conts* (filter-map visit-cont conts))
              ,body))
           (body
            ($letk ,(filter-map visit-cont conts)
              ,body)))))
      (($ $continue k src exp)
       (splice-continuations
        term-k
        (match exp
          (($ $fun 
              ($ $cont fun-k ($ $kfun src meta self ($ $cont tail-k))))
           ;; If the function's tail continuation has been substituted,
           ;; that means it has been contified.
           (continue k src
                     (if (assq tail-k cont-substs)
                         (build-cps-exp ($values ()))
                         (visit-fun exp))))
          (($ $rec names syms funs)
           (match (filter (match-lambda
                           ((n s f) (not (assq s call-substs))))
                          (map list names syms funs))
             (() (continue k src (build-cps-exp ($values ()))))
             (((names syms funs) ...)
              (continue k src
                        (build-cps-exp
                          ($rec names syms (map visit-fun funs)))))))
          (($ $call proc args)
           (or (contify-call src proc args)
               (continue k src exp)))
          (_ (continue k src exp)))))))
  (visit-cont fun))

(define (contify fun)
  (call-with-values (lambda () (compute-contification fun))
    (lambda (call-substs cont-substs cont-splices)
      (if (null? call-substs)
          fun
          ;; Iterate to fixed point.
          (contify
           (apply-contification fun call-substs cont-substs cont-splices))))))