* [PATCH 1/9] Add CPS language
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 20:48 ` Ludovic Courtès
2013-08-29 7:49 ` [PATCH 2/9] (compile foo #:to 'cps) Andy Wingo
` (9 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am:
* module/language/cps.scm:
* module/language/cps/verify.scm: Add CPS language.
* .dir-locals.el: Add indentation rules for some CPS forms.
---
.dir-locals.el | 27 ++-
module/Makefile.am | 5 +
module/language/cps.scm | 469 +++++++++++++++++++++++++++++++++++++++++
module/language/cps/verify.scm | 165 +++++++++++++++
4 files changed, 660 insertions(+), 6 deletions(-)
create mode 100644 module/language/cps.scm
create mode 100644 module/language/cps/verify.scm
diff --git a/.dir-locals.el b/.dir-locals.el
index a24e860..94a2126 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -5,12 +5,27 @@
(c-mode . ((c-file-style . "gnu")))
(scheme-mode
. ((indent-tabs-mode . nil)
- (eval . (put 'pass-if 'scheme-indent-function 1))
- (eval . (put 'pass-if-exception 'scheme-indent-function 2))
- (eval . (put 'pass-if-equal 'scheme-indent-function 2))
- (eval . (put 'with-test-prefix 'scheme-indent-function 1))
- (eval . (put 'with-code-coverage 'scheme-indent-function 1))
- (eval . (put 'with-statprof 'scheme-indent-function 1))))
+ (eval . (put 'pass-if 'scheme-indent-function 1))
+ (eval . (put 'pass-if-exception 'scheme-indent-function 2))
+ (eval . (put 'pass-if-equal 'scheme-indent-function 2))
+ (eval . (put 'with-test-prefix 'scheme-indent-function 1))
+ (eval . (put 'with-code-coverage 'scheme-indent-function 1))
+ (eval . (put 'with-statprof 'scheme-indent-function 1))
+ (eval . (put 'let-gensyms 'scheme-indent-function 1))
+ (eval . (put 'build-cps-term 'scheme-indent-function 0))
+ (eval . (put 'build-cps-exp 'scheme-indent-function 0))
+ (eval . (put 'build-cps-cont 'scheme-indent-function 0))
+ (eval . (put 'rewrite-cps-term 'scheme-indent-function 1))
+ (eval . (put 'rewrite-cps-cont 'scheme-indent-function 1))
+ (eval . (put 'rewrite-cps-exp 'scheme-indent-function 1))
+ (eval . (put '$letk 'scheme-indent-function 1))
+ (eval . (put '$letk* 'scheme-indent-function 1))
+ (eval . (put '$letconst 'scheme-indent-function 1))
+ (eval . (put '$continue 'scheme-indent-function 1))
+ (eval . (put '$kargs 'scheme-indent-function 2))
+ (eval . (put '$kentry 'scheme-indent-function 2))
+ (eval . (put '$kclause 'scheme-indent-function 1))
+ (eval . (put '$fun 'scheme-indent-function 2))))
(emacs-lisp-mode . ((indent-tabs-mode . nil)))
(texinfo-mode . ((indent-tabs-mode . nil)
(fill-column . 72))))
diff --git a/module/Makefile.am b/module/Makefile.am
index dc7d058..1f66ac4 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -53,6 +53,7 @@ SOURCES = \
language/glil.scm \
language/assembly.scm \
$(TREE_IL_LANG_SOURCES) \
+ $(CPS_LANG_SOURCES) \
$(GLIL_LANG_SOURCES) \
$(ASSEMBLY_LANG_SOURCES) \
$(BYTECODE_LANG_SOURCES) \
@@ -115,6 +116,10 @@ TREE_IL_LANG_SOURCES = \
language/tree-il/debug.scm \
language/tree-il/spec.scm
+CPS_LANG_SOURCES = \
+ language/cps.scm \
+ language/cps/verify.scm
+
GLIL_LANG_SOURCES = \
language/glil/spec.scm language/glil/compile-assembly.scm
diff --git a/module/language/cps.scm b/module/language/cps.scm
new file mode 100644
index 0000000..ac5642a
--- /dev/null
+++ b/module/language/cps.scm
@@ -0,0 +1,469 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; This is the continuation-passing style (CPS) intermediate language
+;;; (IL) for Guile.
+;;;
+;;; There are two kinds of terms in CPS: terms that bind continuations,
+;;; and terms that call continuations.
+;;;
+;;; $letk binds a set of mutually recursive continuations, each one an
+;;; instance of $cont. A $cont declares the name and source of a
+;;; continuation, and then contains as a subterm the particular
+;;; continuation instance: $kif for test continuations, $kargs for
+;;; continuations that bind values, etc.
+;;;
+;;; $continue nodes call continuations. The expression contained in the
+;;; $continue node determines the value or values that are passed to the
+;;; target continuation: $const to pass a constant value, $values to
+;;; pass multiple named values, etc.
+;;;
+;;; Additionally there is $letrec, a term that binds mutually recursive
+;;; functions. The contification pass will turn $letrec into $letk if
+;;; it can do so. Otherwise, the closure conversion pass will desugar
+;;; $letrec into an equivalent sequence of make-closure primcalls and
+;;; subsequent initializations of the captured variables of the
+;;; closures. You can think of $letrec as pertaining to "high CPS",
+;;; whereas later passes will only see "low CPS", which does not have
+;;; $letrec.
+;;;
+;;; This particular formulation of CPS was inspired by Andrew Kennedy's
+;;; 2007 paper, "Compiling with Continuations, Continued". All Guile
+;;; hackers should read that excellent paper! As in Kennedy's paper,
+;;; continuations are second-class, and may be thought of as basic block
+;;; labels. All values are bound to variables using continuation calls:
+;;; even constants!
+;;;
+;;; There are some Guile-specific quirks as well:
+;;;
+;;; - $ktrunc represents a continuation that receives multiple values,
+;;; but which truncates them to some number of required values,
+;;; possibly with a rest list.
+;;;
+;;; - $kentry labels an entry point for a $fun (a function), and
+;;; contains a $ktail representing the formal argument which is the
+;;; function's continuation.
+;;;
+;;; - $kentry also contains $kclause continuations, corresponding to
+;;; the case-lambda clauses of the function. $kclause actually
+;;; contains the clause body. This is because the $kclause
+;;; logically matches or doesn't match a given set of actual
+;;; arguments against a formal arity, then proceeds to a "body"
+;;; continuation (which is a $kargs).
+;;;
+;;; That's to say that a $fun can be matched like this:
+;;;
+;;; (match f
+;;; (($ $fun meta free
+;;; ($ $cont kentry src
+;;; ($ $kentry self ($ $cont ktail _ ($ $ktail))
+;;; (($ $kclause arity
+;;; ($ $cont kbody _ ($ $kargs names syms body)))
+;;; ...))))
+;;; #t))
+;;;
+;;; A $continue to ktail is in tail position. $kentry, $kclause,
+;;; and $ktail will never be seen elsewhere in a CPS term.
+;;;
+;;; - $prompt continues to the body of the prompt, having pushed on a
+;;; prompt whose handler will continue at its "handler"
+;;; continuation. The continuation of the prompt is responsible for
+;;; popping the prompt.
+;;;
+;;; In summary:
+;;;
+;;; - $letk, $letrec, and $continue are terms.
+;;;
+;;; - $cont is a continuation, containing a continuation body ($kargs,
+;;; $kif, etc).
+;;;
+;;; - $continue terms contain an expression ($call, $const, $fun,
+;;; etc).
+;;;
+;;; See (language tree-il compile-cps) for details on how Tree-IL
+;;; converts to CPS.
+;;;
+;;; Code:
+
+(define-module (language cps)
+ #:use-module (ice-9 match)
+ #:use-module ((srfi srfi-1) #:select (fold))
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-9 gnu)
+ #:export (;; Helper.
+ $arity
+ make-$arity
+
+ ;; Terms.
+ $letk $continue $letrec
+
+ ;; Continuations.
+ $cont
+
+ ;; Continuation bodies.
+ $kif $ktrunc $kargs $kentry $ktail $kclause
+
+ ;; Expressions.
+ $var $void $const $prim $fun $call $primcall $values $prompt
+
+ ;; Building macros.
+ let-gensyms
+ build-cps-term build-cps-cont build-cps-exp
+ rewrite-cps-term rewrite-cps-cont rewrite-cps-exp
+
+ ;; Misc.
+ parse-cps unparse-cps
+ fold-conts fold-local-conts))
+
+;; FIXME: Use SRFI-99, when Guile adds it.
+(define-syntax define-record-type*
+ (lambda (x)
+ (define (id-append ctx . syms)
+ (datum->syntax ctx (apply symbol-append (map syntax->datum syms))))
+ (syntax-case x ()
+ ((_ name field ...)
+ (and (identifier? #'name) (and-map identifier? #'(field ...)))
+ (with-syntax ((cons (id-append #'name #'make- #'name))
+ (pred (id-append #'name #'name #'?))
+ ((getter ...) (map (lambda (f)
+ (id-append f #'name #'- f))
+ #'(field ...))))
+ #'(define-record-type name
+ (cons field ...)
+ pred
+ (field getter)
+ ...))))))
+
+(define-syntax-rule (define-cps-type name field ...)
+ (begin
+ (define-record-type* name field ...)
+ (set-record-type-printer! name print-cps)))
+
+(define (print-cps exp port)
+ (format port "#<cps ~S>" (unparse-cps exp)))
+
+;; Helper.
+(define-record-type* $arity req opt rest kw allow-other-keys?)
+
+;; Terms.
+(define-cps-type $letk conts body)
+(define-cps-type $continue k exp)
+(define-cps-type $letrec names syms funs body)
+
+;; Continuations
+(define-cps-type $cont k src cont)
+(define-cps-type $kif kt kf)
+(define-cps-type $ktrunc arity k)
+(define-cps-type $kargs names syms body)
+(define-cps-type $kentry self tail clauses)
+(define-cps-type $ktail)
+(define-cps-type $kclause arity cont)
+
+;; Expressions.
+(define-cps-type $var sym)
+(define-cps-type $void)
+(define-cps-type $const val)
+(define-cps-type $prim name)
+(define-cps-type $fun meta free body)
+(define-cps-type $call proc args)
+(define-cps-type $primcall name args)
+(define-cps-type $values args)
+(define-cps-type $prompt escape? tag handler)
+
+(define-syntax let-gensyms
+ (syntax-rules ()
+ ((_ (sym ...) body body* ...)
+ (let ((sym (gensym (symbol->string 'sym))) ...)
+ body body* ...))))
+
+(define-syntax build-arity
+ (syntax-rules (unquote)
+ ((_ (unquote exp)) exp)
+ ((_ (req opt rest kw allow-other-keys?))
+ (make-$arity req opt rest kw allow-other-keys?))))
+
+(define-syntax build-cont-body
+ (syntax-rules (unquote $kif $ktrunc $kargs $kentry $ktail $kclause)
+ ((_ (unquote exp))
+ exp)
+ ((_ ($kif kt kf))
+ (make-$kif kt kf))
+ ((_ ($ktrunc req rest kargs))
+ (make-$ktrunc (make-$arity req '() rest '() #f) kargs))
+ ((_ ($kargs (name ...) (sym ...) body))
+ (make-$kargs (list name ...) (list sym ...) (build-cps-term body)))
+ ((_ ($kargs names syms body))
+ (make-$kargs names syms (build-cps-term body)))
+ ((_ ($kentry self tail (unquote clauses)))
+ (make-$kentry self (build-cps-cont tail) clauses))
+ ((_ ($kentry self tail (clause ...)))
+ (make-$kentry self (build-cps-cont tail) (list (build-cps-cont clause) ...)))
+ ((_ ($ktail))
+ (make-$ktail))
+ ((_ ($kclause arity cont))
+ (make-$kclause (build-arity arity) (build-cps-cont cont)))))
+
+(define-syntax build-cps-cont
+ (syntax-rules (unquote)
+ ((_ (unquote exp)) exp)
+ ((_ (k src cont)) (make-$cont k src (build-cont-body cont)))))
+
+(define-syntax build-cps-exp
+ (syntax-rules (unquote
+ $var $void $const $prim $fun $call $primcall $values $prompt)
+ ((_ (unquote exp)) exp)
+ ((_ ($var sym)) (make-$var sym))
+ ((_ ($void)) (make-$void))
+ ((_ ($const val)) (make-$const val))
+ ((_ ($prim name)) (make-$prim name))
+ ((_ ($fun meta free body)) (make-$fun meta free (build-cps-cont body)))
+ ((_ ($call proc (arg ...))) (make-$call proc (list arg ...)))
+ ((_ ($call proc args)) (make-$call proc args))
+ ((_ ($primcall name (arg ...))) (make-$primcall name (list arg ...)))
+ ((_ ($primcall name args)) (make-$primcall name args))
+ ((_ ($values (arg ...))) (make-$values (list arg ...)))
+ ((_ ($values args)) (make-$values args))
+ ((_ ($prompt escape? tag handler)) (make-$prompt escape? tag handler))))
+
+(define-syntax build-cps-term
+ (syntax-rules (unquote $letk $letk* $letconst $letrec $continue)
+ ((_ (unquote exp))
+ exp)
+ ((_ ($letk (unquote conts) body))
+ (make-$letk conts (build-cps-term body)))
+ ((_ ($letk (cont ...) body))
+ (make-$letk (list (build-cps-cont cont) ...)
+ (build-cps-term body)))
+ ((_ ($letk* () body))
+ (build-cps-term body))
+ ((_ ($letk* (cont conts ...) body))
+ (build-cps-term ($letk (cont) ($letk* (conts ...) body))))
+ ((_ ($letconst () body))
+ (build-cps-term body))
+ ((_ ($letconst ((name sym val) tail ...) body))
+ (let-gensyms (kconst)
+ (build-cps-term
+ ($letk ((kconst #f ($kargs (name) (sym) ($letconst (tail ...) body))))
+ ($continue kconst ($const val))))))
+ ((_ ($letrec names gensyms funs body))
+ (make-$letrec names gensyms funs (build-cps-term body)))
+ ((_ ($continue k exp))
+ (make-$continue k (build-cps-exp exp)))))
+
+(define-syntax-rule (rewrite-cps-term x (pat body) ...)
+ (match x
+ (pat (build-cps-term body)) ...))
+(define-syntax-rule (rewrite-cps-cont x (pat body) ...)
+ (match x
+ (pat (build-cps-cont body)) ...))
+(define-syntax-rule (rewrite-cps-exp x (pat body) ...)
+ (match x
+ (pat (build-cps-exp body)) ...))
+
+(define (parse-cps exp)
+ (define (src exp)
+ (let ((props (source-properties exp)))
+ (and (pair? props) props)))
+ (match exp
+ ;; Continuations.
+ (('letconst k (name sym c) body)
+ (build-cps-term
+ ($letk ((k (src exp) ($kargs (name) (sym)
+ ,(parse-cps body))))
+ ($continue k ($const c)))))
+ (('let k (name sym val) body)
+ (build-cps-term
+ ($letk ((k (src exp) ($kargs (name) (sym)
+ ,(parse-cps body))))
+ ,(parse-cps val))))
+ (('letk (cont ...) body)
+ (build-cps-term
+ ($letk ,(map parse-cps cont) ,(parse-cps body))))
+ (('k sym body)
+ (build-cps-cont
+ (sym (src exp) ,(parse-cps body))))
+ (('kif kt kf)
+ (build-cont-body ($kif kt kf)))
+ (('ktrunc req rest k)
+ (build-cont-body ($ktrunc req rest k)))
+ (('kargs names syms body)
+ (build-cont-body ($kargs names syms ,(parse-cps body))))
+ (('kentry self tail clauses)
+ (build-cont-body
+ ($kentry self ,(parse-cps tail) ,(map parse-cps clauses))))
+ (('ktail)
+ (build-cont-body
+ ($ktail)))
+ (('kclause (req opt rest kw allow-other-keys?) body)
+ (build-cont-body
+ ($kclause (req opt rest kw allow-other-keys?)
+ ,(parse-cps body))))
+ (('kseq body)
+ (build-cont-body ($kargs () () ,(parse-cps body))))
+
+ ;; Calls.
+ (('continue k exp)
+ (build-cps-term ($continue k ,(parse-cps exp))))
+ (('var sym)
+ (build-cps-exp ($var sym)))
+ (('void)
+ (build-cps-exp ($void)))
+ (('const exp)
+ (build-cps-exp ($const exp)))
+ (('prim name)
+ (build-cps-exp ($prim name)))
+ (('fun meta free body)
+ (build-cps-exp ($fun meta free ,(parse-cps body))))
+ (('letrec ((name sym fun) ...) body)
+ (build-cps-term
+ ($letrec name sym (map parse-cps fun) ,(parse-cps body))))
+ (('call proc arg ...)
+ (build-cps-exp ($call proc arg)))
+ (('primcall name arg ...)
+ (build-cps-exp ($primcall name arg)))
+ (('values arg ...)
+ (build-cps-exp ($values arg)))
+ (('prompt escape? tag handler)
+ (build-cps-exp ($prompt escape? tag handler)))
+ (_
+ (error "unexpected cps" exp))))
+
+(define (unparse-cps exp)
+ (match exp
+ ;; Continuations.
+ (($ $letk (($ $cont k src ($ $kargs (name) (sym) body)))
+ ($ $continue k ($ $const c)))
+ `(letconst ,k (,name ,sym ,c)
+ ,(unparse-cps body)))
+ (($ $letk (($ $cont k src ($ $kargs (name) (sym) body))) val)
+ `(let ,k (,name ,sym ,(unparse-cps val))
+ ,(unparse-cps body)))
+ (($ $letk conts body)
+ `(letk ,(map unparse-cps conts) ,(unparse-cps body)))
+ (($ $cont sym src body)
+ `(k ,sym ,(unparse-cps body)))
+ (($ $kif kt kf)
+ `(kif ,kt ,kf))
+ (($ $ktrunc ($ $arity req () rest '() #f) k)
+ `(ktrunc ,req ,rest ,k))
+ (($ $kargs () () body)
+ `(kseq ,(unparse-cps body)))
+ (($ $kargs names syms body)
+ `(kargs ,names ,syms ,(unparse-cps body)))
+ (($ $kentry self tail clauses)
+ `(kentry ,self ,(unparse-cps tail) ,(map unparse-cps clauses)))
+ (($ $ktail)
+ `(ktail))
+ (($ $kclause ($ $arity req opt rest kw allow-other-keys?) body)
+ `(kclause (,req ,opt ,rest ,kw ,allow-other-keys?) ,(unparse-cps body)))
+
+ ;; Calls.
+ (($ $continue k exp)
+ `(continue ,k ,(unparse-cps exp)))
+ (($ $var sym)
+ `(var ,sym))
+ (($ $void)
+ `(void))
+ (($ $const val)
+ `(const ,val))
+ (($ $prim name)
+ `(prim ,name))
+ (($ $fun meta free body)
+ `(fun ,meta ,free ,(unparse-cps body)))
+ (($ $letrec names syms funs body)
+ `(letrec ,(map (lambda (name sym fun)
+ (list name sym (unparse-cps fun)))
+ names syms funs)
+ ,(unparse-cps body)))
+ (($ $call proc args)
+ `(call ,proc ,@args))
+ (($ $primcall name args)
+ `(primcall ,name ,@args))
+ (($ $values args)
+ `(values ,@args))
+ (($ $prompt escape? tag handler)
+ `(prompt ,escape? ,tag ,handler))
+ (_
+ (error "unexpected cps" exp))))
+
+(define (fold-conts proc seed fun)
+ (define (cont-folder cont seed)
+ (match cont
+ (($ $cont k src cont)
+ (let ((seed (proc k src cont seed)))
+ (match cont
+ (($ $kargs names syms body)
+ (term-folder body seed))
+
+ (($ $kentry self tail clauses)
+ (fold cont-folder (cont-folder tail seed) clauses))
+
+ (($ $kclause arity body)
+ (cont-folder body seed))
+
+ (_ seed))))))
+
+ (define (fun-folder fun seed)
+ (match fun
+ (($ $fun meta free body)
+ (cont-folder body seed))))
+
+ (define (term-folder term seed)
+ (match term
+ (($ $letk conts body)
+ (fold cont-folder (term-folder body seed) conts))
+
+ (($ $continue k exp)
+ (match exp
+ (($ $fun) (fun-folder exp seed))
+ (_ seed)))
+
+ (($ $letrec names syms funs body)
+ (fold fun-folder (term-folder body seed) funs))))
+
+ (fun-folder fun seed))
+
+(define (fold-local-conts proc seed cont)
+ (define (cont-folder cont seed)
+ (match cont
+ (($ $cont k src cont)
+ (let ((seed (proc k src cont seed)))
+ (match cont
+ (($ $kargs names syms body)
+ (term-folder body seed))
+
+ (($ $kentry self tail clauses)
+ (fold cont-folder (cont-folder tail seed) clauses))
+
+ (($ $kclause arity body)
+ (cont-folder body seed))
+
+ (_ seed))))))
+
+ (define (term-folder term seed)
+ (match term
+ (($ $letk conts body)
+ (fold cont-folder (term-folder body seed) conts))
+
+ (($ $continue) seed)
+
+ (($ $letrec names syms funs body) (term-folder body seed))))
+
+ (cont-folder cont seed))
diff --git a/module/language/cps/verify.scm b/module/language/cps/verify.scm
new file mode 100644
index 0000000..0276d1d
--- /dev/null
+++ b/module/language/cps/verify.scm
@@ -0,0 +1,165 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;;
+;;; Code:
+
+(define-module (language cps verify)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:export (verify-cps))
+
+(define (verify-cps fun)
+ (define seen-gensyms (make-hash-table))
+
+ (define (add sym env)
+ (if (hashq-ref seen-gensyms sym)
+ (error "duplicate gensym" sym)
+ (begin
+ (hashq-set! seen-gensyms sym #t)
+ (cons sym env))))
+
+ (define (add-env new env)
+ (if (null? new)
+ env
+ (add-env (cdr new) (add (car new) env))))
+
+ (define (check-var sym env)
+ (cond
+ ((not (hashq-ref seen-gensyms sym))
+ (error "unbound lexical" sym))
+ ((not (memq sym env))
+ (error "displaced lexical" sym))))
+
+ (define (check-src src)
+ (if (and src (not (and (list? src) (and-map pair? src)
+ (and-map symbol? (map car src)))))
+ (error "bad src")))
+
+ (define (visit-cont-body cont k-env v-env)
+ (match cont
+ (($ $kif kt kf)
+ (check-var kt k-env)
+ (check-var kf k-env))
+ (($ $ktrunc ($ $arity ((? symbol?) ...) () (or #f (? symbol?)) () #f) k)
+ (check-var k k-env))
+ (($ $kargs ((? symbol? name) ...) ((? symbol? sym) ...) body)
+ (unless (= (length name) (length sym))
+ (error "name and sym lengths don't match" name sym))
+ (visit-term body k-env (add-env sym v-env)))
+ (_
+ ;; $kclause, $kentry, and $ktail are only ever seen in $fun.
+ (error "unexpected cont body" cont))))
+
+ (define (visit-clause clause k-env v-env)
+ (match clause
+ (($ $cont kclause src*
+ ($ $kclause
+ ($ $arity
+ ((? symbol? req) ...)
+ ((? symbol? opt) ...)
+ (and rest (or #f (? symbol?)))
+ (((? keyword? kw) (? symbol? kwname) (? symbol? kwsym)) ...)
+ (or #f #t))
+ ($ $cont kbody src (and body ($ $kargs names syms _)))))
+ (check-src src*)
+ (check-src src)
+ (for-each (lambda (sym)
+ (unless (memq sym syms)
+ (error "bad keyword sym" sym)))
+ kwsym)
+ ;; FIXME: It is technically possible for kw syms to alias other
+ ;; syms.
+ (unless (equal? (append req opt (if rest (list rest) '()) kwname)
+ names)
+ (error "clause body names do not match arity names" exp))
+ (let ((k-env (add-env (list kclause kbody) k-env)))
+ (visit-cont-body body k-env v-env)))
+ (_
+ (error "unexpected clause" clause))))
+
+ (define (visit-fun fun k-env v-env)
+ (match fun
+ (($ $fun meta ((? symbol? free) ...)
+ ($ $cont kbody src
+ ($ $kentry (? symbol? self) ($ $cont ktail _ ($ $ktail)) clauses)))
+ (when (and meta (not (and (list? meta) (and-map pair? meta))))
+ (error "meta should be alist" meta))
+ (for-each (cut check-var <> v-env) free)
+ (check-src src)
+ ;; Reset the continuation environment, because Guile's
+ ;; continuations are local.
+ (let ((v-env (add-env (list self) v-env))
+ (k-env (add-env (list ktail) '())))
+ (for-each (cut visit-clause <> k-env v-env) clauses)))
+ (_
+ (error "unexpected $fun" fun))))
+
+ (define (visit-expression exp k-env v-env)
+ (match exp
+ (($ $var sym)
+ (check-var sym v-env))
+ (($ $void)
+ #t)
+ (($ $const val)
+ #t)
+ (($ $prim (? symbol? name))
+ #t)
+ (($ $fun)
+ (visit-fun fun k-env v-env))
+ (($ $call (? symbol? proc) ((? symbol? arg) ...))
+ (check-var proc v-env)
+ (for-each (cut check-var <> v-env) arg))
+ (($ $primcall (? symbol? name) ((? symbol? arg) ...))
+ (for-each (cut check-var <> v-env) arg))
+ (($ $values ((? symbol? arg) ...))
+ (for-each (cut check-var <> v-env) arg))
+ (($ $prompt escape? tag handler)
+ (unless (boolean? escape?) (error "escape? should be boolean" escape?))
+ (check-var tag v-env)
+ (check-var handler k-env))
+ (_
+ (error "unexpected expression" exp))))
+
+ (define (visit-term term k-env v-env)
+ (match term
+ (($ $letk (($ $cont (? symbol? k) src cont) ...) body)
+ (let ((k-env (add-env k k-env)))
+ (for-each check-src src)
+ (for-each (cut visit-cont-body <> k-env v-env) cont)
+ (visit-term body k-env v-env)))
+
+ (($ $letrec ((? symbol? name) ...) ((? symbol? sym) ...) (fun ...) body)
+ (unless (= (length name) (length sym) (length fun))
+ (error "letrec syms, names, and funs not same length" term))
+ (let ((v-env (add-env sym v-env)))
+ (for-each (cut visit-fun <> k-env v-env) fun)
+ (visit-term body k-env v-env)))
+
+ (($ $continue k exp)
+ (check-var k k-env)
+ (visit-expression exp k-env v-env))
+
+ (_
+ (error "unexpected term" term))))
+
+ (visit-fun fun '() '())
+ fun)
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2/9] (compile foo #:to 'cps)
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
2013-08-29 7:49 ` [PATCH 1/9] Add CPS language Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 7:49 ` [PATCH 3/9] Add closure conversion Andy Wingo
` (8 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/language/tree-il/compile-cps.scm: New module implementing CPS
conversion of Tree-IL.
* module/Makefile.am:
* module/language/tree-il/spec.scm:
* module/language/cps/spec.scm: Integrate CPS in the build and language
system.
---
module/Makefile.am | 2 +
module/language/cps/spec.scm | 36 ++
module/language/tree-il/compile-cps.scm | 594 ++++++++++++++++++++++++++++++++
module/language/tree-il/spec.scm | 4 +-
4 files changed, 635 insertions(+), 1 deletion(-)
create mode 100644 module/language/cps/spec.scm
create mode 100644 module/language/tree-il/compile-cps.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index 1f66ac4..fea910f 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -112,12 +112,14 @@ TREE_IL_LANG_SOURCES = \
language/tree-il/canonicalize.scm \
language/tree-il/analyze.scm \
language/tree-il/inline.scm \
+ language/tree-il/compile-cps.scm \
language/tree-il/compile-glil.scm \
language/tree-il/debug.scm \
language/tree-il/spec.scm
CPS_LANG_SOURCES = \
language/cps.scm \
+ language/cps/spec.scm \
language/cps/verify.scm
GLIL_LANG_SOURCES = \
diff --git a/module/language/cps/spec.scm b/module/language/cps/spec.scm
new file mode 100644
index 0000000..38dc54d
--- /dev/null
+++ b/module/language/cps/spec.scm
@@ -0,0 +1,36 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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
+
+;;; Code:
+
+(define-module (language cps spec)
+ #:use-module (system base language)
+ #:use-module (language cps)
+ #:export (cps))
+
+(define* (write-cps exp #:optional (port (current-output-port)))
+ (write (unparse-cps exp) port))
+
+(define-language cps
+ #:title "CPS Intermediate Language"
+ #:reader (lambda (port env) (read port))
+ #:printer write-cps
+ #:parser parse-cps
+ #:compilers '()
+ #:for-humans? #f
+ )
diff --git a/module/language/tree-il/compile-cps.scm b/module/language/tree-il/compile-cps.scm
new file mode 100644
index 0000000..e7befbe
--- /dev/null
+++ b/module/language/tree-il/compile-cps.scm
@@ -0,0 +1,594 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; This pass converts Tree-IL to the continuation-passing style (CPS)
+;;; language.
+;;;
+;;; CPS is a lower-level representation than Tree-IL. Converting to
+;;; CPS, beyond adding names for all control points and all values,
+;;; simplifies expressions in the following ways, among others:
+;;;
+;;; * Fixing the order of evaluation.
+;;;
+;;; * Converting assigned variables to boxed variables.
+;;;
+;;; * Requiring that Scheme's <letrec> has already been lowered to
+;;; <fix>.
+;;;
+;;; * Inlining default-value initializers into lambda-case
+;;; expressions.
+;;;
+;;; * Inlining prompt bodies.
+;;;
+;;; * Turning toplevel and module references into primcalls. This
+;;; involves explicitly modelling the "scope" of toplevel lookups
+;;; (indicating the module with respect to which toplevel bindings
+;;; are resolved).
+;;;
+;;; The utility of CPS is that it gives a name to everything: every
+;;; intermediate value, and every control point (continuation). As such
+;;; it is more verbose than Tree-IL, but at the same time more simple as
+;;; the number of concepts is reduced.
+;;;
+;;; Code:
+
+(define-module (language tree-il compile-cps)
+ #:use-module (ice-9 match)
+ #:use-module ((srfi srfi-1) #:select (fold fold-right filter-map))
+ #:use-module (srfi srfi-26)
+ #:use-module ((system foreign) #:select (make-pointer pointer->scm))
+ #:use-module (language cps)
+ #:use-module (language cps primitives)
+ #:use-module (language tree-il analyze)
+ #:use-module (language tree-il optimize)
+ #:use-module ((language tree-il)
+ #:select
+ (<void>
+ <const> <primitive-ref> <lexical-ref> <lexical-set>
+ <module-ref> <module-set>
+ <toplevel-ref> <toplevel-set> <toplevel-define>
+ <conditional>
+ <call> <primcall>
+ <seq>
+ <lambda> <lambda-case>
+ <let> <letrec> <fix> <let-values>
+ <prompt> <abort>
+ make-conditional make-const make-primcall
+ tree-il-src
+ tree-il-fold))
+ #:export (compile-cps))
+
+;;; Guile's semantics are that a toplevel lambda captures a reference on
+;;; the current module, and that all contained lambdas use that module
+;;; to resolve toplevel variables. This parameter tracks whether or not
+;;; we are in a toplevel lambda. If we are in a lambda, the parameter
+;;; is bound to a fresh name identifying the module that was current
+;;; when the toplevel lambda is defined.
+;;;
+;;; This is more complicated than it need be. Ideally we should resolve
+;;; all toplevel bindings to bindings from specific modules, unless the
+;;; binding is unbound. This is always valid if the compilation unit
+;;; sets the module explicitly, as when compiling a module, but it
+;;; doesn't work for files auto-compiled for use with `load'.
+;;;
+(define current-topbox-scope (make-parameter #f))
+
+(define (toplevel-box src name bound? val-proc)
+ (let-gensyms (name-sym bound?-sym kbox box)
+ (build-cps-term
+ ($letconst (('name name-sym name)
+ ('bound? bound?-sym bound?))
+ ($letk ((kbox src ($kargs ('box) (box) ,(val-proc box))))
+ ,(match (current-topbox-scope)
+ (#f
+ (build-cps-term
+ ($continue kbox
+ ($primcall 'resolve
+ (name-sym bound?-sym)))))
+ (scope
+ (let-gensyms (scope-sym)
+ (build-cps-term
+ ($letconst (('scope scope-sym scope))
+ ($continue kbox
+ ($primcall 'cached-toplevel-box
+ (scope-sym name-sym bound?-sym)))))))))))))
+
+(define (module-box src module name public? bound? val-proc)
+ (let-gensyms (module-sym name-sym public?-sym bound?-sym kbox box)
+ (build-cps-term
+ ($letconst (('module module-sym module)
+ ('name name-sym name)
+ ('public? public?-sym public?)
+ ('bound? bound?-sym bound?))
+ ($letk ((kbox src ($kargs ('box) (box) ,(val-proc box))))
+ ($continue kbox
+ ($primcall 'cached-module-box
+ (module-sym name-sym public?-sym bound?-sym))))))))
+
+(define (capture-toplevel-scope src scope k)
+ (let-gensyms (module scope-sym kmodule)
+ (build-cps-term
+ ($letconst (('scope scope-sym scope))
+ ($letk ((kmodule src ($kargs ('module) (module)
+ ($continue k
+ ($primcall 'cache-current-module!
+ (module scope-sym))))))
+ ($continue kmodule
+ ($primcall 'current-module ())))))))
+
+(define (fold-formals proc seed arity gensyms inits)
+ (match arity
+ (($ $arity req opt rest kw allow-other-keys?)
+ (let ()
+ (define (fold-req names gensyms seed)
+ (match names
+ (() (fold-opt opt gensyms inits seed))
+ ((name . names)
+ (proc name (car gensyms) #f
+ (fold-req names (cdr gensyms) seed)))))
+ (define (fold-opt names gensyms inits seed)
+ (match names
+ (() (fold-rest rest gensyms inits seed))
+ ((name . names)
+ (proc name (car gensyms) (car inits)
+ (fold-opt names (cdr gensyms) (cdr inits) seed)))))
+ (define (fold-rest rest gensyms inits seed)
+ (match rest
+ (#f (fold-kw kw gensyms inits seed))
+ (name (proc name (car gensyms) #f
+ (fold-kw kw (cdr gensyms) inits seed)))))
+ (define (fold-kw kw gensyms inits seed)
+ (match kw
+ (()
+ (unless (null? gensyms)
+ (error "too many gensyms"))
+ (unless (null? inits)
+ (error "too many inits"))
+ seed)
+ (((key name var) . kw)
+ (unless (eq? var (car gensyms))
+ (error "unexpected keyword arg order"))
+ (proc name var (car inits)
+ (fold-kw kw (cdr gensyms) (cdr inits) seed)))))
+ (fold-req req gensyms seed)))))
+
+(define (unbound? src sym kt kf)
+ (define tc8-iflag 4)
+ (define unbound-val 9)
+ (define unbound-bits (logior (ash unbound-val 8) tc8-iflag))
+ (let-gensyms (unbound ktest)
+ (build-cps-term
+ ($letconst (('unbound unbound (pointer->scm (make-pointer unbound-bits))))
+ ($letk ((ktest src ($kif kt kf)))
+ ($continue ktest
+ ($primcall 'eq? (sym unbound))))))))
+
+(define (init-default-value name sym subst init body)
+ (match (assq-ref subst sym)
+ ((subst-sym box?)
+ (let ((src (tree-il-src init)))
+ (define (maybe-box k make-body)
+ (if box?
+ (let-gensyms (kbox phi)
+ (build-cps-term
+ ($letk ((kbox src ($kargs (name) (phi)
+ ($continue k ($primcall 'box (phi))))))
+ ,(make-body kbox))))
+ (make-body k)))
+ (let-gensyms (knext kbound kunbound)
+ (build-cps-term
+ ($letk ((knext src ($kargs (name) (subst-sym) ,body)))
+ ,(maybe-box
+ knext
+ (lambda (k)
+ (build-cps-term
+ ($letk ((kbound src ($kargs () () ($continue k ($var sym))))
+ (kunbound src ($kargs () () ,(convert init k subst))))
+ ,(unbound? src sym kunbound kbound))))))))))))
+
+;; exp k-name alist -> term
+(define (convert exp k subst)
+ ;; exp (v-name -> term) -> term
+ (define (convert-arg exp k)
+ (match exp
+ (($ <lexical-ref> src name sym)
+ (match (assq-ref subst sym)
+ ((box #t)
+ (let-gensyms (kunboxed unboxed)
+ (build-cps-term
+ ($letk ((kunboxed src ($kargs ('unboxed) (unboxed) ,(k unboxed))))
+ ($continue kunboxed ($primcall 'box-ref (box)))))))
+ ((subst #f) (k subst))
+ (#f (k sym))))
+ (else
+ (let ((src (tree-il-src exp)))
+ (let-gensyms (karg arg)
+ (build-cps-term
+ ($letk ((karg src ($kargs ('arg) (arg) ,(k arg))))
+ ,(convert exp karg subst))))))))
+ ;; (exp ...) ((v-name ...) -> term) -> term
+ (define (convert-args exps k)
+ (match exps
+ (() (k '()))
+ ((exp . exps)
+ (convert-arg exp
+ (lambda (name)
+ (convert-args exps
+ (lambda (names)
+ (k (cons name names)))))))))
+ (define (box-bound-var name sym body)
+ (match (assq-ref subst sym)
+ ((box #t)
+ (let-gensyms (k)
+ (build-cps-term
+ ($letk ((k #f ($kargs (name) (box) ,body)))
+ ($continue k ($primcall 'box (sym)))))))
+ (else body)))
+
+ (match exp
+ (($ <lexical-ref> src name sym)
+ (match (assq-ref subst sym)
+ ((box #t) (build-cps-term ($continue k ($primcall 'box-ref (box)))))
+ ((subst #f) (build-cps-term ($continue k ($var subst))))
+ (#f (build-cps-term ($continue k ($var sym))))))
+
+ (($ <void> src)
+ (build-cps-term ($continue k ($void))))
+
+ (($ <const> src exp)
+ (build-cps-term ($continue k ($const exp))))
+
+ (($ <primitive-ref> src name)
+ (build-cps-term ($continue k ($prim name))))
+
+ (($ <lambda> fun-src meta body)
+ (let ()
+ (define (convert-clauses body ktail)
+ (match body
+ (#f '())
+ (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
+ (let* ((arity (make-$arity req (or opt '()) rest
+ (if kw (cdr kw) '()) (and kw (car kw))))
+ (names (fold-formals (lambda (name sym init names)
+ (cons name names))
+ '()
+ arity gensyms inits)))
+ (cons
+ (let-gensyms (kclause kargs)
+ (build-cps-cont
+ (kclause
+ src
+ ($kclause ,arity
+ (kargs
+ src
+ ($kargs names gensyms
+ ,(fold-formals
+ (lambda (name sym init body)
+ (if init
+ (init-default-value name sym subst init body)
+ (box-bound-var name sym body)))
+ (convert body ktail subst)
+ arity gensyms inits)))))))
+ (convert-clauses alternate ktail))))))
+ (if (current-topbox-scope)
+ (let-gensyms (kentry self ktail)
+ (build-cps-term
+ ($continue k
+ ($fun meta '()
+ (kentry fun-src
+ ($kentry self (ktail #f ($ktail))
+ ,(convert-clauses body ktail)))))))
+ (let-gensyms (scope kscope)
+ (build-cps-term
+ ($letk ((kscope fun-src
+ ($kargs () ()
+ ,(parameterize ((current-topbox-scope scope))
+ (convert exp k subst)))))
+ ,(capture-toplevel-scope fun-src scope kscope)))))))
+
+ (($ <module-ref> src mod name public?)
+ (module-box
+ src mod name public? #t
+ (lambda (box)
+ (build-cps-term ($continue k ($primcall 'box-ref (box)))))))
+
+ (($ <module-set> src mod name public? exp)
+ (convert-arg exp
+ (lambda (val)
+ (module-box
+ src mod name public? #f
+ (lambda (box)
+ (build-cps-term ($continue k ($primcall 'box-set! (box val)))))))))
+
+ (($ <toplevel-ref> src name)
+ (toplevel-box
+ src name #t
+ (lambda (box)
+ (build-cps-term ($continue k ($primcall 'box-ref (box)))))))
+
+ (($ <toplevel-set> src name exp)
+ (convert-arg exp
+ (lambda (val)
+ (toplevel-box
+ src name #f
+ (lambda (box)
+ (build-cps-term ($continue k ($primcall 'box-set! (box val)))))))))
+
+ (($ <toplevel-define> src name exp)
+ (convert-arg exp
+ (lambda (val)
+ (let-gensyms (kname name-sym)
+ (build-cps-term
+ ($letconst (('name name-sym name))
+ ($continue k ($primcall 'define! (name-sym val)))))))))
+
+ (($ <call> src proc args)
+ (convert-args (cons proc args)
+ (match-lambda
+ ((proc . args)
+ (build-cps-term ($continue k ($call proc args)))))))
+
+ (($ <primcall> src name args)
+ (case name
+ ((list)
+ (convert (fold-right (lambda (elem tail)
+ (make-primcall src 'cons
+ (list elem tail)))
+ (make-const src '())
+ args)
+ k subst))
+ (else
+ (if (branching-primitive? name)
+ (convert (make-conditional src exp (make-const #f #t)
+ (make-const #f #f))
+ k subst)
+ (convert-args args
+ (lambda (args)
+ (if (eq? name 'values)
+ (build-cps-term ($continue k ($values args)))
+ (build-cps-term ($continue k ($primcall name args))))))))))
+
+ ;; Prompts with inline handlers.
+ (($ <prompt> src escape-only? tag body
+ ($ <lambda> hsrc hmeta
+ ($ <lambda-case> _ hreq #f hrest #f () hsyms hbody #f)))
+ ;; Handler:
+ ;; khargs: check args returned to handler, -> khbody
+ ;; khbody: the handler, -> k
+ ;;
+ ;; Post-body:
+ ;; krest: collect return vals from body to list, -> kpop
+ ;; kpop: pop the prompt, -> kprim
+ ;; kprim: load the values primitive, -> kret
+ ;; kret: (apply values rvals), -> k
+ ;;
+ ;; Escape prompts evaluate the body with the continuation of krest.
+ ;; Otherwise we do a no-inline call to body, continuing to krest.
+ (convert-arg tag
+ (lambda (tag)
+ (let ((hnames (append hreq (if hrest (list hrest) '()))))
+ (let-gensyms (khargs khbody kret kprim prim kpop krest vals kbody)
+ (build-cps-term
+ ($letk* ((khbody hsrc ($kargs hnames hsyms
+ ,(fold box-bound-var
+ (convert hbody k subst)
+ hnames hsyms)))
+ (khargs hsrc ($ktrunc hreq hrest khbody))
+ (kpop src
+ ($kargs ('rest) (vals)
+ ($letk ((kret
+ src
+ ($kargs () ()
+ ($letk ((kprim
+ src
+ ($kargs ('prim) (prim)
+ ($continue k
+ ($primcall 'apply
+ (prim vals))))))
+ ($continue kprim
+ ($prim 'values))))))
+ ($continue kret
+ ($primcall 'pop-prompt ())))))
+ (krest src ($ktrunc '() 'rest kpop)))
+ ,(if escape-only?
+ (build-cps-term
+ ($letk ((kbody (tree-il-src body)
+ ($kargs () ()
+ ,(convert body krest subst))))
+ ($continue kbody ($prompt #t tag khargs))))
+ (convert-arg body
+ (lambda (thunk)
+ (build-cps-term
+ ($letk ((kbody (tree-il-src body)
+ ($kargs () ()
+ ($continue krest
+ ($primcall 'call-thunk/no-inline
+ (thunk))))))
+ ($continue kbody
+ ($prompt #f tag khargs))))))))))))))
+
+ ;; Eta-convert prompts without inline handlers.
+ (($ <prompt> src escape-only? tag body handler)
+ (convert-args (list tag body handler)
+ (lambda (args)
+ (build-cps-term
+ ($continue k ($primcall 'call-with-prompt args))))))
+
+ (($ <abort> src tag args tail)
+ (convert-args (append (list tag) args (list tail))
+ (lambda (args*)
+ (build-cps-term ($continue k ($primcall 'abort args*))))))
+
+ (($ <conditional> src test consequent alternate)
+ (let-gensyms (kif kt kf)
+ (build-cps-term
+ ($letk* ((kt (tree-il-src consequent) ($kargs () ()
+ ,(convert consequent k subst)))
+ (kf (tree-il-src alternate) ($kargs () ()
+ ,(convert alternate k subst)))
+ (kif src ($kif kt kf)))
+ ,(match test
+ (($ <primcall> src (? branching-primitive? name) args)
+ (convert-args args
+ (lambda (args)
+ (build-cps-term ($continue kif ($primcall name args))))))
+ (_ (convert-arg test
+ (lambda (test)
+ (build-cps-term ($continue kif ($var test)))))))))))
+
+ (($ <lexical-set> src name gensym exp)
+ (convert-arg exp
+ (lambda (exp)
+ (match (assq-ref subst gensym)
+ ((box #t)
+ (build-cps-term
+ ($continue k ($primcall 'box-set! (box exp)))))))))
+
+ (($ <seq> src head tail)
+ (let-gensyms (ktrunc kseq)
+ (build-cps-term
+ ($letk* ((kseq (tree-il-src tail) ($kargs () ()
+ ,(convert tail k subst)))
+ (ktrunc src ($ktrunc '() #f kseq)))
+ ,(convert head ktrunc subst)))))
+
+ (($ <let> src names syms vals body)
+ (let lp ((names names) (syms syms) (vals vals))
+ (match (list names syms vals)
+ ((() () ()) (convert body k subst))
+ (((name . names) (sym . syms) (val . vals))
+ (let-gensyms (klet)
+ (build-cps-term
+ ($letk ((klet src ($kargs (name) (sym)
+ ,(box-bound-var name sym
+ (lp names syms vals)))))
+ ,(convert val klet subst))))))))
+
+ (($ <fix> src names gensyms funs body)
+ ;; Some letrecs can be contified; that happens later.
+ (if (current-topbox-scope)
+ (let-gensyms (self)
+ (build-cps-term
+ ($letrec names
+ gensyms
+ (map (lambda (fun)
+ (match (convert fun k subst)
+ (($ $continue _ (and fun ($ $fun)))
+ fun)))
+ funs)
+ ,(convert body k subst))))
+ (let-gensyms (scope kscope)
+ (build-cps-term
+ ($letk ((kscope src ($kargs () ()
+ ,(parameterize ((current-topbox-scope scope))
+ (convert exp k subst)))))
+ ,(capture-toplevel-scope src scope kscope))))))
+
+ (($ <let-values> src exp
+ ($ <lambda-case> lsrc req #f rest #f () syms body #f))
+ (let ((names (append req (if rest (list rest) '()))))
+ (let-gensyms (ktrunc kargs)
+ (build-cps-term
+ ($letk* ((kargs src ($kargs names syms
+ ,(fold box-bound-var
+ (convert body k subst)
+ names syms)))
+ (ktrunc src ($ktrunc req rest kargs)))
+ ,(convert exp ktrunc subst))))))))
+
+(define (build-subst exp)
+ "Compute a mapping from lexical gensyms to substituted gensyms. The
+usual reason to replace one variable by another is assignment
+conversion. Default argument values is the other reason.
+
+Returns a list of (ORIG-SYM SUBST-SYM BOXED?). A true value for BOXED?
+indicates that the replacement variable is in a box."
+ (define (box-set-vars exp subst)
+ (match exp
+ (($ <lexical-set> src name sym exp)
+ (if (assq sym subst)
+ subst
+ (cons (list sym (gensym "b") #t) subst)))
+ (_ subst)))
+ (define (default-args exp subst)
+ (match exp
+ (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
+ (fold-formals (lambda (name sym init subst)
+ (if init
+ (let ((box? (match (assq-ref subst sym)
+ ((box #t) #t)
+ (#f #f)))
+ (subst-sym (gensym (symbol->string name))))
+ (cons (list sym subst-sym box?) subst))
+ subst))
+ subst
+ (make-$arity req (or opt '()) rest
+ (if kw (cdr kw) '()) (and kw (car kw)))
+ gensyms
+ inits))
+ (_ subst)))
+ (tree-il-fold box-set-vars default-args '() exp))
+
+(define (cps-convert/thunk exp)
+ (let ((src (tree-il-src exp)))
+ (let-gensyms (kinit init ktail kclause kbody)
+ (build-cps-exp
+ ($fun '() '()
+ (kinit src
+ ($kentry init
+ (ktail #f ($ktail))
+ ((kclause src
+ ($kclause ('() '() #f '() #f)
+ (kbody src
+ ($kargs () ()
+ ,(convert exp ktail
+ (build-subst exp))))))))))))))
+
+(define *comp-module* (make-fluid))
+
+(define %warning-passes
+ `((unused-variable . ,unused-variable-analysis)
+ (unused-toplevel . ,unused-toplevel-analysis)
+ (unbound-variable . ,unbound-variable-analysis)
+ (arity-mismatch . ,arity-analysis)
+ (format . ,format-analysis)))
+
+(define (optimize-tree-il x e opts)
+ (define warnings
+ (or (and=> (memq #:warnings opts) cadr)
+ '()))
+
+ ;; Go through the warning passes.
+ (let ((analyses (filter-map (lambda (kind)
+ (assoc-ref %warning-passes kind))
+ warnings)))
+ (analyze-tree analyses x e))
+
+ (optimize x e opts))
+
+(define (compile-cps exp env opts)
+ (values (cps-convert/thunk (optimize-tree-il exp env opts))
+ env
+ env))
+
+;;; Local Variables:
+;;; eval: (put 'convert-arg 'scheme-indent-function 1)
+;;; eval: (put 'convert-args 'scheme-indent-function 1)
+;;; End:
diff --git a/module/language/tree-il/spec.scm b/module/language/tree-il/spec.scm
index 80c32fe..a574eb2 100644
--- a/module/language/tree-il/spec.scm
+++ b/module/language/tree-il/spec.scm
@@ -23,6 +23,7 @@
#:use-module (system base pmatch)
#:use-module (language glil)
#:use-module (language tree-il)
+ #:use-module (language tree-il compile-cps)
#:use-module (language tree-il compile-glil)
#:export (tree-il))
@@ -43,6 +44,7 @@
#:printer write-tree-il
#:parser parse-tree-il
#:joiner join
- #:compilers `((glil . ,compile-glil))
+ #:compilers `((glil . ,compile-glil)
+ (cps . ,compile-cps))
#:for-humans? #f
)
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 3/9] Add closure conversion
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
2013-08-29 7:49 ` [PATCH 1/9] Add CPS language Andy Wingo
2013-08-29 7:49 ` [PATCH 2/9] (compile foo #:to 'cps) Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 7:49 ` [PATCH 4/9] RTL language Andy Wingo
` (7 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am
* module/language/cps/closure-conversion.scm: New module, implementing a
closure conversion pass.
---
module/Makefile.am | 1 +
module/language/cps/closure-conversion.scm | 273 +++++++++++++++++++++++++++++
2 files changed, 274 insertions(+)
create mode 100644 module/language/cps/closure-conversion.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index fea910f..6fd88e6 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -119,6 +119,7 @@ TREE_IL_LANG_SOURCES = \
CPS_LANG_SOURCES = \
language/cps.scm \
+ language/cps/closure-conversion.scm \
language/cps/spec.scm \
language/cps/verify.scm
diff --git a/module/language/cps/closure-conversion.scm b/module/language/cps/closure-conversion.scm
new file mode 100644
index 0000000..9a9738b
--- /dev/null
+++ b/module/language/cps/closure-conversion.scm
@@ -0,0 +1,273 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; This pass converts a CPS term in such a way that no function has any
+;;; free variables. Instead, closures are built explicitly with
+;;; make-closure primcalls, and free variables are referenced through
+;;; the closure.
+;;;
+;;; Closure conversion also removes any $letrec forms that contification
+;;; did not handle. See (language cps) for a further discussion of
+;;; $letrec.
+;;;
+;;; Code:
+
+(define-module (language cps closure-conversion)
+ #:use-module (ice-9 match)
+ #:use-module ((srfi srfi-1) #:select (fold
+ lset-union lset-difference
+ list-index))
+ #:use-module (ice-9 receive)
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:export (convert-closures))
+
+(define (union s1 s2)
+ (lset-union eq? s1 s2))
+
+(define (difference s1 s2)
+ (lset-difference eq? s1 s2))
+
+;; bound := sym ...
+;; free := sym ...
+
+(define (convert-free-var sym self bound k)
+ "Convert one possibly free variable reference to a bound reference.
+
+If @var{sym} is free (i.e., not present in @var{bound},), it is replaced
+by a closure reference via a @code{free-ref} primcall, and @var{k} is
+called with the new var. Otherwise @var{sym} is bound, so @var{k} is
+called with @var{sym}.
+
+@var{k} should return two values: a term and a list of additional free
+values in the term."
+ (if (memq sym bound)
+ (k sym)
+ (let-gensyms (k* sym*)
+ (receive (exp free) (k sym*)
+ (values (build-cps-term
+ ($letk ((k* #f ($kargs (sym*) (sym*) ,exp)))
+ ($continue k* ($primcall 'free-ref (self sym)))))
+ (cons sym free))))))
+
+(define (convert-free-vars syms self bound k)
+ "Convert a number of possibly free references to bound references.
+@var{k} is called with the bound references, and should return two
+values: the term and a list of additional free variables in the term."
+ (match syms
+ (() (k '()))
+ ((sym . syms)
+ (convert-free-var sym self bound
+ (lambda (sym)
+ (convert-free-vars syms self bound
+ (lambda (syms)
+ (k (cons sym syms)))))))))
+
+(define (init-closure src v free outer-self outer-bound body)
+ "Initialize the free variables @var{free} in a closure bound to
+@var{v}, and continue with @var{body}. @var{outer-self} must be the
+label of the outer procedure, where the initialization will be
+performed, and @var{outer-bound} is the list of bound variables there."
+ (fold (lambda (free idx body)
+ (let-gensyms (k idxsym)
+ (build-cps-term
+ ($letk ((k src ($kargs () () ,body)))
+ ,(convert-free-var
+ free outer-self outer-bound
+ (lambda (free)
+ (values (build-cps-term
+ ($letconst (('idx idxsym idx))
+ ($continue k
+ ($primcall 'free-set! (v idxsym free)))))
+ '())))))))
+ body
+ free
+ (iota (length free))))
+
+(define (cc* exps self bound)
+ "Convert all free references in the list of expressions @var{exps} to
+bound references, and convert functions to flat closures. Returns two
+values: the transformed list, and a cumulative set of free variables."
+ (let lp ((exps exps) (exps* '()) (free '()))
+ (match exps
+ (() (values (reverse exps*) free))
+ ((exp . exps)
+ (receive (exp* free*) (cc exp self bound)
+ (lp exps (cons exp* exps*) (union free free*)))))))
+
+;; Closure conversion.
+(define (cc exp self bound)
+ "Convert all free references in @var{exp} to bound references, and
+convert functions to flat closures."
+ (match exp
+ (($ $letk conts body)
+ (receive (conts free) (cc* conts self bound)
+ (receive (body free*) (cc body self bound)
+ (values (build-cps-term ($letk ,conts ,body))
+ (union free free*)))))
+
+ (($ $cont sym src ($ $kargs names syms body))
+ (receive (body free) (cc body self (append syms bound))
+ (values (build-cps-cont (sym src ($kargs names syms ,body)))
+ free)))
+
+ (($ $cont sym src ($ $kentry self tail clauses))
+ (receive (clauses free) (cc* clauses self (list self))
+ (values (build-cps-cont (sym src ($kentry self ,tail ,clauses)))
+ free)))
+
+ (($ $cont sym src ($ $kclause arity body))
+ (receive (body free) (cc body self bound)
+ (values (build-cps-cont (sym src ($kclause ,arity ,body)))
+ free)))
+
+ (($ $cont)
+ ;; Other kinds of continuations don't bind values and don't have
+ ;; bodies.
+ (values exp '()))
+
+ ;; Remove letrec.
+ (($ $letrec names syms funs body)
+ (let ((bound (append bound syms)))
+ (receive (body free) (cc body self bound)
+ (let lp ((in (map list names syms funs))
+ (bindings (lambda (body) body))
+ (body body)
+ (free free))
+ (match in
+ (() (values (bindings body) free))
+ (((name sym ($ $fun meta () fun-body)) . in)
+ (receive (fun-body fun-free) (cc fun-body #f '())
+ (lp in
+ (lambda (body)
+ (let-gensyms (k)
+ (build-cps-term
+ ($letk ((k #f ($kargs (name) (sym) ,(bindings body))))
+ ($continue k
+ ($fun meta fun-free ,fun-body))))))
+ (init-closure #f sym fun-free self bound body)
+ (union free (difference fun-free bound))))))))))
+
+ (($ $continue k ($ $var sym))
+ (convert-free-var sym self bound
+ (lambda (sym)
+ (values (build-cps-term ($continue k ($var sym)))
+ '()))))
+
+ (($ $continue k
+ (or ($ $void)
+ ($ $const)
+ ($ $prim)))
+ (values exp '()))
+
+ (($ $continue k ($ $fun meta () body))
+ (receive (body free) (cc body #f '())
+ (match free
+ (()
+ (values (build-cps-term
+ ($continue k ($fun meta free ,body)))
+ free))
+ (_
+ (values
+ (let-gensyms (kinit v)
+ (build-cps-term
+ ($letk ((kinit #f ($kargs (v) (v)
+ ,(init-closure #f v free self bound
+ (build-cps-term
+ ($continue k ($var v)))))))
+ ($continue kinit ($fun meta free ,body)))))
+ (difference free bound))))))
+
+ (($ $continue k ($ $call proc args))
+ (convert-free-vars (cons proc args) self bound
+ (match-lambda
+ ((proc . args)
+ (values (build-cps-term
+ ($continue k ($call proc args)))
+ '())))))
+
+ (($ $continue k ($ $primcall name args))
+ (convert-free-vars args self bound
+ (lambda (args)
+ (values (build-cps-term
+ ($continue k ($primcall name args)))
+ '()))))
+
+ (($ $continue k ($ $values args))
+ (convert-free-vars args self bound
+ (lambda (args)
+ (values (build-cps-term
+ ($continue k ($values args)))
+ '()))))
+
+ (($ $continue k ($ $prompt escape? tag handler))
+ (convert-free-var
+ tag self bound
+ (lambda (tag)
+ (values (build-cps-term
+ ($continue k ($prompt escape? tag handler)))
+ '()))))
+
+ (_ (error "what" exp))))
+
+;; Convert the slot arguments of 'free-ref' primcalls from symbols to
+;; indices.
+(define (convert-to-indices body free)
+ (define (free-index sym)
+ (or (list-index (cut eq? <> sym) free)
+ (error "free variable not found!" sym free)))
+ (define (visit-term term)
+ (rewrite-cps-term term
+ (($ $letk conts body)
+ ($letk ,(map visit-cont conts) ,(visit-term body)))
+ (($ $continue k ($ $primcall 'free-ref (closure sym)))
+ ,(let-gensyms (idx)
+ (build-cps-term
+ ($letconst (('idx idx (free-index sym)))
+ ($continue k ($primcall 'free-ref (closure idx)))))))
+ (($ $continue k ($ $fun meta free body))
+ ($continue k ($fun meta free ,(convert-to-indices body free))))
+ (($ $continue)
+ ,term)))
+ (define (visit-cont cont)
+ (rewrite-cps-cont cont
+ (($ $cont sym src ($ $kargs names syms body))
+ (sym src ($kargs names syms ,(visit-term body))))
+ (($ $cont sym src ($ $kclause arity body))
+ (sym src ($kclause ,arity ,(visit-cont body))))
+ ;; Other kinds of continuations don't bind values and don't have
+ ;; bodies.
+ (($ $cont)
+ ,cont)))
+
+ (rewrite-cps-cont body
+ (($ $cont sym src ($ $kentry self tail clauses))
+ (sym src ($kentry self ,tail ,(map visit-cont clauses))))))
+
+(define (convert-closures exp)
+ "Convert free reference in @var{exp} to primcalls to @code{free-ref},
+and allocate and initialize flat closures."
+ (match exp
+ (($ $fun meta () body)
+ (receive (body free) (cc body #f '())
+ (unless (null? free)
+ (error "Expected no free vars in toplevel thunk" exp body free))
+ (build-cps-exp
+ ($fun meta free ,(convert-to-indices body free)))))))
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 4/9] RTL language
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (2 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 3/9] Add closure conversion Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 7:49 ` [PATCH 5/9] Add CPS primitives info module Andy Wingo
` (6 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am
* module/language/rtl.scm:
* module/language/rtl/spec.scm: Add a stub RTL language.
---
module/Makefile.am | 5 +++
module/language/rtl.scm | 92 ++++++++++++++++++++++++++++++++++++++++++++
module/language/rtl/spec.scm | 31 +++++++++++++++
3 files changed, 128 insertions(+)
create mode 100644 module/language/rtl.scm
create mode 100644 module/language/rtl/spec.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index 6fd88e6..e2268a8 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -54,6 +54,7 @@ SOURCES = \
language/assembly.scm \
$(TREE_IL_LANG_SOURCES) \
$(CPS_LANG_SOURCES) \
+ $(RTL_LANG_SOURCES) \
$(GLIL_LANG_SOURCES) \
$(ASSEMBLY_LANG_SOURCES) \
$(BYTECODE_LANG_SOURCES) \
@@ -123,6 +124,10 @@ CPS_LANG_SOURCES = \
language/cps/spec.scm \
language/cps/verify.scm
+RTL_LANG_SOURCES = \
+ language/rtl.scm \
+ language/rtl/spec.scm
+
GLIL_LANG_SOURCES = \
language/glil/spec.scm language/glil/compile-assembly.scm
diff --git a/module/language/rtl.scm b/module/language/rtl.scm
new file mode 100644
index 0000000..d217517
--- /dev/null
+++ b/module/language/rtl.scm
@@ -0,0 +1,92 @@
+;;; Register Transfer Language (RTL)
+
+;; Copyright (C) 2013 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
+
+;;; Code:
+
+(define-module (language rtl)
+ #:use-module (ice-9 match)
+ #:use-module ((srfi srfi-1) #:select (fold))
+ #:use-module (system vm instruction)
+ #:re-export (rtl-instruction-list)
+ #:export (rtl-instruction-arity))
+
+(define (compute-rtl-instruction-arity name args)
+ (define (first-word-arity word)
+ (case word
+ ((U8_X24) 0)
+ ((U8_U24) 1)
+ ((U8_L24) 1)
+ ((U8_U8_I16) 2)
+ ((U8_U12_U12) 2)
+ ((U8_U8_U8_U8) 3)))
+ (define (tail-word-arity word)
+ (case word
+ ((U8_U24) 2)
+ ((U8_L24) 2)
+ ((U8_U8_I16) 3)
+ ((U8_U12_U12) 3)
+ ((U8_U8_U8_U8) 4)
+ ((U32) 1)
+ ((I32) 1)
+ ((A32) 1)
+ ((B32) 0)
+ ((N32) 1)
+ ((S32) 1)
+ ((L32) 1)
+ ((LO32) 1)
+ ((X8_U24) 1)
+ ((X8_U12_U12) 2)
+ ((X8_L24) 1)
+ ((B1_X7_L24) 2)
+ ((B1_U7_L24) 3)
+ ((B1_X31) 1)
+ ((B1_X7_U24) 2)))
+ (match args
+ ((arg0 . args)
+ (fold (lambda (arg arity)
+ (+ (tail-word-arity arg) arity))
+ (first-word-arity arg0)
+ args))))
+
+(define *macro-instruction-arities*
+ '((cache-current-module! . (0 . 2))
+ (cached-toplevel-box . (1 . 3))
+ (cached-module-box . (1 . 4))))
+
+(define (compute-rtl-instruction-arities)
+ (let ((table (make-hash-table)))
+ (for-each
+ (match-lambda
+ ;; Put special cases here.
+ ((name op '! . args)
+ (hashq-set! table name
+ (cons 0 (compute-rtl-instruction-arity name args))))
+ ((name op '<- . args)
+ (hashq-set! table name
+ (cons 1 (1- (compute-rtl-instruction-arity name args))))))
+ (rtl-instruction-list))
+ (for-each (match-lambda
+ ((name . arity)
+ (hashq-set! table name arity)))
+ *macro-instruction-arities*)
+ table))
+
+(define *rtl-instruction-arities* (delay (compute-rtl-instruction-arities)))
+
+(define (rtl-instruction-arity name)
+ (hashq-ref (force *rtl-instruction-arities*) name))
diff --git a/module/language/rtl/spec.scm b/module/language/rtl/spec.scm
new file mode 100644
index 0000000..0a8c4ee
--- /dev/null
+++ b/module/language/rtl/spec.scm
@@ -0,0 +1,31 @@
+;;; Register Transfer Language (RTL)
+
+;; Copyright (C) 2013 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
+
+;;; Code:
+
+(define-module (language rtl spec)
+ #:use-module (system base language)
+ #:use-module (ice-9 binary-ports)
+ #:export (rtl))
+
+(define-language rtl
+ #:title "Register Transfer Language"
+ #:compilers '()
+ #:printer (lambda (rtl port) (put-bytevector port rtl))
+ #:reader get-bytevector-all
+ #:for-humans? #f)
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 5/9] Add CPS primitives info module
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (3 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 4/9] RTL language Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 7:49 ` [PATCH 6/9] Add arity-adapting module Andy Wingo
` (5 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am:
* module/language/cps/primitives.scm: New file.
---
module/Makefile.am | 1 +
module/language/cps/primitives.scm | 96 ++++++++++++++++++++++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 module/language/cps/primitives.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index e2268a8..790db8c 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -121,6 +121,7 @@ TREE_IL_LANG_SOURCES = \
CPS_LANG_SOURCES = \
language/cps.scm \
language/cps/closure-conversion.scm \
+ language/cps/primitives.scm \
language/cps/spec.scm \
language/cps/verify.scm
diff --git a/module/language/cps/primitives.scm b/module/language/cps/primitives.scm
new file mode 100644
index 0000000..1c683e2
--- /dev/null
+++ b/module/language/cps/primitives.scm
@@ -0,0 +1,96 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; Information about named primitives, as they appear in $prim and $primcall.
+;;;
+;;; Code:
+
+(define-module (language cps primitives)
+ #:use-module (ice-9 match)
+ #:use-module ((srfi srfi-1) #:select (fold))
+ #:use-module (srfi srfi-26)
+ #:use-module (language rtl)
+ #:export (prim-rtl-instruction
+ branching-primitive?
+ prim-arity
+ ))
+
+(define *rtl-instruction-aliases*
+ '((+ . add) (1+ . add1)
+ (- . sub) (1- . sub1)
+ (* . mul) (/ . div)
+ (quotient . quo) (remainder . rem)
+ (modulo . mod)
+ (define! . define)
+ (vector-set! . vector-set)))
+
+(define *macro-instruction-arities*
+ '((cache-current-module! . (0 . 2))
+ (cached-toplevel-box . (1 . 3))
+ (cached-module-box . (1 . 4))))
+
+(define *branching-primcall-arities*
+ '((null? . (1 . 1))
+ (nil? . (1 . 1))
+ (pair? . (1 . 1))
+ (struct? . (1 . 1))
+ (char? . (1 . 1))
+ (eq? . (1 . 2))
+ (eqv? . (1 . 2))
+ (equal? . (1 . 2))
+ (= . (1 . 2))
+ (< . (1 . 2))
+ (> . (1 . 2))
+ (<= . (1 . 2))
+ (>= . (1 . 2))))
+
+(define (compute-prim-rtl-instructions)
+ (let ((table (make-hash-table)))
+ (for-each
+ (match-lambda ((inst . _) (hashq-set! table inst inst)))
+ (rtl-instruction-list))
+ (for-each
+ (match-lambda ((prim . inst) (hashq-set! table prim inst)))
+ *rtl-instruction-aliases*)
+ (for-each
+ (match-lambda ((inst . arity) (hashq-set! table inst inst)))
+ *macro-instruction-arities*)
+ table))
+
+(define *prim-rtl-instructions* (delay (compute-prim-rtl-instructions)))
+
+;; prim -> rtl-instruction | #f
+(define (prim-rtl-instruction name)
+ (hashq-ref (force *prim-rtl-instructions*) name))
+
+(define (branching-primitive? name)
+ (and (assq name *branching-primcall-arities*) #t))
+
+(define *prim-arities* (make-hash-table))
+
+(define (prim-arity name)
+ (or (hashq-ref *prim-arities* name)
+ (let ((arity (cond
+ ((prim-rtl-instruction name) => rtl-instruction-arity)
+ ((assq name *branching-primcall-arities*) => cdr)
+ (else
+ (error "Primitive of unknown arity" name)))))
+ (hashq-set! *prim-arities* name arity)
+ arity)))
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 6/9] Add arity-adapting module
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (4 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 5/9] Add CPS primitives info module Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 21:08 ` Ludovic Courtès
2013-08-29 7:49 ` [PATCH 7/9] Add pass to reify primcalls without corresponding VM ops Andy Wingo
` (4 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am:
* module/language/cps/arities.scm: New module. Adapts call and return
arities, especially for primcalls.
---
module/Makefile.am | 1 +
module/language/cps/arities.scm | 152 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 153 insertions(+)
create mode 100644 module/language/cps/arities.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index 790db8c..bcef4bf 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -120,6 +120,7 @@ TREE_IL_LANG_SOURCES = \
CPS_LANG_SOURCES = \
language/cps.scm \
+ language/cps/arities.scm \
language/cps/closure-conversion.scm \
language/cps/primitives.scm \
language/cps/spec.scm \
diff --git a/module/language/cps/arities.scm b/module/language/cps/arities.scm
new file mode 100644
index 0000000..b697ec0
--- /dev/null
+++ b/module/language/cps/arities.scm
@@ -0,0 +1,152 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; A pass to adapt expressions to the arities of their continuations,
+;;; and to rewrite some tail expressions as primcalls to "return".
+;;;
+;;; Code:
+
+(define-module (language cps arities)
+ #:use-module (ice-9 match)
+ #:use-module ((srfi srfi-1) #:select (fold))
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:use-module (language cps dfg)
+ #:use-module (language cps primitives)
+ #:export (fix-arities))
+
+(define (fix-clause-arities clause)
+ (let ((conts (build-local-cont-table clause))
+ (ktail (match clause
+ (($ $cont _ _ ($ $kentry _ ($ $cont ktail) _)) ktail))))
+ (define (visit-term term)
+ (rewrite-cps-term term
+ (($ $letk conts body)
+ ($letk ,(map visit-cont conts) ,(visit-term body)))
+ (($ $letrec names syms funs body)
+ ($letrec names syms (map fix-arities funs) ,(visit-term body)))
+ (($ $continue k exp)
+ ,(visit-exp k exp))))
+
+ (define (adapt-exp nvals k exp)
+ (match nvals
+ (0
+ (rewrite-cps-term (lookup-cont k conts)
+ (($ $ktail)
+ ,(let-gensyms (kvoid kunspec unspec)
+ (build-cps-term
+ ($letk* ((kunspec #f ($kargs (unspec) (unspec)
+ ($continue k
+ ($primcall 'return (unspec)))))
+ (kvoid #f ($kargs () ()
+ ($continue kunspec ($void)))))
+ ($continue kvoid ,exp)))))
+ (($ $ktrunc ($ $arity () () #f () #f) kseq)
+ ($continue kseq ,exp))
+ (($ $kargs () () _)
+ ($continue k ,exp))
+ (_
+ ,(let-gensyms (k*)
+ (build-cps-term
+ ($letk ((k* #f ($kargs () () ($continue k ($void)))))
+ ($continue k* ,exp)))))))
+ (1
+ (let ((drop-result
+ (lambda (kseq)
+ (let-gensyms (k* drop)
+ (build-cps-term
+ ($letk ((k* #f ($kargs ('drop) (drop)
+ ($continue kseq ($values ())))))
+ ($continue k* ,exp)))))))
+ (rewrite-cps-term (lookup-cont k conts)
+ (($ $ktail)
+ ,(rewrite-cps-term exp
+ (($var sym)
+ ($continue ktail ($primcall 'return (sym))))
+ (_
+ ,(let-gensyms (k* v)
+ (build-cps-term
+ ($letk ((k* #f ($kargs (v) (v)
+ ($continue k
+ ($primcall 'return (v))))))
+ ($continue k* ,exp)))))))
+ (($ $ktrunc ($ $arity () () #f () #f) kseq)
+ ,(drop-result kseq))
+ (($ $kargs () () _)
+ ,(drop-result k))
+ (_
+ ($continue k ,exp)))))))
+
+ (define (visit-exp k exp)
+ (rewrite-cps-term exp
+ ((or ($ $void)
+ ($ $const)
+ ($ $prim)
+ ($ $var))
+ ,(adapt-exp 1 k exp))
+ (($ $fun)
+ ,(adapt-exp 1 k (fix-arities exp)))
+ (($ $call)
+ ;; In general, calls have unknown return arity. For that
+ ;; reason every non-tail call has an implicit adaptor
+ ;; continuation to adapt the return to the target
+ ;; continuation, and we don't need to do any adapting here.
+ ($continue k ,exp))
+ (($ $primcall 'return (arg))
+ ;; Primcalls to return are in tail position.
+ ($continue ktail ,exp))
+ (($ $primcall (? (lambda (name)
+ (and (not (prim-rtl-instruction name))
+ (not (branching-primitive? name))))))
+ ($continue k ,exp))
+ (($ $primcall name args)
+ ,(match (prim-arity name)
+ ((out . in)
+ (if (= in (length args))
+ (adapt-exp out k exp)
+ (let-gensyms (k* p*)
+ (build-cps-term
+ ($letk ((k* #f ($kargs ('prim) (p*)
+ ($continue k ($call p* args)))))
+ ($continue k* ($prim name)))))))))
+ (($ $values)
+ ;; Values nodes are inserted by CPS optimization passes, so
+ ;; we assume they are correct.
+ ($continue k ,exp))
+ (($ $prompt)
+ ($continue k ,exp))))
+
+ (define (visit-cont cont)
+ (rewrite-cps-cont cont
+ (($ $cont sym src ($ $kargs names syms body))
+ (sym src ($kargs names syms ,(visit-term body))))
+ (($ $cont sym src ($ $kclause arity body))
+ (sym src ($kclause ,arity ,(visit-cont body))))
+ (($ $cont)
+ ,cont)))
+
+ (rewrite-cps-cont clause
+ (($ $cont sym src ($ $kentry self tail clauses))
+ (sym src ($kentry self ,tail ,(map visit-cont clauses)))))))
+
+(define (fix-arities fun)
+ (rewrite-cps-exp fun
+ (($ $fun meta free body)
+ ($fun meta free ,(fix-clause-arities body)))))
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 6/9] Add arity-adapting module
2013-08-29 7:49 ` [PATCH 6/9] Add arity-adapting module Andy Wingo
@ 2013-08-29 21:08 ` Ludovic Courtès
2013-08-29 22:26 ` Mark H Weaver
0 siblings, 1 reply; 17+ messages in thread
From: Ludovic Courtès @ 2013-08-29 21:08 UTC (permalink / raw)
To: guile-devel
Andy Wingo <wingo@pobox.com> skribis:
> +;;; Commentary:
> +;;;
> +;;; A pass to adapt expressions to the arities of their continuations,
> +;;; and to rewrite some tail expressions as primcalls to "return".
> +;;;
> +;;; Code:
[...]
> + (define (adapt-exp nvals k exp)
> + (match nvals
> + (0
> + (rewrite-cps-term (lookup-cont k conts)
> + (($ $ktail)
> + ,(let-gensyms (kvoid kunspec unspec)
> + (build-cps-term
> + ($letk* ((kunspec #f ($kargs (unspec) (unspec)
> + ($continue k
> + ($primcall 'return (unspec)))))
> + (kvoid #f ($kargs () ()
> + ($continue kunspec ($void)))))
> + ($continue kvoid ,exp)))))
[...]
> + (let ((drop-result
> + (lambda (kseq)
> + (let-gensyms (k* drop)
> + (build-cps-term
> + ($letk ((k* #f ($kargs ('drop) (drop)
> + ($continue kseq ($values ())))))
> + ($continue k* ,exp)))))))
Are ($void) and ($values ()) both equivalent to *unspecified*?
> + (rewrite-cps-term (lookup-cont k conts)
> + (($ $ktail)
> + ,(rewrite-cps-term exp
> + (($var sym)
> + ($continue ktail ($primcall 'return (sym))))
> + (_
> + ,(let-gensyms (k* v)
> + (build-cps-term
> + ($letk ((k* #f ($kargs (v) (v)
> + ($continue k
> + ($primcall 'return (v))))))
> + ($continue k* ,exp)))))))
> + (($ $ktrunc ($ $arity () () #f () #f) kseq)
> + ,(drop-result kseq))
> + (($ $kargs () () _)
> + ,(drop-result k))
> + (_
> + ($continue k ,exp)))))))
Perhaps that happens in the Tree-IL->CPS pass, but is there/could there
be an effect analysis when truncating multiple-value returns?
Such that (f (values 1 2 3)) reduces to (f 1).
Ludo’.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 6/9] Add arity-adapting module
2013-08-29 21:08 ` Ludovic Courtès
@ 2013-08-29 22:26 ` Mark H Weaver
2013-08-31 7:45 ` Andy Wingo
0 siblings, 1 reply; 17+ messages in thread
From: Mark H Weaver @ 2013-08-29 22:26 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: guile-devel
ludo@gnu.org (Ludovic Courtès) writes:
> Andy Wingo <wingo@pobox.com> skribis:
>
>> +;;; Commentary:
>> +;;;
>> +;;; A pass to adapt expressions to the arities of their continuations,
>> +;;; and to rewrite some tail expressions as primcalls to "return".
>> +;;;
>> +;;; Code:
>
> [...]
>
>> + (define (adapt-exp nvals k exp)
>> + (match nvals
>> + (0
>> + (rewrite-cps-term (lookup-cont k conts)
>> + (($ $ktail)
>> + ,(let-gensyms (kvoid kunspec unspec)
>> + (build-cps-term
>> + ($letk* ((kunspec #f ($kargs (unspec) (unspec)
>> + ($continue k
>> + ($primcall 'return (unspec)))))
>> + (kvoid #f ($kargs () ()
>> + ($continue kunspec ($void)))))
>> + ($continue kvoid ,exp)))))
>
> [...]
>
>> + (let ((drop-result
>> + (lambda (kseq)
>> + (let-gensyms (k* drop)
>> + (build-cps-term
>> + ($letk ((k* #f ($kargs ('drop) (drop)
>> + ($continue kseq ($values ())))))
>> + ($continue k* ,exp)))))))
>
> Are ($void) and ($values ()) both equivalent to *unspecified*?
No. ($void) passes a single value (*unspecified*), to the continuation.
($values ()) passes zero values to the continuation, like (values).
>> + (rewrite-cps-term (lookup-cont k conts)
>> + (($ $ktail)
>> + ,(rewrite-cps-term exp
>> + (($var sym)
>> + ($continue ktail ($primcall 'return (sym))))
>> + (_
>> + ,(let-gensyms (k* v)
>> + (build-cps-term
>> + ($letk ((k* #f ($kargs (v) (v)
>> + ($continue k
>> + ($primcall 'return (v))))))
>> + ($continue k* ,exp)))))))
>> + (($ $ktrunc ($ $arity () () #f () #f) kseq)
>> + ,(drop-result kseq))
>> + (($ $kargs () () _)
>> + ,(drop-result k))
>> + (_
>> + ($continue k ,exp)))))))
>
> Perhaps that happens in the Tree-IL->CPS pass, but is there/could there
> be an effect analysis when truncating multiple-value returns?
>
> Such that (f (values 1 2 3)) reduces to (f 1).
I guess this is a question for Andy, but some experimentation suggests
that this transformation is already done by an earlier optimization pass
in tree-il.
Regards,
Mark
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 6/9] Add arity-adapting module
2013-08-29 22:26 ` Mark H Weaver
@ 2013-08-31 7:45 ` Andy Wingo
0 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-31 7:45 UTC (permalink / raw)
To: Mark H Weaver; +Cc: Ludovic Courtès, guile-devel
On Fri 30 Aug 2013 00:26, Mark H Weaver <mhw@netris.org> writes:
> ludo@gnu.org (Ludovic Courtès) writes:
>
>> Perhaps that happens in the Tree-IL->CPS pass, but is there/could there
>> be an effect analysis when truncating multiple-value returns?
>>
>> Such that (f (values 1 2 3)) reduces to (f 1).
>
> I guess this is a question for Andy, but some experimentation suggests
> that this transformation is already done by an earlier optimization pass
> in tree-il.
Yes, I think it's peval that does this.
scheme@(guile-user)> ,optimize (f (values 1 2 3))
$1 = (f 1)
Andy
--
http://wingolog.org/
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 7/9] Add pass to reify primcalls without corresponding VM ops
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (5 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 6/9] Add arity-adapting module Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 7:49 ` [PATCH 8/9] Add CPS -> RTL compiler Andy Wingo
` (3 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am:
* module/language/cps/reify-primitives.scm: New pass.
---
module/Makefile.am | 1 +
module/language/cps/reify-primitives.scm | 117 +++++++++++++++++++++++++++++++
2 files changed, 118 insertions(+)
create mode 100644 module/language/cps/reify-primitives.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index bcef4bf..d7e524b 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -123,6 +123,7 @@ CPS_LANG_SOURCES = \
language/cps/arities.scm \
language/cps/closure-conversion.scm \
language/cps/primitives.scm \
+ language/cps/reify-primitives.scm \
language/cps/spec.scm \
language/cps/verify.scm
diff --git a/module/language/cps/reify-primitives.scm b/module/language/cps/reify-primitives.scm
new file mode 100644
index 0000000..ebd2da0
--- /dev/null
+++ b/module/language/cps/reify-primitives.scm
@@ -0,0 +1,117 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; A pass to reify lone $prim's that were never folded into a
+;;; $primcall, and $primcall's to primitives that don't have a
+;;; corresponding VM op.
+;;;
+;;; Code:
+
+(define-module (language cps reify-primitives)
+ #:use-module (ice-9 match)
+ #:use-module (language cps)
+ #:use-module (language cps dfg)
+ #:use-module (language cps primitives)
+ #:use-module (language rtl)
+ #:export (reify-primitives))
+
+(define (module-box src module name public? bound? val-proc)
+ (let-gensyms (module-sym name-sym public?-sym bound?-sym kbox box)
+ (build-cps-term
+ ($letconst (('module module-sym module)
+ ('name name-sym name)
+ ('public? public?-sym public?)
+ ('bound? bound?-sym bound?))
+ ($letk ((kbox src ($kargs ('box) (box) ,(val-proc box))))
+ ($continue kbox
+ ($primcall 'cached-module-box
+ (module-sym name-sym public?-sym bound?-sym))))))))
+
+(define (primitive-ref name k)
+ (module-box #f '(guile) name #f #t
+ (lambda (box)
+ (build-cps-term
+ ($continue k ($primcall 'box-ref (box)))))))
+
+(define (reify-clause ktail)
+ (let-gensyms (kclause kbody wna false str eol kthrow throw)
+ (build-cps-cont
+ (kclause #f ($kclause ('() '() #f '() #f)
+ (kbody
+ #f
+ ($kargs () ()
+ ($letconst (('wna wna 'wrong-number-of-args)
+ ('false false #f)
+ ('str str "Wrong number of arguments")
+ ('eol eol '()))
+ ($letk ((kthrow
+ #f
+ ($kargs ('throw) (throw)
+ ($continue ktail
+ ($call throw
+ (wna false str eol false))))))
+ ,(primitive-ref 'throw kthrow))))))))))
+
+;; FIXME: Operate on one function at a time, for efficiency.
+(define (reify-primitives fun)
+ (let ((conts (build-cont-table fun)))
+ (define (visit-fun term)
+ (rewrite-cps-exp term
+ (($ $fun meta free body)
+ ($fun meta free ,(visit-cont body)))))
+ (define (visit-cont cont)
+ (rewrite-cps-cont cont
+ (($ $cont sym src ($ $kargs names syms body))
+ (sym src ($kargs names syms ,(visit-term body))))
+ (($ $cont sym src ($ $kentry self (and tail ($ $cont ktail)) ()))
+ ;; A case-lambda with no clauses. Reify a clause.
+ (sym src ($kentry self ,tail (,(reify-clause ktail)))))
+ (($ $cont sym src ($ $kentry self tail clauses))
+ (sym src ($kentry self ,tail ,(map visit-cont clauses))))
+ (($ $cont sym src ($ $kclause arity body))
+ (sym src ($kclause ,arity ,(visit-cont body))))
+ (($ $cont)
+ ,cont)))
+ (define (visit-term term)
+ (rewrite-cps-term term
+ (($ $letk conts body)
+ ($letk ,(map visit-cont conts) ,(visit-term body)))
+ (($ $continue k exp)
+ ,(match exp
+ (($ $prim name)
+ (match (lookup-cont k conts)
+ (($ $kargs (_)) (primitive-ref name k))
+ (_ (build-cps-term ($continue k ($void))))))
+ (($ $fun)
+ (build-cps-term ($continue k ,(visit-fun exp))))
+ (($ $primcall name args)
+ (cond
+ ((or (prim-rtl-instruction name) (branching-primitive? name))
+ ;; Assume arities are correct.
+ term)
+ (else
+ (let-gensyms (k* v)
+ (build-cps-term
+ ($letk ((k* #f ($kargs (v) (v)
+ ($continue k ($call v args)))))
+ ,(primitive-ref name k*)))))))
+ (_ term)))))
+
+ (visit-fun fun)))
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 8/9] Add CPS -> RTL compiler
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (6 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 7/9] Add pass to reify primcalls without corresponding VM ops Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 7:49 ` [PATCH 9/9] Add contification pass Andy Wingo
` (2 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am:
* module/language/cps/compile-rtl.scm:
* module/language/cps/dfg.scm:
* module/language/cps/slot-allocation.scm: New modules.
* module/language/cps/spec.scm: Register the compiler.
* test-suite/Makefile.am:
* test-suite/tests/rtl-compilation.test: Add tests.
---
module/Makefile.am | 3 +
module/language/cps/compile-rtl.scm | 371 +++++++++++++++++++++++++++
module/language/cps/dfg.scm | 432 ++++++++++++++++++++++++++++++++
module/language/cps/slot-allocation.scm | 419 +++++++++++++++++++++++++++++++
module/language/cps/spec.scm | 3 +-
test-suite/Makefile.am | 1 +
test-suite/tests/rtl-compilation.test | 200 +++++++++++++++
7 files changed, 1428 insertions(+), 1 deletion(-)
create mode 100644 module/language/cps/compile-rtl.scm
create mode 100644 module/language/cps/dfg.scm
create mode 100644 module/language/cps/slot-allocation.scm
create mode 100644 test-suite/tests/rtl-compilation.test
diff --git a/module/Makefile.am b/module/Makefile.am
index d7e524b..5a0ff69 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -122,8 +122,11 @@ CPS_LANG_SOURCES = \
language/cps.scm \
language/cps/arities.scm \
language/cps/closure-conversion.scm \
+ language/cps/compile-rtl.scm \
+ language/cps/dfg.scm \
language/cps/primitives.scm \
language/cps/reify-primitives.scm \
+ language/cps/slot-allocation.scm \
language/cps/spec.scm \
language/cps/verify.scm
diff --git a/module/language/cps/compile-rtl.scm b/module/language/cps/compile-rtl.scm
new file mode 100644
index 0000000..9277adf
--- /dev/null
+++ b/module/language/cps/compile-rtl.scm
@@ -0,0 +1,371 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; Compiling CPS to RTL. The result is in the RTL language, which
+;;; happens to be an ELF image as a bytecode.
+;;;
+;;; Code:
+
+(define-module (language cps compile-rtl)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (language cps)
+ #:use-module (language cps arities)
+ #:use-module (language cps closure-conversion)
+ #:use-module (language cps dfg)
+ #:use-module (language cps primitives)
+ #:use-module (language cps reify-primitives)
+ #:use-module (language cps slot-allocation)
+ #:use-module (system vm assembler)
+ #:export (compile-rtl))
+
+;; TODO: Source info, local var names. Needs work in the linker and the
+;; debugger.
+
+(define (kw-arg-ref args kw default)
+ (match (memq kw args)
+ ((_ val . _) val)
+ (_ default)))
+
+(define (optimize exp opts)
+ (define (run-pass exp pass kw default)
+ (if (kw-arg-ref opts kw default)
+ (pass exp)
+ exp))
+
+ ;; Calls to source-to-source optimization passes go here.
+ (let* ()
+ ;; Passes that are needed:
+ ;;
+ ;; * Contification: turning $letrec-bound $funs into $letk-bound $conts.
+ ;;
+ ;; * Abort contification: turning abort primcalls into continuation
+ ;; calls, and eliding prompts if possible.
+ ;;
+ ;; * Common subexpression elimination. Desperately needed. Requires
+ ;; effects analysis.
+ ;;
+ ;; * Loop peeling. Unrolls the first round through a loop if the
+ ;; loop has effects that CSE can work on. Requires effects
+ ;; analysis. When run before CSE, loop peeling is the equivalent
+ ;; of loop-invariant code motion (LICM).
+ ;;
+ ;; * Generic simplification pass, to be run as needed. Used to
+ ;; "clean up", both on the original raw input and after specific
+ ;; optimization passes.
+
+ exp))
+
+(define (visit-funs proc exp)
+ (match exp
+ (($ $continue _ exp)
+ (visit-funs proc exp))
+
+ (($ $fun meta free body)
+ (proc exp)
+ (visit-funs proc body))
+
+ (($ $letk conts body)
+ (visit-funs proc body)
+ (for-each (lambda (cont) (visit-funs proc cont)) conts))
+
+ (($ $cont sym src ($ $kargs names syms body))
+ (visit-funs proc body))
+
+ (($ $cont sym src ($ $kclause arity body))
+ (visit-funs proc body))
+
+ (($ $cont sym src ($ $kentry self tail clauses))
+ (for-each (lambda (clause) (visit-funs proc clause)) clauses))
+
+ (_ (values))))
+
+(define (emit-rtl-sequence asm exp allocation nlocals cont-table)
+ (define (slot sym)
+ (lookup-slot sym allocation))
+
+ (define (constant sym)
+ (lookup-constant-value sym allocation))
+
+ (define (emit-rtl label k exp next-label)
+ (define (maybe-mov dst src)
+ (unless (= dst src)
+ (emit-mov asm dst src)))
+
+ (define (maybe-jump label)
+ (unless (eq? label next-label)
+ (emit-br asm label)))
+
+ (define (maybe-load-constant slot src)
+ (call-with-values (lambda ()
+ (lookup-maybe-constant-value src allocation))
+ (lambda (has-const? val)
+ (and has-const?
+ (begin
+ (emit-load-constant asm slot val)
+ #t)))))
+
+ (define (emit-tail)
+ ;; There are only three kinds of expressions in tail position:
+ ;; tail calls, multiple-value returns, and single-value returns.
+ (match exp
+ (($ $call proc args)
+ (for-each (match-lambda
+ ((src . dst) (emit-mov asm dst src)))
+ (lookup-parallel-moves label allocation))
+ (let ((tail-slots (cdr (iota (1+ (length args))))))
+ (for-each maybe-load-constant tail-slots args))
+ (emit-tail-call asm (1+ (length args))))
+ (($ $values args)
+ (let ((tail-slots (cdr (iota (1+ (length args))))))
+ (for-each (match-lambda
+ ((src . dst) (emit-mov asm dst src)))
+ (lookup-parallel-moves label allocation))
+ (for-each maybe-load-constant tail-slots args))
+ (emit-reset-frame asm (1+ (length args)))
+ (emit-return-values asm))
+ (($ $primcall 'return (arg))
+ (emit-return asm (slot arg)))))
+
+ (define (emit-val sym)
+ (let ((dst (slot sym)))
+ (match exp
+ (($ $var sym)
+ (maybe-mov dst (slot sym)))
+ (($ $void)
+ (when dst
+ (emit-load-constant asm dst *unspecified*)))
+ (($ $const exp)
+ (when dst
+ (emit-load-constant asm dst exp)))
+ (($ $fun meta () ($ $cont k))
+ (emit-load-static-procedure asm dst k))
+ (($ $fun meta free ($ $cont k))
+ (emit-make-closure asm dst k (length free)))
+ (($ $call proc args)
+ (let ((proc-slot (lookup-call-proc-slot label allocation))
+ (nargs (length args)))
+ (or (maybe-load-constant proc-slot proc)
+ (maybe-mov proc-slot (slot proc)))
+ (let lp ((n (1+ proc-slot)) (args args))
+ (match args
+ (()
+ (emit-call asm proc-slot (+ nargs 1))
+ (emit-receive asm dst proc-slot nlocals))
+ ((arg . args)
+ (or (maybe-load-constant n arg)
+ (maybe-mov n (slot arg)))
+ (lp (1+ n) args))))))
+ (($ $primcall 'current-module)
+ (emit-current-module asm dst))
+ (($ $primcall 'cached-toplevel-box (scope name bound?))
+ (emit-cached-toplevel-box asm dst (constant scope) (constant name)
+ (constant bound?)))
+ (($ $primcall 'cached-module-box (mod name public? bound?))
+ (emit-cached-module-box asm dst (constant mod) (constant name)
+ (constant public?) (constant bound?)))
+ (($ $primcall 'resolve (name bound?))
+ (emit-resolve asm dst (constant bound?) (slot name)))
+ (($ $primcall 'free-ref (closure idx))
+ (emit-free-ref asm dst (slot closure) (constant idx)))
+ (($ $primcall name args)
+ ;; FIXME: Inline all the cases.
+ (let ((inst (prim-rtl-instruction name)))
+ (emit-text asm `((,inst ,dst ,@(map slot args))))))
+ (($ $values (arg))
+ (or (maybe-load-constant dst arg)
+ (maybe-mov dst (slot arg))))
+ (($ $prompt escape? tag handler)
+ (emit-prompt asm escape? tag handler)))
+ (maybe-jump k)))
+
+ (define (emit-vals syms)
+ (match exp
+ (($ $primcall name args)
+ (error "unimplemented primcall in values context" name))
+ (($ $values args)
+ (for-each (match-lambda
+ ((src . dst) (emit-mov asm dst src)))
+ (lookup-parallel-moves label allocation))
+ (for-each maybe-load-constant (map slot syms) args)))
+ (maybe-jump k))
+
+ (define (emit-seq)
+ (match exp
+ (($ $primcall 'cache-current-module! (sym scope))
+ (emit-cache-current-module! asm (slot sym) (constant scope)))
+ (($ $primcall 'free-set! (closure idx value))
+ (emit-free-set! asm (slot closure) (slot value) (constant idx)))
+ (($ $primcall 'box-set! (box value))
+ (emit-box-set! asm (slot box) (slot value)))
+ (($ $primcall 'struct-set! (struct index value))
+ (emit-struct-set! asm (slot struct) (slot index) (slot value)))
+ (($ $primcall 'vector-set! (vector index value))
+ (emit-vector-set asm (slot vector) (slot index) (slot value)))
+ (($ $primcall 'set-car! (pair value))
+ (emit-set-car! asm (slot pair) (slot value)))
+ (($ $primcall 'set-cdr! (pair value))
+ (emit-set-cdr! asm (slot pair) (slot value)))
+ (($ $primcall 'define! (sym value))
+ (emit-define asm (slot sym) (slot value)))
+ (($ $primcall name args)
+ (error "unhandled primcall in seq context" name))
+ (($ $values ()) #f))
+ (maybe-jump k))
+
+ (define (emit-test kt kf)
+ (define (unary op sym)
+ (cond
+ ((eq? kt next-label)
+ (op asm (slot sym) #t kf))
+ (else
+ (op asm (slot sym) #f kt)
+ (maybe-jump kf))))
+ (define (binary op a b)
+ (cond
+ ((eq? kt next-label)
+ (op asm (slot a) (slot b) #t kf))
+ (else
+ (op asm (slot a) (slot b) #f kt)
+ (maybe-jump kf))))
+ (match exp
+ (($ $var sym) (unary emit-br-if-true sym))
+ (($ $primcall 'null? (a)) (unary emit-br-if-null a))
+ (($ $primcall 'nil? (a)) (unary emit-br-if-nil a))
+ (($ $primcall 'pair? (a)) (unary emit-br-if-pair a))
+ (($ $primcall 'struct? (a)) (unary emit-br-if-struct a))
+ (($ $primcall 'char? (a)) (unary emit-br-if-char a))
+ ;; Add TC7 tests here
+ (($ $primcall 'eq? (a b)) (binary emit-br-if-eq a b))
+ (($ $primcall 'eq? (a b)) (binary emit-br-if-eq a b))
+ (($ $primcall 'eqv? (a b)) (binary emit-br-if-eqv a b))
+ (($ $primcall 'equal? (a b)) (binary emit-br-if-equal a b))
+ (($ $primcall '< (a b)) (binary emit-br-if-< a b))
+ (($ $primcall '<= (a b)) (binary emit-br-if-<= a b))
+ (($ $primcall '= (a b)) (binary emit-br-if-= a b))
+ (($ $primcall '>= (a b)) (binary emit-br-if-<= b a))
+ (($ $primcall '> (a b)) (binary emit-br-if-< b a))))
+
+ (define (emit-trunc nreq rest? k)
+ (match exp
+ (($ $call proc args)
+ (let ((proc-slot (lookup-call-proc-slot label allocation))
+ (nargs (length args)))
+ (or (maybe-load-constant proc-slot proc)
+ (maybe-mov proc-slot (slot proc)))
+ (let lp ((n (1+ proc-slot)) (args args))
+ (match args
+ (()
+ (emit-call asm proc-slot (+ nargs 1))
+ (emit-receive-values asm proc-slot nreq)
+ (when rest?
+ (emit-bind-rest asm (+ proc-slot 1 nreq)))
+ (for-each (match-lambda
+ ((src . dst) (emit-mov asm dst src)))
+ (lookup-parallel-moves label allocation))
+ (emit-reset-frame asm nlocals))
+ ((arg . args)
+ (or (maybe-load-constant n arg)
+ (maybe-mov n (slot arg)))
+ (lp (1+ n) args)))))))
+ (maybe-jump k))
+
+ (match (lookup-cont k cont-table)
+ (($ $ktail) (emit-tail))
+ (($ $kargs (name) (sym)) (emit-val sym))
+ (($ $kargs () ()) (emit-seq))
+ (($ $kargs names syms) (emit-vals syms))
+ (($ $kargs (name) (sym)) (emit-val sym))
+ (($ $kif kt kf) (emit-test kt kf))
+ (($ $ktrunc ($ $arity req () rest () #f) k)
+ (emit-trunc (length req) (and rest #t) k))))
+
+ (define (collect-exps k src cont tail)
+ (define (find-exp k src term)
+ (match term
+ (($ $continue exp-k exp)
+ (cons (list k src exp-k exp) tail))
+ (($ $letk conts body)
+ (find-exp k src body))))
+ (match cont
+ (($ $kargs names syms body)
+ (find-exp k src body))
+ (_ tail)))
+
+ (let lp ((exps (reverse (fold-local-conts collect-exps '() exp))))
+ (match exps
+ (() #t)
+ (((k src exp-k exp) . exps)
+ (let ((next-label (match exps
+ (((k . _) . _) k)
+ (() #f))))
+ (emit-label asm k)
+ (emit-rtl k exp-k exp next-label)
+ (lp exps))))))
+
+(define (compile-fun f asm)
+ (let ((allocation (allocate-slots f))
+ (cont-table (match f
+ (($ $fun meta free body)
+ (build-local-cont-table body)))))
+ (define (emit-fun-clause clause alternate)
+ (match clause
+ (($ $cont k src
+ ($ $kclause ($ $arity req opt rest kw allow-other-keys?)
+ body))
+ (let ((kw-indices (map (match-lambda
+ ((key name sym)
+ (cons key (lookup-slot sym allocation))))
+ kw))
+ (nlocals (lookup-nlocals k allocation)))
+ (emit-label asm k)
+ (emit-begin-kw-arity asm req opt rest kw-indices allow-other-keys?
+ nlocals alternate)
+ (emit-rtl-sequence asm body allocation nlocals cont-table)
+ (emit-end-arity asm)))))
+
+ (define (emit-fun-clauses clauses)
+ (match clauses
+ ((clause . clauses)
+ (let ((kalternate (match clauses
+ (() #f)
+ ((($ $cont k) . _) k))))
+ (emit-fun-clause clause kalternate)
+ (when kalternate
+ (emit-fun-clauses clauses))))))
+
+ (match f
+ (($ $fun meta free ($ $cont k src ($ $kentry self tail clauses)))
+ (emit-begin-program asm k (or meta '()))
+ (emit-fun-clauses clauses)
+ (emit-end-program asm)))))
+
+(define (compile-rtl exp env opts)
+ (let* ((exp (fix-arities exp))
+ (exp (optimize exp opts))
+ (exp (convert-closures exp))
+ (exp (reify-primitives exp))
+ (asm (make-assembler)))
+ (visit-funs (lambda (fun)
+ (compile-fun fun asm))
+ exp)
+ (values (link-assembly asm #:page-aligned? (kw-arg-ref opts #:to-file? #f))
+ env
+ env)))
diff --git a/module/language/cps/dfg.scm b/module/language/cps/dfg.scm
new file mode 100644
index 0000000..0826451
--- /dev/null
+++ b/module/language/cps/dfg.scm
@@ -0,0 +1,432 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; Many passes rely on a local or global static analysis of a function.
+;;; This module implements a simple data-flow graph (DFG) analysis,
+;;; tracking the definitions and uses of variables and continuations.
+;;; It also builds a table of continuations and parent links, to be able
+;;; to easily determine if one continuation is in the scope of another,
+;;; and to get to the expression inside a continuation.
+;;;
+;;; Note that the data-flow graph of continuation labels is a
+;;; control-flow graph.
+;;;
+;;; We currently don't expose details of the DFG type outside this
+;;; module, preferring to only expose accessors. That may change in the
+;;; future but it seems to work for now.
+;;;
+;;; Code:
+
+(define-module (language cps dfg)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:export (build-cont-table
+ build-local-cont-table
+ lookup-cont
+
+ compute-dfg
+ dfg-cont-table
+ lookup-def
+ lookup-uses
+ find-call
+ call-expression
+ find-expression
+ find-defining-expression
+ find-constant-value
+ lift-definition!
+ variable-used-in?
+ constant-needs-allocation?
+ dead-after-def?
+ dead-after-use?
+ branch?
+ find-other-branches
+ dead-after-branch?
+ lookup-bound-syms))
+
+(define (build-cont-table fun)
+ (fold-conts (lambda (k src cont table)
+ (hashq-set! table k cont)
+ table)
+ (make-hash-table)
+ fun))
+
+(define (build-local-cont-table cont)
+ (fold-local-conts (lambda (k src cont table)
+ (hashq-set! table k cont)
+ table)
+ (make-hash-table)
+ cont))
+
+(define (lookup-cont sym conts)
+ (let ((res (hashq-ref conts sym)))
+ (unless res
+ (error "Unknown continuation!" sym (hash-fold acons '() conts)))
+ res))
+
+;; Data-flow graph for CPS: both for values and continuations.
+(define-record-type $dfg
+ (make-dfg conts use-maps uplinks)
+ dfg?
+ ;; hash table of sym -> $kargs, $kif, etc
+ (conts dfg-cont-table)
+ ;; hash table of sym -> $use-map
+ (use-maps dfg-use-maps)
+ ;; hash table of sym -> $parent-link
+ (uplinks dfg-uplinks))
+
+(define-record-type $use-map
+ (make-use-map sym def uses)
+ use-map?
+ (sym use-map-sym)
+ (def use-map-def)
+ (uses use-map-uses set-use-map-uses!))
+
+(define-record-type $uplink
+ (make-uplink parent level)
+ uplink?
+ (parent uplink-parent)
+ (level uplink-level))
+
+(define (visit-fun fun conts use-maps uplinks global?)
+ (define (add-def! sym def-k)
+ (unless def-k
+ (error "Term outside labelled continuation?"))
+ (hashq-set! use-maps sym (make-use-map sym def-k '())))
+
+ (define (add-use! sym use-k)
+ (match (hashq-ref use-maps sym)
+ (#f (error "Symbol out of scope?" sym))
+ ((and use-map ($ $use-map sym def uses))
+ (set-use-map-uses! use-map (cons use-k uses)))))
+
+ (define (link-parent! k parent)
+ (match (hashq-ref uplinks parent)
+ (($ $uplink _ level)
+ (hashq-set! uplinks k (make-uplink parent (1+ level))))))
+
+ (define (visit exp exp-k)
+ (define (def! sym)
+ (add-def! sym exp-k))
+ (define (use! sym)
+ (add-use! sym exp-k))
+ (define (recur exp)
+ (visit exp exp-k))
+ (match exp
+ (($ $letk (($ $cont k src cont) ...) body)
+ ;; Set up recursive environment before visiting cont bodies.
+ (for-each (lambda (cont k)
+ (def! k)
+ (hashq-set! conts k cont)
+ (link-parent! k exp-k))
+ cont k)
+ (for-each visit cont k)
+ (recur body))
+
+ (($ $kargs names syms body)
+ (for-each def! syms)
+ (recur body))
+
+ (($ $kif kt kf)
+ (use! kt)
+ (use! kf))
+
+ (($ $ktrunc arity k)
+ (use! k))
+
+ (($ $letrec names syms funs body)
+ (unless global?
+ (error "$letrec should not be present when building a local DFG"))
+ (for-each def! syms)
+ (for-each (cut visit-fun <> conts use-maps uplinks global?) funs)
+ (visit body exp-k))
+
+ (($ $continue k exp)
+ (use! k)
+ (match exp
+ (($ $var sym)
+ (use! sym))
+
+ (($ $call proc args)
+ (use! proc)
+ (for-each use! args))
+
+ (($ $primcall name args)
+ (for-each use! args))
+
+ (($ $values args)
+ (for-each use! args))
+
+ (($ $prompt escape? tag handler)
+ (use! tag)
+ (use! handler))
+
+ (($ $fun)
+ (when global?
+ (visit-fun exp conts use-maps uplinks global?)))
+
+ (_ #f)))))
+
+ (match fun
+ (($ $fun meta free
+ ($ $cont kentry src
+ (and entry
+ ($ $kentry self ($ $cont ktail _ tail) clauses))))
+ ;; Treat the fun continuation as its own parent.
+ (add-def! kentry kentry)
+ (add-def! self kentry)
+ (hashq-set! uplinks kentry (make-uplink #f 0))
+ (hashq-set! conts kentry entry)
+
+ (add-def! ktail kentry)
+ (hashq-set! conts ktail tail)
+ (link-parent! ktail kentry)
+
+ (for-each
+ (match-lambda
+ (($ $cont kclause _
+ (and clause ($ $kclause arity ($ $cont kbody _ body))))
+ (add-def! kclause kentry)
+ (hashq-set! conts kclause clause)
+ (link-parent! kclause kentry)
+
+ (add-def! kbody kclause)
+ (hashq-set! conts kbody body)
+ (link-parent! kbody kclause)
+
+ (visit body kbody)))
+ clauses))))
+
+(define* (compute-dfg fun #:key (global? #t))
+ (let* ((conts (make-hash-table))
+ (use-maps (make-hash-table))
+ (uplinks (make-hash-table)))
+ (visit-fun fun conts use-maps uplinks global?)
+ (make-dfg conts use-maps uplinks)))
+
+(define (lookup-uplink k uplinks)
+ (let ((res (hashq-ref uplinks k)))
+ (unless res
+ (error "Unknown continuation!" k (hash-fold acons '() uplinks)))
+ res))
+
+(define (lookup-use-map sym use-maps)
+ (let ((res (hashq-ref use-maps sym)))
+ (unless res
+ (error "Unknown lexical!" sym (hash-fold acons '() use-maps)))
+ res))
+
+(define (lookup-def sym dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map sym use-maps)
+ (($ $use-map sym def uses)
+ def)))))
+
+(define (lookup-uses sym dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map sym use-maps)
+ (($ $use-map sym def uses)
+ uses)))))
+
+(define (find-defining-term sym dfg)
+ (match (lookup-uses (lookup-def sym dfg) dfg)
+ ((def-exp-k)
+ (lookup-cont def-exp-k (dfg-cont-table dfg)))
+ (else #f)))
+
+(define (find-call term)
+ (match term
+ (($ $kargs names syms body) (find-call body))
+ (($ $letk conts body) (find-call body))
+ (($ $letrec names syms funs body) (find-call body))
+ (($ $continue) term)))
+
+(define (call-expression call)
+ (match call
+ (($ $continue k exp) exp)))
+
+(define (find-expression term)
+ (call-expression (find-call term)))
+
+(define (find-defining-expression sym dfg)
+ (match (find-defining-term sym dfg)
+ (#f #f)
+ (($ $ktrunc) #f)
+ (term (find-expression term))))
+
+(define (find-constant-value sym dfg)
+ (match (find-defining-expression sym dfg)
+ (($ $const val)
+ (values #t val))
+ (($ $continue k ($ $void))
+ (values #t *unspecified*))
+ (else
+ (values #f #f))))
+
+(define (constant-needs-allocation? sym val dfg)
+ (define (find-exp term)
+ (match term
+ (($ $kargs names syms body) (find-exp body))
+ (($ $letk conts body) (find-exp body))
+ (else term)))
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map sym use-maps)
+ (($ $use-map _ def uses)
+ (or-map
+ (lambda (use)
+ (match (find-expression (lookup-cont use conts))
+ (($ $call) #f)
+ (($ $values) #f)
+ (($ $primcall 'free-ref (closure slot))
+ (not (eq? sym slot)))
+ (($ $primcall 'free-set! (closure slot value))
+ (not (eq? sym slot)))
+ (($ $primcall 'cache-current-module! (mod . _))
+ (eq? sym mod))
+ (($ $primcall 'cached-toplevel-box _)
+ #f)
+ (($ $primcall 'cached-module-box _)
+ #f)
+ (($ $primcall 'resolve (name bound?))
+ (eq? sym name))
+ (_ #t)))
+ uses))))))
+
+(define (continuation-scope-contains? parent-k k uplinks)
+ (match (lookup-uplink parent-k uplinks)
+ (($ $uplink _ parent-level)
+ (let lp ((k k))
+ (or (eq? parent-k k)
+ (match (lookup-uplink k uplinks)
+ (($ $uplink parent level)
+ (and (< parent-level level)
+ (lp parent)))))))))
+
+(define (lift-definition! k parent-k dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-uplink parent-k uplinks)
+ (($ $uplink parent level)
+ (hashq-set! uplinks k
+ (make-uplink parent-k (1+ level)))
+ ;; Lift definitions of all conts in K.
+ (let lp ((cont (lookup-cont k conts)))
+ (match cont
+ (($ $letk (($ $cont kid) ...) body)
+ (for-each (cut lift-definition! <> k dfg) kid)
+ (lp body))
+ (($ $letrec names syms funs body)
+ (lp body))
+ (_ #t))))))))
+
+(define (variable-used-in? var parent-k dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (or-map (lambda (use)
+ (continuation-scope-contains? parent-k use uplinks))
+ (match (lookup-use-map var use-maps)
+ (($ $use-map sym def uses)
+ uses))))))
+
+;; Does k1 dominate k2?
+;;
+;; Note that this is a conservative predicate: a false return value does
+;; not indicate that k1 _doesn't_ dominate k2. The reason for this is
+;; that we are using the scope tree as an approximation of the dominator
+;; relationship. See
+;; http://mlton.org/pipermail/mlton/2003-January/023054.html for a
+;; deeper discussion.
+(define (conservatively-dominates? k1 k2 uplinks)
+ (continuation-scope-contains? k1 k2 uplinks))
+
+(define (dead-after-def? sym dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map sym use-maps)
+ (($ $use-map sym def uses)
+ (null? uses))))))
+
+(define (dead-after-use? sym use-k dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map sym use-maps)
+ (($ $use-map sym def uses)
+ ;; If all other uses dominate this use, it is now dead. There
+ ;; are other ways for it to be dead, but this is an
+ ;; approximation. A better check would be if the successor
+ ;; post-dominates all uses.
+ (and-map (cut conservatively-dominates? <> use-k uplinks)
+ uses))))))
+
+;; A continuation is a "branch" if all of its predecessors are $kif
+;; continuations.
+(define (branch? k dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map k use-maps)
+ (($ $use-map sym def uses)
+ (and (not (null? uses))
+ (and-map (lambda (k)
+ (match (lookup-cont k conts)
+ (($ $kif) #t)
+ (_ #f)))
+ uses)))))))
+
+(define (find-other-branches k dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map k use-maps)
+ (($ $use-map sym def (uses ..1))
+ (map (lambda (kif)
+ (match (lookup-cont kif conts)
+ (($ $kif (? (cut eq? <> k)) kf)
+ kf)
+ (($ $kif kt (? (cut eq? <> k)))
+ kt)
+ (_ (error "Not all predecessors are branches"))))
+ uses))))))
+
+(define (dead-after-branch? sym branch other-branches dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-use-map sym use-maps)
+ (($ $use-map sym def uses)
+ (and-map
+ (lambda (use-k)
+ ;; A symbol is dead after a branch if at least one of the
+ ;; other branches dominates a use of the symbol, and all
+ ;; other uses of the symbol dominate the test.
+ (if (or-map (cut conservatively-dominates? <> use-k uplinks)
+ other-branches)
+ (not (conservatively-dominates? branch use-k uplinks))
+ (conservatively-dominates? use-k branch uplinks)))
+ uses))))))
+
+(define (lookup-bound-syms k dfg)
+ (match dfg
+ (($ $dfg conts use-maps uplinks)
+ (match (lookup-cont k conts)
+ (($ $kargs names syms body)
+ syms)))))
diff --git a/module/language/cps/slot-allocation.scm b/module/language/cps/slot-allocation.scm
new file mode 100644
index 0000000..a7b9f74
--- /dev/null
+++ b/module/language/cps/slot-allocation.scm
@@ -0,0 +1,419 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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:
+;;;
+;;; A module to assign stack slots to variables in a CPS term.
+;;;
+;;; Code:
+
+(define-module (language cps slot-allocation)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:use-module (language cps dfg)
+ #:export (allocate-slots
+ lookup-slot
+ lookup-constant-value
+ lookup-maybe-constant-value
+ lookup-nlocals
+ lookup-call-proc-slot
+ lookup-parallel-moves))
+
+;; Continuations can bind variables. The $allocation structure
+;; represents the slot in which a variable is stored.
+;;
+;; Not all variables have slots allocated. Variables that are constant
+;; and that are only used by primcalls that can accept constants
+;; directly are not allocated to slots, and their SLOT value is false.
+;; Likewise constants that are only used by calls are not allocated into
+;; slots, to avoid needless copying. If a variable is constant, its
+;; constant value is set to the CONST slot and HAS-CONST? is set to a
+;; true value.
+;;
+;; DEF holds the label of the continuation that defines the variable,
+;; and DEAD is a list of continuations at which the variable becomes
+;; dead.
+(define-record-type $allocation
+ (make-allocation def slot dead has-const? const)
+ allocation?
+ (def allocation-def)
+ (slot allocation-slot)
+ (dead allocation-dead set-allocation-dead!)
+ (has-const? allocation-has-const?)
+ (const allocation-const))
+
+;; Continuations can also have associated allocation data. For example,
+;; when a call happens in a labelled continuation, we need to know what
+;; slot the procedure goes in. Likewise before branching to the target
+;; continuation, we might need to shuffle values into the right place: a
+;; parallel move. $cont-allocation stores allocation data keyed on the
+;; continuation label.
+(define-record-type $cont-allocation
+ (make-cont-allocation call-proc-slot parallel-moves)
+ cont-allocation?
+
+ ;; Currently calls are allocated in the caller frame, above all locals
+ ;; that are live at the time of the call. Therefore there is no
+ ;; parallel move problem. We could be more clever here.
+ (call-proc-slot cont-call-proc-slot)
+
+ ;; Tail calls, multiple-value returns, and jumps to continuations with
+ ;; multiple arguments are forms of parallel assignment. A
+ ;; $parallel-move represents a specific solution to the parallel
+ ;; assignment problem, with an ordered list of (SRC . DST) moves. This
+ ;; may involve a temporary variable.
+ ;;
+ ;; ((src . dst) ...)
+ (parallel-moves cont-parallel-moves))
+
+(define (find-first-zero n)
+ ;; Naive implementation.
+ (let lp ((slot 0))
+ (if (logbit? slot n)
+ (lp (1+ slot))
+ slot)))
+
+(define (find-first-trailing-zero n count)
+ (let lp ((slot count))
+ (if (or (zero? slot) (logbit? (1- slot) n))
+ slot
+ (lp (1- slot)))))
+
+(define (lookup-allocation sym allocation)
+ (let ((res (hashq-ref allocation sym)))
+ (unless res
+ (error "Variable or continuation not defined" sym))
+ res))
+
+(define (lookup-slot sym allocation)
+ (match (lookup-allocation sym allocation)
+ (($ $allocation def slot dead has-const? const) slot)))
+
+(define (lookup-constant-value sym allocation)
+ (match (lookup-allocation sym allocation)
+ (($ $allocation def slot dead #t const) const)
+ (_
+ (error "Variable does not have constant value" sym))))
+
+(define (lookup-maybe-constant-value sym allocation)
+ (match (lookup-allocation sym allocation)
+ (($ $allocation def slot dead has-const? const)
+ (values has-const? const))))
+
+(define (lookup-call-proc-slot k allocation)
+ (match (lookup-allocation k allocation)
+ (($ $cont-allocation proc-slot parallel-moves)
+ (unless proc-slot
+ (error "Continuation not a call" k))
+ proc-slot)
+ (_
+ (error "Continuation not a call" k))))
+
+(define (lookup-nlocals k allocation)
+ (match (lookup-allocation k allocation)
+ ((? number? nlocals) nlocals)
+ (_
+ (error "Not a clause continuation" k))))
+
+(define (lookup-parallel-moves k allocation)
+ (match (lookup-allocation k allocation)
+ (($ $cont-allocation proc-slot parallel-moves)
+ (unless parallel-moves
+ (error "Continuation does not have parallel moves" k))
+ parallel-moves)
+ (_
+ (error "Continuation not a call" k))))
+
+(define (solve-parallel-move src dst tmp)
+ "Solve the parallel move problem between src and dst slot lists, which
+are comparable with eqv?. A tmp slot may be used."
+
+ ;; This algorithm is taken from: "Tilting at windmills with Coq:
+ ;; formal verification of a compilation algorithm for parallel moves"
+ ;; by Laurence Rideau, Bernard Paul Serpette, and Xavier Leroy
+ ;; <http://gallium.inria.fr/~xleroy/publi/parallel-move.pdf>
+
+ (define (split-move moves reg)
+ (let loop ((revhead '()) (tail moves))
+ (match tail
+ (((and s+d (s . d)) . rest)
+ (if (eqv? s reg)
+ (cons d (append-reverse revhead rest))
+ (loop (cons s+d revhead) rest)))
+ (_ #f))))
+
+ (define (replace-last-source reg moves)
+ (match moves
+ ((moves ... (s . d))
+ (append moves (list (cons reg d))))))
+
+ (let loop ((to-move (map cons src dst))
+ (being-moved '())
+ (moved '())
+ (last-source #f))
+ ;; 'last-source' should always be equivalent to:
+ ;; (and (pair? being-moved) (car (last being-moved)))
+ (match being-moved
+ (() (match to-move
+ (() (reverse moved))
+ (((and s+d (s . d)) . t1)
+ (if (or (eqv? s d) ; idempotent
+ (not s)) ; src is a constant and can be loaded directly
+ (loop t1 '() moved #f)
+ (loop t1 (list s+d) moved s)))))
+ (((and s+d (s . d)) . b)
+ (match (split-move to-move d)
+ ((r . t1) (loop t1 (acons d r being-moved) moved last-source))
+ (#f (match b
+ (() (loop to-move '() (cons s+d moved) #f))
+ (_ (if (eqv? d last-source)
+ (loop to-move
+ (replace-last-source tmp b)
+ (cons s+d (acons d tmp moved))
+ tmp)
+ (loop to-move b (cons s+d moved) last-source))))))))))
+
+(define (allocate-slots fun)
+ (define (empty-live-set)
+ (cons #b0 '()))
+
+ (define (add-live-variable sym slot live-set)
+ (cons (logior (car live-set) (ash 1 slot))
+ (acons sym slot (cdr live-set))))
+
+ (define (remove-live-variable sym slot live-set)
+ (cons (logand (car live-set) (lognot (ash 1 slot)))
+ (acons sym #f (cdr live-set))))
+
+ (define (fold-live-set proc seed live-set)
+ (let lp ((bits (car live-set)) (clauses (cdr live-set)) (seed seed))
+ (if (zero? bits)
+ seed
+ (match clauses
+ (((sym . slot) . clauses)
+ (if (and slot (logbit? slot bits))
+ (lp (logand bits (lognot (ash 1 slot)))
+ clauses
+ (proc sym slot seed))
+ (lp bits clauses seed)))))))
+
+ (define (compute-slot live-set hint)
+ (if (and hint (not (logbit? hint (car live-set))))
+ hint
+ (find-first-zero (car live-set))))
+
+ (define (compute-call-proc-slot live-set nlocals)
+ (+ 3 (find-first-trailing-zero (car live-set) nlocals)))
+
+ (define dfg (compute-dfg fun #:global? #f))
+ (define allocation (make-hash-table))
+
+ (define (visit-clause clause live-set)
+ (define nlocals (compute-slot live-set #f))
+ (define nargs
+ (match clause
+ (($ $cont _ _ ($ $kclause _ ($ $cont _ _ ($ $kargs names syms))))
+ (length syms))))
+
+ (define (allocate! sym k hint live-set)
+ (match (hashq-ref allocation sym)
+ (($ $allocation def slot dead has-const)
+ ;; Parallel move already allocated this one.
+ (if slot
+ (add-live-variable sym slot live-set)
+ live-set))
+ (_
+ (call-with-values (lambda () (find-constant-value sym dfg))
+ (lambda (has-const? const)
+ (cond
+ ((and has-const? (not (constant-needs-allocation? sym const dfg)))
+ (hashq-set! allocation sym
+ (make-allocation k #f '() has-const? const))
+ live-set)
+ (else
+ (let ((slot (compute-slot live-set hint)))
+ (when (>= slot nlocals)
+ (set! nlocals (+ slot 1)))
+ (hashq-set! allocation sym
+ (make-allocation k slot '() has-const? const))
+ (add-live-variable sym slot live-set)))))))))
+
+ (define (dead sym k live-set)
+ (match (lookup-allocation sym allocation)
+ ((and allocation ($ $allocation def slot dead has-const? const))
+ (set-allocation-dead! allocation (cons k dead))
+ (remove-live-variable sym slot live-set))))
+
+ (define (allocate-frame! k nargs live-set)
+ (let ((proc-slot (compute-call-proc-slot live-set nlocals)))
+ (set! nlocals (max nlocals (+ proc-slot 1 nargs)))
+ (hashq-set! allocation k
+ (make-cont-allocation
+ proc-slot
+ (match (hashq-ref allocation k)
+ (($ $cont-allocation #f moves) moves)
+ (#f #f))))
+ live-set))
+
+ (define (parallel-move! src-k src-slots pre-live-set post-live-set dst-slots)
+ (let* ((tmp-slot (find-first-zero (logior (car pre-live-set)
+ (car post-live-set))))
+ (moves (solve-parallel-move src-slots dst-slots tmp-slot)))
+ (when (and (>= tmp-slot nlocals) (assv tmp-slot moves))
+ (set! nlocals (+ tmp-slot 1)))
+ (hashq-set! allocation src-k
+ (make-cont-allocation
+ (match (hashq-ref allocation src-k)
+ (($ $cont-allocation proc-slot #f) proc-slot)
+ (#f #f))
+ moves))
+ post-live-set))
+
+ (define (visit-cont cont label live-set)
+ (define (maybe-kill-definition sym live-set)
+ (if (and (lookup-slot sym allocation) (dead-after-def? sym dfg))
+ (dead sym label live-set)
+ live-set))
+
+ (define (kill-conditionally-dead live-set)
+ (if (branch? label dfg)
+ (let ((branches (find-other-branches label dfg)))
+ (fold-live-set
+ (lambda (sym slot live-set)
+ (if (and (> slot nargs)
+ (dead-after-branch? sym label branches dfg))
+ (dead sym label live-set)
+ live-set))
+ live-set
+ live-set))
+ live-set))
+
+ (match cont
+ (($ $kentry self tail clauses)
+ (let ((live-set (allocate! self label 0 live-set)))
+ (for-each (cut visit-cont <> label live-set) clauses))
+ live-set)
+
+ (($ $kclause arity ($ $cont k src body))
+ (visit-cont body k live-set))
+
+ (($ $kargs names syms body)
+ (visit-term body label
+ (kill-conditionally-dead
+ (fold maybe-kill-definition
+ (fold (cut allocate! <> label #f <>) live-set syms)
+ syms))))
+
+ (($ $ktrunc) live-set)
+ (($ $kif) live-set)))
+
+ (define (visit-term term label live-set)
+ (match term
+ (($ $letk conts body)
+ (let ((live-set (visit-term body label live-set)))
+ (for-each (match-lambda
+ (($ $cont k src cont)
+ (visit-cont cont k live-set)))
+ conts))
+ live-set)
+
+ (($ $continue k exp)
+ (visit-exp exp label k live-set))))
+
+ (define (visit-exp exp label k live-set)
+ (define (use sym live-set)
+ (if (and (lookup-slot sym allocation) (dead-after-use? sym k dfg))
+ (dead sym k live-set)
+ live-set))
+
+ (match exp
+ (($ $var sym)
+ (use sym live-set))
+
+ (($ $call proc args)
+ (match (lookup-cont k (dfg-cont-table dfg))
+ (($ $ktail)
+ (let ((tail-nlocals (1+ (length args))))
+ (set! nlocals (max nlocals tail-nlocals))
+ (parallel-move! label
+ (map (cut lookup-slot <> allocation)
+ (cons proc args))
+ live-set (fold use live-set (cons proc args))
+ (iota tail-nlocals))))
+ (($ $ktrunc arity kargs)
+ (let* ((live-set
+ (fold use
+ (use proc
+ (allocate-frame! label (length args) live-set))
+ args))
+ (proc-slot (lookup-call-proc-slot label allocation))
+ (dst-syms (lookup-bound-syms kargs dfg))
+ (nvals (length dst-syms))
+ (src-slots (map (cut + proc-slot 1 <>) (iota nvals)))
+ (live-set* (fold (cut allocate! <> kargs <> <>)
+ live-set dst-syms src-slots))
+ (dst-slots (map (cut lookup-slot <> allocation)
+ dst-syms)))
+ (parallel-move! label src-slots live-set live-set* dst-slots)))
+ (else
+ (fold use
+ (use proc (allocate-frame! label (length args) live-set))
+ args))))
+
+ (($ $primcall name args)
+ (fold use live-set args))
+
+ (($ $values args)
+ (let ((live-set* (fold use live-set args)))
+ (define (compute-dst-slots)
+ (match (lookup-cont k (dfg-cont-table dfg))
+ (($ $ktail)
+ (let ((tail-nlocals (1+ (length args))))
+ (set! nlocals (max nlocals tail-nlocals))
+ (cdr (iota tail-nlocals))))
+ (_
+ (let* ((src-slots (map (cut lookup-slot <> allocation) args))
+ (dst-syms (lookup-bound-syms k dfg))
+ (dst-live-set (fold (cut allocate! <> k <> <>)
+ live-set* dst-syms src-slots)))
+ (map (cut lookup-slot <> allocation) dst-syms)))))
+
+ (parallel-move! label
+ (map (cut lookup-slot <> allocation) args)
+ live-set live-set*
+ (compute-dst-slots))))
+
+ (($ $prompt escape? tag handler)
+ (use tag live-set))
+
+ (_ live-set)))
+
+ (match clause
+ (($ $cont k _ body)
+ (visit-cont body k live-set)
+ (hashq-set! allocation k nlocals))))
+
+ (match fun
+ (($ $fun meta free ($ $cont k _ ($ $kentry self tail clauses)))
+ (let ((live-set (add-live-variable self 0 (empty-live-set))))
+ (hashq-set! allocation self (make-allocation k 0 '() #f #f))
+ (for-each (cut visit-clause <> live-set) clauses)
+ allocation))))
diff --git a/module/language/cps/spec.scm b/module/language/cps/spec.scm
index 38dc54d..493b547 100644
--- a/module/language/cps/spec.scm
+++ b/module/language/cps/spec.scm
@@ -21,6 +21,7 @@
(define-module (language cps spec)
#:use-module (system base language)
#:use-module (language cps)
+ #:use-module (language cps compile-rtl)
#:export (cps))
(define* (write-cps exp #:optional (port (current-output-port)))
@@ -31,6 +32,6 @@
#:reader (lambda (port env) (read port))
#:printer write-cps
#:parser parse-cps
- #:compilers '()
+ #:compilers `((rtl . ,compile-rtl))
#:for-humans? #f
)
diff --git a/test-suite/Makefile.am b/test-suite/Makefile.am
index fad64b7..c4e4d1f 100644
--- a/test-suite/Makefile.am
+++ b/test-suite/Makefile.am
@@ -115,6 +115,7 @@ SCM_TESTS = tests/00-initial-env.test \
tests/receive.test \
tests/regexp.test \
tests/rtl.test \
+ tests/rtl-compilation.test \
tests/session.test \
tests/signals.test \
tests/srcprop.test \
diff --git a/test-suite/tests/rtl-compilation.test b/test-suite/tests/rtl-compilation.test
new file mode 100644
index 0000000..cf00a4f
--- /dev/null
+++ b/test-suite/tests/rtl-compilation.test
@@ -0,0 +1,200 @@
+;;;; rtl-compilation.test --- test suite for compiling via rtl -*- scheme -*-
+;;;;
+;;;; Copyright (C) 2013 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
+
+(define-module (test-suite rtl-compilation)
+ #:use-module (test-suite lib)
+ #:use-module (system base compile)
+ #:use-module (system vm objcode))
+
+(define* (compile-via-rtl exp #:key peval? cse? (env (make-fresh-user-module)))
+ (load-thunk-from-memory
+ (compile exp #:env env #:to 'rtl
+ #:opts `(#:partial-eval? ,peval? #:cse? ,cse?))))
+
+(define* (run-rtl exp #:key (env (make-fresh-user-module)))
+ (let ((thunk (compile-via-rtl exp #:env env)))
+ (save-module-excursion
+ (lambda ()
+ (set-current-module env)
+ (thunk)))))
+
+(with-test-prefix "tail context"
+ (pass-if-equal 1
+ (run-rtl '(let ((x 1)) x)))
+
+ (pass-if-equal 1
+ (run-rtl 1))
+
+ (pass-if-equal (if #f #f)
+ (run-rtl '(if #f #f)))
+
+ (pass-if-equal "top-level define"
+ (list (if #f #f) 1)
+ (let ((mod (make-fresh-user-module)))
+ (let ((result (run-rtl '(define v 1) #:env mod)))
+ (list result (module-ref mod 'v)))))
+
+ (pass-if-equal "top-level set!"
+ (list (if #f #f) 1)
+ (let ((mod (make-fresh-user-module)))
+ (module-define! mod 'v #f)
+ (let ((result (run-rtl '(set! v 1) #:env mod)))
+ (list result (module-ref mod 'v)))))
+
+ (pass-if-equal "top-level apply [single value]"
+ 8
+ (let ((mod (make-fresh-user-module)))
+ (module-define! mod 'args '(2 3))
+ (run-rtl '(apply expt args) #:env mod)))
+
+ (pass-if-equal "top-level apply [zero values]"
+ '()
+ (let ((mod (make-fresh-user-module)))
+ (module-define! mod 'proc (lambda () (values)))
+ (module-define! mod 'args '())
+ (call-with-values
+ (lambda () (run-rtl '(apply proc args) #:env mod))
+ list)))
+
+ (pass-if-equal "top-level apply [two values]"
+ '(1 2)
+ (let ((mod (make-fresh-user-module)))
+ (module-define! mod 'proc (lambda (n d) (floor/ n d)))
+ (module-define! mod 'args '(5 3))
+ (call-with-values
+ (lambda () (run-rtl '(apply proc args) #:env mod))
+ list)))
+
+ (pass-if-equal "call-with-values"
+ '(1 2 3)
+ ((run-rtl '(lambda (n d)
+ (call-with-values (lambda () (floor/ n d))
+ (lambda (q r) (list q r (+ q r))))))
+ 5 3))
+
+ (pass-if-equal cons
+ (run-rtl 'cons))
+
+ (pass-if-equal 1
+ ((run-rtl '(lambda () 1))))
+
+ (pass-if-equal 1
+ ((run-rtl '(lambda (x) 1)) 2))
+
+ (pass-if-equal 1
+ ((run-rtl '(lambda (x) x)) 1))
+
+ (pass-if-equal 6
+ ((((run-rtl '(lambda (x)
+ (lambda (y)
+ (lambda (z)
+ (+ x y z))))) 1) 2) 3))
+
+ (pass-if-equal 1
+ (run-rtl '(identity 1)))
+
+ (pass-if-equal '(1 . 2)
+ (run-rtl '(cons 1 2)))
+
+ (pass-if-equal '(1 2)
+ (call-with-values (lambda () (run-rtl '(values 1 2))) list))
+
+ (pass-if-equal 28
+ ((run-rtl '(lambda (x y z rest) (apply + x y z rest)))
+ 2 3 5 '(7 11)))
+
+ ;; prompts
+ )
+
+(with-test-prefix "value context"
+ 1
+ )
+
+(with-test-prefix "drop context"
+ 1
+ )
+
+(with-test-prefix "test context"
+ 1
+ )
+
+(with-test-prefix "values context"
+ (pass-if-equal '(3 . 1)
+ (run-rtl
+ '(let ((rat (lambda (n d)
+ (call-with-values
+ (lambda () (floor/ n d))
+ (lambda (q r)
+ (cons q r))))))
+ (rat 10 3)))))
+
+(with-test-prefix "contification"
+ (pass-if ((run-rtl '(lambda (x)
+ (define (even? x)
+ (if (null? x) #t (odd? (cdr x))))
+ (define (odd? x)
+ (if (null? x) #f (even? (cdr x))))
+ (even? x)))
+ '(1 2 3 4)))
+
+ (pass-if (not ((run-rtl '(lambda (x)
+ (define (even? x)
+ (if (null? x) #t (odd? (cdr x))))
+ (define (odd? x)
+ (if (null? x) #f (even? (cdr x))))
+ (even? x)))
+ '(1 2 3)))))
+
+(with-test-prefix "case-lambda"
+ (pass-if-equal "simple"
+ '(0 3 9 28)
+ (let ((proc (run-rtl '(case-lambda
+ (() 0)
+ ((x) x)
+ ((x y) (+ x y))
+ ((x y z . rest) (apply + x y z rest))))))
+ (map (lambda (args) (apply proc args))
+ '(() (3) (2 7) (2 3 5 7 11)))))
+
+ (pass-if-exception "no match"
+ exception:wrong-num-args
+ ((run-rtl '(case-lambda ((x) x) ((x y) (+ x y))))
+ 1 2 3))
+
+ (pass-if-exception "zero clauses called with no args"
+ exception:wrong-num-args
+ ((run-rtl '(case-lambda))))
+
+ (pass-if-exception "zero clauses called with args"
+ exception:wrong-num-args
+ ((run-rtl '(case-lambda)) 1)))
+
+(with-test-prefix "mixed contexts"
+ (pass-if-equal "sequences" '(3 4 5)
+ (let* ((pair (cons 1 2))
+ (result ((run-rtl '(lambda (pair)
+ (set-car! pair 3)
+ (set-cdr! pair 4)
+ 5))
+ pair)))
+ (list (car pair)
+ (cdr pair)
+ result)))
+
+ (pass-if-equal "mutable lexicals" 2
+ (run-rtl '(let ((n 1)) (set! n 2) n))))
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 9/9] Add contification pass
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (7 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 8/9] Add CPS -> RTL compiler Andy Wingo
@ 2013-08-29 7:49 ` Andy Wingo
2013-08-29 20:42 ` CPS language and Tree-IL->CPS->RTL compiler Ludovic Courtès
2013-08-29 21:52 ` Noah Lavine
10 siblings, 0 replies; 17+ messages in thread
From: Andy Wingo @ 2013-08-29 7:49 UTC (permalink / raw)
To: guile-devel; +Cc: Andy Wingo
* module/Makefile.am:
* module/language/cps/contification.scm: New pass.
* module/language/cps/compile-rtl.scm (optimize): Wire it into the
compiler.
---
module/Makefile.am | 1 +
module/language/cps/compile-rtl.scm | 5 +-
module/language/cps/contification.scm | 238 ++++++++++++++++++++++++++++++++++
3 files changed, 241 insertions(+), 3 deletions(-)
create mode 100644 module/language/cps/contification.scm
diff --git a/module/Makefile.am b/module/Makefile.am
index 5a0ff69..0e6fdf6 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -123,6 +123,7 @@ CPS_LANG_SOURCES = \
language/cps/arities.scm \
language/cps/closure-conversion.scm \
language/cps/compile-rtl.scm \
+ language/cps/contification.scm \
language/cps/dfg.scm \
language/cps/primitives.scm \
language/cps/reify-primitives.scm \
diff --git a/module/language/cps/compile-rtl.scm b/module/language/cps/compile-rtl.scm
index 9277adf..b126738 100644
--- a/module/language/cps/compile-rtl.scm
+++ b/module/language/cps/compile-rtl.scm
@@ -29,6 +29,7 @@
#:use-module (language cps)
#:use-module (language cps arities)
#:use-module (language cps closure-conversion)
+ #:use-module (language cps contification)
#:use-module (language cps dfg)
#:use-module (language cps primitives)
#:use-module (language cps reify-primitives)
@@ -51,11 +52,9 @@
exp))
;; Calls to source-to-source optimization passes go here.
- (let* ()
+ (let* ((exp (run-pass exp contify #:contify? #t)))
;; Passes that are needed:
;;
- ;; * Contification: turning $letrec-bound $funs into $letk-bound $conts.
- ;;
;; * Abort contification: turning abort primcalls into continuation
;; calls, and eliding prompts if possible.
;;
diff --git a/module/language/cps/contification.scm b/module/language/cps/contification.scm
new file mode 100644
index 0000000..b1932dd
--- /dev/null
+++ b/module/language/cps/contification.scm
@@ -0,0 +1,238 @@
+;;; Continuation-passing style (CPS) intermediate language (IL)
+
+;; Copyright (C) 2013 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))
+ #:use-module (srfi srfi-26)
+ #:use-module (language cps)
+ #:use-module (language cps dfg)
+ #:use-module (language cps primitives)
+ #:use-module (language rtl)
+ #:export (contify))
+
+(define (contify fun)
+ (let* ((dfg (compute-dfg fun))
+ (cont-table (dfg-cont-table dfg))
+ (call-substs '())
+ (cont-substs '()))
+ (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 (lookup-return-cont k)
+ (or (assq-ref cont-substs k) k))
+
+ (define (contify-call 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 ($values args)))
+ (lp clauses)))
+ ((_ . clauses) (lp clauses)))))))
+
+ ;; 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 cont-table)
+ (($ $kargs (_) (sym))
+ (match (lookup-uses k dfg)
+ ((_)
+ ;; K has one predecessor, the one that defined SYM.
+ sym)
+ (_ #f)))
+ (_ #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)))
+
+ (define (contify-funs term-k syms selfs tails arities bodies)
+ ;; Are the given args compatible with any of the arities?
+ (define (applicable? proc args)
+ (or-map (match-lambda
+ (($ $arity req () #f () #f)
+ (= (length args) (length req)))
+ (_ #f))
+ (assq-ref (map cons syms arities) proc)))
+
+ ;; 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 cont-table))
+ (($ $continue k ($ $call proc* args))
+ (and (eq? proc proc*) (not (memq proc args)) (applicable? proc args)
+ k))
+ (_ #f)))
+
+ (and
+ (and-map null? (map (cut lookup-uses <> dfg) selfs))
+ (and=> (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))))))))))
+ (lambda (k)
+ ;; We have a common continuation, so we contify: mark
+ ;; all SYMs for replacement in calls, and mark the tail
+ ;; continuations for replacement by K.
+ (for-each (lambda (sym tail arities bodies)
+ (for-each (cut lift-definition! <> term-k dfg)
+ bodies)
+ (subst-call! sym arities bodies)
+ (subst-return! tail k))
+ syms tails arities bodies)
+ k))))
+
+ ;; This is a first cut at a contification algorithm. It contifies
+ ;; non-recursive functions that only have positional arguments.
+ (define (visit-fun term)
+ (rewrite-cps-exp term
+ (($ $fun meta free body)
+ ($fun meta free ,(visit-cont body)))))
+ (define (visit-cont cont)
+ (rewrite-cps-cont cont
+ (($ $cont sym src
+ ($ $kargs (name) (and sym (? (cut assq <> call-substs)))
+ body))
+ (sym src ($kargs () () ,(visit-term body sym))))
+ (($ $cont sym src ($ $kargs names syms body))
+ (sym src ($kargs names syms ,(visit-term body sym))))
+ (($ $cont sym src ($ $kentry self tail clauses))
+ (sym src ($kentry self ,tail ,(map visit-cont clauses))))
+ (($ $cont sym src ($ $kclause arity body))
+ (sym src ($kclause ,arity ,(visit-cont body))))
+ (($ $cont)
+ ,cont)))
+ (define (visit-term term term-k)
+ (match term
+ (($ $letk conts body)
+ ;; Visit the body first, so we visit depth-first.
+ (let ((body (visit-term body term-k)))
+ (build-cps-term
+ ($letk ,(map visit-cont conts) ,body))))
+ (($ $letrec names syms funs body)
+ (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-used-in? <> k dfg) syms))
+ (let lp ((nsf nsf) (rec '()))
+ (match nsf
+ (()
+ (if (null? rec)
+ '()
+ (list rec)))
+ (((and elt (n s ($ $fun meta free ($ $cont kentry))))
+ . nsf)
+ (if (recursive? kentry)
+ (lp nsf (cons elt rec))
+ (cons (list elt) (lp nsf rec)))))))
+ (define (visit-components components)
+ (match components
+ (() (visit-term body term-k))
+ ((((name sym fun) ...) . components)
+ (match fun
+ ((($ $fun meta free
+ ($ $cont fun-k _
+ ($ $kentry self
+ ($ $cont tail-k _ ($ $ktail))
+ (($ $cont _ _ ($ $kclause arity
+ (and body ($ $cont body-k))))
+ ...))))
+ ...)
+ (if (contify-funs term-k sym self tail-k arity body-k)
+ (let ((body* (visit-components components)))
+ (build-cps-term
+ ($letk ,(map visit-cont (concatenate body))
+ ,body*)))
+ (let-gensyms (k)
+ (build-cps-term
+ ($letrec name sym (map visit-fun fun)
+ ,(visit-components components))))))))))
+ (visit-components (split-components (map list names syms funs))))
+ (($ $continue k exp)
+ (let ((k* (lookup-return-cont k)))
+ (define (default)
+ (rewrite-cps-term exp
+ (($ $fun) ($continue k* ,(visit-fun exp)))
+ (($ $primcall 'return (val))
+ ,(if (eq? k k*)
+ (build-cps-term ($continue k* ,exp))
+ (build-cps-term ($continue k* ($values (val))))))
+ (($ $primcall 'return-values vals)
+ ,(if (eq? k k*)
+ (build-cps-term ($continue k* ,exp))
+ (build-cps-term ($continue k* ($values vals)))))
+ (_ ($continue k* ,exp))))
+ (match exp
+ (($ $fun meta free
+ ($ $cont fun-k _
+ ($ $kentry self
+ ($ $cont tail-k _ ($ $ktail))
+ (($ $cont _ _ ($ $kclause arity
+ (and body ($ $cont body-k))))
+ ...))))
+ (if (and=> (bound-symbol k*)
+ (lambda (sym)
+ (contify-fun term-k sym self tail-k arity body-k)))
+ (build-cps-term
+ ($letk ,(map visit-cont body)
+ ($continue k* ($values ()))))
+ (default)))
+ (($ $call proc args)
+ (or (contify-call proc args)
+ (default)))
+ (_ (default)))))))
+
+ (let ((fun (visit-fun fun)))
+ (if (null? call-substs)
+ fun
+ ;; Iterate to fixed point.
+ (contify fun)))))
--
1.8.3.2
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: CPS language and Tree-IL->CPS->RTL compiler
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (8 preceding siblings ...)
2013-08-29 7:49 ` [PATCH 9/9] Add contification pass Andy Wingo
@ 2013-08-29 20:42 ` Ludovic Courtès
2013-08-31 7:47 ` Andy Wingo
2013-08-29 21:52 ` Noah Lavine
10 siblings, 1 reply; 17+ messages in thread
From: Ludovic Courtès @ 2013-08-29 20:42 UTC (permalink / raw)
To: guile-devel
Hello!
Andy Wingo <wingo@pobox.com> skribis:
> This patchset implements the CPS compiler I've been working on over the
> last couple months. At the end of it, you can
>
> guild compile -t rtl foo.scm
>
> and, if there are no bugs or unimplemented bits, at that point you have
> a loadable .go with RTL code. (Try readelf -a on the .go!)
Woow, wonderful!
> As a little example:
>
> (+ 1 2)
>
> Here + is a primcall, so we would get:
>
> ($letk ((kone ($kargs (oneb)
> ($letk ((ktwo ($kargs (two)
> ($continue ktail
> ($primcall + one two)))))
> ($continue ktwo ($const 2))))))
> ($continue kone ($const 1)))
>
> Here all CPS language constructs are prefixed with "$". Everything else
> is a variable, except the + in the primcall.
Thanks for the example that nicely complements a first-time read of
Kennedy’s paper.
[...]
> If you ever worked with the old (language tree-il compile-glil) module,
> you know it's very hairy, mostly because it's mixing code emission with
> semantic transformations. Nowhere is this more evident than the
> so-called "labels allocation" strategy, in which we try to allocate
> procedures as basic blocks, if they all return to the same place. It's
> a funky pass. It turns out this concept is well-studied and has a name,
> "contification", and is cleanly expressed as a source-to-source pass
> over CPS. So compile-rtl.scm is dumb: it just loops over all
> expressions, emitting code for each of them, and emitting jumps and
> shuffling registers as needed (based on a prior analysis pass).
That’s really appreciable. :-)
> OK. WDYT, Ludo? Comments? OK to merge? :)
Well, I guess so!
Thanks to Noah for getting you started, and to Mark and you for getting
this far!
(I’m going to browse the patches, mostly so I can learn from this.)
Ludo’.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: CPS language and Tree-IL->CPS->RTL compiler
2013-08-29 7:49 CPS language and Tree-IL->CPS->RTL compiler Andy Wingo
` (9 preceding siblings ...)
2013-08-29 20:42 ` CPS language and Tree-IL->CPS->RTL compiler Ludovic Courtès
@ 2013-08-29 21:52 ` Noah Lavine
10 siblings, 0 replies; 17+ messages in thread
From: Noah Lavine @ 2013-08-29 21:52 UTC (permalink / raw)
To: Andy Wingo; +Cc: guile-devel
[-- Attachment #1: Type: text/plain, Size: 6792 bytes --]
Hello,
This is great! I read some patches, and it's much cleaner than mine. I'm
glad we have a cool new CPS compiler now, and we can start to really use
the RTL VM!
Noah
On Thu, Aug 29, 2013 at 3:49 AM, Andy Wingo <wingo@pobox.com> wrote:
> Hi all,
>
> This patchset implements the CPS compiler I've been working on over the
> last couple months. At the end of it, you can
>
> guild compile -t rtl foo.scm
>
> and, if there are no bugs or unimplemented bits, at that point you have
> a loadable .go with RTL code. (Try readelf -a on the .go!)
>
> From the console the way to do it is:
>
> (use-modules (system vm objcode))
> (load-thunk-from-memory (compile foo #:to 'rtl))
>
> That gives you the thunk that, when called, will execute FOO.
>
> So with that intro, some more on CPS and the RTL VM. As you know we
> have a new register-based VM, where all values have names (slots). It's
> appropriate that the language that compiles to this VM also give names
> to all intermediate values. CPS has this property. In addition, CPS
> gives names to all control points, effectively giving a label to each
> expression.
>
> This CPS language was inspired by Andrew Kennedy's great 2007 paper,
> "Compiling with Continuations, Continued". In particular, continuations
> are /local/ to a function, so they really are basic block labels, and
> all values are bound to variables via continuation calls -- even
> constants.
>
> As a little example:
>
> (+ 1 2)
>
> Here + is a primcall, so we would get:
>
> ($letk ((kone ($kargs (oneb)
> ($letk ((ktwo ($kargs (two)
> ($continue ktail
> ($primcall + one two)))))
> ($continue ktwo ($const 2))))))
> ($continue kone ($const 1)))
>
> Here all CPS language constructs are prefixed with "$". Everything else
> is a variable, except the + in the primcall.
>
> As you can see it is incredibly verbose. At the same time it's very
> simple, as there are only two kinds of terms: terms that bind
> continuations, and terms that call continuations.
>
> $letk binds a set of mutually recursive continuations, each one an
> instance of $cont. A $cont declares the name and source of a
> continuation, and then contains as a subterm the particular
> continuation instance: $kif for test continuations, $kargs for
> continuations that bind values, etc.
>
> $continue nodes call continuations. The expression contained in the
> $continue node determines the value or values that are passed to the
> target continuation: $const to pass a constant value, $values to
> pass multiple named values, etc.
>
> Additionally there is $letrec, a term that binds mutually recursive
> functions. The contification pass will turn $letrec into $letk if
> it can do so. Otherwise, the closure conversion pass will desugar
> $letrec into an equivalent sequence of make-closure primcalls and
> subsequent initializations of the captured variables of the
> closures. You can think of $letrec as pertaining to "high CPS",
> whereas later passes will only see "low CPS", which does not have
> $letrec.
>
> There are a bunch of Guile-specific quirks in this language, mostly
> related to function prologues for the different kinds of arities, and
> for things like multiple-value truncation and prompts. Check out
> (language cps) for all the deal.
>
> So, after that patch is the Tree-IL->CPS compiler. It simplifies a
> number of Tree-IL concepts, fixing argument order, turning toplevel
> references to primcalls, transforming prompts, assignment conversion,
> etc. For this reason it's a bit hairy, but it seems to work fine. This
> compiler runs *after* optimization passes on Tree-IL, so you still have
> peval that runs over tree-il.
>
> After that follow a bunch of passes to build up an RTL compiler. The
> idea is to compile by incremental source-to-source passes, and at the
> end you just assign slots to all variables and emit code directly.
>
> If you ever worked with the old (language tree-il compile-glil) module,
> you know it's very hairy, mostly because it's mixing code emission with
> semantic transformations. Nowhere is this more evident than the
> so-called "labels allocation" strategy, in which we try to allocate
> procedures as basic blocks, if they all return to the same place. It's
> a funky pass. It turns out this concept is well-studied and has a name,
> "contification", and is cleanly expressed as a source-to-source pass
> over CPS. So compile-rtl.scm is dumb: it just loops over all
> expressions, emitting code for each of them, and emitting jumps and
> shuffling registers as needed (based on a prior analysis pass).
>
> In the end you can ,disassemble the code, thanks to the earlier RTL
> work.
>
> I don't want people to benchmark this stuff yet -- it's buggy and not
> optimized. Don't use this work for anything serious. But if you're in
> to hacking on it, that's cool. There are a number of optimization
> passes that are needed (see compile-rtl.scm for a list), but currently
> it's looking like the RTL compiler does significantly better on loops,
> but struggles to keep up with the old VM for calls. This is
> unsurprising, as calls really do work like stacks, and a stack VM has
> many advantages there. But we think that with better slot allocation we
> can probably be faster for calls as well.
>
> Thank you, thank you, thank you to Mark Weaver for helping out with this
> work! Without him there would be many more bugs. I've folded all of
> his patches into this patchset, so please consider him the joint author
> of all of this. In any case the bugs have forced him to page much of
> this code into his head so we are better equipped as a project because
> of that ;-)
>
> Thank you also to Noah Lavine, who did an earlier pass at CPS
> conversion. I considered starting from his work but it became clear
> that many aspects of CPS would be nicer with changes in Tree-IL -- so I
> took advantage of my maintenance of the old compiler to make various
> changes there and in other parts of the runtime to get a cleaner CPS
> compiler. In the end we took advantage of his vanguard-hacking, though
> as ideas rather than as code. Thank you Noah!
>
> There are not as many tests and documentation as one would like.
> Ultimately it all just came together in the last couple of weeks, so I
> think the next step is to write more tests and try the compiler on new
> pieces of code. There are a couple bugs that we know about at this
> point, and surely many more that we don't know about. I ask for some
> lenience on this front while we figure out what the compiler should look
> like :)
>
> OK. WDYT, Ludo? Comments? OK to merge? :)
>
> Cheers,
>
> Andy
>
>
>
[-- Attachment #2: Type: text/html, Size: 7708 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread