1 ;;; Continuation-passing style (CPS) intermediate language (IL)
3 ;; Copyright (C) 2013 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 Tree-IL to the continuation-passing style (CPS)
24 ;;; CPS is a lower-level representation than Tree-IL. Converting to
25 ;;; CPS, beyond adding names for all control points and all values,
26 ;;; simplifies expressions in the following ways, among others:
28 ;;; * Fixing the order of evaluation.
30 ;;; * Converting assigned variables to boxed variables.
32 ;;; * Requiring that Scheme's <letrec> has already been lowered to
35 ;;; * Inlining default-value initializers into lambda-case
38 ;;; * Inlining prompt bodies.
40 ;;; * Turning toplevel and module references into primcalls. This
41 ;;; involves explicitly modelling the "scope" of toplevel lookups
42 ;;; (indicating the module with respect to which toplevel bindings
45 ;;; The utility of CPS is that it gives a name to everything: every
46 ;;; intermediate value, and every control point (continuation). As such
47 ;;; it is more verbose than Tree-IL, but at the same time more simple as
48 ;;; the number of concepts is reduced.
52 (define-module (language tree-il compile-cps)
53 #:use-module (ice-9 match)
54 #:use-module ((srfi srfi-1) #:select (fold fold-right filter-map))
55 #:use-module (srfi srfi-26)
56 #:use-module ((system foreign) #:select (make-pointer pointer->scm))
57 #:use-module (language cps)
58 #:use-module (language cps primitives)
59 #:use-module (language tree-il analyze)
60 #:use-module (language tree-il optimize)
61 #:use-module ((language tree-il)
64 <const> <primitive-ref> <lexical-ref> <lexical-set>
65 <module-ref> <module-set>
66 <toplevel-ref> <toplevel-set> <toplevel-define>
70 <lambda> <lambda-case>
71 <let> <letrec> <fix> <let-values>
73 make-conditional make-const make-primcall
76 #:export (compile-cps))
78 ;;; Guile's semantics are that a toplevel lambda captures a reference on
79 ;;; the current module, and that all contained lambdas use that module
80 ;;; to resolve toplevel variables. This parameter tracks whether or not
81 ;;; we are in a toplevel lambda. If we are in a lambda, the parameter
82 ;;; is bound to a fresh name identifying the module that was current
83 ;;; when the toplevel lambda is defined.
85 ;;; This is more complicated than it need be. Ideally we should resolve
86 ;;; all toplevel bindings to bindings from specific modules, unless the
87 ;;; binding is unbound. This is always valid if the compilation unit
88 ;;; sets the module explicitly, as when compiling a module, but it
89 ;;; doesn't work for files auto-compiled for use with `load'.
91 (define current-topbox-scope (make-parameter #f))
93 (define (toplevel-box src name bound? val-proc)
94 (let-gensyms (name-sym bound?-sym kbox box)
96 ($letconst (('name name-sym name)
97 ('bound? bound?-sym bound?))
98 ($letk ((kbox src ($kargs ('box) (box) ,(val-proc box))))
99 ,(match (current-topbox-scope)
104 (name-sym bound?-sym)))))
106 (let-gensyms (scope-sym)
108 ($letconst (('scope scope-sym scope))
110 ($primcall 'cached-toplevel-box
111 (scope-sym name-sym bound?-sym)))))))))))))
113 (define (module-box src module name public? bound? val-proc)
114 (let-gensyms (module-sym name-sym public?-sym bound?-sym kbox box)
116 ($letconst (('module module-sym module)
117 ('name name-sym name)
118 ('public? public?-sym public?)
119 ('bound? bound?-sym bound?))
120 ($letk ((kbox src ($kargs ('box) (box) ,(val-proc box))))
122 ($primcall 'cached-module-box
123 (module-sym name-sym public?-sym bound?-sym))))))))
125 (define (capture-toplevel-scope src scope k)
126 (let-gensyms (module scope-sym kmodule)
128 ($letconst (('scope scope-sym scope))
129 ($letk ((kmodule src ($kargs ('module) (module)
131 ($primcall 'cache-current-module!
132 (module scope-sym))))))
134 ($primcall 'current-module ())))))))
136 (define (fold-formals proc seed arity gensyms inits)
138 (($ $arity req opt rest kw allow-other-keys?)
140 (define (fold-req names gensyms seed)
142 (() (fold-opt opt gensyms inits seed))
144 (proc name (car gensyms) #f
145 (fold-req names (cdr gensyms) seed)))))
146 (define (fold-opt names gensyms inits seed)
148 (() (fold-rest rest gensyms inits seed))
150 (proc name (car gensyms) (car inits)
151 (fold-opt names (cdr gensyms) (cdr inits) seed)))))
152 (define (fold-rest rest gensyms inits seed)
154 (#f (fold-kw kw gensyms inits seed))
155 (name (proc name (car gensyms) #f
156 (fold-kw kw (cdr gensyms) inits seed)))))
157 (define (fold-kw kw gensyms inits seed)
160 (unless (null? gensyms)
161 (error "too many gensyms"))
162 (unless (null? inits)
163 (error "too many inits"))
165 (((key name var) . kw)
166 (unless (eq? var (car gensyms))
167 (error "unexpected keyword arg order"))
168 (proc name var (car inits)
169 (fold-kw kw (cdr gensyms) (cdr inits) seed)))))
170 (fold-req req gensyms seed)))))
172 (define (unbound? src sym kt kf)
174 (define unbound-val 9)
175 (define unbound-bits (logior (ash unbound-val 8) tc8-iflag))
176 (let-gensyms (unbound ktest)
178 ($letconst (('unbound unbound (pointer->scm (make-pointer unbound-bits))))
179 ($letk ((ktest src ($kif kt kf)))
181 ($primcall 'eq? (sym unbound))))))))
183 (define (init-default-value name sym subst init body)
184 (match (assq-ref subst sym)
186 (let ((src (tree-il-src init)))
187 (define (maybe-box k make-body)
189 (let-gensyms (kbox phi)
191 ($letk ((kbox src ($kargs (name) (phi)
192 ($continue k ($primcall 'box (phi))))))
195 (let-gensyms (knext kbound kunbound)
197 ($letk ((knext src ($kargs (name) (subst-sym) ,body)))
202 ($letk ((kbound src ($kargs () () ($continue k ($var sym))))
203 (kunbound src ($kargs () () ,(convert init k subst))))
204 ,(unbound? src sym kunbound kbound))))))))))))
206 ;; exp k-name alist -> term
207 (define (convert exp k subst)
208 ;; exp (v-name -> term) -> term
209 (define (convert-arg exp k)
211 (($ <lexical-ref> src name sym)
212 (match (assq-ref subst sym)
214 (let-gensyms (kunboxed unboxed)
216 ($letk ((kunboxed src ($kargs ('unboxed) (unboxed) ,(k unboxed))))
217 ($continue kunboxed ($primcall 'box-ref (box)))))))
218 ((subst #f) (k subst))
221 (let ((src (tree-il-src exp)))
222 (let-gensyms (karg arg)
224 ($letk ((karg src ($kargs ('arg) (arg) ,(k arg))))
225 ,(convert exp karg subst))))))))
226 ;; (exp ...) ((v-name ...) -> term) -> term
227 (define (convert-args exps k)
235 (k (cons name names)))))))))
236 (define (box-bound-var name sym body)
237 (match (assq-ref subst sym)
241 ($letk ((k #f ($kargs (name) (box) ,body)))
242 ($continue k ($primcall 'box (sym)))))))
246 (($ <lexical-ref> src name sym)
247 (match (assq-ref subst sym)
248 ((box #t) (build-cps-term ($continue k ($primcall 'box-ref (box)))))
249 ((subst #f) (build-cps-term ($continue k ($var subst))))
250 (#f (build-cps-term ($continue k ($var sym))))))
253 (build-cps-term ($continue k ($void))))
256 (build-cps-term ($continue k ($const exp))))
258 (($ <primitive-ref> src name)
259 (build-cps-term ($continue k ($prim name))))
261 (($ <lambda> fun-src meta body)
263 (define (convert-clauses body ktail)
266 (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
267 (let* ((arity (make-$arity req (or opt '()) rest
268 (if kw (cdr kw) '()) (and kw (car kw))))
269 (names (fold-formals (lambda (name sym init names)
272 arity gensyms inits)))
274 (let-gensyms (kclause kargs)
281 ($kargs names gensyms
283 (lambda (name sym init body)
285 (init-default-value name sym subst init body)
286 (box-bound-var name sym body)))
287 (convert body ktail subst)
288 arity gensyms inits)))))))
289 (convert-clauses alternate ktail))))))
290 (if (current-topbox-scope)
291 (let-gensyms (kentry self ktail)
296 ($kentry self (ktail #f ($ktail))
297 ,(convert-clauses body ktail)))))))
298 (let-gensyms (scope kscope)
300 ($letk ((kscope fun-src
302 ,(parameterize ((current-topbox-scope scope))
303 (convert exp k subst)))))
304 ,(capture-toplevel-scope fun-src scope kscope)))))))
306 (($ <module-ref> src mod name public?)
308 src mod name public? #t
310 (build-cps-term ($continue k ($primcall 'box-ref (box)))))))
312 (($ <module-set> src mod name public? exp)
316 src mod name public? #f
318 (build-cps-term ($continue k ($primcall 'box-set! (box val)))))))))
320 (($ <toplevel-ref> src name)
324 (build-cps-term ($continue k ($primcall 'box-ref (box)))))))
326 (($ <toplevel-set> src name exp)
332 (build-cps-term ($continue k ($primcall 'box-set! (box val)))))))))
334 (($ <toplevel-define> src name exp)
337 (let-gensyms (kname name-sym)
339 ($letconst (('name name-sym name))
340 ($continue k ($primcall 'define! (name-sym val)))))))))
342 (($ <call> src proc args)
343 (convert-args (cons proc args)
346 (build-cps-term ($continue k ($call proc args)))))))
348 (($ <primcall> src name args)
351 (convert (fold-right (lambda (elem tail)
352 (make-primcall src 'cons
358 (if (branching-primitive? name)
359 (convert (make-conditional src exp (make-const #f #t)
364 (if (eq? name 'values)
365 (build-cps-term ($continue k ($values args)))
366 (build-cps-term ($continue k ($primcall name args))))))))))
368 ;; Prompts with inline handlers.
369 (($ <prompt> src escape-only? tag body
370 ($ <lambda> hsrc hmeta
371 ($ <lambda-case> _ hreq #f hrest #f () hsyms hbody #f)))
373 ;; khargs: check args returned to handler, -> khbody
374 ;; khbody: the handler, -> k
377 ;; krest: collect return vals from body to list, -> kpop
378 ;; kpop: pop the prompt, -> kprim
379 ;; kprim: load the values primitive, -> kret
380 ;; kret: (apply values rvals), -> k
382 ;; Escape prompts evaluate the body with the continuation of krest.
383 ;; Otherwise we do a no-inline call to body, continuing to krest.
386 (let ((hnames (append hreq (if hrest (list hrest) '()))))
387 (let-gensyms (khargs khbody kret kprim prim kpop krest vals kbody)
389 ($letk* ((khbody hsrc ($kargs hnames hsyms
391 (convert hbody k subst)
393 (khargs hsrc ($ktrunc hreq hrest khbody))
395 ($kargs ('rest) (vals)
401 ($kargs ('prim) (prim)
408 ($primcall 'pop-prompt ())))))
409 (krest src ($ktrunc '() 'rest kpop)))
412 ($letk ((kbody (tree-il-src body)
414 ,(convert body krest subst))))
415 ($continue kbody ($prompt #t tag khargs))))
419 ($letk ((kbody (tree-il-src body)
422 ($primcall 'call-thunk/no-inline
425 ($prompt #f tag khargs))))))))))))))
427 ;; Eta-convert prompts without inline handlers.
428 (($ <prompt> src escape-only? tag body handler)
429 (convert-args (list tag body handler)
432 ($continue k ($primcall 'call-with-prompt args))))))
434 (($ <abort> src tag args tail)
435 (convert-args (append (list tag) args (list tail))
437 (build-cps-term ($continue k ($primcall 'abort args*))))))
439 (($ <conditional> src test consequent alternate)
440 (let-gensyms (kif kt kf)
442 ($letk* ((kt (tree-il-src consequent) ($kargs () ()
443 ,(convert consequent k subst)))
444 (kf (tree-il-src alternate) ($kargs () ()
445 ,(convert alternate k subst)))
446 (kif src ($kif kt kf)))
448 (($ <primcall> src (? branching-primitive? name) args)
451 (build-cps-term ($continue kif ($primcall name args))))))
454 (build-cps-term ($continue kif ($var test)))))))))))
456 (($ <lexical-set> src name gensym exp)
459 (match (assq-ref subst gensym)
462 ($continue k ($primcall 'box-set! (box exp)))))))))
464 (($ <seq> src head tail)
465 (let-gensyms (ktrunc kseq)
467 ($letk* ((kseq (tree-il-src tail) ($kargs () ()
468 ,(convert tail k subst)))
469 (ktrunc src ($ktrunc '() #f kseq)))
470 ,(convert head ktrunc subst)))))
472 (($ <let> src names syms vals body)
473 (let lp ((names names) (syms syms) (vals vals))
474 (match (list names syms vals)
475 ((() () ()) (convert body k subst))
476 (((name . names) (sym . syms) (val . vals))
479 ($letk ((klet src ($kargs (name) (sym)
480 ,(box-bound-var name sym
481 (lp names syms vals)))))
482 ,(convert val klet subst))))))))
484 (($ <fix> src names gensyms funs body)
485 ;; Some letrecs can be contified; that happens later.
486 (if (current-topbox-scope)
492 (match (convert fun k subst)
493 (($ $continue _ (and fun ($ $fun)))
496 ,(convert body k subst))))
497 (let-gensyms (scope kscope)
499 ($letk ((kscope src ($kargs () ()
500 ,(parameterize ((current-topbox-scope scope))
501 (convert exp k subst)))))
502 ,(capture-toplevel-scope src scope kscope))))))
504 (($ <let-values> src exp
505 ($ <lambda-case> lsrc req #f rest #f () syms body #f))
506 (let ((names (append req (if rest (list rest) '()))))
507 (let-gensyms (ktrunc kargs)
509 ($letk* ((kargs src ($kargs names syms
511 (convert body k subst)
513 (ktrunc src ($ktrunc req rest kargs)))
514 ,(convert exp ktrunc subst))))))))
516 (define (build-subst exp)
517 "Compute a mapping from lexical gensyms to substituted gensyms. The
518 usual reason to replace one variable by another is assignment
519 conversion. Default argument values is the other reason.
521 Returns a list of (ORIG-SYM SUBST-SYM BOXED?). A true value for BOXED?
522 indicates that the replacement variable is in a box."
523 (define (box-set-vars exp subst)
525 (($ <lexical-set> src name sym exp)
528 (cons (list sym (gensym "b") #t) subst)))
530 (define (default-args exp subst)
532 (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
533 (fold-formals (lambda (name sym init subst)
535 (let ((box? (match (assq-ref subst sym)
538 (subst-sym (gensym (symbol->string name))))
539 (cons (list sym subst-sym box?) subst))
542 (make-$arity req (or opt '()) rest
543 (if kw (cdr kw) '()) (and kw (car kw)))
547 (tree-il-fold box-set-vars default-args '() exp))
549 (define (cps-convert/thunk exp)
550 (let ((src (tree-il-src exp)))
551 (let-gensyms (kinit init ktail kclause kbody)
558 ($kclause ('() '() #f '() #f)
562 (build-subst exp))))))))))))))
564 (define *comp-module* (make-fluid))
566 (define %warning-passes
567 `((unused-variable . ,unused-variable-analysis)
568 (unused-toplevel . ,unused-toplevel-analysis)
569 (unbound-variable . ,unbound-variable-analysis)
570 (arity-mismatch . ,arity-analysis)
571 (format . ,format-analysis)))
573 (define (optimize-tree-il x e opts)
575 (or (and=> (memq #:warnings opts) cadr)
578 ;; Go through the warning passes.
579 (let ((analyses (filter-map (lambda (kind)
580 (assoc-ref %warning-passes kind))
582 (analyze-tree analyses x e))
586 (define (compile-cps exp env opts)
587 (values (cps-convert/thunk (optimize-tree-il exp env opts))
592 ;;; eval: (put 'convert-arg 'scheme-indent-function 1)
593 ;;; eval: (put 'convert-args 'scheme-indent-function 1)