1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-09 21:40:33 +02:00

Remove CPS1 language

* module/language/cps.scm:
* module/language/cps/compile-bytecode.scm:
* module/language/cps/dfg.scm:
* module/language/cps/renumber.scm:
* module/language/cps/slot-allocation.scm:
* module/language/cps/spec.scm:
* module/language/cps/verify.scm:
* module/language/cps2/compile-cps.scm: Delete.
* module/Makefile.am: Remove deleted files.
This commit is contained in:
Andy Wingo 2015-07-22 17:15:06 +02:00
parent 39777b11b3
commit 0d4c937722
9 changed files with 1 additions and 3379 deletions

View file

@ -122,20 +122,12 @@ TREE_IL_LANG_SOURCES = \
language/tree-il/spec.scm
CPS_LANG_SOURCES = \
language/cps.scm \
language/cps/compile-bytecode.scm \
language/cps/dfg.scm \
language/cps/primitives.scm \
language/cps/renumber.scm \
language/cps/slot-allocation.scm \
language/cps/spec.scm \
language/cps/verify.scm
language/cps/primitives.scm
CPS2_LANG_SOURCES = \
language/cps2.scm \
language/cps2/closure-conversion.scm \
language/cps2/compile-bytecode.scm \
language/cps2/compile-cps.scm \
language/cps2/constructors.scm \
language/cps2/contification.scm \
language/cps2/cse.scm \

View file

@ -1,620 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2013, 2014, 2015 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 of a continuation, and
;;; then contains as a subterm the particular continuation instance:
;;; $kargs for continuations that bind values, $ktail for the tail
;;; continuation, 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. $continue nodes also record the source at which
;;;
;;; 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:
;;;
;;; - $kreceive represents a continuation that receives multiple values,
;;; but which truncates them to some number of required values,
;;; possibly with a rest list.
;;;
;;; - $kfun labels an entry point for a $fun (a function), and
;;; contains a $ktail representing the formal argument which is the
;;; function's continuation.
;;;
;;; - $kfun also contain a $kclause continuation, corresponding to
;;; the first case-lambda clause of the function. $kclause actually
;;; contains the clause body, and the subsequent clause (if any).
;;; 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
;;; ($ $cont kfun
;;; ($ $kfun src meta self ($ $cont ktail ($ $ktail))
;;; ($ $kclause arity
;;; ($ $cont kbody ($ $kargs names syms body))
;;; alternate))))
;;; #t))
;;;
;;; A $continue to ktail is in tail position. $kfun, $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,
;;; $ktail, 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)
#:use-module (srfi srfi-11)
#:export (;; Helper.
$arity
make-$arity
;; Terms.
$letk $continue
;; Continuations.
$cont
;; Continuation bodies.
$kreceive $kargs $kfun $ktail $kclause
;; Expressions.
$const $prim $fun $rec $closure $branch
$call $callk $primcall $values $prompt
;; First-order CPS root.
$program
;; Fresh names.
label-counter var-counter
fresh-label fresh-var
with-fresh-name-state compute-max-label-and-var
let-fresh
;; Building macros.
build-cps-term build-cps-cont build-cps-exp
rewrite-cps-term rewrite-cps-cont rewrite-cps-exp
;; Misc.
parse-cps unparse-cps
make-global-cont-folder make-local-cont-folder
fold-conts fold-local-conts
visit-cont-successors))
;; 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 src exp)
;; Continuations
(define-cps-type $cont k cont)
(define-cps-type $kreceive arity k)
(define-cps-type $kargs names syms body)
(define-cps-type $kfun src meta self tail clause)
(define-cps-type $ktail)
(define-cps-type $kclause arity cont alternate)
;; Expressions.
(define-cps-type $const val)
(define-cps-type $prim name)
(define-cps-type $fun body) ; Higher-order.
(define-cps-type $rec names syms funs) ; Higher-order.
(define-cps-type $closure label nfree) ; First-order.
(define-cps-type $branch k exp)
(define-cps-type $call proc args)
(define-cps-type $callk k proc args) ; First-order.
(define-cps-type $primcall name args)
(define-cps-type $values args)
(define-cps-type $prompt escape? tag handler)
;; The root of a higher-order CPS term is $cont containing a $kfun. The
;; root of a first-order CPS term is a $program.
(define-cps-type $program funs)
(define label-counter (make-parameter #f))
(define var-counter (make-parameter #f))
(define (fresh-label)
(let ((count (or (label-counter)
(error "fresh-label outside with-fresh-name-state"))))
(label-counter (1+ count))
count))
(define (fresh-var)
(let ((count (or (var-counter)
(error "fresh-var outside with-fresh-name-state"))))
(var-counter (1+ count))
count))
(define-syntax-rule (let-fresh (label ...) (var ...) body ...)
(let ((label (fresh-label)) ...
(var (fresh-var)) ...)
body ...))
(define-syntax-rule (with-fresh-name-state fun body ...)
(call-with-values (lambda () (compute-max-label-and-var fun))
(lambda (max-label max-var)
(parameterize ((label-counter (1+ max-label))
(var-counter (1+ max-var)))
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 $kreceive $kargs $kfun $ktail $kclause)
((_ (unquote exp))
exp)
((_ ($kreceive req rest kargs))
(make-$kreceive (make-$arity req '() rest '() #f) kargs))
((_ ($kargs (name ...) (unquote syms) body))
(make-$kargs (list name ...) syms (build-cps-term body)))
((_ ($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)))
((_ ($kfun src meta self tail clause))
(make-$kfun src meta self (build-cps-cont tail) (build-cps-cont clause)))
((_ ($ktail))
(make-$ktail))
((_ ($kclause arity cont alternate))
(make-$kclause (build-arity arity) (build-cps-cont cont)
(build-cps-cont alternate)))))
(define-syntax build-cps-cont
(syntax-rules (unquote)
((_ (unquote exp)) exp)
((_ (k cont)) (make-$cont k (build-cont-body cont)))))
(define-syntax build-cps-exp
(syntax-rules (unquote
$const $prim $fun $rec $closure $branch
$call $callk $primcall $values $prompt)
((_ (unquote exp)) exp)
((_ ($const val)) (make-$const val))
((_ ($prim name)) (make-$prim name))
((_ ($fun body)) (make-$fun (build-cps-cont body)))
((_ ($rec names gensyms funs)) (make-$rec names gensyms funs))
((_ ($closure k nfree)) (make-$closure k nfree))
((_ ($call proc (unquote args))) (make-$call proc args))
((_ ($call proc (arg ...))) (make-$call proc (list arg ...)))
((_ ($call proc args)) (make-$call proc args))
((_ ($callk k proc (unquote args))) (make-$callk k proc args))
((_ ($callk k proc (arg ...))) (make-$callk k proc (list arg ...)))
((_ ($callk k proc args)) (make-$callk k proc args))
((_ ($primcall name (unquote args))) (make-$primcall name args))
((_ ($primcall name (arg ...))) (make-$primcall name (list arg ...)))
((_ ($primcall name args)) (make-$primcall name args))
((_ ($values (unquote args))) (make-$values args))
((_ ($values (arg ...))) (make-$values (list arg ...)))
((_ ($values args)) (make-$values args))
((_ ($branch k exp)) (make-$branch k (build-cps-exp exp)))
((_ ($prompt escape? tag handler))
(make-$prompt escape? tag handler))))
(define-syntax build-cps-term
(syntax-rules (unquote $letk $letk* $letconst $program $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-fresh (kconst) ()
(build-cps-term
($letk ((kconst ($kargs (name) (sym) ($letconst (tail ...) body))))
($continue kconst (let ((props (source-properties val)))
(and (pair? props) props))
($const val))))))
((_ ($program (unquote conts)))
(make-$program conts))
((_ ($program (cont ...)))
(make-$program (list (build-cps-cont cont) ...)))
((_ ($program conts))
(make-$program conts))
((_ ($continue k src exp))
(make-$continue k src (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 ($kargs (name) (sym)
,(parse-cps body))))
($continue k (src exp) ($const c)))))
(('let k (name sym val) body)
(build-cps-term
($letk ((k ($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 ,(parse-cps body))))
(('kreceive req rest k)
(build-cont-body ($kreceive req rest k)))
(('kargs names syms body)
(build-cont-body ($kargs names syms ,(parse-cps body))))
(('kfun src meta self tail clause)
(build-cont-body
($kfun (src exp) meta self ,(parse-cps tail)
,(and=> clause parse-cps))))
(('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)
,#f)))
(('kclause (req opt rest kw allow-other-keys?) body alternate)
(build-cont-body
($kclause (req opt rest kw allow-other-keys?)
,(parse-cps body)
,(parse-cps alternate))))
(('kseq body)
(build-cont-body ($kargs () () ,(parse-cps body))))
;; Calls.
(('continue k exp)
(build-cps-term ($continue k (src exp) ,(parse-cps exp))))
(('const exp)
(build-cps-exp ($const exp)))
(('prim name)
(build-cps-exp ($prim name)))
(('fun body)
(build-cps-exp ($fun ,(parse-cps body))))
(('closure k nfree)
(build-cps-exp ($closure k nfree)))
(('rec (name sym fun) ...)
(build-cps-exp ($rec name sym (map parse-cps fun))))
(('program (cont ...))
(build-cps-term ($program ,(map parse-cps cont))))
(('call proc arg ...)
(build-cps-exp ($call proc arg)))
(('callk k proc arg ...)
(build-cps-exp ($callk k proc arg)))
(('primcall name arg ...)
(build-cps-exp ($primcall name arg)))
(('branch k exp)
(build-cps-exp ($branch k ,(parse-cps exp))))
(('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 ($ $kargs (name) (sym) body)))
($ $continue k src ($ $const c)))
`(letconst ,k (,name ,sym ,c)
,(unparse-cps body)))
(($ $letk (($ $cont k ($ $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 body)
`(k ,sym ,(unparse-cps body)))
(($ $kreceive ($ $arity req () rest '() #f) k)
`(kreceive ,req ,rest ,k))
(($ $kargs () () body)
`(kseq ,(unparse-cps body)))
(($ $kargs names syms body)
`(kargs ,names ,syms ,(unparse-cps body)))
(($ $kfun src meta self tail clause)
`(kfun ,meta ,self ,(unparse-cps tail) ,(unparse-cps clause)))
(($ $ktail)
`(ktail))
(($ $kclause ($ $arity req opt rest kw allow-other-keys?) body alternate)
`(kclause (,req ,opt ,rest ,kw ,allow-other-keys?) ,(unparse-cps body)
. ,(if alternate (list (unparse-cps alternate)) '())))
;; Calls.
(($ $continue k src exp)
`(continue ,k ,(unparse-cps exp)))
(($ $const val)
`(const ,val))
(($ $prim name)
`(prim ,name))
(($ $fun body)
`(fun ,(unparse-cps body)))
(($ $closure k nfree)
`(closure ,k ,nfree))
(($ $rec names syms funs)
`(rec ,@(map (lambda (name sym fun)
(list name sym (unparse-cps fun)))
names syms funs)))
(($ $program conts)
`(program ,(map unparse-cps conts)))
(($ $call proc args)
`(call ,proc ,@args))
(($ $callk k proc args)
`(callk ,k ,proc ,@args))
(($ $primcall name args)
`(primcall ,name ,@args))
(($ $branch k exp)
`(branch ,k ,(unparse-cps exp)))
(($ $values args)
`(values ,@args))
(($ $prompt escape? tag handler)
`(prompt ,escape? ,tag ,handler))
(_
(error "unexpected cps" exp))))
(define-syntax-rule (make-global-cont-folder seed ...)
(lambda (proc cont seed ...)
(define (cont-folder cont seed ...)
(match cont
(($ $cont k cont)
(let-values (((seed ...) (proc k cont seed ...)))
(match cont
(($ $kargs names syms body)
(term-folder body seed ...))
(($ $kfun src meta self tail clause)
(let-values (((seed ...) (cont-folder tail seed ...)))
(if clause
(cont-folder clause seed ...)
(values seed ...))))
(($ $kclause arity body alternate)
(let-values (((seed ...) (cont-folder body seed ...)))
(if alternate
(cont-folder alternate seed ...)
(values seed ...))))
(_ (values seed ...)))))))
(define (fun-folder fun seed ...)
(match fun
(($ $fun body)
(cont-folder body seed ...))))
(define (term-folder term seed ...)
(match term
(($ $letk conts body)
(let-values (((seed ...) (term-folder body seed ...)))
(let lp ((conts conts) (seed seed) ...)
(if (null? conts)
(values seed ...)
(let-values (((seed ...) (cont-folder (car conts) seed ...)))
(lp (cdr conts) seed ...))))))
(($ $continue k src exp)
(match exp
(($ $fun) (fun-folder exp seed ...))
(($ $rec names syms funs)
(let lp ((funs funs) (seed seed) ...)
(if (null? funs)
(values seed ...)
(let-values (((seed ...) (fun-folder (car funs) seed ...)))
(lp (cdr funs) seed ...)))))
(_ (values seed ...))))))
(cont-folder cont seed ...)))
(define-syntax-rule (make-local-cont-folder seed ...)
(lambda (proc cont seed ...)
(define (cont-folder cont seed ...)
(match cont
(($ $cont k (and cont ($ $kargs names syms body)))
(let-values (((seed ...) (proc k cont seed ...)))
(term-folder body seed ...)))
(($ $cont k cont)
(proc k cont seed ...))))
(define (term-folder term seed ...)
(match term
(($ $letk conts body)
(let-values (((seed ...) (term-folder body seed ...)))
(let lp ((conts conts) (seed seed) ...)
(match conts
(() (values seed ...))
((cont) (cont-folder cont seed ...))
((cont . conts)
(let-values (((seed ...) (cont-folder cont seed ...)))
(lp conts seed ...)))))))
(_ (values seed ...))))
(define (clause-folder clause seed ...)
(match clause
(($ $cont k (and cont ($ $kclause arity body alternate)))
(let-values (((seed ...) (proc k cont seed ...)))
(if alternate
(let-values (((seed ...) (cont-folder body seed ...)))
(clause-folder alternate seed ...))
(cont-folder body seed ...))))))
(match cont
(($ $cont k (and cont ($ $kfun src meta self tail clause)))
(let*-values (((seed ...) (proc k cont seed ...))
((seed ...) (if clause
(clause-folder clause seed ...)
(values seed ...))))
(cont-folder tail seed ...))))))
(define (compute-max-label-and-var fun)
(match fun
(($ $cont)
((make-global-cont-folder max-label max-var)
(lambda (label cont max-label max-var)
(values (max label max-label)
(match cont
(($ $kargs names vars body)
(fold max max-var vars))
(($ $kfun src meta self)
(max self max-var))
(_ max-var))))
fun -1 -1))
(($ $program conts)
(define (fold/2 proc in s0 s1)
(if (null? in)
(values s0 s1)
(let-values (((s0 s1) (proc (car in) s0 s1)))
(fold/2 proc (cdr in) s0 s1))))
(let lp ((conts conts) (max-label -1) (max-var -1))
(if (null? conts)
(values max-label max-var)
(call-with-values (lambda ()
((make-local-cont-folder max-label max-var)
(lambda (label cont max-label max-var)
(values (max label max-label)
(match cont
(($ $kargs names vars body)
(fold max max-var vars))
(($ $kfun src meta self)
(max self max-var))
(_ max-var))))
(car conts) max-label max-var))
(lambda (max-label max-var)
(lp (cdr conts) max-label max-var))))))))
(define (fold-conts proc seed fun)
((make-global-cont-folder seed) proc fun seed))
(define (fold-local-conts proc seed fun)
((make-local-cont-folder seed) proc fun seed))
(define (visit-cont-successors proc cont)
(match cont
(($ $kargs names syms body)
(let lp ((body body))
(match body
(($ $letk conts body) (lp body))
(($ $continue k src exp)
(match exp
(($ $prompt escape? tag handler) (proc k handler))
(($ $branch kt) (proc k kt))
(_ (proc k)))))))
(($ $kreceive arity k) (proc k))
(($ $kclause arity ($ $cont kbody) #f) (proc kbody))
(($ $kclause arity ($ $cont kbody) ($ $cont kalt)) (proc kbody kalt))
(($ $kfun src meta self tail ($ $cont clause)) (proc clause))
(($ $kfun src meta self tail #f) (proc))
(($ $ktail) (proc))))

View file

@ -1,453 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2013, 2014, 2015 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 bytecode. The result is in the bytecode language,
;;; which happens to be an ELF image as a bytecode.
;;;
;;; Code:
(define-module (language cps compile-bytecode)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (language cps)
#:use-module (language cps dfg)
#:use-module (language cps primitives)
#:use-module (language cps renumber)
#:use-module (language cps slot-allocation)
#:use-module (system vm assembler)
#:export (compile-bytecode))
(define (kw-arg-ref args kw default)
(match (memq kw args)
((_ val . _) val)
(_ default)))
(define (compile-fun f asm)
(let* ((dfg (compute-dfg f #:global? #f))
(allocation (allocate-slots f dfg)))
(define (maybe-slot sym)
(lookup-maybe-slot sym allocation))
(define (slot sym)
(lookup-slot sym allocation))
(define (constant sym)
(lookup-constant-value sym allocation))
(define (maybe-mov dst src)
(unless (= dst src)
(emit-mov asm dst src)))
(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 (compile-entry)
(let ((label (dfg-min-label dfg)))
(match (lookup-cont label dfg)
(($ $kfun src meta self tail clause)
(when src
(emit-source asm src))
(emit-begin-program asm label meta)
(compile-clause (1+ label))
(emit-end-program asm)))))
(define (compile-clause label)
(match (lookup-cont label dfg)
(($ $kclause ($ $arity req opt rest kw allow-other-keys?)
body alternate)
(let* ((kw-indices (map (match-lambda
((key name sym)
(cons key (lookup-slot sym allocation))))
kw))
(nlocals (lookup-nlocals label allocation)))
(emit-label asm label)
(emit-begin-kw-arity asm req opt rest kw-indices allow-other-keys?
nlocals
(match alternate (#f #f) (($ $cont alt) alt)))
(let ((next (compile-body (1+ label) nlocals)))
(emit-end-arity asm)
(match alternate
(($ $cont alt)
(unless (eq? next alt)
(error "unexpected k" alt))
(compile-clause next))
(#f
(unless (= next (+ (dfg-min-label dfg) (dfg-label-count dfg)))
(error "unexpected end of clauses")))))))))
(define (compile-body label nlocals)
(let compile-cont ((label label))
(if (eq? label (+ (dfg-min-label dfg) (dfg-label-count dfg)))
label
(match (lookup-cont label dfg)
(($ $kclause) label)
(($ $kargs names vars term)
(emit-label asm label)
(for-each (lambda (name var)
(let ((slot (maybe-slot var)))
(when slot
(emit-definition asm name slot))))
names vars)
(let find-exp ((term term))
(match term
(($ $letk conts term)
(find-exp term))
(($ $continue k src exp)
(when src
(emit-source asm src))
(compile-expression label k exp nlocals)
(compile-cont (1+ label))))))
(_
(emit-label asm label)
(compile-cont (1+ label)))))))
(define (compile-expression label k exp nlocals)
(let* ((fallthrough? (= k (1+ label))))
(define (maybe-emit-jump)
(unless fallthrough?
(emit-br asm k)))
(match (lookup-cont k dfg)
(($ $ktail)
(compile-tail label exp))
(($ $kargs (name) (sym))
(let ((dst (maybe-slot sym)))
(when dst
(compile-value label exp dst nlocals)))
(maybe-emit-jump))
(($ $kargs () ())
(match exp
(($ $branch kt exp)
(compile-test label exp kt k (1+ label)))
(_
(compile-effect label exp k nlocals)
(maybe-emit-jump))))
(($ $kargs names syms)
(compile-values label exp syms)
(maybe-emit-jump))
(($ $kreceive ($ $arity req () rest () #f) kargs)
(compile-trunc label k exp (length req)
(and rest
(match (lookup-cont kargs dfg)
(($ $kargs names (_ ... rest)) rest)))
nlocals)
(unless (and fallthrough? (= kargs (1+ k)))
(emit-br asm kargs))))))
(define (compile-tail label exp)
;; 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))))
(($ $callk k 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-label asm (1+ (length args)) k))
(($ $values ())
(emit-reset-frame asm 1)
(emit-return-values asm))
(($ $values (arg))
(if (maybe-slot arg)
(emit-return asm (slot arg))
(begin
(emit-load-constant asm 1 (constant arg))
(emit-return asm 1))))
(($ $values 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-reset-frame asm (1+ (length args)))
(emit-return-values asm))
(($ $primcall 'return (arg))
(emit-return asm (slot arg)))))
(define (compile-value label exp dst nlocals)
(match exp
(($ $values (arg))
(or (maybe-load-constant dst arg)
(maybe-mov dst (slot arg))))
(($ $const exp)
(emit-load-constant asm dst exp))
(($ $closure k 0)
(emit-load-static-procedure asm dst k))
(($ $closure k nfree)
(emit-make-closure asm dst k nfree))
(($ $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 'vector-ref (vector index))
(emit-vector-ref asm dst (slot vector) (slot index)))
(($ $primcall 'make-vector (length init))
(emit-make-vector asm dst (slot length) (slot init)))
(($ $primcall 'make-vector/immediate (length init))
(emit-make-vector/immediate asm dst (constant length) (slot init)))
(($ $primcall 'vector-ref/immediate (vector index))
(emit-vector-ref/immediate asm dst (slot vector) (constant index)))
(($ $primcall 'allocate-struct (vtable nfields))
(emit-allocate-struct asm dst (slot vtable) (slot nfields)))
(($ $primcall 'allocate-struct/immediate (vtable nfields))
(emit-allocate-struct/immediate asm dst (slot vtable) (constant nfields)))
(($ $primcall 'struct-ref (struct n))
(emit-struct-ref asm dst (slot struct) (slot n)))
(($ $primcall 'struct-ref/immediate (struct n))
(emit-struct-ref/immediate asm dst (slot struct) (constant n)))
(($ $primcall 'builtin-ref (name))
(emit-builtin-ref asm dst (constant name)))
(($ $primcall 'bv-u8-ref (bv idx))
(emit-bv-u8-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-s8-ref (bv idx))
(emit-bv-s8-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-u16-ref (bv idx))
(emit-bv-u16-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-s16-ref (bv idx))
(emit-bv-s16-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-u32-ref (bv idx val))
(emit-bv-u32-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-s32-ref (bv idx val))
(emit-bv-s32-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-u64-ref (bv idx val))
(emit-bv-u64-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-s64-ref (bv idx val))
(emit-bv-s64-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-f32-ref (bv idx val))
(emit-bv-f32-ref asm dst (slot bv) (slot idx)))
(($ $primcall 'bv-f64-ref (bv idx val))
(emit-bv-f64-ref asm dst (slot bv) (slot idx)))
(($ $primcall name args)
;; FIXME: Inline all the cases.
(let ((inst (prim-instruction name)))
(emit-text asm `((,inst ,dst ,@(map slot args))))))))
(define (compile-effect label exp k nlocals)
(match exp
(($ $values ()) #f)
(($ $prompt escape? tag handler)
(match (lookup-cont handler dfg)
(($ $kreceive ($ $arity req () rest () #f) khandler-body)
(let ((receive-args (gensym "handler"))
(nreq (length req))
(proc-slot (lookup-call-proc-slot handler allocation)))
(emit-prompt asm (slot tag) escape? proc-slot receive-args)
(emit-br asm k)
(emit-label asm receive-args)
(unless (and rest (zero? nreq))
(emit-receive-values asm proc-slot (->bool rest) nreq))
(when (and rest
(match (lookup-cont khandler-body dfg)
(($ $kargs names (_ ... rest))
(maybe-slot rest))))
(emit-bind-rest asm (+ proc-slot 1 nreq)))
(for-each (match-lambda
((src . dst) (emit-mov asm dst src)))
(lookup-parallel-moves handler allocation))
(emit-reset-frame asm nlocals)
(emit-br asm khandler-body)))))
(($ $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 'struct-set!/immediate (struct index value))
(emit-struct-set!/immediate asm (slot struct) (constant index) (slot value)))
(($ $primcall 'vector-set! (vector index value))
(emit-vector-set! asm (slot vector) (slot index) (slot value)))
(($ $primcall 'vector-set!/immediate (vector index value))
(emit-vector-set!/immediate asm (slot vector) (constant 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 'push-fluid (fluid val))
(emit-push-fluid asm (slot fluid) (slot val)))
(($ $primcall 'pop-fluid ())
(emit-pop-fluid asm))
(($ $primcall 'wind (winder unwinder))
(emit-wind asm (slot winder) (slot unwinder)))
(($ $primcall 'bv-u8-set! (bv idx val))
(emit-bv-u8-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-s8-set! (bv idx val))
(emit-bv-s8-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-u16-set! (bv idx val))
(emit-bv-u16-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-s16-set! (bv idx val))
(emit-bv-s16-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-u32-set! (bv idx val))
(emit-bv-u32-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-s32-set! (bv idx val))
(emit-bv-s32-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-u64-set! (bv idx val))
(emit-bv-u64-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-s64-set! (bv idx val))
(emit-bv-s64-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-f32-set! (bv idx val))
(emit-bv-f32-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'bv-f64-set! (bv idx val))
(emit-bv-f64-set! asm (slot bv) (slot idx) (slot val)))
(($ $primcall 'unwind ())
(emit-unwind asm))))
(define (compile-values label exp syms)
(match exp
(($ $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))))
(define (compile-test label exp kt kf next-label)
(define (unary op sym)
(cond
((eq? kt next-label)
(op asm (slot sym) #t kf))
(else
(op asm (slot sym) #f kt)
(unless (eq? kf next-label)
(emit-br asm 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)
(unless (eq? kf next-label)
(emit-br asm kf)))))
(match exp
(($ $values (sym))
(call-with-values (lambda ()
(lookup-maybe-constant-value sym allocation))
(lambda (has-const? val)
(if has-const?
(if val
(unless (eq? kt next-label)
(emit-br asm kt))
(unless (eq? kf next-label)
(emit-br asm kf)))
(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))
(($ $primcall 'symbol? (a)) (unary emit-br-if-symbol a))
(($ $primcall 'variable? (a)) (unary emit-br-if-variable a))
(($ $primcall 'vector? (a)) (unary emit-br-if-vector a))
(($ $primcall 'string? (a)) (unary emit-br-if-string a))
(($ $primcall 'bytevector? (a)) (unary emit-br-if-bytevector a))
(($ $primcall 'bitvector? (a)) (unary emit-br-if-bitvector a))
(($ $primcall 'keyword? (a)) (unary emit-br-if-keyword a))
;; Add more TC7 tests here. Keep in sync with
;; *branching-primcall-arities* in (language cps primitives) and
;; the set of macro-instructions in assembly.scm.
(($ $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))
(($ $primcall 'logtest (a b)) (binary emit-br-if-logtest a b))))
(define (compile-trunc label k exp nreq rest-var nlocals)
(define (do-call proc args emit-call)
(let* ((proc-slot (lookup-call-proc-slot label allocation))
(nargs (1+ (length args)))
(arg-slots (map (lambda (x) (+ x proc-slot)) (iota nargs))))
(for-each (match-lambda
((src . dst) (emit-mov asm dst src)))
(lookup-parallel-moves label allocation))
(for-each maybe-load-constant arg-slots (cons proc args))
(emit-call asm proc-slot nargs)
(emit-dead-slot-map asm proc-slot
(lookup-dead-slot-map label allocation))
(cond
((and (= 1 nreq) (and rest-var) (not (maybe-slot rest-var))
(match (lookup-parallel-moves k allocation)
((((? (lambda (src) (= src (1+ proc-slot))) src)
. dst)) dst)
(_ #f)))
;; The usual case: one required live return value, ignoring
;; any additional values.
=> (lambda (dst)
(emit-receive asm dst proc-slot nlocals)))
(else
(unless (and (zero? nreq) rest-var)
(emit-receive-values asm proc-slot (->bool rest-var) nreq))
(when (and rest-var (maybe-slot rest-var))
(emit-bind-rest asm (+ proc-slot 1 nreq)))
(for-each (match-lambda
((src . dst) (emit-mov asm dst src)))
(lookup-parallel-moves k allocation))
(emit-reset-frame asm nlocals)))))
(match exp
(($ $call proc args)
(do-call proc args
(lambda (asm proc-slot nargs)
(emit-call asm proc-slot nargs))))
(($ $callk k proc args)
(do-call proc args
(lambda (asm proc-slot nargs)
(emit-call-label asm proc-slot nargs k))))))
(match f
(($ $cont k ($ $kfun src meta self tail clause))
(compile-entry)))))
(define (compile-bytecode exp env opts)
(let* ((exp (renumber exp))
(asm (make-assembler)))
(match exp
(($ $program funs)
(for-each (lambda (fun) (compile-fun fun asm))
funs)))
(values (link-assembly asm #:page-aligned? (kw-arg-ref opts #:to-file? #f))
env
env)))

View file

@ -1,904 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2013, 2014, 2015 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 scope 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 (ice-9 format)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-26)
#:use-module (language cps)
#:use-module (language cps intset)
#:export (build-cont-table
lookup-cont
compute-dfg
dfg-cont-table
dfg-min-label
dfg-label-count
dfg-min-var
dfg-var-count
with-fresh-name-state-from-dfg
lookup-def
lookup-uses
lookup-predecessors
lookup-successors
lookup-block-scope
find-call
call-expression
find-expression
find-defining-expression
find-constant-value
continuation-bound-in?
variable-free-in?
constant-needs-allocation?
control-point?
lookup-bound-syms
compute-idoms
compute-dom-edges
;; Data flow analysis.
compute-live-variables
dfa-k-idx dfa-k-sym dfa-k-count dfa-k-in dfa-k-out
dfa-var-idx dfa-var-sym dfa-var-count
print-dfa))
;; These definitions are here because currently we don't do cross-module
;; inlining. They can be removed once that restriction is gone.
(define-inlinable (for-each f l)
(unless (list? l)
(scm-error 'wrong-type-arg "for-each" "Not a list: ~S" (list l) #f))
(let for-each1 ((l l))
(unless (null? l)
(f (car l))
(for-each1 (cdr l)))))
(define-inlinable (for-each/2 f l1 l2)
(unless (= (length l1) (length l2))
(scm-error 'wrong-type-arg "for-each" "List of wrong length: ~S"
(list l2) #f))
(let for-each2 ((l1 l1) (l2 l2))
(unless (null? l1)
(f (car l1) (car l2))
(for-each2 (cdr l1) (cdr l2)))))
(define (build-cont-table fun)
(let ((max-k (fold-conts (lambda (k cont max-k) (max k max-k))
-1 fun)))
(fold-conts (lambda (k cont table)
(vector-set! table k cont)
table)
(make-vector (1+ max-k) #f)
fun)))
;; Data-flow graph for CPS: both for values and continuations.
(define-record-type $dfg
(make-dfg conts preds defs uses scopes scope-levels
min-label max-label label-count
min-var max-var var-count)
dfg?
;; vector of label -> $kargs, etc
(conts dfg-cont-table)
;; vector of label -> (pred-label ...)
(preds dfg-preds)
;; vector of var -> def-label
(defs dfg-defs)
;; vector of var -> (use-label ...)
(uses dfg-uses)
;; vector of label -> label
(scopes dfg-scopes)
;; vector of label -> int
(scope-levels dfg-scope-levels)
(min-label dfg-min-label)
(max-label dfg-max-label)
(label-count dfg-label-count)
(min-var dfg-min-var)
(max-var dfg-max-var)
(var-count dfg-var-count))
(define-inlinable (vector-push! vec idx val)
(let ((v vec) (i idx))
(vector-set! v i (cons val (vector-ref v i)))))
(define (compute-reachable dfg min-label label-count)
"Compute and return the continuations that may be reached if flow
reaches a continuation N. Returns a vector of intsets, whose first
index corresponds to MIN-LABEL, and so on."
(let (;; Vector of intsets, indicating that continuation N can
;; reach a set M...
(reachable (make-vector label-count #f)))
(define (label->idx label) (- label min-label))
;; Iterate labels backwards, to converge quickly.
(let lp ((label (+ min-label label-count)) (changed? #f))
(cond
((= label min-label)
(if changed?
(lp (+ min-label label-count) #f)
reachable))
(else
(let* ((label (1- label))
(idx (label->idx label))
(old (vector-ref reachable idx))
(new (fold (lambda (succ set)
(cond
((vector-ref reachable (label->idx succ))
=> (lambda (succ-set)
(intset-union set succ-set)))
(else set)))
(or (vector-ref reachable idx)
(intset-add empty-intset label))
(visit-cont-successors list
(lookup-cont label dfg)))))
(cond
((eq? old new)
(lp label changed?))
(else
(vector-set! reachable idx new)
(lp label #t)))))))))
(define (find-prompts dfg min-label label-count)
"Find the prompts in DFG between MIN-LABEL and MIN-LABEL +
LABEL-COUNT, and return them as a list of PROMPT-LABEL, HANDLER-LABEL
pairs."
(let lp ((label min-label) (prompts '()))
(cond
((= label (+ min-label label-count))
(reverse prompts))
(else
(match (lookup-cont label dfg)
(($ $kargs names syms body)
(match (find-expression body)
(($ $prompt escape? tag handler)
(lp (1+ label) (acons label handler prompts)))
(_ (lp (1+ label) prompts))))
(_ (lp (1+ label) prompts)))))))
(define (compute-interval reachable min-label label-count start end)
"Compute and return the set of continuations that may be reached from
START, inclusive, but not reached by END, exclusive. Returns an
intset."
(intset-subtract (vector-ref reachable (- start min-label))
(vector-ref reachable (- end min-label))))
(define (find-prompt-bodies dfg min-label label-count)
"Find all the prompts in DFG from the LABEL-COUNT continuations
starting at MIN-LABEL, and compute the set of continuations that is
reachable from the prompt bodies but not from the corresponding handler.
Returns a list of PROMPT, HANDLER, BODY lists, where the BODY is an
intset."
(match (find-prompts dfg min-label label-count)
(() '())
(((prompt . handler) ...)
(let ((reachable (compute-reachable dfg min-label label-count)))
(map (lambda (prompt handler)
;; FIXME: It isn't correct to use all continuations
;; reachable from the prompt, because that includes
;; continuations outside the prompt body. This point is
;; moot if the handler's control flow joins with the the
;; body, as is usually but not always the case.
;;
;; One counter-example is when the handler contifies an
;; infinite loop; in that case we compute a too-large
;; prompt body. This error is currently innocuous, but we
;; should fix it at some point.
;;
;; The fix is to end the body at the corresponding "pop"
;; primcall, if any.
(let ((body (compute-interval reachable min-label label-count
prompt handler)))
(list prompt handler body)))
prompt handler)))))
(define* (visit-prompt-control-flow dfg min-label label-count f #:key complete?)
"For all prompts in DFG in the range [MIN-LABEL, MIN-LABEL +
LABEL-COUNT), invoke F with arguments PROMPT, HANDLER, and BODY for each
body continuation in the prompt."
(define (label->idx label) (- label min-label))
(define (idx->label idx) (+ idx min-label))
(for-each
(match-lambda
((prompt handler body)
(define (out-or-back-edge? label)
;; Most uses of visit-prompt-control-flow don't need every body
;; continuation, and would be happy getting called only for
;; continuations that postdominate the rest of the body. Unless
;; you pass #:complete? #t, we only invoke F on continuations
;; that can leave the body, or on back-edges in loops.
;;
;; You would think that looking for the final "pop" primcall
;; would be sufficient, but that is incorrect; it's possible for
;; a loop in the prompt body to be contified, and that loop need
;; not continue to the pop if it never terminates. The pop could
;; even be removed by DCE, in that case.
(or-map (lambda (succ)
(or (not (intset-ref body succ))
(<= succ label)))
(lookup-successors label dfg)))
(let lp ((label min-label))
(let ((label (intset-next body label)))
(when label
(when (or complete? (out-or-back-edge? label))
(f prompt handler label))
(lp (1+ label)))))))
(find-prompt-bodies dfg min-label label-count)))
(define (analyze-reverse-control-flow fun dfg min-label label-count)
(define (compute-reverse-control-flow-order ktail dfg)
(let ((label-map (make-vector label-count #f))
(next -1))
(define (label->idx label) (- label min-label))
(define (idx->label idx) (+ idx min-label))
(let visit ((k ktail))
;; Mark this label as visited.
(vector-set! label-map (label->idx k) #t)
(for-each (lambda (k)
;; Visit predecessors unless they are already visited.
(unless (vector-ref label-map (label->idx k))
(visit k)))
(lookup-predecessors k dfg))
;; Add to reverse post-order chain.
(vector-set! label-map (label->idx k) next)
(set! next k))
(let lp ((n 0) (head next))
(if (< head 0)
;; Add nodes that are not reachable from the tail.
(let lp ((n n) (m label-count))
(unless (= n label-count)
(let find-unvisited ((m (1- m)))
(if (vector-ref label-map m)
(find-unvisited (1- m))
(begin
(vector-set! label-map m n)
(lp (1+ n) m))))))
;; Pop the head off the chain, give it its
;; reverse-post-order numbering, and continue.
(let ((next (vector-ref label-map (label->idx head))))
(vector-set! label-map (label->idx head) n)
(lp (1+ n) next))))
label-map))
(define (convert-successors k-map)
(define (idx->label idx) (+ idx min-label))
(define (renumber label)
(vector-ref k-map (- label min-label)))
(let ((succs (make-vector (vector-length k-map) #f)))
(let lp ((n 0))
(when (< n (vector-length succs))
(vector-set! succs (vector-ref k-map n)
(map renumber
(lookup-successors (idx->label n) dfg)))
(lp (1+ n))))
succs))
(match fun
(($ $cont kfun ($ $kfun src meta self ($ $cont ktail tail)))
(let* ((k-map (compute-reverse-control-flow-order ktail dfg))
(succs (convert-successors k-map)))
;; Any expression in the prompt body could cause an abort to
;; the handler. This code adds links from every block in the
;; prompt body to the handler. This causes all values used
;; by the handler to be seen as live in the prompt body, as
;; indeed they are.
(visit-prompt-control-flow
dfg min-label label-count
(lambda (prompt handler body)
(define (renumber label)
(vector-ref k-map (- label min-label)))
(vector-push! succs (renumber body) (renumber handler))))
(values k-map succs)))))
(define (compute-idoms dfg min-label label-count)
(define preds (dfg-preds dfg))
(define (label->idx label) (- label min-label))
(define (idx->label idx) (+ idx min-label))
(define (idx->dfg-idx idx) (- (idx->label idx) (dfg-min-label dfg)))
(let ((idoms (make-vector label-count #f)))
(define (common-idom d0 d1)
;; We exploit the fact that a reverse post-order is a topological
;; sort, and so the idom of a node is always numerically less than
;; the node itself.
(cond
((= d0 d1) d0)
((< d0 d1) (common-idom d0 (vector-ref idoms (label->idx d1))))
(else (common-idom (vector-ref idoms (label->idx d0)) d1))))
(define (compute-idom preds)
(define (has-idom? pred)
(vector-ref idoms (label->idx pred)))
(match preds
(() min-label)
((pred . preds)
(if (has-idom? pred)
(let lp ((idom pred) (preds preds))
(match preds
(() idom)
((pred . preds)
(lp (if (has-idom? pred)
(common-idom idom pred)
idom)
preds))))
(compute-idom preds)))))
;; This is the iterative O(n^2) fixpoint algorithm, originally from
;; Allen and Cocke ("Graph-theoretic constructs for program flow
;; analysis", 1972). See the discussion in Cooper, Harvey, and
;; Kennedy's "A Simple, Fast Dominance Algorithm", 2001.
(let iterate ((n 0) (changed? #f))
(cond
((< n label-count)
(let ((idom (vector-ref idoms n))
(idom* (compute-idom (vector-ref preds (idx->dfg-idx n)))))
(cond
((eqv? idom idom*)
(iterate (1+ n) changed?))
(else
(vector-set! idoms n idom*)
(iterate (1+ n) #t)))))
(changed?
(iterate 0 #f))
(else idoms)))))
;; Compute a vector containing, for each node, a list of the nodes that
;; it immediately dominates. These are the "D" edges in the DJ tree.
(define (compute-dom-edges idoms min-label)
(define (label->idx label) (- label min-label))
(define (idx->label idx) (+ idx min-label))
(let ((doms (make-vector (vector-length idoms) '())))
(let lp ((n 0))
(when (< n (vector-length idoms))
(let ((idom (vector-ref idoms n)))
(vector-push! doms (label->idx idom) (idx->label n)))
(lp (1+ n))))
doms))
;; There used to be some loop detection code here, but it bitrotted.
;; We'll need it again eventually but for now it can be found in the git
;; history.
;; Data-flow analysis.
(define-record-type $dfa
(make-dfa min-label min-var var-count in out)
dfa?
;; Minimum label in this function.
(min-label dfa-min-label)
;; Minimum var in this function.
(min-var dfa-min-var)
;; Var count in this function.
(var-count dfa-var-count)
;; Vector of k-idx -> intset
(in dfa-in)
;; Vector of k-idx -> intset
(out dfa-out))
(define (dfa-k-idx dfa k)
(- k (dfa-min-label dfa)))
(define (dfa-k-sym dfa idx)
(+ idx (dfa-min-label dfa)))
(define (dfa-k-count dfa)
(vector-length (dfa-in dfa)))
(define (dfa-var-idx dfa var)
(let ((idx (- var (dfa-min-var dfa))))
(unless (< -1 idx (dfa-var-count dfa))
(error "var out of range" var))
idx))
(define (dfa-var-sym dfa idx)
(unless (< -1 idx (dfa-var-count dfa))
(error "idx out of range" idx))
(+ idx (dfa-min-var dfa)))
(define (dfa-k-in dfa idx)
(vector-ref (dfa-in dfa) idx))
(define (dfa-k-out dfa idx)
(vector-ref (dfa-out dfa) idx))
(define (compute-live-variables fun dfg)
;; Compute the maximum fixed point of the data-flow constraint problem.
;;
;; This always completes, as the graph is finite and the in and out sets
;; are complete semi-lattices. If the graph is reducible and the blocks
;; are sorted in reverse post-order, this completes in a maximum of LC +
;; 2 iterations, where LC is the loop connectedness number. See Hecht
;; and Ullman, "Analysis of a simple algorithm for global flow
;; problems", POPL 1973, or the recent summary in "Notes on graph
;; algorithms used in optimizing compilers", Offner 2013.
(define (compute-maximum-fixed-point preds inv outv killv genv)
(define (fold f seed l)
(if (null? l) seed (fold f (f (car l) seed) (cdr l))))
(let lp ((n 0) (changed? #f))
(cond
((< n (vector-length preds))
(let* ((in (vector-ref inv n))
(in* (or
(fold (lambda (pred set)
(cond
((vector-ref outv pred)
=> (lambda (out)
(if set
(intset-union set out)
out)))
(else set)))
in
(vector-ref preds n))
empty-intset)))
(if (eq? in in*)
(lp (1+ n) changed?)
(let ((out* (fold (lambda (gen set)
(intset-add set gen))
(fold (lambda (kill set)
(intset-remove set kill))
in*
(vector-ref killv n))
(vector-ref genv n))))
(vector-set! inv n in*)
(vector-set! outv n out*)
(lp (1+ n) #t)))))
(changed?
(lp 0 #f)))))
(unless (and (= (vector-length (dfg-uses dfg)) (dfg-var-count dfg))
(= (vector-length (dfg-cont-table dfg)) (dfg-label-count dfg)))
(error "function needs renumbering"))
(let* ((min-label (dfg-min-label dfg))
(nlabels (dfg-label-count dfg))
(min-var (dfg-min-var dfg))
(nvars (dfg-var-count dfg))
(usev (make-vector nlabels '()))
(defv (make-vector nlabels '()))
(live-in (make-vector nlabels #f))
(live-out (make-vector nlabels #f)))
(call-with-values
(lambda ()
(analyze-reverse-control-flow fun dfg min-label nlabels))
(lambda (k-map succs)
(define (var->idx var) (- var min-var))
(define (idx->var idx) (+ idx min-var))
(define (label->idx label)
(vector-ref k-map (- label min-label)))
;; Initialize defv and usev.
(let ((defs (dfg-defs dfg))
(uses (dfg-uses dfg)))
(let lp ((n 0))
(when (< n (vector-length defs))
(let ((def (vector-ref defs n)))
(unless def
(error "internal error -- var array not packed"))
(for-each (lambda (def)
(vector-push! defv (label->idx def) n))
(lookup-predecessors def dfg))
(for-each (lambda (use)
(vector-push! usev (label->idx use) n))
(vector-ref uses n))
(lp (1+ n))))))
;; Liveness is a reverse data-flow problem, so we give
;; compute-maximum-fixed-point a reversed graph, swapping in for
;; out, usev for defv, and using successors instead of
;; predecessors. Continuation 0 is ktail.
(compute-maximum-fixed-point succs live-out live-in defv usev)
;; Now rewrite the live-in and live-out sets to be indexed by
;; (LABEL - MIN-LABEL).
(let ((live-in* (make-vector nlabels #f))
(live-out* (make-vector nlabels #f)))
(let lp ((idx 0))
(when (< idx nlabels)
(let ((dfa-idx (vector-ref k-map idx)))
(vector-set! live-in* idx (vector-ref live-in dfa-idx))
(vector-set! live-out* idx (vector-ref live-out dfa-idx))
(lp (1+ idx)))))
(make-dfa min-label min-var nvars live-in* live-out*))))))
(define (print-dfa dfa)
(match dfa
(($ $dfa min-label min-var var-count in out)
(define (print-var-set bv)
(let lp ((n 0))
(let ((n (intset-next bv n)))
(when n
(format #t " ~A" (+ n min-var))
(lp (1+ n))))))
(let lp ((n 0))
(when (< n (vector-length in))
(format #t "~A:\n" (+ n min-label))
(format #t " in:")
(print-var-set (vector-ref in n))
(newline)
(format #t " out:")
(print-var-set (vector-ref out n))
(newline)
(lp (1+ n)))))))
(define (compute-label-and-var-ranges fun global?)
(define (min* a b)
(if b (min a b) a))
(define-syntax-rule (do-fold make-cont-folder)
((make-cont-folder min-label max-label label-count
min-var max-var var-count)
(lambda (label cont
min-label max-label label-count
min-var max-var var-count)
(let ((min-label (min* label min-label))
(max-label (max label max-label)))
(match cont
(($ $kargs names vars body)
(values min-label max-label (1+ label-count)
(cond (min-var (fold min min-var vars))
((pair? vars) (fold min (car vars) (cdr vars)))
(else min-var))
(fold max max-var vars)
(+ var-count (length vars))))
(($ $kfun src meta self)
(values min-label max-label (1+ label-count)
(min* self min-var) (max self max-var) (1+ var-count)))
(_ (values min-label max-label (1+ label-count)
min-var max-var var-count)))))
fun
#f -1 0 #f -1 0))
(if global?
(do-fold make-global-cont-folder)
(do-fold make-local-cont-folder)))
(define* (compute-dfg fun #:key (global? #t))
(call-with-values (lambda () (compute-label-and-var-ranges fun global?))
(lambda (min-label max-label label-count min-var max-var var-count)
(when (or (zero? label-count) (zero? var-count))
(error "internal error (no vars or labels for fun?)"))
(let* ((nlabels (- (1+ max-label) min-label))
(nvars (- (1+ max-var) min-var))
(conts (make-vector nlabels #f))
(preds (make-vector nlabels '()))
(defs (make-vector nvars #f))
(uses (make-vector nvars '()))
(scopes (make-vector nlabels #f))
(scope-levels (make-vector nlabels #f)))
(define (var->idx var) (- var min-var))
(define (label->idx label) (- label min-label))
(define (add-def! var def-k)
(vector-set! defs (var->idx var) def-k))
(define (add-use! var use-k)
(vector-push! uses (var->idx var) use-k))
(define* (declare-block! label cont parent
#:optional (level
(1+ (vector-ref
scope-levels
(label->idx parent)))))
(vector-set! conts (label->idx label) cont)
(vector-set! scopes (label->idx label) parent)
(vector-set! scope-levels (label->idx label) level))
(define (link-blocks! pred succ)
(vector-push! preds (label->idx succ) pred))
(define (visit-cont cont label)
(match cont
(($ $kargs names syms body)
(for-each (cut add-def! <> label) syms)
(visit-term body label))
(($ $kreceive arity k)
(link-blocks! label k))))
(define (visit-term term label)
(match term
(($ $letk (($ $cont k cont) ...) body)
;; Set up recursive environment before visiting cont bodies.
(for-each/2 (lambda (cont k)
(declare-block! k cont label))
cont k)
(for-each/2 visit-cont cont k)
(visit-term body label))
(($ $continue k src exp)
(link-blocks! label k)
(visit-exp exp label))))
(define (visit-exp exp label)
(define (use! sym)
(add-use! sym label))
(match exp
((or ($ $const) ($ $prim) ($ $closure)) #f)
(($ $call proc args)
(use! proc)
(for-each use! args))
(($ $callk k proc args)
(use! proc)
(for-each use! args))
(($ $primcall name args)
(for-each use! args))
(($ $branch kt exp)
(link-blocks! label kt)
(visit-exp exp label))
(($ $values args)
(for-each use! args))
(($ $prompt escape? tag handler)
(use! tag)
(link-blocks! label handler))
(($ $fun body)
(when global?
(visit-fun body)))
(($ $rec names syms funs)
(unless global?
(error "$rec should not be present when building a local DFG"))
(for-each (lambda (fun)
(match fun
(($ $fun body)
(visit-fun body))))
funs))))
(define (visit-clause clause kfun)
(match clause
(#f #t)
(($ $cont kclause
(and clause ($ $kclause arity ($ $cont kbody body)
alternate)))
(declare-block! kclause clause kfun)
(link-blocks! kfun kclause)
(declare-block! kbody body kclause)
(link-blocks! kclause kbody)
(visit-cont body kbody)
(visit-clause alternate kfun))))
(define (visit-fun fun)
(match fun
(($ $cont kfun
(and cont
($ $kfun src meta self ($ $cont ktail tail) clause)))
(declare-block! kfun cont #f 0)
(add-def! self kfun)
(declare-block! ktail tail kfun)
(visit-clause clause kfun))))
(visit-fun fun)
(make-dfg conts preds defs uses scopes scope-levels
min-label max-label label-count
min-var max-var var-count)))))
(define* (dump-dfg dfg #:optional (port (current-output-port)))
(let ((min-label (dfg-min-label dfg))
(min-var (dfg-min-var dfg)))
(define (label->idx label) (- label min-label))
(define (idx->label idx) (+ idx min-label))
(define (var->idx var) (- var min-var))
(define (idx->var idx) (+ idx min-var))
(let lp ((label (dfg-min-label dfg)))
(when (<= label (dfg-max-label dfg))
(let ((cont (vector-ref (dfg-cont-table dfg) (label->idx label))))
(when cont
(unless (equal? (lookup-predecessors label dfg) (list (1- label)))
(newline port))
(format port "k~a:~8t" label)
(match cont
(($ $kreceive arity k)
(format port "$kreceive ~a k~a\n" arity k))
(($ $kfun src meta self tail clause)
(format port "$kfun ~a ~a v~a\n" src meta self))
(($ $ktail)
(format port "$ktail\n"))
(($ $kclause arity ($ $cont kbody) alternate)
(format port "$kclause ~a k~a" arity kbody)
(match alternate
(#f #f)
(($ $cont kalt) (format port " -> k~a" kalt)))
(newline port))
(($ $kargs names vars term)
(unless (null? vars)
(format port "v~a[~a]~:{ v~a[~a]~}: "
(car vars) (car names) (map list (cdr vars) (cdr names))))
(match (find-call term)
(($ $continue kf src ($ $branch kt exp))
(format port "if ")
(match exp
(($ $primcall name args)
(format port "(~a~{ v~a~})" name args))
(($ $values (arg))
(format port "v~a" arg)))
(format port " k~a k~a\n" kt kf))
(($ $continue k src exp)
(match exp
(($ $const val) (format port "const ~@y" val))
(($ $prim name) (format port "prim ~a" name))
(($ $fun ($ $cont kbody)) (format port "fun k~a" kbody))
(($ $rec names syms funs) (format port "rec~{ v~a~}" syms))
(($ $closure label nfree) (format port "closure k~a (~a free)" label nfree))
(($ $call proc args) (format port "call~{ v~a~}" (cons proc args)))
(($ $callk k proc args) (format port "callk k~a~{ v~a~}" k (cons proc args)))
(($ $primcall name args) (format port "~a~{ v~a~}" name args))
(($ $values args) (format port "values~{ v~a~}" args))
(($ $prompt escape? tag handler) (format port "prompt ~a v~a k~a" escape? tag handler)))
(unless (= k (1+ label))
(format port " -> k~a" k))
(newline port))))))
(lp (1+ label)))))))
(define-syntax-rule (with-fresh-name-state-from-dfg dfg body ...)
(parameterize ((label-counter (1+ (dfg-max-label dfg)))
(var-counter (1+ (dfg-max-var dfg))))
body ...))
(define (lookup-cont label dfg)
(let ((res (vector-ref (dfg-cont-table dfg) (- label (dfg-min-label dfg)))))
(unless res
(error "Unknown continuation!" label))
res))
(define (lookup-predecessors k dfg)
(vector-ref (dfg-preds dfg) (- k (dfg-min-label dfg))))
(define (lookup-successors k dfg)
(let ((cont (vector-ref (dfg-cont-table dfg) (- k (dfg-min-label dfg)))))
(visit-cont-successors list cont)))
(define (lookup-def var dfg)
(vector-ref (dfg-defs dfg) (- var (dfg-min-var dfg))))
(define (lookup-uses var dfg)
(vector-ref (dfg-uses dfg) (- var (dfg-min-var dfg))))
(define (lookup-block-scope k dfg)
(vector-ref (dfg-scopes dfg) (- k (dfg-min-label dfg))))
(define (lookup-scope-level k dfg)
(vector-ref (dfg-scope-levels dfg) (- k (dfg-min-label dfg))))
(define (find-defining-term sym dfg)
(match (lookup-predecessors (lookup-def sym dfg) dfg)
((def-exp-k)
(lookup-cont def-exp-k dfg))
(else #f)))
(define (find-call term)
(match term
(($ $kargs names syms body) (find-call body))
(($ $letk conts body) (find-call body))
(($ $continue) term)))
(define (call-expression call)
(match call
(($ $continue k src 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)
(($ $kreceive) #f)
(($ $kclause) #f)
(term (find-expression term))))
(define (find-constant-value sym dfg)
(match (find-defining-expression sym dfg)
(($ $const val)
(values #t val))
(else
(values #f #f))))
(define (constant-needs-allocation? var val dfg)
(define (immediate-u8? val)
(and (integer? val) (exact? val) (<= 0 val 255)))
(define (find-exp term)
(match term
(($ $kargs names vars body) (find-exp body))
(($ $letk conts body) (find-exp body))
(else term)))
(or-map
(lambda (use)
(match (find-expression (lookup-cont use dfg))
(($ $call) #f)
(($ $callk) #f)
(($ $values) #f)
(($ $primcall 'free-ref (closure slot))
(eq? var closure))
(($ $primcall 'free-set! (closure slot value))
(or (eq? var closure) (eq? var value)))
(($ $primcall 'cache-current-module! (mod . _))
(eq? var mod))
(($ $primcall 'cached-toplevel-box _)
#f)
(($ $primcall 'cached-module-box _)
#f)
(($ $primcall 'resolve (name bound?))
(eq? var name))
(($ $primcall 'make-vector/immediate (len init))
(eq? var init))
(($ $primcall 'vector-ref/immediate (v i))
(eq? var v))
(($ $primcall 'vector-set!/immediate (v i x))
(or (eq? var v) (eq? var x)))
(($ $primcall 'allocate-struct/immediate (vtable nfields))
(eq? var vtable))
(($ $primcall 'struct-ref/immediate (s n))
(eq? var s))
(($ $primcall 'struct-set!/immediate (s n x))
(or (eq? var s) (eq? var x)))
(($ $primcall 'builtin-ref (idx))
#f)
(_ #t)))
(vector-ref (dfg-uses dfg) (- var (dfg-min-var dfg)))))
(define (continuation-scope-contains? scope-k k dfg)
(let ((scope-level (lookup-scope-level scope-k dfg)))
(let lp ((k k))
(or (eq? scope-k k)
(and (< scope-level (lookup-scope-level k dfg))
(lp (lookup-block-scope k dfg)))))))
(define (continuation-bound-in? k use-k dfg)
(continuation-scope-contains? (lookup-block-scope k dfg) use-k dfg))
(define (variable-free-in? var k dfg)
(or-map (lambda (use)
(continuation-scope-contains? k use dfg))
(lookup-uses var dfg)))
;; A continuation is a control point if it has multiple predecessors, or
;; if its single predecessor does not have a single successor.
(define (control-point? k dfg)
(match (lookup-predecessors k dfg)
((pred)
(let ((cont (vector-ref (dfg-cont-table dfg)
(- pred (dfg-min-label dfg)))))
(visit-cont-successors (case-lambda
(() #t)
((succ0) #f)
((succ1 succ2) #t))
cont)))
(_ #t)))
(define (lookup-bound-syms k dfg)
(match (lookup-cont k dfg)
(($ $kargs names syms body)
syms)))

View file

@ -1,343 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2014, 2015 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 renumber variables and continuation labels so that they
;;; are contiguous within each function and, in the case of labels,
;;; topologically sorted.
;;;
;;; Code:
(define-module (language cps renumber)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (language cps)
#:export (renumber))
;; Topologically sort the continuation tree starting at k0, using
;; reverse post-order numbering.
(define (sort-conts k0 conts new-k0 path-lengths)
(let ((next -1))
(let visit ((k k0))
(define (maybe-visit k)
(let ((entry (vector-ref conts k)))
;; Visit the successor if it has not been
;; visited yet.
(when (and entry (not (exact-integer? entry)))
(visit k))))
(let ((cont (vector-ref conts k)))
;; Clear the cont table entry to mark this continuation as
;; visited.
(vector-set! conts k #f)
(match cont
(($ $kargs names syms body)
(let lp ((body body))
(match body
(($ $letk conts body) (lp body))
(($ $continue k src exp)
(match exp
(($ $prompt escape? tag handler)
(maybe-visit handler)
(maybe-visit k))
(($ $branch kt)
;; Visit the successor with the shortest path length
;; to the tail first, so that if the branches are
;; unsorted, the longer path length will appear
;; first. This will move a loop exit out of a loop.
(let ((k-len (vector-ref path-lengths k))
(kt-len (vector-ref path-lengths kt)))
(cond
((if kt-len
(or (not k-len)
(< k-len kt-len)
;; If the path lengths are the
;; same, preserve original order
;; to avoid squirreliness.
(and (= k-len kt-len) (< kt k)))
(if k-len #f (< kt k)))
(maybe-visit k)
(maybe-visit kt))
(else
(maybe-visit kt)
(maybe-visit k)))))
(_
(maybe-visit k)))))))
(($ $kreceive arity k) (maybe-visit k))
(($ $kclause arity ($ $cont kbody) alt)
(match alt
(($ $cont kalt) (maybe-visit kalt))
(_ #f))
(maybe-visit kbody))
(($ $kfun src meta self tail clause)
(match clause
(($ $cont kclause) (maybe-visit kclause))
(_ #f)))
(_ #f))
;; Chain this label to the label that will follow it in the sort
;; order, and record this label as the new head of the order.
(vector-set! conts k next)
(set! next k)))
;; Finally traverse the label chain, giving each label its final
;; name.
(let lp ((n new-k0) (head next))
(if (< head 0)
n
(let ((next (vector-ref conts head)))
(vector-set! conts head n)
(lp (1+ n) next))))))
(define (compute-tail-path-lengths preds ktail path-lengths)
(let visit ((k ktail) (length-in 0))
(let ((length (vector-ref path-lengths k)))
(unless (and length (<= length length-in))
(vector-set! path-lengths k length-in)
(let lp ((preds (vector-ref preds k)))
(match preds
(() #t)
((pred . preds)
(visit pred (1+ length-in))
(lp preds))))))))
(define (compute-new-labels-and-vars fun)
(call-with-values (lambda () (compute-max-label-and-var fun))
(lambda (max-label max-var)
(let ((labels (make-vector (1+ max-label) #f))
(next-label 0)
(vars (make-vector (1+ max-var) #f))
(next-var 0)
(preds (make-vector (1+ max-label) '()))
(path-lengths (make-vector (1+ max-label) #f)))
(define (add-predecessor! pred succ)
(vector-set! preds succ (cons pred (vector-ref preds succ))))
(define (rename! var)
(vector-set! vars var next-var)
(set! next-var (1+ next-var)))
(define (collect-conts fun)
(define (visit-cont cont)
(match cont
(($ $cont label cont)
(vector-set! labels label cont)
(match cont
(($ $kargs names vars body)
(visit-term body label))
(($ $kfun src meta self tail clause)
(visit-cont tail)
(match clause
(($ $cont kclause)
(add-predecessor! label kclause)
(visit-cont clause))
(#f #f)))
(($ $kclause arity (and body ($ $cont kbody)) alternate)
(add-predecessor! label kbody)
(visit-cont body)
(match alternate
(($ $cont kalt)
(add-predecessor! label kalt)
(visit-cont alternate))
(#f #f)))
(($ $kreceive arity kargs)
(add-predecessor! label kargs))
(($ $ktail) #f)))))
(define (visit-term term label)
(match term
(($ $letk conts body)
(let lp ((conts conts))
(unless (null? conts)
(visit-cont (car conts))
(lp (cdr conts))))
(visit-term body label))
(($ $continue k src exp)
(add-predecessor! label k)
(match exp
(($ $branch kt)
(add-predecessor! label kt))
(($ $prompt escape? tag handler)
(add-predecessor! label handler))
(_ #f)))))
(visit-cont fun))
(define (compute-names-in-fun fun)
(define queue '())
(define (visit-cont cont)
(match cont
(($ $cont label cont)
(let ((reachable? (exact-integer? (vector-ref labels label))))
;; This cont is reachable if it was given a number.
;; Otherwise the cont table entry still contains the
;; cont itself; clear it out to indicate that the cont
;; should not be residualized.
(unless reachable?
(vector-set! labels label #f))
(match cont
(($ $kargs names vars body)
(when reachable?
(for-each rename! vars))
(visit-term body reachable?))
(($ $kfun src meta self tail clause)
(unless reachable? (error "entry should be reachable"))
(rename! self)
(visit-cont tail)
(when clause
(visit-cont clause)))
(($ $kclause arity body alternate)
(unless reachable? (error "clause should be reachable"))
(visit-cont body)
(when alternate
(visit-cont alternate)))
(($ $ktail)
(unless reachable?
;; It's possible for the tail to be unreachable,
;; if all paths contify to infinite loops. Make
;; sure we mark as reachable.
(vector-set! labels label next-label)
(set! next-label (1+ next-label))))
(($ $kreceive)
#f))))))
(define (visit-term term reachable?)
(match term
(($ $letk conts body)
(for-each visit-cont conts)
(visit-term body reachable?))
(($ $continue k src ($ $fun body))
(when reachable?
(set! queue (cons body queue))))
(($ $continue k src ($ $rec names syms funs))
(when reachable?
(set! queue (fold (lambda (fun queue)
(match fun
(($ $fun body)
(cons body queue))))
queue
funs))))
(($ $continue) #f)))
(match fun
(($ $cont kfun ($ $kfun src meta self ($ $cont ktail)))
(collect-conts fun)
(compute-tail-path-lengths preds ktail path-lengths)
(set! next-label (sort-conts kfun labels next-label path-lengths))
(visit-cont fun)
(for-each compute-names-in-fun (reverse queue)))
(($ $program conts)
(for-each compute-names-in-fun conts))))
(compute-names-in-fun fun)
(values labels vars next-label next-var)))))
(define (apply-renumbering term labels vars)
(define (relabel label) (vector-ref labels label))
(define (rename var) (vector-ref vars var))
(define (rename-kw-arity arity)
(match arity
(($ $arity req opt rest kw aok?)
(make-$arity req opt rest
(map (match-lambda
((kw kw-name kw-var)
(list kw kw-name (rename kw-var))))
kw)
aok?))))
(define (must-visit-cont cont)
(or (visit-cont cont)
(error "internal error -- failed to visit cont")))
(define (visit-conts conts)
(match conts
(() '())
((cont . conts)
(cond
((visit-cont cont)
=> (lambda (cont)
(cons cont (visit-conts conts))))
(else (visit-conts conts))))))
(define (visit-cont cont)
(match cont
(($ $cont label cont)
(let ((label (relabel label)))
(and
label
(rewrite-cps-cont cont
(($ $kargs names vars body)
(label ($kargs names (map rename vars) ,(visit-term body))))
(($ $kfun src meta self tail clause)
(label
($kfun src meta (rename self) ,(must-visit-cont tail)
,(and clause (must-visit-cont clause)))))
(($ $ktail)
(label ($ktail)))
(($ $kclause arity body alternate)
(label
($kclause ,(rename-kw-arity arity) ,(must-visit-cont body)
,(and alternate (must-visit-cont alternate)))))
(($ $kreceive ($ $arity req () rest () #f) kargs)
(label ($kreceive req rest (relabel kargs))))))))))
(define (visit-term term)
(rewrite-cps-term term
(($ $letk conts body)
,(match (visit-conts conts)
(() (visit-term body))
(conts (build-cps-term ($letk ,conts ,(visit-term body))))))
(($ $continue k src exp)
($continue (relabel k) src ,(visit-exp exp)))))
(define (visit-exp exp)
(match exp
((or ($ $const) ($ $prim))
exp)
(($ $closure k nfree)
(build-cps-exp ($closure (relabel k) nfree)))
(($ $fun)
(visit-fun exp))
(($ $rec names vars funs)
(build-cps-exp ($rec names (map rename vars) (map visit-fun funs))))
(($ $values args)
(let ((args (map rename args)))
(build-cps-exp ($values args))))
(($ $call proc args)
(let ((args (map rename args)))
(build-cps-exp ($call (rename proc) args))))
(($ $callk k proc args)
(let ((args (map rename args)))
(build-cps-exp ($callk (relabel k) (rename proc) args))))
(($ $branch kt exp)
(build-cps-exp ($branch (relabel kt) ,(visit-exp exp))))
(($ $primcall name args)
(let ((args (map rename args)))
(build-cps-exp ($primcall name args))))
(($ $prompt escape? tag handler)
(build-cps-exp
($prompt escape? (rename tag) (relabel handler))))))
(define (visit-fun fun)
(rewrite-cps-exp fun
(($ $fun body)
($fun ,(must-visit-cont body)))))
(match term
(($ $cont)
(must-visit-cont term))
(($ $program conts)
(build-cps-term
($program ,(map must-visit-cont conts))))))
(define (renumber term)
(call-with-values (lambda () (compute-new-labels-and-vars term))
(lambda (labels vars nlabels nvars)
(values (apply-renumbering term labels vars) nlabels nvars))))

View file

@ -1,689 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2013, 2014, 2015 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)
#:use-module (language cps intset)
#:export (allocate-slots
lookup-slot
lookup-maybe-slot
lookup-constant-value
lookup-maybe-constant-value
lookup-nlocals
lookup-call-proc-slot
lookup-parallel-moves
lookup-dead-slot-map))
(define-record-type $allocation
(make-allocation dfa slots
has-constv constant-values
call-allocations
nlocals)
allocation?
;; A DFA records all variables bound in a function, and assigns them
;; indices. The slot in which a variable is stored at runtime can be
;; had by indexing into the SLOTS vector with the variable's index.
;;
(dfa allocation-dfa)
(slots allocation-slots)
;; 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 in the CONSTANT-VALUES vector
;; and the corresponding bit in the HAS-CONSTV bitvector is set.
;;
(has-constv allocation-has-constv)
(constant-values allocation-constant-values)
;; Some continuations have additional associated information. This
;; addition information is a /call allocation/. Call allocations
;; record the way that functions are passed values, and how their
;; return values are rebound to local variables.
;;
;; A call allocation contains three pieces of information: the call's
;; /proc slot/, a set of /parallel moves/, and a /dead slot map/. The
;; proc slot indicates the slot of a procedure in a procedure call, or
;; where the procedure would be in a multiple-value return. The
;; parallel moves shuffle locals into position for a call, or shuffle
;; returned values back into place. Though they use the same slot,
;; moves for a call are called "call moves", and moves to handle a
;; return are "return moves". The dead slot map indicates, for a
;; call, what slots should be ignored by GC when marking the frame.
;;
;; $kreceive continuations record a proc slot and a set of return moves
;; to adapt multiple values from the stack to local variables.
;;
;; Tail calls record arg moves, but no proc slot.
;;
;; Non-tail calls record arg moves, a call slot, and a dead slot map.
;; Multiple-valued returns will have an associated $kreceive
;; continuation, which records the same proc slot, but has return
;; moves and no dead slot map.
;;
;; $prompt handlers are $kreceive continuations like any other.
;;
;; $values expressions with more than 1 value record moves but have no
;; proc slot or dead slot map.
;;
;; A set of moves is expressed as an ordered list of (SRC . DST)
;; moves, where SRC and DST are slots. This may involve a temporary
;; variable. A dead slot map is a bitfield, as an integer.
;;
(call-allocations allocation-call-allocations)
;; The number of locals for a $kclause.
;;
(nlocals allocation-nlocals))
(define-record-type $call-allocation
(make-call-allocation proc-slot moves dead-slot-map)
call-allocation?
(proc-slot call-allocation-proc-slot)
(moves call-allocation-moves)
(dead-slot-map call-allocation-dead-slot-map))
(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)
(let lp ((slot (let lp ((count 2))
(if (< n (ash 1 (1- count)))
count
;; Grow upper bound slower than factor 2 to avoid
;; needless bignum allocation on 32-bit systems
;; when there are more than 16 locals.
(lp (+ count (ash count -1)))))))
(if (or (zero? slot) (logbit? (1- slot) n))
slot
(lp (1- slot)))))
(define (lookup-maybe-slot sym allocation)
(match allocation
(($ $allocation dfa slots)
(vector-ref slots (dfa-var-idx dfa sym)))))
(define (lookup-slot sym allocation)
(or (lookup-maybe-slot sym allocation)
(error "Variable not allocated to a slot" sym)))
(define (lookup-constant-value sym allocation)
(match allocation
(($ $allocation dfa slots has-constv constant-values)
(let ((idx (dfa-var-idx dfa sym)))
(if (bitvector-ref has-constv idx)
(vector-ref constant-values idx)
(error "Variable does not have constant value" sym))))))
(define (lookup-maybe-constant-value sym allocation)
(match allocation
(($ $allocation dfa slots has-constv constant-values)
(let ((idx (dfa-var-idx dfa sym)))
(values (bitvector-ref has-constv idx)
(vector-ref constant-values idx))))))
(define (lookup-call-allocation k allocation)
(or (hashq-ref (allocation-call-allocations allocation) k)
(error "Continuation not a call" k)))
(define (lookup-call-proc-slot k allocation)
(or (call-allocation-proc-slot (lookup-call-allocation k allocation))
(error "Call has no proc slot" k)))
(define (lookup-parallel-moves k allocation)
(or (call-allocation-moves (lookup-call-allocation k allocation))
(error "Call has no use parallel moves slot" k)))
(define (lookup-dead-slot-map k allocation)
(or (call-allocation-dead-slot-map (lookup-call-allocation k allocation))
(error "Call has no dead slot map" k)))
(define (lookup-nlocals k allocation)
(or (hashq-ref (allocation-nlocals allocation) k)
(error "Not a clause continuation" 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 (dead-after-def? k-idx v-idx dfa)
(not (intset-ref (dfa-k-in dfa k-idx) v-idx)))
(define (dead-after-use? k-idx v-idx dfa)
(not (intset-ref (dfa-k-out dfa k-idx) v-idx)))
(define (allocate-slots fun dfg)
(let* ((dfa (compute-live-variables fun dfg))
(min-label (dfg-min-label dfg))
(label-count (dfg-label-count dfg))
(usev (make-vector label-count '()))
(defv (make-vector label-count '()))
(slots (make-vector (dfa-var-count dfa) #f))
(constant-values (make-vector (dfa-var-count dfa) #f))
(has-constv (make-bitvector (dfa-var-count dfa) #f))
(has-slotv (make-bitvector (dfa-var-count dfa) #t))
(needs-slotv (make-bitvector (dfa-var-count dfa) #t))
(needs-hintv (make-bitvector (dfa-var-count dfa) #f))
(call-allocations (make-hash-table))
(nlocals 0) ; Mutable. It pains me.
(nlocals-table (make-hash-table)))
(define (label->idx label) (- label min-label))
(define (idx->label idx) (+ idx min-label))
(define (bump-nlocals! nlocals*)
(when (< nlocals nlocals*)
(set! nlocals nlocals*)))
(define (empty-live-slots)
#b0)
(define (add-live-slot slot live-slots)
(logior live-slots (ash 1 slot)))
(define (kill-dead-slot slot live-slots)
(logand live-slots (lognot (ash 1 slot))))
(define (compute-slot live-slots hint)
;; Slots 253-255 are reserved for shuffling; see comments in
;; assembler.scm.
(if (and hint (not (logbit? hint live-slots))
(or (< hint 253) (> hint 255)))
hint
(let ((slot (find-first-zero live-slots)))
(if (or (< slot 253) (> slot 255))
slot
(+ 256 (find-first-zero (ash live-slots -256)))))))
(define (compute-call-proc-slot live-slots)
(+ 2 (find-first-trailing-zero live-slots)))
(define (compute-prompt-handler-proc-slot live-slots)
(if (zero? live-slots)
0
(1- (find-first-trailing-zero live-slots))))
(define (recompute-live-slots k)
(let ((in (dfa-k-in dfa (label->idx k))))
(let lp ((v 0) (live-slots 0))
(let ((v (intset-next in v)))
(if v
(let ((slot (vector-ref slots v)))
(lp (1+ v)
(if slot
(add-live-slot slot live-slots)
live-slots)))
live-slots)))))
(define* (allocate! var-idx hint live)
(cond
((not (bitvector-ref needs-slotv var-idx)) live)
((vector-ref slots var-idx) => (cut add-live-slot <> live))
((and (not hint) (bitvector-ref needs-hintv var-idx)) live)
(else
(let ((slot (compute-slot live hint)))
(bump-nlocals! (1+ slot))
(vector-set! slots var-idx slot)
(add-live-slot slot live)))))
;; Although some parallel moves may proceed without a temporary
;; slot, in general one is needed. That temporary slot must not be
;; part of the source or destination sets, and that slot should not
;; correspond to a live variable. Usually the source and
;; destination sets are a subset of the union of the live sets
;; before and after the move. However for stack slots that don't
;; have names -- those slots that correspond to function arguments
;; or to function return values -- it could be that they are out of
;; the computed live set. In that case they need to be adjoined to
;; the live set, used when choosing a temporary slot.
;;
;; Note that although we reserve slots 253-255 for shuffling
;; operands that address less than the full 24-bit range of locals,
;; that reservation doesn't apply here, because this temporary
;; itself is used while doing parallel assignment via "mov", and
;; "mov" does not need shuffling.
(define (compute-tmp-slot live stack-slots)
(find-first-zero (fold add-live-slot live stack-slots)))
(define (parallel-move src-slots dst-slots tmp-slot)
(let ((moves (solve-parallel-move src-slots dst-slots tmp-slot)))
(when (assv tmp-slot moves)
(bump-nlocals! (1+ tmp-slot)))
moves))
;; Find variables that are actually constant, and determine which
;; of those can avoid slot allocation.
(define (compute-constants!)
(let lp ((n 0))
(when (< n (vector-length constant-values))
(let ((sym (dfa-var-sym dfa n)))
(call-with-values (lambda () (find-constant-value sym dfg))
(lambda (has-const? const)
(when has-const?
(bitvector-set! has-constv n has-const?)
(vector-set! constant-values n const)
(when (not (constant-needs-allocation? sym const dfg))
(bitvector-set! needs-slotv n #f)))
(lp (1+ n))))))))
;; Record uses and defs, as lists of variable indexes, indexed by
;; label index.
(define (compute-uses-and-defs!)
(let lp ((n 0))
(when (< n (vector-length usev))
(match (lookup-cont (idx->label n) dfg)
(($ $kfun src meta self)
(vector-set! defv n (list (dfa-var-idx dfa self))))
(($ $kargs names syms body)
(vector-set! defv n (map (cut dfa-var-idx dfa <>) syms))
(vector-set! usev n
(map (cut dfa-var-idx dfa <>)
(match (find-expression body)
(($ $call proc args)
(cons proc args))
(($ $callk k proc args)
(cons proc args))
(($ $primcall name args)
args)
(($ $branch kt ($ $primcall name args))
args)
(($ $branch kt ($ $values args))
args)
(($ $values args)
args)
(($ $prompt escape? tag handler)
(list tag))
(_ '())))))
(_ #f))
(lp (1+ n)))))
;; Results of function calls that are not used don't need to be
;; allocated to slots.
(define (compute-unused-results!)
(define (kreceive-get-kargs kreceive)
(match (lookup-cont kreceive dfg)
(($ $kreceive arity kargs) kargs)
(_ #f)))
(let ((candidates (make-bitvector label-count #f)))
;; Find all $kargs that are the successors of $kreceive nodes.
(let lp ((n 0))
(when (< n label-count)
(and=> (kreceive-get-kargs (idx->label n))
(lambda (kargs)
(bitvector-set! candidates (label->idx kargs) #t)))
(lp (1+ n))))
;; For $kargs that only have $kreceive predecessors, remove unused
;; variables from the needs-slotv set.
(let lp ((n 0))
(let ((n (bit-position #t candidates n)))
(when n
(match (lookup-predecessors (idx->label n) dfg)
;; At least one kreceive is in the predecessor set, so we
;; only need to do the check for nodes with >1
;; predecessor.
((or (_) ((? kreceive-get-kargs) ...))
(for-each (lambda (var)
(when (dead-after-def? n var dfa)
(bitvector-set! needs-slotv var #f)))
(vector-ref defv n)))
(_ #f))
(lp (1+ n)))))))
;; Compute the set of variables whose allocation should be delayed
;; until a "hint" is known about where to allocate them. This is
;; the case for some procedure arguments.
;;
;; This algorithm used is a conservative approximation of what
;; really should happen, which would be eager allocation of call
;; frames as soon as it's known that a call will happen. It would
;; be nice to recast this as a proper data-flow problem.
(define (compute-needs-hint!)
(define (live-before n)
(dfa-k-in dfa n))
(define (live-after n)
(dfa-k-out dfa n))
(define needs-slot
(bitvector->intset needs-slotv))
;; Walk backwards. At a call, compute the set of variables that
;; have allocated slots and are live before but not after. This
;; set contains candidates for needs-hintv.
(define (scan-for-call n)
(when (<= 0 n)
(match (lookup-cont (idx->label n) dfg)
(($ $kargs names syms body)
(match (find-expression body)
((or ($ $call) ($ $callk))
(let* ((args (intset-subtract (live-before n) (live-after n)))
(args-needing-slots (intset-intersect args needs-slot)))
(if (intset-next args-needing-slots #f)
(scan-for-hints (1- n) args-needing-slots)
(scan-for-call (1- n)))))
(_ (scan-for-call (1- n)))))
(_ (scan-for-call (1- n))))))
;; Walk backwards in the current basic block. Stop when the block
;; ends, we reach a call, or when an expression kills a value.
(define (scan-for-hints n args)
(when (< 0 n)
(match (lookup-cont (idx->label n) dfg)
(($ $kargs names syms body)
(match (lookup-predecessors (idx->label (1+ n)) dfg)
(((? (cut eqv? <> (idx->label n))))
;; If we are indeed in the same basic block, then if we
;; are finished with the scan, we kill uses of the
;; terminator, but leave its definitions.
(match (find-expression body)
((or ($ $const) ($ $prim) ($ $closure)
($ $primcall) ($ $prompt)
;; If $values has more than one argument, it may
;; use a temporary, which would invalidate our
;; assumptions that slots not allocated are not
;; used.
($ $values (or () (_))))
(define (intset-empty? intset) (not (intset-next intset)))
(let ((killed (intset-subtract (live-before n) (live-after n))))
;; If the expression kills no values needing slots,
;; and defines no value needing a slot that's not
;; in our args, then we keep on trucking.
(if (intset-empty? (intset-intersect
(fold (lambda (def clobber)
(if (intset-ref args def)
clobber
(intset-add clobber def)))
killed
(vector-ref defv n))
needs-slot))
(scan-for-hints (1- n) args)
(finish-hints n (live-before n) args))))
((or ($ $call) ($ $callk) ($ $values) ($ $branch))
(finish-hints n (live-before n) args))))
;; Otherwise we kill uses of the block entry.
(_ (finish-hints n (live-before (1+ n)) args))))
(_ (finish-hints n (live-before (1+ n)) args)))))
;; Add definitions ARGS minus KILL to NEED-HINTS, and go back to
;; looking for calls.
(define (finish-hints n kill args)
(let ((new-hints (intset-subtract args kill)))
(let lp ((n 0))
(let ((n (intset-next new-hints n)))
(when n
(bitvector-set! needs-hintv n #t)
(lp (1+ n))))))
(scan-for-call n))
(scan-for-call (1- label-count)))
(define (allocate-call label k uses pre-live post-live)
(match (lookup-cont k dfg)
(($ $ktail)
(let* ((tail-nlocals (length uses))
(tail-slots (iota tail-nlocals))
(pre-live (fold allocate! pre-live uses tail-slots))
(moves (parallel-move (map (cut vector-ref slots <>) uses)
tail-slots
(compute-tmp-slot pre-live tail-slots))))
(bump-nlocals! tail-nlocals)
(hashq-set! call-allocations label
(make-call-allocation #f moves #f))))
(($ $kreceive arity kargs)
(let* ((proc-slot (compute-call-proc-slot post-live))
(call-slots (map (cut + proc-slot <>) (iota (length uses))))
(pre-live (fold allocate! pre-live uses call-slots))
(arg-moves (parallel-move (map (cut vector-ref slots <>) uses)
call-slots
(compute-tmp-slot pre-live
call-slots)))
(result-vars (vector-ref defv (label->idx kargs)))
(value-slots (map (cut + proc-slot 1 <>)
(iota (length result-vars))))
;; Shuffle the first result down to the lowest slot, and
;; leave any remaining results where they are. This
;; strikes a balance between avoiding shuffling,
;; especially for unused extra values, and avoiding
;; frame size growth due to sparse locals.
(result-live (match (cons result-vars value-slots)
((() . ()) post-live)
(((var . vars) . (slot . slots))
(fold allocate!
(allocate! var #f post-live)
vars slots))))
(result-slots (map (cut vector-ref slots <>) result-vars))
;; Filter out unused results.
(value-slots (filter-map (lambda (val result) (and result val))
value-slots result-slots))
(result-slots (filter (lambda (x) x) result-slots))
(result-moves (parallel-move value-slots
result-slots
(compute-tmp-slot result-live
value-slots)))
(dead-slot-map (logand (1- (ash 1 (- proc-slot 2)))
(lognot post-live))))
(bump-nlocals! (+ proc-slot (length uses)))
(hashq-set! call-allocations label
(make-call-allocation proc-slot arg-moves dead-slot-map))
(hashq-set! call-allocations k
(make-call-allocation proc-slot result-moves #f))))))
(define (allocate-values label k uses pre-live post-live)
(match (lookup-cont k dfg)
(($ $ktail)
(let* ((src-slots (map (cut vector-ref slots <>) uses))
(tail-nlocals (1+ (length uses)))
(dst-slots (cdr (iota tail-nlocals)))
(moves (parallel-move src-slots dst-slots
(compute-tmp-slot pre-live dst-slots))))
(bump-nlocals! tail-nlocals)
(hashq-set! call-allocations label
(make-call-allocation #f moves #f))))
(($ $kargs (_) (_))
;; When there is only one value in play, we allow the dst to be
;; hinted (see scan-for-hints). If the src doesn't have a
;; slot, then the actual slot for the dst would end up being
;; decided by the call that uses it. Because we don't know the
;; slot, we can't really compute the parallel moves in that
;; case, so just bail and rely on the bytecode emitter to
;; handle the one-value case specially.
(match (cons uses (vector-ref defv (label->idx k)))
(((src) . (dst))
(allocate! dst (vector-ref slots src) post-live))))
(($ $kargs)
(let* ((src-slots (map (cut vector-ref slots <>) uses))
(dst-vars (vector-ref defv (label->idx k)))
(result-live (fold allocate! post-live dst-vars src-slots))
(dst-slots (map (cut vector-ref slots <>) dst-vars))
(moves (parallel-move src-slots dst-slots
(compute-tmp-slot (logior pre-live result-live)
'()))))
(hashq-set! call-allocations label
(make-call-allocation #f moves #f))))))
(define (allocate-prompt label k handler)
(match (lookup-cont handler dfg)
(($ $kreceive arity kargs)
(let* ((handler-live (recompute-live-slots handler))
(proc-slot (compute-prompt-handler-proc-slot handler-live))
(result-vars (vector-ref defv (label->idx kargs)))
(value-slots (map (cut + proc-slot 1 <>)
(iota (length result-vars))))
(result-live (fold allocate!
handler-live result-vars value-slots))
(result-slots (map (cut vector-ref slots <>) result-vars))
;; Filter out unused results.
(value-slots (filter-map (lambda (val result) (and result val))
value-slots result-slots))
(result-slots (filter (lambda (x) x) result-slots))
(moves (parallel-move value-slots
result-slots
(compute-tmp-slot result-live
value-slots))))
(bump-nlocals! (+ proc-slot 1 (length result-vars)))
(hashq-set! call-allocations handler
(make-call-allocation proc-slot moves #f))))))
(define (allocate-defs! n live)
(fold (cut allocate! <> #f <>) live (vector-ref defv n)))
;; This traversal will visit definitions before uses, as
;; definitions dominate uses and a block's dominator will appear
;; before it, in reverse post-order.
(define (visit-clause n live)
(let lp ((n n) (live (recompute-live-slots (idx->label n))))
(define (kill-dead live vars-by-label-idx pred)
(fold (lambda (v live)
(let ((slot (vector-ref slots v)))
(if (and slot (pred n v dfa))
(kill-dead-slot slot live)
live)))
live
(vector-ref vars-by-label-idx n)))
(define (kill-dead-defs live)
(kill-dead live defv dead-after-def?))
(define (kill-dead-uses live)
(kill-dead live usev dead-after-use?))
(if (= n label-count)
n
(let* ((label (idx->label n))
(live (if (control-point? label dfg)
(recompute-live-slots label)
live))
(live (kill-dead-defs (allocate-defs! n live)))
(post-live (kill-dead-uses live)))
;; LIVE are the live slots coming into the term.
;; POST-LIVE is the subset that is still live after the
;; term uses its inputs.
(match (lookup-cont (idx->label n) dfg)
(($ $kclause) n)
(($ $kargs names syms body)
(define (compute-k-live k)
(match (lookup-predecessors k dfg)
((_) post-live)
(_ (recompute-live-slots k))))
(let ((uses (vector-ref usev n)))
(match (find-call body)
(($ $continue k src (or ($ $call) ($ $callk)))
(allocate-call label k uses live (compute-k-live k)))
(($ $continue k src ($ $primcall)) #t)
(($ $continue k src ($ $values))
(allocate-values label k uses live (compute-k-live k)))
(($ $continue k src ($ $prompt escape? tag handler))
(allocate-prompt label k handler))
(_ #f)))
(lp (1+ n) post-live))
((or ($ $kreceive) ($ $ktail))
(lp (1+ n) post-live)))))))
(define (visit-entry)
(define (visit-clauses n live)
(unless (eqv? live (add-live-slot 0 (empty-live-slots)))
(error "Unexpected clause live set"))
(set! nlocals 1)
(match (lookup-cont (idx->label n) dfg)
(($ $kclause arity ($ $cont kbody ($ $kargs names)) alternate)
(unless (eq? (idx->label (1+ n)) kbody)
(error "Unexpected label order"))
(let* ((nargs (length names))
(next (visit-clause (1+ n)
(fold allocate! live
(vector-ref defv (1+ n))
(cdr (iota (1+ nargs)))))))
(hashq-set! nlocals-table (idx->label n) nlocals)
(when (< next label-count)
(match alternate
(($ $cont kalt)
(unless (eq? kalt (idx->label next))
(error "Unexpected clause order"))))
(visit-clauses next live))))))
(match (lookup-cont (idx->label 0) dfg)
(($ $kfun src meta self)
(visit-clauses 1 (allocate-defs! 0 (empty-live-slots))))))
(compute-constants!)
(compute-uses-and-defs!)
(compute-unused-results!)
(compute-needs-hint!)
(visit-entry)
(make-allocation dfa slots
has-constv constant-values
call-allocations
nlocals-table)))

View file

@ -1,37 +0,0 @@
;;; 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)
#:use-module (language cps compile-bytecode)
#: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 `((bytecode . ,compile-bytecode))
#:for-humans? #f
)

View file

@ -1,195 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2013, 2014, 2015 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-labels (make-hash-table))
(define seen-vars (make-hash-table))
(define (add sym seen env)
(when (hashq-ref seen sym)
(error "duplicate gensym" sym))
(hashq-set! seen sym #t)
(cons sym env))
(define (add-env new seen env)
(if (null? new)
env
(add-env (cdr new) seen (add (car new) seen env))))
(define (add-vars new env)
(unless (and-map exact-integer? new)
(error "bad vars" new))
(add-env new seen-vars env))
(define (add-labels new env)
(unless (and-map exact-integer? new)
(error "bad labels" new))
(add-env new seen-labels env))
(define (check-ref sym seen env)
(cond
((not (hashq-ref seen sym))
(error "unbound lexical" sym))
((not (memq sym env))
(error "displaced lexical" sym))))
(define (check-label sym env)
(check-ref sym seen-labels env))
(define (check-var sym env)
(check-ref sym seen-vars env))
(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
(($ $kreceive ($ $arity ((? symbol?) ...) () (or #f (? symbol?)) () #f) k)
(check-label k k-env))
(($ $kargs (name ...) (sym ...) body)
(unless (= (length name) (length sym))
(error "name and sym lengths don't match" name sym))
(visit-term body k-env (add-vars sym v-env)))
(_
;; $kclause, $kfun, 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
($ $kclause
($ $arity
((? symbol? req) ...)
((? symbol? opt) ...)
(and rest (or #f (? symbol?)))
(((? keyword? kw) (? symbol? kwname) kwsym) ...)
(or #f #t))
($ $cont kbody (and body ($ $kargs names syms _)))
alternate))
(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-labels (list kclause kbody) k-env)))
(visit-cont-body body k-env v-env))
(when alternate
(visit-clause alternate k-env v-env)))
(_
(error "unexpected clause" clause))))
(define (visit-entry entry k-env v-env)
(match entry
(($ $cont kbody
($ $kfun src meta self ($ $cont ktail ($ $ktail)) clause))
(when (and meta (not (and (list? meta) (and-map pair? meta))))
(error "meta should be alist" meta))
(check-src src)
;; Reset the continuation environment, because Guile's
;; continuations are local.
(let ((v-env (add-vars (list self) v-env))
(k-env (add-labels (list ktail) '())))
(when clause
(visit-clause clause k-env v-env))))
(_ (error "unexpected $kfun" entry))))
(define (visit-fun fun k-env v-env)
(match fun
(($ $fun entry)
(visit-entry entry '() v-env))
(_
(error "unexpected $fun" fun))))
(define (visit-expression exp k-env v-env)
(match exp
(($ $const val)
#t)
(($ $prim (? symbol? name))
#t)
(($ $closure kfun n)
#t)
(($ $fun)
(visit-fun exp k-env v-env))
(($ $rec (name ...) (sym ...) (fun ...))
(unless (= (length name) (length sym) (length fun))
(error "letrec syms, names, and funs not same length" term))
;; FIXME: syms added in two places (here in $rec versus also in
;; target $kargs)
(let ((v-env (add-vars sym v-env)))
(for-each (cut visit-fun <> k-env v-env) fun)))
(($ $call proc (arg ...))
(check-var proc v-env)
(for-each (cut check-var <> v-env) arg))
(($ $callk k* proc (arg ...))
;; We don't check that k* is in scope; it's actually inside some
;; other function, probably. We rely on the transformation that
;; introduces the $callk to be correct, and the linker to resolve
;; the reference.
(check-var proc v-env)
(for-each (cut check-var <> v-env) arg))
(($ $branch kt ($ $primcall (? symbol? name) (arg ...)))
(check-var kt k-env)
(for-each (cut check-var <> v-env) arg))
(($ $branch kt ($ $values (arg ...)))
(check-var kt k-env)
(for-each (cut check-var <> v-env) arg))
(($ $primcall (? symbol? name) (arg ...))
(for-each (cut check-var <> v-env) arg))
(($ $values (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-label handler k-env))
(_
(error "unexpected expression" exp))))
(define (visit-term term k-env v-env)
(match term
(($ $letk (($ $cont k cont) ...) body)
(let ((k-env (add-labels k k-env)))
(for-each (cut visit-cont-body <> k-env v-env) cont)
(visit-term body k-env v-env)))
(($ $continue k src exp)
(check-label k k-env)
(check-src src)
(visit-expression exp k-env v-env))
(_
(error "unexpected term" term))))
(visit-entry fun '() '())
fun)

View file

@ -1,129 +0,0 @@
;;; Continuation-passing style (CPS) intermediate language (IL)
;; Copyright (C) 2013, 2014, 2015 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 CPS2 to CPS. When/if CPS2 replaces CPS, this module will be removed.
;;;
;;; Code:
(define-module (language cps2 compile-cps)
#:use-module (ice-9 match)
#:use-module (language cps2)
#:use-module ((language cps) #:prefix cps:)
#:use-module (language cps2 utils)
#:use-module (language cps2 closure-conversion)
#:use-module (language cps2 optimize)
#:use-module (language cps2 reify-primitives)
#:use-module (language cps2 renumber)
#:use-module (language cps intmap)
#:export (compile-cps))
;; Precondition: For each function in CONTS, the continuation names are
;; topologically sorted.
(define* (conts->fun conts #:optional (kfun 0))
(define (convert-fun kfun)
(let ((doms (compute-dom-edges (compute-idoms conts kfun))))
(define (visit-cont label)
(cps:rewrite-cps-cont (intmap-ref conts label)
(($ $kargs names syms body)
(label (cps:$kargs names syms ,(redominate label (visit-term body)))))
(($ $ktail)
(label (cps:$ktail)))
(($ $kreceive ($ $arity req () rest () #f) kargs)
(label (cps:$kreceive req rest kargs)))))
(define (visit-clause label)
(and label
(cps:rewrite-cps-cont (intmap-ref conts label)
(($ $kclause ($ $arity req opt rest kw aok?) kbody kalt)
(label (cps:$kclause (req opt rest kw aok?)
,(visit-cont kbody)
,(visit-clause kalt)))))))
(define (redominate label term)
(define (visit-dom-conts label)
(match (intmap-ref conts label)
(($ $ktail) '())
(($ $kargs) (list (visit-cont label)))
(else
(cons (visit-cont label)
(visit-dom-conts* (intmap-ref doms label))))))
(define (visit-dom-conts* labels)
(match labels
(() '())
((label . labels)
(append (visit-dom-conts label)
(visit-dom-conts* labels)))))
(cps:rewrite-cps-term (visit-dom-conts* (intmap-ref doms label))
(() ,term)
(conts (cps:$letk ,conts ,term))))
(define (visit-term term)
(cps:rewrite-cps-term term
(($ $continue k src (and ($ $fun) fun))
(cps:$continue k src ,(visit-fun fun)))
(($ $continue k src ($ $rec names syms funs))
(cps:$continue k src (cps:$rec names syms (map visit-fun funs))))
(($ $continue k src exp)
(cps:$continue k src ,(visit-exp exp)))))
(define (visit-exp exp)
(cps:rewrite-cps-exp exp
(($ $const val) (cps:$const val))
(($ $prim name) (cps:$prim name))
(($ $closure k nfree) (cps:$closure k nfree))
(($ $call proc args) (cps:$call proc args))
(($ $callk k proc args) (cps:$callk k proc args))
(($ $primcall name args) (cps:$primcall name args))
(($ $branch k exp) (cps:$branch k ,(visit-exp exp)))
(($ $values args) (cps:$values args))
(($ $prompt escape? tag handler) (cps:$prompt escape? tag handler))))
(define (visit-fun fun)
(cps:rewrite-cps-exp fun
(($ $fun body)
(cps:$fun ,(convert-fun body)))))
(cps:rewrite-cps-cont (intmap-ref conts kfun)
(($ $kfun src meta self tail clause)
(kfun (cps:$kfun src meta self (tail (cps:$ktail))
,(visit-clause clause)))))))
(convert-fun kfun))
(define (conts->fun* conts)
(cps:build-cps-term
(cps:$program
,(intmap-fold-right (lambda (label cont out)
(match cont
(($ $kfun)
(cons (conts->fun conts label) out))
(_ out)))
conts
'()))))
(define (kw-arg-ref args kw default)
(match (memq kw args)
((_ val . _) val)
(_ default)))
(define (compile-cps exp env opts)
;; Use set! to save memory at bootstrap-time. (The interpreter holds
;; onto all free variables locally bound in a function, so if we used
;; let*, we'd hold onto earlier copies of the term.)
(set! exp (optimize-higher-order-cps exp opts))
(set! exp (convert-closures exp))
(set! exp (optimize-first-order-cps exp opts))
(set! exp (reify-primitives exp))
(set! exp (renumber exp))
(values (conts->fun* exp) env env))