1 ;;; Continuation-passing style (CPS) intermediate language (IL)
3 ;; Copyright (C) 2013, 2014 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)
62 #:export (compile-cps))
64 ;;; Guile's semantics are that a toplevel lambda captures a reference on
65 ;;; the current module, and that all contained lambdas use that module
66 ;;; to resolve toplevel variables. This parameter tracks whether or not
67 ;;; we are in a toplevel lambda. If we are in a lambda, the parameter
68 ;;; is bound to a fresh name identifying the module that was current
69 ;;; when the toplevel lambda is defined.
71 ;;; This is more complicated than it need be. Ideally we should resolve
72 ;;; all toplevel bindings to bindings from specific modules, unless the
73 ;;; binding is unbound. This is always valid if the compilation unit
74 ;;; sets the module explicitly, as when compiling a module, but it
75 ;;; doesn't work for files auto-compiled for use with `load'.
77 (define current-topbox-scope (make-parameter #f))
79 (define (toplevel-box src name bound? val-proc)
80 (let-fresh (kbox) (name-sym bound?-sym box)
82 ($letconst (('name name-sym name)
83 ('bound? bound?-sym bound?))
84 ($letk ((kbox ($kargs ('box) (box) ,(val-proc box))))
85 ,(match (current-topbox-scope)
90 (name-sym bound?-sym)))))
92 (let-fresh () (scope-sym)
94 ($letconst (('scope scope-sym scope))
96 ($primcall 'cached-toplevel-box
97 (scope-sym name-sym bound?-sym)))))))))))))
99 (define (module-box src module name public? bound? val-proc)
100 (let-fresh (kbox) (module-sym name-sym public?-sym bound?-sym box)
102 ($letconst (('module module-sym module)
103 ('name name-sym name)
104 ('public? public?-sym public?)
105 ('bound? bound?-sym bound?))
106 ($letk ((kbox ($kargs ('box) (box) ,(val-proc box))))
108 ($primcall 'cached-module-box
109 (module-sym name-sym public?-sym bound?-sym))))))))
111 (define (capture-toplevel-scope src scope k)
112 (let-fresh (kmodule) (module scope-sym)
114 ($letconst (('scope scope-sym scope))
115 ($letk ((kmodule ($kargs ('module) (module)
117 ($primcall 'cache-current-module!
118 (module scope-sym))))))
119 ($continue kmodule src
120 ($primcall 'current-module ())))))))
122 (define (fold-formals proc seed arity gensyms inits)
124 (($ $arity req opt rest kw allow-other-keys?)
126 (define (fold-req names gensyms seed)
128 (() (fold-opt opt gensyms inits seed))
130 (proc name (car gensyms) #f
131 (fold-req names (cdr gensyms) seed)))))
132 (define (fold-opt names gensyms inits seed)
134 (() (fold-rest rest gensyms inits seed))
136 (proc name (car gensyms) (car inits)
137 (fold-opt names (cdr gensyms) (cdr inits) seed)))))
138 (define (fold-rest rest gensyms inits seed)
140 (#f (fold-kw kw gensyms inits seed))
141 (name (proc name (car gensyms) #f
142 (fold-kw kw (cdr gensyms) inits seed)))))
143 (define (fold-kw kw gensyms inits seed)
146 (unless (null? gensyms)
147 (error "too many gensyms"))
148 (unless (null? inits)
149 (error "too many inits"))
151 (((key name var) . kw)
152 (unless (eq? var (car gensyms))
153 (error "unexpected keyword arg order"))
154 (proc name var (car inits)
155 (fold-kw kw (cdr gensyms) (cdr inits) seed)))))
156 (fold-req req gensyms seed)))))
158 (define (unbound? src sym kt kf)
160 (define unbound-val 9)
161 (define unbound-bits (logior (ash unbound-val 8) tc8-iflag))
162 (let-fresh (ktest) (unbound)
164 ($letconst (('unbound unbound
165 (pointer->scm (make-pointer unbound-bits))))
166 ($letk ((ktest ($kif kt kf)))
168 ($primcall 'eq? (sym unbound))))))))
170 (define (init-default-value name sym subst init body)
171 (match (assq-ref subst sym)
173 (let ((src (tree-il-src init)))
174 (define (maybe-box k make-body)
176 (let-fresh (kbox) (phi)
178 ($letk ((kbox ($kargs (name) (phi)
179 ($continue k src ($primcall 'box (phi))))))
182 (let-fresh (knext kbound kunbound kreceive krest) (val rest)
184 ($letk ((knext ($kargs (name) (subst-sym) ,body)))
189 ($letk ((kbound ($kargs () () ($continue k src
191 (krest ($kargs (name 'rest) (val rest)
192 ($continue k src ($values (val)))))
193 (kreceive ($kreceive (list name) 'rest krest))
194 (kunbound ($kargs () ()
195 ,(convert init kreceive subst))))
196 ,(unbound? src sym kunbound kbound))))))))))))
198 ;; exp k-name alist -> term
199 (define (convert exp k subst)
200 ;; exp (v-name -> term) -> term
201 (define (convert-arg exp k)
203 (($ <lexical-ref> src name sym)
204 (match (assq-ref subst sym)
206 (let-fresh (kunboxed) (unboxed)
208 ($letk ((kunboxed ($kargs ('unboxed) (unboxed) ,(k unboxed))))
209 ($continue kunboxed src ($primcall 'box-ref (box)))))))
210 ((subst #f) (k subst))
213 (let-fresh (kreceive karg) (arg rest)
215 ($letk ((karg ($kargs ('arg 'rest) (arg rest) ,(k arg)))
216 (kreceive ($kreceive '(arg) 'rest karg)))
217 ,(convert exp kreceive subst)))))))
218 ;; (exp ...) ((v-name ...) -> term) -> term
219 (define (convert-args exps k)
227 (k (cons name names)))))))))
228 (define (box-bound-var name sym body)
229 (match (assq-ref subst sym)
233 ($letk ((k ($kargs (name) (box) ,body)))
234 ($continue k #f ($primcall 'box (sym)))))))
238 (($ <lexical-ref> src name sym)
239 (match (assq-ref subst sym)
240 ((box #t) (build-cps-term ($continue k src ($primcall 'box-ref (box)))))
241 ((subst #f) (build-cps-term ($continue k src ($values (subst)))))
242 (#f (build-cps-term ($continue k src ($values (sym)))))))
245 (build-cps-term ($continue k src ($void))))
248 (build-cps-term ($continue k src ($const exp))))
250 (($ <primitive-ref> src name)
251 (build-cps-term ($continue k src ($prim name))))
253 (($ <lambda> fun-src meta body)
255 (define (convert-clauses body ktail)
258 (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
259 (let* ((arity (make-$arity req (or opt '()) rest
260 (if kw (cdr kw) '()) (and kw (car kw))))
261 (names (fold-formals (lambda (name sym init names)
264 arity gensyms inits)))
266 (let-fresh (kclause kargs) ()
271 ($kargs names gensyms
273 (lambda (name sym init body)
275 (init-default-value name sym subst init body)
276 (box-bound-var name sym body)))
277 (convert body ktail subst)
278 arity gensyms inits)))))))
279 (convert-clauses alternate ktail))))))
280 (if (current-topbox-scope)
281 (let-fresh (kentry ktail) (self)
284 ($fun fun-src meta '()
285 (kentry ($kentry self (ktail ($ktail))
286 ,(convert-clauses body ktail)))))))
287 (let-fresh (kscope) (scope)
289 ($letk ((kscope ($kargs () ()
290 ,(parameterize ((current-topbox-scope scope))
291 (convert exp k subst)))))
292 ,(capture-toplevel-scope fun-src scope kscope)))))))
294 (($ <module-ref> src mod name public?)
296 src mod name public? #t
298 (build-cps-term ($continue k src ($primcall 'box-ref (box)))))))
300 (($ <module-set> src mod name public? exp)
304 src mod name public? #f
307 ($continue k src ($primcall 'box-set! (box val)))))))))
309 (($ <toplevel-ref> src name)
313 (build-cps-term ($continue k src ($primcall 'box-ref (box)))))))
315 (($ <toplevel-set> src name exp)
322 ($continue k src ($primcall 'box-set! (box val)))))))))
324 (($ <toplevel-define> src name exp)
327 (let-fresh (kname) (name-sym)
329 ($letconst (('name name-sym name))
330 ($continue k src ($primcall 'define! (name-sym val)))))))))
332 (($ <call> src proc args)
333 (convert-args (cons proc args)
336 (build-cps-term ($continue k src ($call proc args)))))))
338 (($ <primcall> src name args)
340 ((branching-primitive? name)
341 (convert (make-conditional src exp (make-const #f #t)
344 ((and (eq? name 'vector)
345 (and-map (match-lambda
349 ($ <lexical-ref>)) #t)
352 ;; Some macros generate calls to "vector" with like 300
353 ;; arguments. Since we eventually compile to make-vector and
354 ;; vector-set!, it reduces live variable pressure to allocate the
355 ;; vector first, then set values as they are produced, if we can
356 ;; prove that no value can capture the continuation. (More on
358 ;; http://wingolog.org/archives/2013/11/02/scheme-quiz-time).
360 ;; Normally we would do this transformation in the compiler, but
361 ;; it's quite tricky there and quite easy here, so hold your nose
362 ;; while we drop some smelly code.
363 (convert (let ((len (length args)))
368 (list (make-primcall src 'make-vector
369 (list (make-const #f len)
370 (make-const #f #f))))
371 (fold (lambda (arg n tail)
376 (list (make-lexical-ref src 'v v)
380 (make-lexical-ref src 'v v)
381 (reverse args) (reverse (iota len))))))
383 ((and (eq? name 'list)
384 (and-map (match-lambda
388 ($ <lexical-ref>)) #t)
391 ;; The same situation occurs with "list".
392 (let lp ((args args) (k k))
396 ($continue k src ($const '()))))
398 (let-fresh (ktail) (tail)
400 ($letk ((ktail ($kargs ('tail) (tail)
405 ($primcall 'cons (head tail)))))))))
406 ,(lp args ktail))))))))
410 (build-cps-term ($continue k src ($primcall name args))))))))
412 ;; Prompts with inline handlers.
413 (($ <prompt> src escape-only? tag body
414 ($ <lambda> hsrc hmeta
415 ($ <lambda-case> _ hreq #f hrest #f () hsyms hbody #f)))
417 ;; khargs: check args returned to handler, -> khbody
418 ;; khbody: the handler, -> k
421 ;; krest: collect return vals from body to list, -> kpop
422 ;; kpop: pop the prompt, -> kprim
423 ;; kprim: load the values primitive, -> kret
424 ;; kret: (apply values rvals), -> k
426 ;; Escape prompts evaluate the body with the continuation of krest.
427 ;; Otherwise we do a no-inline call to body, continuing to krest.
430 (let ((hnames (append hreq (if hrest (list hrest) '()))))
431 (let-fresh (khargs khbody kret kprim kpop krest kbody) (prim vals)
433 ;; FIXME: Attach hsrc to $kreceive.
434 ($letk* ((khbody ($kargs hnames hsyms
436 (convert hbody k subst)
438 (khargs ($kreceive hreq hrest khbody))
439 (kpop ($kargs ('rest) (vals)
443 ($kargs ('prim) (prim)
450 ($primcall 'unwind ())))))
451 (krest ($kreceive '() 'rest kpop)))
454 ($letk ((kbody ($kargs () ()
455 ,(convert body krest subst))))
456 ($continue kbody src ($prompt #t tag khargs))))
460 ($letk ((kbody ($kargs () ()
461 ($continue krest (tree-il-src body)
462 ($primcall 'call-thunk/no-inline
464 ($continue kbody (tree-il-src body)
465 ($prompt #f tag khargs))))))))))))))
467 (($ <abort> src tag args ($ <const> _ ()))
468 (convert-args (cons tag args)
472 ($primcall 'abort-to-prompt args*))))))
474 (($ <abort> src tag args tail)
475 (convert-args (append (list (make-primitive-ref #f 'abort-to-prompt)
481 ($continue k src ($primcall 'apply args*))))))
483 (($ <conditional> src test consequent alternate)
484 (let-fresh (kif kt kf) ()
486 ($letk* ((kt ($kargs () () ,(convert consequent k subst)))
487 (kf ($kargs () () ,(convert alternate k subst)))
490 (($ <primcall> src (? branching-primitive? name) args)
494 ($continue kif src ($primcall name args))))))
498 ($continue kif src ($values (test))))))))))))
500 (($ <lexical-set> src name gensym exp)
503 (match (assq-ref subst gensym)
506 ($continue k src ($primcall 'box-set! (box exp)))))))))
508 (($ <seq> src head tail)
509 (let-fresh (kreceive kseq) (vals)
511 ($letk* ((kseq ($kargs ('vals) (vals)
512 ,(convert tail k subst)))
513 (kreceive ($kreceive '() 'vals kseq)))
514 ,(convert head kreceive subst)))))
516 (($ <let> src names syms vals body)
517 (let lp ((names names) (syms syms) (vals vals))
518 (match (list names syms vals)
519 ((() () ()) (convert body k subst))
520 (((name . names) (sym . syms) (val . vals))
521 (let-fresh (kreceive klet) (rest)
523 ($letk* ((klet ($kargs (name 'rest) (sym rest)
524 ,(box-bound-var name sym
525 (lp names syms vals))))
526 (kreceive ($kreceive (list name) 'rest klet)))
527 ,(convert val kreceive subst))))))))
529 (($ <fix> src names gensyms funs body)
530 ;; Some letrecs can be contified; that happens later.
531 (if (current-topbox-scope)
537 (match (convert fun k subst)
538 (($ $continue _ _ (and fun ($ $fun)))
541 ,(convert body k subst))))
542 (let-fresh (kscope) (scope)
544 ($letk ((kscope ($kargs () ()
545 ,(parameterize ((current-topbox-scope scope))
546 (convert exp k subst)))))
547 ,(capture-toplevel-scope src scope kscope))))))
549 (($ <let-values> src exp
550 ($ <lambda-case> lsrc req #f rest #f () syms body #f))
551 (let ((names (append req (if rest (list rest) '()))))
552 (let-fresh (kreceive kargs) ()
554 ($letk* ((kargs ($kargs names syms
556 (convert body k subst)
558 (kreceive ($kreceive req rest kargs)))
559 ,(convert exp kreceive subst))))))))
561 (define (build-subst exp)
562 "Compute a mapping from lexical gensyms to substituted gensyms. The
563 usual reason to replace one variable by another is assignment
564 conversion. Default argument values is the other reason.
566 Returns a list of (ORIG-SYM SUBST-SYM BOXED?). A true value for BOXED?
567 indicates that the replacement variable is in a box."
568 (define (box-set-vars exp subst)
570 (($ <lexical-set> src name sym exp)
573 (cons (list sym (gensym "b") #t) subst)))
575 (define (default-args exp subst)
577 (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
578 (fold-formals (lambda (name sym init subst)
580 (let ((box? (match (assq-ref subst sym)
583 (subst-sym (gensym (symbol->string name))))
584 (cons (list sym subst-sym box?) subst))
587 (make-$arity req (or opt '()) rest
588 (if kw (cdr kw) '()) (and kw (car kw)))
592 (tree-il-fold box-set-vars default-args '() exp))
594 (define (cps-convert/thunk exp)
595 (parameterize ((label-counter 0)
597 (let ((src (tree-il-src exp)))
598 (let-fresh (kinit ktail kclause kbody) (init)
604 ($kclause ('() '() #f '() #f)
607 (build-subst exp)))))))))))))))
609 (define *comp-module* (make-fluid))
611 (define %warning-passes
612 `((unused-variable . ,unused-variable-analysis)
613 (unused-toplevel . ,unused-toplevel-analysis)
614 (unbound-variable . ,unbound-variable-analysis)
615 (arity-mismatch . ,arity-analysis)
616 (format . ,format-analysis)))
618 (define (optimize-tree-il x e opts)
620 (or (and=> (memq #:warnings opts) cadr)
623 ;; Go through the warning passes.
624 (let ((analyses (filter-map (lambda (kind)
625 (assoc-ref %warning-passes kind))
627 (analyze-tree analyses x e))
631 (define (fix-prompts exp)
635 (($ <prompt> src escape-only? tag body
636 ($ <lambda> hsrc hmeta
637 ($ <lambda-case> _ hreq #f hrest #f () hsyms hbody #f)))
640 ;; Eta-convert prompts without inline handlers.
641 (($ <prompt> src escape-only? tag body handler)
642 (let ((h (gensym "h "))
643 (args (gensym "args ")))
645 src (list 'h) (list h) (list handler)
650 (make-primcall src 'procedure? (list (make-lexical-ref #f 'h h)))
655 (make-const #f 'wrong-type-arg)
656 (make-const #f "call-with-prompt")
657 (make-const #f "Wrong type (expecting procedure): ~S")
658 (make-primcall #f 'list (list (make-lexical-ref #f 'h h)))
659 (make-primcall #f 'list (list (make-lexical-ref #f 'h h))))))
661 src escape-only? tag body
665 src '() #f 'args #f '() (list args)
668 (list (make-lexical-ref #f 'h h)
669 (make-lexical-ref #f 'args args)))
674 (define (compile-cps exp env opts)
675 (values (cps-convert/thunk
676 (fix-prompts (optimize-tree-il exp env opts)))
681 ;;; eval: (put 'convert-arg 'scheme-indent-function 1)
682 ;;; eval: (put 'convert-args 'scheme-indent-function 1)