1 ;;; Continuation-passing style (CPS) intermediate language (IL)
3 ;; Copyright (C) 2013, 2014, 2015 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))
78 (define scope-counter (make-parameter #f))
80 (define (fresh-scope-id)
81 (let ((scope-id (scope-counter)))
82 (scope-counter (1+ scope-id))
85 (define (toplevel-box src name bound? val-proc)
86 (let-fresh (kbox) (name-sym bound?-sym box)
88 ($letconst (('name name-sym name)
89 ('bound? bound?-sym bound?))
90 ($letk ((kbox ($kargs ('box) (box) ,(val-proc box))))
91 ,(match (current-topbox-scope)
96 (name-sym bound?-sym)))))
98 (let-fresh () (scope-sym)
100 ($letconst (('scope scope-sym scope-id))
102 ($primcall 'cached-toplevel-box
103 (scope-sym name-sym bound?-sym)))))))))))))
105 (define (module-box src module name public? bound? val-proc)
106 (let-fresh (kbox) (module-sym name-sym public?-sym bound?-sym box)
108 ($letconst (('module module-sym module)
109 ('name name-sym name)
110 ('public? public?-sym public?)
111 ('bound? bound?-sym bound?))
112 ($letk ((kbox ($kargs ('box) (box) ,(val-proc box))))
114 ($primcall 'cached-module-box
115 (module-sym name-sym public?-sym bound?-sym))))))))
117 (define (capture-toplevel-scope src scope-id k)
118 (let-fresh (kmodule) (module scope-sym)
120 ($letconst (('scope scope-sym scope-id))
121 ($letk ((kmodule ($kargs ('module) (module)
123 ($primcall 'cache-current-module!
124 (module scope-sym))))))
125 ($continue kmodule src
126 ($primcall 'current-module ())))))))
128 (define (fold-formals proc seed arity gensyms inits)
130 (($ $arity req opt rest kw allow-other-keys?)
132 (define (fold-req names gensyms seed)
134 (() (fold-opt opt gensyms inits seed))
136 (proc name (car gensyms) #f
137 (fold-req names (cdr gensyms) seed)))))
138 (define (fold-opt names gensyms inits seed)
140 (() (fold-rest rest gensyms inits seed))
142 (proc name (car gensyms) (car inits)
143 (fold-opt names (cdr gensyms) (cdr inits) seed)))))
144 (define (fold-rest rest gensyms inits seed)
146 (#f (fold-kw kw gensyms inits seed))
147 (name (proc name (car gensyms) #f
148 (fold-kw kw (cdr gensyms) inits seed)))))
149 (define (fold-kw kw gensyms inits seed)
152 (unless (null? gensyms)
153 (error "too many gensyms"))
154 (unless (null? inits)
155 (error "too many inits"))
157 (((key name var) . kw)
158 ;; Could be that var is not a gensym any more.
160 (unless (eq? var (car gensyms))
161 (error "unexpected keyword arg order")))
162 (proc name (car gensyms) (car inits)
163 (fold-kw kw (cdr gensyms) (cdr inits) seed)))))
164 (fold-req req gensyms seed)))))
166 (define (unbound? src var kt kf)
168 (define unbound-val 9)
169 (define unbound-bits (logior (ash unbound-val 8) tc8-iflag))
170 (let-fresh () (unbound)
172 ($letconst (('unbound unbound
173 (pointer->scm (make-pointer unbound-bits))))
175 ($branch kt ($primcall 'eq? (var unbound))))))))
177 (define (init-default-value name sym subst init body)
178 (match (hashq-ref subst sym)
179 ((orig-var subst-var box?)
180 (let ((src (tree-il-src init)))
181 (define (maybe-box k make-body)
183 (let-fresh (kbox) (phi)
185 ($letk ((kbox ($kargs (name) (phi)
186 ($continue k src ($primcall 'box (phi))))))
189 (let-fresh (knext kbound kunbound kreceive krest) (val rest)
191 ($letk ((knext ($kargs (name) (subst-var) ,body)))
196 ($letk ((kbound ($kargs () () ($continue k src
197 ($values (orig-var)))))
198 (krest ($kargs (name 'rest) (val rest)
199 ($continue k src ($values (val)))))
200 (kreceive ($kreceive (list name) 'rest krest))
201 (kunbound ($kargs () ()
202 ,(convert init kreceive subst))))
203 ,(unbound? src orig-var kunbound kbound))))))))))))
205 ;; exp k-name alist -> term
206 (define (convert exp k subst)
207 ;; exp (v-name -> term) -> term
208 (define (convert-arg exp k)
210 (($ <lexical-ref> src name sym)
211 (match (hashq-ref subst sym)
213 (let-fresh (kunboxed) (unboxed)
215 ($letk ((kunboxed ($kargs ('unboxed) (unboxed) ,(k unboxed))))
216 ($continue kunboxed src ($primcall 'box-ref (box)))))))
217 ((orig-var subst-var #f) (k subst-var))
220 (let-fresh (kreceive karg) (arg rest)
222 ($letk ((karg ($kargs ('arg 'rest) (arg rest) ,(k arg)))
223 (kreceive ($kreceive '(arg) 'rest karg)))
224 ,(convert exp kreceive subst)))))))
225 ;; (exp ...) ((v-name ...) -> term) -> term
226 (define (convert-args exps k)
234 (k (cons name names)))))))))
235 (define (box-bound-var name sym body)
236 (match (hashq-ref subst sym)
237 ((orig-var subst-var #t)
240 ($letk ((k ($kargs (name) (subst-var) ,body)))
241 ($continue k #f ($primcall 'box (orig-var)))))))
243 (define (bound-var sym)
244 (match (hashq-ref subst sym)
246 ((? exact-integer? var) var)))
249 (($ <lexical-ref> src name sym)
250 (rewrite-cps-term (hashq-ref subst sym)
251 ((orig-var box #t) ($continue k src ($primcall 'box-ref (box))))
252 ((orig-var subst-var #f) ($continue k src ($values (subst-var))))
253 (var ($continue k src ($values (var))))))
256 (build-cps-term ($continue k src ($const *unspecified*))))
259 (build-cps-term ($continue k src ($const exp))))
261 (($ <primitive-ref> src name)
262 (build-cps-term ($continue k src ($prim name))))
264 (($ <lambda> fun-src meta body)
266 (define (convert-clauses body ktail)
269 (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
270 (let* ((arity (make-$arity req (or opt '()) rest
273 (list kw name (bound-var sym))))
274 (if kw (cdr kw) '()))
276 (names (fold-formals (lambda (name sym init names)
279 arity gensyms inits)))
280 (let ((bound-vars (map bound-var gensyms)))
281 (let-fresh (kclause kargs) ()
286 ($kargs names bound-vars
288 (lambda (name sym init body)
290 (init-default-value name sym subst init body)
291 (box-bound-var name sym body)))
292 (convert body ktail subst)
293 arity gensyms inits)))
294 ,(convert-clauses alternate ktail))))))))))
295 (if (current-topbox-scope)
296 (let-fresh (kfun ktail) (self)
300 (kfun ($kfun fun-src meta self (ktail ($ktail))
301 ,(convert-clauses body ktail)))))))
302 (let ((scope-id (fresh-scope-id)))
303 (let-fresh (kscope) ()
307 ,(parameterize ((current-topbox-scope scope-id))
308 (convert exp k subst)))))
309 ,(capture-toplevel-scope fun-src scope-id kscope))))))))
311 (($ <module-ref> src mod name public?)
313 src mod name public? #t
315 (build-cps-term ($continue k src ($primcall 'box-ref (box)))))))
317 (($ <module-set> src mod name public? exp)
321 src mod name public? #f
324 ($continue k src ($primcall 'box-set! (box val)))))))))
326 (($ <toplevel-ref> src name)
330 (build-cps-term ($continue k src ($primcall 'box-ref (box)))))))
332 (($ <toplevel-set> src name exp)
339 ($continue k src ($primcall 'box-set! (box val)))))))))
341 (($ <toplevel-define> src name exp)
344 (let-fresh (kname) (name-sym)
346 ($letconst (('name name-sym name))
347 ($continue k src ($primcall 'define! (name-sym val)))))))))
349 (($ <call> src proc args)
350 (convert-args (cons proc args)
353 (build-cps-term ($continue k src ($call proc args)))))))
355 (($ <primcall> src name args)
357 ((branching-primitive? name)
360 (let-fresh (kt kf) ()
362 ($letk ((kt ($kargs () () ($continue k src ($const #t))))
363 (kf ($kargs () () ($continue k src ($const #f)))))
365 ($branch kt ($primcall name args)))))))))
366 ((and (eq? name 'not) (match args ((_) #t) (_ #f)))
369 (let-fresh (kt kf) ()
371 ($letk ((kt ($kargs () () ($continue k src ($const #f))))
372 (kf ($kargs () () ($continue k src ($const #t)))))
374 ($branch kt ($values args)))))))))
375 ((and (eq? name 'list)
376 (and-map (match-lambda
380 ($ <lexical-ref>)) #t)
383 ;; See note below in `canonicalize' about `vector'. The same
384 ;; thing applies to `list'.
385 (let lp ((args args) (k k))
389 ($continue k src ($const '()))))
391 (let-fresh (ktail) (tail)
393 ($letk ((ktail ($kargs ('tail) (tail)
398 ($primcall 'cons (head tail)))))))))
399 ,(lp args ktail))))))))
403 (build-cps-term ($continue k src ($primcall name args))))))))
405 ;; Prompts with inline handlers.
406 (($ <prompt> src escape-only? tag body
407 ($ <lambda> hsrc hmeta
408 ($ <lambda-case> _ hreq #f hrest #f () hsyms hbody #f)))
410 ;; khargs: check args returned to handler, -> khbody
411 ;; khbody: the handler, -> k
414 ;; krest: collect return vals from body to list, -> kpop
415 ;; kpop: pop the prompt, -> kprim
416 ;; kprim: load the values primitive, -> kret
417 ;; kret: (apply values rvals), -> k
419 ;; Escape prompts evaluate the body with the continuation of krest.
420 ;; Otherwise we do a no-inline call to body, continuing to krest.
423 (let ((hnames (append hreq (if hrest (list hrest) '())))
424 (bound-vars (map bound-var hsyms)))
425 (let-fresh (khargs khbody kret kprim kpop krest kbody) (prim vals)
427 ;; FIXME: Attach hsrc to $kreceive.
428 ($letk* ((khbody ($kargs hnames bound-vars
430 (convert hbody k subst)
432 (khargs ($kreceive hreq hrest khbody))
433 (kpop ($kargs ('rest) (vals)
437 ($kargs ('prim) (prim)
444 ($primcall 'unwind ())))))
445 (krest ($kreceive '() 'rest kpop)))
448 ($letk ((kbody ($kargs () ()
449 ,(convert body krest subst))))
450 ($continue kbody src ($prompt #t tag khargs))))
454 ($letk ((kbody ($kargs () ()
455 ($continue krest (tree-il-src body)
456 ($primcall 'call-thunk/no-inline
458 ($continue kbody (tree-il-src body)
459 ($prompt #f tag khargs))))))))))))))
461 (($ <abort> src tag args ($ <const> _ ()))
462 (convert-args (cons tag args)
466 ($primcall 'abort-to-prompt args*))))))
468 (($ <abort> src tag args tail)
469 (convert-args (append (list (make-primitive-ref #f 'abort-to-prompt)
475 ($continue k src ($primcall 'apply args*))))))
477 (($ <conditional> src test consequent alternate)
478 (let-fresh (kt kf) ()
480 ($letk* ((kt ($kargs () () ,(convert consequent k subst)))
481 (kf ($kargs () () ,(convert alternate k subst))))
483 (($ <primcall> src (? branching-primitive? name) args)
488 ($branch kt ($primcall name args)))))))
493 ($branch kt ($values (test)))))))))))))
495 (($ <lexical-set> src name gensym exp)
498 (match (hashq-ref subst gensym)
501 ($continue k src ($primcall 'box-set! (box exp)))))))))
503 (($ <seq> src head tail)
504 (let-fresh (kreceive kseq) (vals)
506 ($letk* ((kseq ($kargs ('vals) (vals)
507 ,(convert tail k subst)))
508 (kreceive ($kreceive '() 'vals kseq)))
509 ,(convert head kreceive subst)))))
511 (($ <let> src names syms vals body)
512 (let lp ((names names) (syms syms) (vals vals))
513 (match (list names syms vals)
514 ((() () ()) (convert body k subst))
515 (((name . names) (sym . syms) (val . vals))
516 (let-fresh (kreceive klet) (rest)
518 ($letk* ((klet ($kargs (name 'rest) ((bound-var sym) rest)
519 ,(box-bound-var name sym
520 (lp names syms vals))))
521 (kreceive ($kreceive (list name) 'rest klet)))
522 ,(convert val kreceive subst))))))))
524 (($ <fix> src names gensyms funs body)
525 ;; Some letrecs can be contified; that happens later.
526 (if (current-topbox-scope)
527 (let ((vars (map bound-var gensyms)))
530 ($letk ((krec ($kargs names vars
531 ,(convert body k subst))))
535 (match (convert fun k subst)
536 (($ $continue _ _ (and fun ($ $fun)))
539 (let ((scope-id (fresh-scope-id)))
540 (let-fresh (kscope) ()
544 ,(parameterize ((current-topbox-scope scope-id))
545 (convert exp k subst)))))
546 ,(capture-toplevel-scope src scope-id kscope)))))))
548 (($ <let-values> src exp
549 ($ <lambda-case> lsrc req #f rest #f () syms body #f))
550 (let ((names (append req (if rest (list rest) '())))
551 (bound-vars (map bound-var syms)))
552 (let-fresh (kreceive kargs) ()
554 ($letk* ((kargs ($kargs names bound-vars
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 CPS variable indexes. CPS
563 uses small integers to identify variables, instead of gensyms.
565 This subst table serves an additional purpose of mapping variables to
566 replacements. The usual reason to replace one variable by another is
567 assignment conversion. Default argument values is the other reason.
569 The result is a hash table mapping symbols to substitutions (in the case
570 that a variable is substituted) or to indexes. A substitution is a list
573 (ORIG-INDEX SUBST-INDEX BOXED?)
575 A true value for BOXED? indicates that the replacement variable is in a
576 box. If a variable is not substituted, the mapped value is a small
578 (let ((table (make-hash-table)))
581 (($ <lexical-set> src name sym exp)
582 (match (hashq-ref table sym)
584 ((orig subst #f) (hashq-set! table sym (list orig subst #t)))
585 ((? number? idx) (hashq-set! table sym (list idx (fresh-var) #t)))))
586 (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
587 (fold-formals (lambda (name sym init seed)
588 (hashq-set! table sym
590 (list (fresh-var) (fresh-var) #f)
593 (make-$arity req (or opt '()) rest
594 (if kw (cdr kw) '()) (and kw (car kw)))
597 (($ <let> src names gensyms vals body)
598 (for-each (lambda (sym)
599 (hashq-set! table sym (fresh-var)))
601 (($ <fix> src names gensyms vals body)
602 (for-each (lambda (sym)
603 (hashq-set! table sym (fresh-var)))
607 (define (up exp) (values))
608 ((make-tree-il-folder) exp down up)
611 (define (cps-convert/thunk exp)
612 (parameterize ((label-counter 0)
615 (let ((src (tree-il-src exp)))
616 (let-fresh (kinit ktail kclause kbody) (init)
618 (kinit ($kfun src '() init (ktail ($ktail))
620 ($kclause ('() '() #f '() #f)
626 (define *comp-module* (make-fluid))
628 (define %warning-passes
629 `((unused-variable . ,unused-variable-analysis)
630 (unused-toplevel . ,unused-toplevel-analysis)
631 (unbound-variable . ,unbound-variable-analysis)
632 (arity-mismatch . ,arity-analysis)
633 (format . ,format-analysis)))
635 (define (optimize-tree-il x e opts)
637 (or (and=> (memq #:warnings opts) cadr)
640 ;; Go through the warning passes.
641 (let ((analyses (filter-map (lambda (kind)
642 (assoc-ref %warning-passes kind))
644 (analyze-tree analyses x e))
648 (define (canonicalize exp)
652 (($ <primcall> src 'vector
654 ((or ($ <const>) ($ <void>) ($ <lambda>) ($ <lexical-ref>))
656 ;; Some macros generate calls to "vector" with like 300
657 ;; arguments. Since we eventually compile to make-vector and
658 ;; vector-set!, it reduces live variable pressure to allocate the
659 ;; vector first, then set values as they are produced, if we can
660 ;; prove that no value can capture the continuation. (More on
662 ;; http://wingolog.org/archives/2013/11/02/scheme-quiz-time).
664 ;; Normally we would do this transformation in the compiler, but
665 ;; it's quite tricky there and quite easy here, so hold your nose
666 ;; while we drop some smelly code.
667 (let ((len (length args))
672 (list (make-primcall src 'make-vector
673 (list (make-const #f len)
674 (make-const #f #f))))
675 (fold (lambda (arg n tail)
680 (list (make-lexical-ref src 'v v)
684 (make-lexical-ref src 'v v)
685 (reverse args) (reverse (iota len))))))
687 (($ <primcall> src 'struct-set! (struct index value))
688 ;; Unhappily, and undocumentedly, struct-set! returns the value
689 ;; that was set. There is code that relies on this. Hackety
691 (let ((v (gensym "v ")))
697 (make-primcall src 'struct-set!
700 (make-lexical-ref src 'v v)))
701 (make-lexical-ref src 'v v)))))
703 (($ <prompt> src escape-only? tag body
704 ($ <lambda> hsrc hmeta
705 ($ <lambda-case> _ hreq #f hrest #f () hsyms hbody #f)))
708 ;; Eta-convert prompts without inline handlers.
709 (($ <prompt> src escape-only? tag body handler)
710 (let ((h (gensym "h "))
711 (args (gensym "args ")))
713 src (list 'h) (list h) (list handler)
718 (make-primcall src 'procedure? (list (make-lexical-ref #f 'h h)))
723 (make-const #f 'wrong-type-arg)
724 (make-const #f "call-with-prompt")
725 (make-const #f "Wrong type (expecting procedure): ~S")
726 (make-primcall #f 'list (list (make-lexical-ref #f 'h h)))
727 (make-primcall #f 'list (list (make-lexical-ref #f 'h h))))))
729 src escape-only? tag body
733 src '() #f 'args #f '() (list args)
736 (list (make-lexical-ref #f 'h h)
737 (make-lexical-ref #f 'args args)))
742 (define (compile-cps exp env opts)
743 (values (cps-convert/thunk
744 (canonicalize (optimize-tree-il exp env opts)))
749 ;;; eval: (put 'convert-arg 'scheme-indent-function 1)
750 ;;; eval: (put 'convert-args 'scheme-indent-function 1)