1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-30 20:00:19 +02:00
guile/module/language/tree-il/optimize.scm
Andy Wingo e43921a982 peval handles lexical-set
* module/language/tree-il/optimize.scm (alpha-rename, peval): Add
  support for lexical-set, while avoiding copy propagation and pruning
  of assigned variables.
2011-09-24 19:02:53 +02:00

745 lines
30 KiB
Scheme
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; Tree-il optimizer
;; Copyright (C) 2009, 2011 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 tree-il optimize)
#:use-module (language tree-il)
#:use-module (language tree-il primitives)
#:use-module (language tree-il inline)
#:use-module (language tree-il fix-letrec)
#:use-module (ice-9 vlist)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:export (optimize!))
(define (optimize! x env opts)
(let ((peval (match (memq #:partial-eval? opts)
((#:partial-eval? #f _ ...)
;; Disable partial evaluation.
(lambda (x e) x))
(_ peval))))
(inline!
(fix-letrec!
(peval (expand-primitives! (resolve-primitives! x env))
env)))))
;;;
;;; Partial evaluation.
;;;
(define (fresh-gensyms syms)
(map (lambda (x) (gensym (string-append (symbol->string x) " ")))
syms))
(define (alpha-rename exp)
"Alpha-rename EXP. For any lambda in EXP, generate new symbols and
replace all lexical references to the former symbols with lexical
references to the new symbols."
;; XXX: This should be factorized somehow.
(let loop ((exp exp)
(mapping vlist-null)) ; maps old to new gensyms
(match exp
(($ <lambda-case> src req opt rest kw inits gensyms body alt)
;; Create new symbols to replace GENSYMS and propagate them down
;; in BODY and ALT.
(let* ((new (fresh-gensyms
(append req
(or opt '())
(if rest (list rest) '())
(match kw
((aok? (_ name _) ...) name)
(_ '())))))
(mapping (fold vhash-consq mapping gensyms new)))
(make-lambda-case src req opt rest
(match kw
((aok? (kw name old) ...)
(cons aok? (map list
kw
name
(take-right new (length old)))))
(_ #f))
(map (cut loop <> mapping) inits)
new
(loop body mapping)
(and alt (loop alt mapping)))))
(($ <lexical-ref> src name gensym)
;; Possibly replace GENSYM by the new gensym defined in MAPPING.
(let ((val (vhash-assq gensym mapping)))
(if val
(make-lexical-ref src name (cdr val))
exp)))
(($ <lexical-set> src name gensym exp)
(let ((val (vhash-assq gensym mapping)))
(make-lexical-set src name (if val (cdr val) gensym)
(loop exp mapping))))
(($ <lambda> src meta body)
(make-lambda src meta (loop body mapping)))
(($ <let> src names gensyms vals body)
;; As for `lambda-case' rename GENSYMS to avoid any collision.
(let* ((new (fresh-gensyms names))
(mapping (fold vhash-consq mapping gensyms new))
(vals (map (cut loop <> mapping) vals))
(body (loop body mapping)))
(make-let src names new vals body)))
(($ <letrec> src in-order? names gensyms vals body)
;; Likewise.
(let* ((new (fresh-gensyms names))
(mapping (fold vhash-consq mapping gensyms new))
(vals (map (cut loop <> mapping) vals))
(body (loop body mapping)))
(make-letrec src in-order? names new vals body)))
(($ <fix> src names gensyms vals body)
;; Likewise.
(let* ((new (fresh-gensyms names))
(mapping (fold vhash-consq mapping gensyms new))
(vals (map (cut loop <> mapping) vals))
(body (loop body mapping)))
(make-fix src names new vals body)))
(($ <let-values> src exp body)
(make-let-values src (loop exp mapping) (loop body mapping)))
(($ <const>)
exp)
(($ <void>)
exp)
(($ <toplevel-ref>)
exp)
(($ <module-ref>)
exp)
(($ <primitive-ref>)
exp)
(($ <toplevel-set> src name exp)
(make-toplevel-set src name (loop exp mapping)))
(($ <toplevel-define> src name exp)
(make-toplevel-define src name (loop exp mapping)))
(($ <module-set> src mod name public? exp)
(make-module-set src mod name public? (loop exp mapping)))
(($ <dynlet> src fluids vals body)
(make-dynlet src
(map (cut loop <> mapping) fluids)
(map (cut loop <> mapping) vals)
(loop body mapping)))
(($ <dynwind> src winder body unwinder)
(make-dynwind src
(loop winder mapping)
(loop body mapping)
(loop unwinder mapping)))
(($ <dynref> src fluid)
(make-dynref src (loop fluid mapping)))
(($ <conditional> src condition subsequent alternate)
(make-conditional src
(loop condition mapping)
(loop subsequent mapping)
(loop alternate mapping)))
(($ <application> src proc args)
(make-application src (loop proc mapping)
(map (cut loop <> mapping) args)))
(($ <sequence> src exps)
(make-sequence src (map (cut loop <> mapping) exps))))))
(define-syntax-rule (let/ec k e e* ...)
(let ((tag (make-prompt-tag)))
(call-with-prompt
tag
(lambda ()
(let ((k (lambda args (apply abort-to-prompt tag args))))
e e* ...))
(lambda (_ res) res))))
(define (tree-il-any proc exp)
(let/ec k
(tree-il-fold (lambda (exp res)
(let ((res (proc exp)))
(if res (k res) #f)))
(lambda (exp res)
(let ((res (proc exp)))
(if res (k res) #f)))
(lambda (exp res) #f)
#f exp)))
(define (code-contains-calls? body proc lookup)
"Return true if BODY contains calls to PROC. Use LOOKUP to look up
lexical references."
(tree-il-any
(lambda (exp)
(match exp
(($ <application> _
(and ref ($ <lexical-ref> _ _ gensym)) _)
(or (equal? ref proc)
(equal? (lookup gensym) proc)))
(($ <application>
(and proc* ($ <lambda>)))
(equal? proc* proc))
(_ #f)))
body))
(define (vlist-any proc vlist)
(let ((len (vlist-length vlist)))
(let lp ((i 0))
(and (< i len)
(or (proc (vlist-ref vlist i))
(lp (1+ i)))))))
(define-record-type <var>
(make-var name gensym refcount set?)
var?
(name var-name)
(gensym var-gensym)
(refcount var-refcount set-var-refcount!)
(set? var-set? set-var-set?!))
(define* (build-var-table exp #:optional (table vlist-null))
(tree-il-fold
(lambda (exp res)
(match exp
(($ <lexical-ref> src name gensym)
(let ((var (vhash-assq gensym res)))
(if var
(begin
(set-var-refcount! (cdr var) (1+ (var-refcount (cdr var))))
res)
(vhash-consq gensym (make-var name gensym 1 #f) res))))
(_ res)))
(lambda (exp res)
(match exp
(($ <lexical-set> src name gensym exp)
(let ((var (vhash-assq gensym res)))
(if var
(begin
(set-var-set?! (cdr var) #t)
res)
(vhash-consq gensym (make-var name gensym 0 #t) res))))
(_ res)))
(lambda (exp res) res)
table exp))
(define* (peval exp #:optional (cenv (current-module)) (env vlist-null))
"Partially evaluate EXP in compilation environment CENV, with
top-level bindings from ENV and return the resulting expression. Since
it does not handle <fix> and <let-values>, it should be called before
`fix-letrec'."
;; This is a simple partial evaluator. It effectively performs
;; constant folding, copy propagation, dead code elimination, and
;; inlining, but not across top-level bindings---there should be a way
;; to allow this (TODO).
;;
;; Unlike a full-blown partial evaluator, it does not emit definitions
;; of specialized versions of lambdas encountered on its way. Also,
;; it's not yet complete: it bails out for `prompt', etc.
(define local-toplevel-env
;; The top-level environment of the module being compiled.
(match exp
(($ <toplevel-define> _ name)
(vhash-consq name #t env))
(($ <sequence> _ exps)
(fold (lambda (x r)
(match x
(($ <toplevel-define> _ name)
(vhash-consq name #t r))
(_ r)))
env
exps))
(_ env)))
(define (local-toplevel? name)
(vhash-assq name local-toplevel-env))
(define var-table (build-var-table exp))
(define (record-lexicals! x)
(set! var-table (build-var-table x var-table))
x)
(define (assigned-lexical? sym)
(let ((v (vhash-assq sym var-table)))
(and v (var-set? (cdr v)))))
(define (lexical-refcount sym)
(let ((v (vhash-assq sym var-table)))
(if v (var-refcount (cdr v)) 0)))
(define (apply-primitive name args)
;; todo: further optimize commutative primitives
(catch #t
(lambda ()
(call-with-values
(lambda ()
(apply (module-ref the-scm-module name) args))
(lambda results
(values #t results))))
(lambda _
(values #f '()))))
(define (inline-values exp src names gensyms body)
(let loop ((exp exp))
(match exp
;; Some expression types are always singly-valued.
((or ($ <const>)
($ <void>)
($ <lambda>)
($ <lexical-ref>)
($ <toplevel-ref>)
($ <module-ref>)
($ <primitive-ref>)
($ <dynref>)
($ <lexical-set>) ; FIXME: these set! expressions
($ <toplevel-set>) ; could return zero values in
($ <toplevel-define>) ; the future
($ <module-set>)) ;
(and (= (length names) 1)
(make-let src names gensyms (list exp) body)))
(($ <application> src
($ <primitive-ref> _ (? singly-valued-primitive? name)))
(and (= (length names) 1)
(make-let src names gensyms (list exp) body)))
;; Statically-known number of values.
(($ <application> src ($ <primitive-ref> _ 'values) vals)
(and (= (length names) (length vals))
(make-let src names gensyms vals body)))
;; Not going to copy code into both branches.
(($ <conditional>) #f)
;; Bail on other applications.
(($ <application>) #f)
;; Propagate to tail positions.
(($ <let> src names gensyms vals body)
(let ((body (loop body)))
(and body
(make-let src names gensyms vals body))))
(($ <letrec> src in-order? names gensyms vals body)
(let ((body (loop body)))
(and body
(make-letrec src in-order? names gensyms vals body))))
(($ <fix> src names gensyms vals body)
(let ((body (loop body)))
(and body
(make-fix src names gensyms vals body))))
(($ <let-values> src exp
($ <lambda-case> src2 req opt rest kw inits gensyms body #f))
(let ((body (loop body)))
(and body
(make-let-values src exp
(make-lambda-case src2 req opt rest kw
inits gensyms body #f)))))
(($ <dynwind> src winder body unwinder)
(let ((body (loop body)))
(and body
(make-dynwind src winder body unwinder))))
(($ <dynlet> src fluids vals body)
(let ((body (loop body)))
(and body
(make-dynlet src fluids vals body))))
(($ <sequence> src exps)
(match exps
((head ... tail)
(let ((tail (loop tail)))
(and tail
(make-sequence src (append head (list tail)))))))))))
(define (make-values src values)
(match values
((single) single) ; 1 value
((_ ...) ; 0, or 2 or more values
(make-application src (make-primitive-ref src 'values)
values))))
(define (const*? x)
(or (const? x) (lambda? x) (void? x)))
(define (constant-expression? x)
;; Return true if X is constant---i.e., if it is known to have no
;; effects, does not allocate storage for a mutable object, and does
;; not access mutable data (like `car' or toplevel references).
(let loop ((x x))
(match x
(($ <void>) #t)
(($ <const>) #t)
(($ <lambda>) #t)
(($ <lambda-case> _ req opt rest kw inits _ body alternate)
(and (every loop inits) (loop body) (loop alternate)))
(($ <lexical-ref> _ _ gensym)
(not (assigned-lexical? gensym)))
(($ <primitive-ref>) #t)
(($ <conditional> _ condition subsequent alternate)
(and (loop condition) (loop subsequent) (loop alternate)))
(($ <application> _ ($ <primitive-ref> _ name) args)
(and (effect-free-primitive? name)
(not (constructor-primitive? name))
(every loop args)))
(($ <application> _ ($ <lambda> _ _ body) args)
(and (loop body) (every loop args)))
(($ <sequence> _ exps)
(every loop exps))
(($ <let> _ _ _ vals body)
(and (every loop vals) (loop body)))
(($ <letrec> _ _ _ _ vals body)
(and (every loop vals) (loop body)))
(($ <fix> _ _ _ vals body)
(and (every loop vals) (loop body)))
(($ <let-values> _ exp body)
(and (loop exp) (loop body)))
(_ #f))))
(define (mutable? exp)
;; Return #t if EXP is a mutable object.
;; todo: add an option to assume pairs are immutable
(or (pair? exp)
(vector? exp)
(struct? exp)
(string? exp)))
(define (make-value-construction src exp)
;; Return an expression that builds a fresh copy of EXP at run-time,
;; or #f.
(let loop ((exp exp))
(match exp
((_ _ ...) ; non-empty proper list
(let ((args (map loop exp)))
(and (every struct? args)
(make-application src (make-primitive-ref src 'list)
args))))
((h . (? (negate pair?) t)) ; simple pair
(let ((h (loop h))
(t (loop t)))
(and h t
(make-application src (make-primitive-ref src 'cons)
(list h t)))))
((? vector?) ; vector
(let ((args (map loop (vector->list exp))))
(and (every struct? args)
(make-application src (make-primitive-ref src 'vector)
args))))
((? number?) (make-const src exp))
((? string?) (make-const src exp))
((? symbol?) (make-const src exp))
;((? bytevector?) (make-const src exp))
(_ #f))))
(define (maybe-unconst orig new)
;; If NEW is a constant, change it to a non-constant if need be.
;; Expressions that build a mutable object, such as `(list 1 2)',
;; must not be replaced by a constant; this procedure "undoes" the
;; change from `(list 1 2)' to `'(1 2)'.
(match new
(($ <const> src (? mutable? value))
(if (equal? new orig)
new
(or (make-value-construction src value) orig)))
(_ new)))
(define (maybe-unlambda orig new env)
;; If NEW is a named lambda and ORIG is what it looked like before
;; partial evaluation, then attempt to replace NEW with a lexical
;; ref, to avoid code duplication.
(match new
(($ <lambda> src (= (cut assq-ref <> 'name) (? symbol? name))
($ <lambda-case> _ req opt rest kw inits gensyms body))
;; Look for NEW in the current environment, starting from the
;; outermost frame.
(or (vlist-any (lambda (x)
(and (equal? (cdr x) new)
(make-lexical-ref src name (car x))))
env)
new))
(($ <lambda> src ()
(and lc ($ <lambda-case>)))
;; This is an anonymous lambda that we're going to inline.
;; Inlining creates new variable bindings, so we need to provide
;; the new code with fresh names.
(make-lambda src '() (record-lexicals! (alpha-rename lc))))
(_ new)))
(catch 'match-error
(lambda ()
(let loop ((exp exp)
(env vlist-null) ; static environment
(calls '()) ; inlined call stack
(ctx 'value)) ; effect, value, test, or call
(define (lookup var)
(and=> (vhash-assq var env) cdr))
(match exp
(($ <const>)
(case ctx
((effect) (make-void #f))
(else exp)))
(($ <void>)
(case ctx
((test) (make-const #f #t))
(else exp)))
(($ <lexical-ref> _ _ gensym)
;; Propagate only pure expressions that are not assigned to.
(case ctx
((effect) (make-void #f))
(else
(let ((val (lookup gensym)))
(if (and (not (assigned-lexical? gensym))
(constant-expression? val))
(case ctx
;; fixme: cache this? it is a divergence from
;; O(n).
((test) (loop val env calls 'test))
(else val))
exp)))))
(($ <lexical-set> src name gensym exp)
(if (zero? (lexical-refcount gensym))
(let ((exp (loop exp env calls 'effect)))
(if (void? exp)
exp
(make-sequence src (list exp (make-void #f)))))
(make-lexical-set src name gensym
(maybe-unconst
exp
(loop exp env calls 'value)))))
(($ <let> src names gensyms vals body)
(let* ((vals* (map (cut loop <> env calls 'value) vals))
(vals (map maybe-unconst vals vals*))
(body* (loop body
(fold vhash-consq env gensyms vals)
calls
ctx))
(body (maybe-unconst body body*)))
(if (const? body*)
body
;; Constants have already been propagated, so there is
;; no need to bind them to lexicals.
(let*-values (((stripped) (remove
(lambda (x)
(and (const? (car x))
(not (assigned-lexical?
(cadr x)))))
(zip vals gensyms names)))
((vals gensyms names) (unzip3 stripped)))
(if (null? stripped)
body
(make-let src names gensyms vals body))))))
(($ <letrec> src in-order? names gensyms vals body)
;; Things could be done more precisely when IN-ORDER? but
;; it's OK not to do it---at worst we lost an optimization
;; opportunity.
(let* ((vals* (map (cut loop <> env calls 'value) vals))
(vals (map maybe-unconst vals vals*))
(body* (loop body
(fold vhash-consq env gensyms vals)
calls
ctx))
(body (maybe-unconst body body*)))
(if (const? body*)
body
(make-letrec src in-order? names gensyms vals body))))
(($ <fix> src names gensyms vals body)
(let* ((vals (map (cut loop <> env calls 'value) vals))
(body* (loop body
(fold vhash-consq env gensyms vals)
calls
ctx))
(body (maybe-unconst body body*)))
(if (const? body*)
body
(make-fix src names gensyms vals body))))
(($ <let-values> lv-src producer consumer)
;; Peval the producer, then try to inline the consumer into
;; the producer. If that succeeds, peval again. Otherwise
;; reconstruct the let-values, pevaling the consumer.
(let ((producer (maybe-unconst producer
(loop producer env calls 'value))))
(or (match consumer
(($ <lambda-case> src req #f #f #f () gensyms body #f)
(cond
((inline-values producer src req gensyms body)
=> (cut loop <> env calls ctx))
(else #f)))
(_ #f))
(make-let-values lv-src producer
(loop consumer env calls ctx)))))
(($ <dynwind> src winder body unwinder)
(make-dynwind src (loop winder env calls 'value)
(loop body env calls ctx)
(loop unwinder env calls 'value)))
(($ <dynlet> src fluids vals body)
(make-dynlet src
(map maybe-unconst fluids
(map (cut loop <> env calls 'value) fluids))
(map maybe-unconst vals
(map (cut loop <> env calls 'value) vals))
(maybe-unconst body (loop body env calls ctx))))
(($ <dynref> src fluid)
(make-dynref src
(maybe-unconst fluid (loop fluid env calls 'value))))
(($ <toplevel-ref> src (? effect-free-primitive? name))
(if (local-toplevel? name)
exp
(resolve-primitives! exp cenv)))
(($ <toplevel-ref>)
;; todo: open private local bindings.
exp)
(($ <module-ref>)
exp)
(($ <module-set> src mod name public? exp)
(make-module-set src mod name public?
(maybe-unconst exp (loop exp env '() 'value))))
(($ <toplevel-define> src name exp)
(make-toplevel-define src name
(maybe-unconst exp (loop exp env '() 'value))))
(($ <toplevel-set> src name exp)
(make-toplevel-set src name
(maybe-unconst exp (loop exp env '() 'value))))
(($ <primitive-ref>)
(case ctx
((effect) (make-void #f))
((test) (make-const #f #t))
(else exp)))
(($ <conditional> src condition subsequent alternate)
(let ((condition (loop condition env calls 'test)))
(if (const? condition)
(if (const-exp condition)
(loop subsequent env calls ctx)
(loop alternate env calls ctx))
(make-conditional src condition
(loop subsequent env calls ctx)
(loop alternate env calls ctx)))))
(($ <application> src
($ <primitive-ref> _ '@call-with-values)
(producer
($ <lambda> _ _
(and consumer
;; No optional or kwargs.
($ <lambda-case>
_ req #f rest #f () gensyms body #f)))))
(loop (make-let-values src (make-application src producer '())
consumer)
env calls ctx))
(($ <application> src orig-proc orig-args)
;; todo: augment the global env with specialized functions
(let* ((proc (loop orig-proc env calls 'call))
(proc* (maybe-unlambda orig-proc proc env))
(args (map (cut loop <> env calls 'value) orig-args))
(args* (map (cut maybe-unlambda <> <> env)
orig-args
(map maybe-unconst orig-args args)))
(app (make-application src proc* args*)))
;; If at least one of ARGS is static (to avoid infinite
;; inlining) and this call hasn't already been expanded
;; before (to avoid infinite recursion), then expand it
;; (todo: emit an infinite recursion warning.)
(if (and (or (null? args) (any const*? args))
(not (member (cons proc args) calls)))
(match proc
(($ <primitive-ref> _ (? effect-free-primitive? name))
(if (every const? args) ; only simple constants
(let-values (((success? values)
(apply-primitive name
(map const-exp args))))
(if success?
(case ctx
((effect) (make-void #f))
((test)
;; Values truncation: only take the first
;; value.
(if (pair? values)
(make-const #f (car values))
(make-values src '())))
(else
(make-values src (map (cut make-const src <>)
values))))
app))
app))
(($ <primitive-ref>)
;; An effectful primitive.
app)
(($ <lambda> _ _
($ <lambda-case> _ req opt #f #f inits gensyms body))
;; Simple case: no rest, no keyword arguments.
;; todo: handle the more complex cases
(let ((nargs (length args))
(nreq (length req))
(nopt (if opt (length opt) 0)))
(if (and (>= nargs nreq) (<= nargs (+ nreq nopt))
(every constant-expression? args))
(let* ((params
(append args
(drop inits
(max 0
(- nargs
(+ nreq nopt))))))
(body
(loop body
(fold vhash-consq env gensyms params)
(cons (cons proc args) calls)
ctx)))
;; If the residual code contains recursive
;; calls, give up inlining.
(if (code-contains-calls? body proc lookup)
app
body))
app)))
(($ <lambda>)
app)
(($ <toplevel-ref>)
app)
;; In practice, this is the clause that stops peval:
;; module-ref applications (produced by macros,
;; typically) don't match, and so this throws,
;; aborting peval for an entire expression.
)
app)))
(($ <lambda> src meta body)
(case ctx
((effect) (make-void #f))
((test) (make-const #f #t))
(else
(make-lambda src meta (loop body env calls 'value)))))
(($ <lambda-case> src req opt rest kw inits gensyms body alt)
(make-lambda-case src req opt rest kw
(map maybe-unconst inits
(map (cut loop <> env calls 'value) inits))
gensyms
(maybe-unconst body (loop body env calls ctx))
alt))
(($ <sequence> src exps)
(let lp ((exps exps) (effects '()))
(match exps
((last)
(if (null? effects)
(loop last env calls ctx)
(make-sequence src (append (reverse effects)
(list
(maybe-unconst last
(loop last env calls ctx)))))))
((head . rest)
(let ((head (loop head env calls 'effect)))
(cond
((sequence? head)
(lp (append (sequence-exps head) rest) effects))
((void? head)
(lp rest effects))
(else
(lp rest (cons head effects))))))))))))
(lambda _
;; We encountered something we don't handle, like <abort> or
;; <prompt>, so bail out.
exp)))