Commit | Line | Data |
---|---|---|
8ac8e2df AW |
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)) | |
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 (contify fun) | |
42 | (let* ((dfg (compute-dfg fun)) | |
43 | (cont-table (dfg-cont-table dfg)) | |
44 | (call-substs '()) | |
45 | (cont-substs '())) | |
46 | (define (subst-call! sym arities body-ks) | |
47 | (set! call-substs (acons sym (map cons arities body-ks) call-substs))) | |
48 | (define (subst-return! old-tail new-tail) | |
49 | (set! cont-substs (acons old-tail new-tail cont-substs))) | |
50 | (define (lookup-return-cont k) | |
51 | (or (assq-ref cont-substs k) k)) | |
52 | ||
53 | (define (contify-call proc args) | |
54 | (and=> (assq-ref call-substs proc) | |
55 | (lambda (clauses) | |
56 | (let lp ((clauses clauses)) | |
57 | (match clauses | |
58 | (() (error "invalid contification")) | |
59 | (((($ $arity req () #f () #f) . k) . clauses) | |
60 | (if (= (length req) (length args)) | |
61 | (build-cps-term | |
62 | ($continue k ($values args))) | |
63 | (lp clauses))) | |
64 | ((_ . clauses) (lp clauses))))))) | |
65 | ||
66 | ;; If K is a continuation that binds one variable, and it has only | |
67 | ;; one predecessor, return that variable. | |
68 | (define (bound-symbol k) | |
69 | (match (lookup-cont k cont-table) | |
70 | (($ $kargs (_) (sym)) | |
71 | (match (lookup-uses k dfg) | |
72 | ((_) | |
73 | ;; K has one predecessor, the one that defined SYM. | |
74 | sym) | |
75 | (_ #f))) | |
76 | (_ #f))) | |
77 | ||
78 | (define (contify-fun term-k sym self tail arities bodies) | |
79 | (contify-funs term-k | |
80 | (list sym) (list self) (list tail) | |
81 | (list arities) (list bodies))) | |
82 | ||
d51fb1e6 AW |
83 | ;; Given a set of mutually recursive functions bound to local |
84 | ;; variables SYMS, with self symbols SELFS, tail continuations | |
85 | ;; TAILS, arities ARITIES, and bodies BODIES, all bound in TERM-K, | |
86 | ;; contify them if we can prove that they all return to the same | |
87 | ;; continuation. If successful, return that common continuation. | |
88 | ;; Otherwise return #f. | |
8ac8e2df AW |
89 | (define (contify-funs term-k syms selfs tails arities bodies) |
90 | ;; Are the given args compatible with any of the arities? | |
91 | (define (applicable? proc args) | |
92 | (or-map (match-lambda | |
93 | (($ $arity req () #f () #f) | |
94 | (= (length args) (length req))) | |
95 | (_ #f)) | |
96 | (assq-ref (map cons syms arities) proc))) | |
97 | ||
98 | ;; If the use of PROC in continuation USE is a call to PROC that | |
99 | ;; is compatible with one of the procedure's arities, return the | |
100 | ;; target continuation. Otherwise return #f. | |
101 | (define (call-target use proc) | |
102 | (match (find-call (lookup-cont use cont-table)) | |
103 | (($ $continue k ($ $call proc* args)) | |
104 | (and (eq? proc proc*) (not (memq proc args)) (applicable? proc args) | |
105 | k)) | |
106 | (_ #f))) | |
107 | ||
108 | (and | |
109 | (and-map null? (map (cut lookup-uses <> dfg) selfs)) | |
110 | (and=> (let visit-syms ((syms syms) (k #f)) | |
111 | (match syms | |
112 | (() k) | |
113 | ((sym . syms) | |
114 | (let visit-uses ((uses (lookup-uses sym dfg)) (k k)) | |
115 | (match uses | |
116 | (() (visit-syms syms k)) | |
117 | ((use . uses) | |
118 | (and=> (call-target use sym) | |
119 | (lambda (k*) | |
120 | (cond | |
121 | ((memq k* tails) (visit-uses uses k)) | |
122 | ((not k) (visit-uses uses k*)) | |
123 | ((eq? k k*) (visit-uses uses k)) | |
124 | (else #f)))))))))) | |
125 | (lambda (k) | |
126 | ;; We have a common continuation, so we contify: mark | |
127 | ;; all SYMs for replacement in calls, and mark the tail | |
128 | ;; continuations for replacement by K. | |
129 | (for-each (lambda (sym tail arities bodies) | |
130 | (for-each (cut lift-definition! <> term-k dfg) | |
131 | bodies) | |
132 | (subst-call! sym arities bodies) | |
133 | (subst-return! tail k)) | |
134 | syms tails arities bodies) | |
135 | k)))) | |
136 | ||
137 | ;; This is a first cut at a contification algorithm. It contifies | |
138 | ;; non-recursive functions that only have positional arguments. | |
139 | (define (visit-fun term) | |
140 | (rewrite-cps-exp term | |
141 | (($ $fun meta free body) | |
142 | ($fun meta free ,(visit-cont body))))) | |
143 | (define (visit-cont cont) | |
144 | (rewrite-cps-cont cont | |
145 | (($ $cont sym src | |
146 | ($ $kargs (name) (and sym (? (cut assq <> call-substs))) | |
147 | body)) | |
148 | (sym src ($kargs () () ,(visit-term body sym)))) | |
149 | (($ $cont sym src ($ $kargs names syms body)) | |
150 | (sym src ($kargs names syms ,(visit-term body sym)))) | |
151 | (($ $cont sym src ($ $kentry self tail clauses)) | |
152 | (sym src ($kentry self ,tail ,(map visit-cont clauses)))) | |
153 | (($ $cont sym src ($ $kclause arity body)) | |
154 | (sym src ($kclause ,arity ,(visit-cont body)))) | |
155 | (($ $cont) | |
156 | ,cont))) | |
157 | (define (visit-term term term-k) | |
158 | (match term | |
159 | (($ $letk conts body) | |
160 | ;; Visit the body first, so we visit depth-first. | |
161 | (let ((body (visit-term body term-k))) | |
162 | (build-cps-term | |
163 | ($letk ,(map visit-cont conts) ,body)))) | |
164 | (($ $letrec names syms funs body) | |
165 | (define (split-components nsf) | |
166 | ;; FIXME: Compute strongly-connected components. Currently | |
167 | ;; we just put non-recursive functions in their own | |
168 | ;; components, and lump everything else in the remaining | |
169 | ;; component. | |
170 | (define (recursive? k) | |
d51fb1e6 | 171 | (or-map (cut variable-free-in? <> k dfg) syms)) |
8ac8e2df AW |
172 | (let lp ((nsf nsf) (rec '())) |
173 | (match nsf | |
174 | (() | |
175 | (if (null? rec) | |
176 | '() | |
177 | (list rec))) | |
178 | (((and elt (n s ($ $fun meta free ($ $cont kentry)))) | |
179 | . nsf) | |
180 | (if (recursive? kentry) | |
181 | (lp nsf (cons elt rec)) | |
182 | (cons (list elt) (lp nsf rec))))))) | |
183 | (define (visit-components components) | |
184 | (match components | |
185 | (() (visit-term body term-k)) | |
186 | ((((name sym fun) ...) . components) | |
187 | (match fun | |
188 | ((($ $fun meta free | |
189 | ($ $cont fun-k _ | |
190 | ($ $kentry self | |
191 | ($ $cont tail-k _ ($ $ktail)) | |
192 | (($ $cont _ _ ($ $kclause arity | |
193 | (and body ($ $cont body-k)))) | |
194 | ...)))) | |
195 | ...) | |
196 | (if (contify-funs term-k sym self tail-k arity body-k) | |
197 | (let ((body* (visit-components components))) | |
198 | (build-cps-term | |
199 | ($letk ,(map visit-cont (concatenate body)) | |
200 | ,body*))) | |
201 | (let-gensyms (k) | |
202 | (build-cps-term | |
203 | ($letrec name sym (map visit-fun fun) | |
204 | ,(visit-components components)))))))))) | |
205 | (visit-components (split-components (map list names syms funs)))) | |
206 | (($ $continue k exp) | |
207 | (let ((k* (lookup-return-cont k))) | |
208 | (define (default) | |
209 | (rewrite-cps-term exp | |
210 | (($ $fun) ($continue k* ,(visit-fun exp))) | |
211 | (($ $primcall 'return (val)) | |
212 | ,(if (eq? k k*) | |
213 | (build-cps-term ($continue k* ,exp)) | |
214 | (build-cps-term ($continue k* ($values (val)))))) | |
215 | (($ $primcall 'return-values vals) | |
216 | ,(if (eq? k k*) | |
217 | (build-cps-term ($continue k* ,exp)) | |
218 | (build-cps-term ($continue k* ($values vals))))) | |
219 | (_ ($continue k* ,exp)))) | |
220 | (match exp | |
221 | (($ $fun meta free | |
222 | ($ $cont fun-k _ | |
223 | ($ $kentry self | |
224 | ($ $cont tail-k _ ($ $ktail)) | |
225 | (($ $cont _ _ ($ $kclause arity | |
226 | (and body ($ $cont body-k)))) | |
227 | ...)))) | |
228 | (if (and=> (bound-symbol k*) | |
229 | (lambda (sym) | |
230 | (contify-fun term-k sym self tail-k arity body-k))) | |
231 | (build-cps-term | |
232 | ($letk ,(map visit-cont body) | |
233 | ($continue k* ($values ())))) | |
234 | (default))) | |
235 | (($ $call proc args) | |
236 | (or (contify-call proc args) | |
237 | (default))) | |
238 | (_ (default))))))) | |
239 | ||
240 | (let ((fun (visit-fun fun))) | |
241 | (if (null? call-substs) | |
242 | fun | |
243 | ;; Iterate to fixed point. | |
244 | (contify fun))))) |