1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-22 04:30:19 +02:00

lambda, the ultimate goto

* module/language/tree-il/analyze.scm (analyze-lexicals): Rework to
  actually determine when a fixed-point procedure may be allocated as a
  label.
* module/language/tree-il/compile-glil.scm (emit-bindings): Always emit
  a <glil-bind>. Otherwise it's too hard to pair with unbindings.
  (flatten-lambda): Consequently, here we only `bind' if there are any
  vars to bind. This doesn't make any difference, given that lambdas
  don't have trailing unbind instructions, but it does keep the GLIL
  output the same for thunks -- no extraneous (bind) instructions. Keeps
  tree-il.test happy.
  (flatten): Some bugfixes. Yaaay, it works!!!
This commit is contained in:
Andy Wingo 2009-08-07 19:06:15 +02:00
parent 230cfcfb3e
commit d97b69d9cd
2 changed files with 155 additions and 39 deletions

View file

@ -135,8 +135,8 @@
;; NB, this includes identifiers referenced by contained lambdas ;; NB, this includes identifiers referenced by contained lambdas
(define free-vars (make-hash-table)) (define free-vars (make-hash-table))
;; assigned: sym -> #t ;; assigned: sym -> #t
(define assigned (make-hash-table))
;; variables that are assigned ;; variables that are assigned
(define assigned (make-hash-table))
;; refcounts: sym -> count ;; refcounts: sym -> count
;; allows us to detect the or-expansion in O(1) time ;; allows us to detect the or-expansion in O(1) time
(define refcounts (make-hash-table)) (define refcounts (make-hash-table))
@ -146,23 +146,35 @@
(define labels (make-hash-table)) (define labels (make-hash-table))
;; returns variables referenced in expr ;; returns variables referenced in expr
(define (analyze! x proc) (define (analyze! x proc labels-in-proc tail? tail-call-args)
(define (step y) (analyze! y proc)) (define (step y) (analyze! y proc labels-in-proc #f #f))
(define (recur x new-proc) (analyze! x new-proc)) (define (step-tail y) (analyze! y proc labels-in-proc tail? #f))
(define (step-tail-call y args) (analyze! y proc labels-in-proc #f
(and tail? args)))
(define (recur/labels x new-proc labels)
(analyze! x new-proc (append labels labels-in-proc) #t #f))
(define (recur x new-proc) (analyze! x new-proc '() tail? #f))
(record-case x (record-case x
((<application> proc args) ((<application> proc args)
(apply lset-union eq? (step proc) (map step args))) (apply lset-union eq? (step-tail-call proc args)
(map step args)))
((<conditional> test then else) ((<conditional> test then else)
(lset-union eq? (step test) (step then) (step else))) (lset-union eq? (step test) (step-tail then) (step-tail else)))
((<lexical-ref> name gensym) ((<lexical-ref> name gensym)
(hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0))) (hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0)))
(if (not (and tail-call-args
(memq gensym labels-in-proc)
(let ((args (hashq-ref labels gensym)))
(and (list? args)
(= (length args) (length tail-call-args))))))
(hashq-set! labels gensym #f))
(list gensym)) (list gensym))
((<lexical-set> name gensym exp) ((<lexical-set> name gensym exp)
(hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0)))
(hashq-set! assigned gensym #t) (hashq-set! assigned gensym #t)
(hashq-set! labels gensym #f)
(lset-adjoin eq? (step exp) gensym)) (lset-adjoin eq? (step exp) gensym))
((<module-set> mod name public? exp) ((<module-set> mod name public? exp)
@ -175,7 +187,12 @@
(step exp)) (step exp))
((<sequence> exps) ((<sequence> exps)
(apply lset-union eq? (map step exps))) (let lp ((exps exps) (ret '()))
(cond ((null? exps) '())
((null? (cdr exps))
(lset-union eq? ret (step-tail (car exps))))
(else
(lp (cdr exps) (lset-union eq? ret (step (car exps))))))))
((<lambda> vars meta body) ((<lambda> vars meta body)
(let ((locally-bound (let rev* ((vars vars) (out '())) (let ((locally-bound (let rev* ((vars vars) (out '()))
@ -195,7 +212,7 @@
(hashq-set! bound-vars proc (hashq-set! bound-vars proc
(append (reverse vars) (hashq-ref bound-vars proc))) (append (reverse vars) (hashq-ref bound-vars proc)))
(lset-difference eq? (lset-difference eq?
(apply lset-union eq? (step body) (map step vals)) (apply lset-union eq? (step-tail body) (map step vals))
vars)) vars))
((<letrec> vars vals body) ((<letrec> vars vals body)
@ -203,15 +220,86 @@
(append (reverse vars) (hashq-ref bound-vars proc))) (append (reverse vars) (hashq-ref bound-vars proc)))
(for-each (lambda (sym) (hashq-set! assigned sym #t)) vars) (for-each (lambda (sym) (hashq-set! assigned sym #t)) vars)
(lset-difference eq? (lset-difference eq?
(apply lset-union eq? (step body) (map step vals)) (apply lset-union eq? (step-tail body) (map step vals))
vars)) vars))
((<fix> vars vals body) ((<fix> vars vals body)
;; Try to allocate these procedures as labels.
(for-each (lambda (sym val) (hashq-set! labels sym (lambda-vars val)))
vars vals)
(hashq-set! bound-vars proc (hashq-set! bound-vars proc
(append (reverse vars) (hashq-ref bound-vars proc))) (append (reverse vars) (hashq-ref bound-vars proc)))
;; Step into subexpressions.
(let* ((var-refs
(map
;; Since we're trying to label-allocate the lambda,
;; pretend it's not a closure, and just recurse into its
;; body directly. (Otherwise, recursing on a closure
;; that references one of the fix's bound vars would
;; prevent label allocation.)
(lambda (x)
(record-case x
((<lambda> (lvars vars) body)
(let ((locally-bound
(let rev* ((lvars lvars) (out '()))
(cond ((null? lvars) out)
((pair? lvars) (rev* (cdr lvars)
(cons (car lvars) out)))
(else (cons lvars out))))))
(hashq-set! bound-vars x locally-bound)
;; recur/labels, the difference from the closure case
(let* ((referenced (recur/labels body x vars))
(free (lset-difference eq? referenced locally-bound))
(all-bound (reverse! (hashq-ref bound-vars x))))
(hashq-set! bound-vars x all-bound)
(hashq-set! free-vars x free)
free)))))
vals))
(vars-with-refs (map cons vars var-refs))
(body-refs (recur/labels body proc vars)))
(define (delabel-dependents! sym)
(let ((refs (assq-ref vars-with-refs sym)))
(if refs
(for-each (lambda (sym)
(if (hashq-ref labels sym)
(begin
(hashq-set! labels sym #f)
(delabel-dependents! sym))))
refs))))
;; Stepping into the lambdas and the body might have made some
;; procedures not label-allocatable -- which might have
;; knock-on effects. For example:
;; (fix ((a (lambda () (b)))
;; (b (lambda () a)))
;; (a))
;; As far as `a' is concerned, both `a' and `b' are
;; label-allocatable. But `b' references `a' not in a proc-tail
;; position, which makes `a' not label-allocatable. The
;; knock-on effect is that, when back-propagating this
;; information to `a', `b' will also become not
;; label-allocatable, as it is referenced within `a', which is
;; allocated as a closure. This is a transitive relationship.
(for-each (lambda (sym)
(if (not (hashq-ref labels sym))
(delabel-dependents! sym)))
vars)
;; Now lift bound variables with label-allocated lambdas to the
;; parent procedure.
(for-each
(lambda (sym val)
(if (hashq-ref labels sym)
;; Remove traces of the label-bound lambda. The free
;; vars will propagate up via the return val.
(begin
(hashq-set! bound-vars proc
(append (hashq-ref bound-vars val)
(hashq-ref bound-vars proc)))
(hashq-remove! bound-vars val)
(hashq-remove! free-vars val))))
vars vals)
(lset-difference eq? (lset-difference eq?
(apply lset-union eq? (step body) (map step vals)) (apply lset-union eq? body-refs var-refs)
vars)) vars)))
((<let-values> vars exp body) ((<let-values> vars exp body)
(let ((bound (let lp ((out (hashq-ref bound-vars proc)) (in vars)) (let ((bound (let lp ((out (hashq-ref bound-vars proc)) (in vars))
@ -220,7 +308,7 @@
(if (null? in) out (cons in out)))))) (if (null? in) out (cons in out))))))
(hashq-set! bound-vars proc bound) (hashq-set! bound-vars proc bound)
(lset-difference eq? (lset-difference eq?
(lset-union eq? (step exp) (step body)) (lset-union eq? (step exp) (step-tail body))
bound))) bound)))
(else '()))) (else '())))
@ -330,18 +418,46 @@
(lp (cdr vars) (1+ n)))))) (lp (cdr vars) (1+ n))))))
((<fix> vars vals body) ((<fix> vars vals body)
(let lp ((vars vars) (n n)) (let lp ((in vars) (n n))
(if (null? vars) (if (null? in)
(let ((nmax (apply max (let lp ((vars vars) (vals vals) (nmax n))
(map (lambda (x) (cond
(allocate! x proc n)) ((null? vars)
vals))))
(max nmax (allocate! body proc n))) (max nmax (allocate! body proc n)))
(let ((v (car vars))) ((hashq-ref labels (car vars))
(if (hashq-ref assigned v) ;; allocate label bindings & body inline to proc
(lp (cdr vars)
(cdr vals)
(record-case (car vals)
((<lambda> vars body)
(let lp ((vars vars) (n n))
(if (not (null? vars))
;; allocate bindings
(let ((v (if (pair? vars) (car vars) vars)))
(hashq-set!
allocation v
(make-hashq
proc `(#t ,(hashq-ref assigned v) . ,n)))
(lp (if (pair? vars) (cdr vars) '()) (1+ n)))
;; allocate body
(max nmax (allocate! body proc n))))))))
(else
;; allocate closure
(lp (cdr vars)
(cdr vals)
(max nmax (allocate! (car vals) proc n))))))
(let ((v (car in)))
(cond
((hashq-ref assigned v)
(error "fixpoint procedures may not be assigned" x)) (error "fixpoint procedures may not be assigned" x))
((hashq-ref labels v)
;; no binding, it's a label
(lp (cdr in) n))
(else
;; allocate closure binding
(hashq-set! allocation v (make-hashq proc `(#t #f . ,n))) (hashq-set! allocation v (make-hashq proc `(#t #f . ,n)))
(lp (cdr vars) (1+ n)))))) (lp (cdr in) (1+ n))))))))
((<let-values> vars exp body) ((<let-values> vars exp body)
(let ((nmax (recur exp))) (let ((nmax (recur exp)))
@ -365,7 +481,7 @@
(else n))) (else n)))
(analyze! x #f) (analyze! x #f '() #t #f)
(allocate! x #f 0) (allocate! x #f 0)
allocation) allocation)

View file

@ -165,9 +165,8 @@
;; FIXME: always emit? otherwise it's hard to pair bind with unbind ;; FIXME: always emit? otherwise it's hard to pair bind with unbind
(define (emit-bindings src ids vars allocation proc emit-code) (define (emit-bindings src ids vars allocation proc emit-code)
(if (pair? vars)
(emit-code src (make-glil-bind (emit-code src (make-glil-bind
(vars->bind-list ids vars allocation proc))))) (vars->bind-list ids vars allocation proc))))
(define (with-output-to-code proc) (define (with-output-to-code proc)
(let ((out '())) (let ((out '()))
@ -199,7 +198,8 @@
(if self-label (if self-label
(emit-code #f (make-glil-label self-label))) (emit-code #f (make-glil-label self-label)))
;; write bindings and source debugging info ;; write bindings and source debugging info
(emit-bindings #f ids vars allocation x emit-code) (if (not (null? ids))
(emit-bindings #f ids vars allocation x emit-code))
(if (lambda-src x) (if (lambda-src x)
(emit-code #f (make-glil-source (lambda-src x)))) (emit-code #f (make-glil-source (lambda-src x))))
;; box args if necessary ;; box args if necessary
@ -475,14 +475,14 @@
(comp-push test) (comp-push test)
(emit-branch src 'br-if-not L1) (emit-branch src 'br-if-not L1)
(comp-tail then) (comp-tail then)
(if (not (eq? context 'tail)) ;; if there is an RA, comp-tail will cause a jump to it -- just
(emit-branch #f 'br (or RA L2))) ;; have to clean up here if there is no RA.
(if (and (not RA) (not (eq? context 'tail)))
(emit-branch #f 'br L2))
(emit-label L1) (emit-label L1)
(comp-tail else) (comp-tail else)
(if (not (eq? context 'tail)) (if (and (not RA) (not (eq? context 'tail)))
(if RA (emit-label L2))))
(emit-branch #f 'br RA)
(emit-label L2)))))
((<primitive-ref> src name) ((<primitive-ref> src name)
(cond (cond
@ -654,7 +654,7 @@
;; Emit bindings metadata for closures ;; Emit bindings metadata for closures
(let ((binds (let lp ((out '()) (vars vars) (names names)) (let ((binds (let lp ((out '()) (vars vars) (names names))
(cond ((null? vars) (reverse! out)) (cond ((null? vars) (reverse! out))
((memq (car vars) fix-labels) ((assq (car vars) fix-labels)
(lp out (cdr vars) (cdr names))) (lp out (cdr vars) (cdr names)))
(else (else
(lp (acons (car vars) (car names) out) (lp (acons (car vars) (car names) out)