398 lines
11 KiB
Scheme
398 lines
11 KiB
Scheme
; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING.
|
|
|
|
|
|
|
|
; Simple code analysis to determine whether it's a good idea to
|
|
; in-line calls to a given procedure.
|
|
|
|
; Hook into the byte code compiler.
|
|
|
|
(set-optimizer! 'auto-integrate
|
|
(lambda (stuff p)
|
|
(set-package-integrate?! p #t)
|
|
(newline)
|
|
(display "Analyzing... ") (force-output (current-output-port))
|
|
(let* ((names '())
|
|
(stuff
|
|
(map (lambda (filename+nodes)
|
|
(let ((filename (car filename+nodes))
|
|
(nodes (cdr filename+nodes)))
|
|
(set! names
|
|
(append (analyze-forms nodes p) names))
|
|
(cons filename nodes)))
|
|
stuff)))
|
|
(cond ((not (null? names))
|
|
(newline)
|
|
(display "Calls will be compiled in line: ")
|
|
(write (reverse names)))
|
|
(else
|
|
(display "no in-line procedures")))
|
|
(newline)
|
|
stuff)))
|
|
|
|
(define (analyze-forms scanned-nodes p)
|
|
(let ((inlines '()))
|
|
(for-each (lambda (node)
|
|
(let ((lhs (analyze-form node p)))
|
|
(if lhs
|
|
(set! inlines (cons lhs inlines)))))
|
|
scanned-nodes)
|
|
inlines))
|
|
|
|
(define (analyze-form node p) ;Return LHS iff calls will be inlined.
|
|
(if (define-node? node)
|
|
(let ((form (node-form node)))
|
|
(let ((lhs (cadr form))
|
|
(rhs (caddr form)))
|
|
(let ((type (package-lookup-type p lhs)))
|
|
(if (variable-type? type)
|
|
(require "not assigned" lhs #f)
|
|
(let ((method (inlinable-rhs? rhs type p lhs)))
|
|
(if method
|
|
(begin (package-define! p lhs method)
|
|
(if (transform? method)
|
|
lhs
|
|
#f))
|
|
#f))))))
|
|
#f))
|
|
|
|
(define lambda-node? (node-predicate 'lambda))
|
|
(define name-node? (node-predicate 'name))
|
|
(define loophole-node? (node-predicate 'loophole))
|
|
|
|
|
|
(define (inlinable-rhs? node type p lhs)
|
|
(cond ((lambda-node? node)
|
|
(if (simple-lambda? node lhs p)
|
|
(make-inline-transform node type p lhs)
|
|
#f))
|
|
((name-node? node)
|
|
(let ((name (node-form node)))
|
|
(if (and (require "symbol rhs" (list lhs name)
|
|
(symbol? name))
|
|
(require "rhs unassigned" (list lhs name)
|
|
(not (variable-type? (package-lookup-type p name))))
|
|
(require "definitely procedure" (list lhs name)
|
|
(procedure-type? (package-lookup-type p name))))
|
|
(make-inline-transform node type p lhs)
|
|
#f)))
|
|
((loophole-node? node)
|
|
(inlinable-rhs? (caddr (node-form node)) type p lhs))
|
|
((primitive-procedure-node? node)
|
|
(get-operator (cadr (node-form node))))
|
|
(else #f)))
|
|
|
|
(define primitive-procedure-node? (node-predicate 'primitive-procedure))
|
|
|
|
|
|
; We elect to integrate a procedure definition when
|
|
; 1. The procedure in not n-ary,
|
|
; 2. Every parameter is used exactly once and not assigned, and
|
|
; 3. The analysis phase says that the body is acceptable (see below).
|
|
|
|
(define (simple-lambda? node id p)
|
|
(let* ((exp (node-form node))
|
|
(formals (cadr exp))
|
|
(body (caddr exp))
|
|
(var-nodes (node-ref node 'var-nodes)))
|
|
(and (require "not n-ary" id
|
|
(not (n-ary? formals)))
|
|
(require "unique references" id
|
|
(every (lambda (var-node)
|
|
(let ((lexical (node-ref var-node 'lexical)))
|
|
(and (= (lexical-reference-count lexical) 1)
|
|
(= (lexical-assignment-count lexical) 0))))
|
|
var-nodes))
|
|
(require "good analysis" id
|
|
(simple? (caddr exp)
|
|
(bind formals
|
|
(map (lambda (name)
|
|
(make-node operator/name name))
|
|
formals)
|
|
(package->environment p))
|
|
ret)))))
|
|
|
|
(define operator/name (get-operator 'name 'leaf))
|
|
|
|
; --------------------
|
|
; SIMPLE? takes an alpha-converted expression and returns either
|
|
; - #f, meaning that the procedure in which the expression occurs
|
|
; has no chance of being fully inlinable, so we might as well give up,
|
|
; - #t, if there's no problem, or
|
|
; - 'empty, if there's no problem AND there are no lexical variable
|
|
; references at or below this node.
|
|
; Foul situations are:
|
|
; - complex quotations (we don't want to make multiple copies of them)
|
|
; - a LAMBDA occurs (too much overhead, presumably)
|
|
; - a call that is not to a primitive and not a tail call
|
|
|
|
|
|
; Main dispatch for analyzer
|
|
|
|
(define (simple? node env ret?)
|
|
((operator-table-ref analyzers (node-operator-id node))
|
|
(node-form node)
|
|
env ret?))
|
|
|
|
(define (simple-list? exp-list env)
|
|
(if (null? exp-list)
|
|
'empty
|
|
(let ((s1 (simple? (car exp-list) env no-ret)))
|
|
(if (eq? s1 'empty)
|
|
(simple-list? (cdr exp-list) env)
|
|
(if s1
|
|
(and (simple-list? (cdr exp-list) env)
|
|
#t)
|
|
#f)))))
|
|
|
|
|
|
; Particular operators
|
|
|
|
(define analyzers
|
|
(make-operator-table (lambda (exp env ret?)
|
|
(simple-list? (cdr exp) env))))
|
|
|
|
(define (define-analyzer name proc)
|
|
(operator-define! analyzers name #f proc))
|
|
|
|
(define-analyzer 'literal
|
|
(lambda (exp env ret?)
|
|
(if (require "repeatable literal" #f
|
|
(simple-literal? exp))
|
|
'empty
|
|
#f)))
|
|
|
|
(define-analyzer 'name
|
|
(lambda (exp env ret?)
|
|
;; (if (node-ref node 'lexical) #t 'empty)
|
|
;; ... (not (generated? exp)) ugh ...
|
|
#t))
|
|
|
|
(define-analyzer 'quote
|
|
(lambda (exp env ret?)
|
|
(if (require "repeatable quotation" #f
|
|
(simple-literal? (cadr exp)))
|
|
'empty
|
|
#f)))
|
|
|
|
(define-analyzer 'lambda
|
|
(lambda (exp env ret?) #f))
|
|
|
|
(define-analyzer 'letrec
|
|
(lambda (exp env ret?) #f))
|
|
|
|
(define-analyzer 'set!
|
|
(lambda (exp env ret?)
|
|
(simple? (caddr exp) env no-ret)))
|
|
|
|
(define-analyzer 'loophole
|
|
(lambda (exp env ret?)
|
|
(simple? (caddr exp) env ret?)))
|
|
|
|
; Can't always fully in-line things like (lambda (a b c) (if a b c))
|
|
|
|
(define-analyzer 'if
|
|
(lambda (exp env ret?)
|
|
(and (eq? (simple? (caddr exp) env ret?) 'empty)
|
|
(eq? (simple? (cadddr exp) env ret?) 'empty)
|
|
(simple? (cadr exp) env no-ret))))
|
|
|
|
(define-analyzer 'begin
|
|
(lambda (exp env ret?)
|
|
(let loop ((exps (cdr exp)))
|
|
(if (null? (cdr exps))
|
|
(if (simple? (car exps) env ret?) #t #f)
|
|
(and (simple? (car exps) env no-ret)
|
|
(loop (cdr exps)))))))
|
|
|
|
(define-analyzer 'call
|
|
(lambda (exp env ret?)
|
|
;; Retry transforming calls in hopes of finding procedures that
|
|
;; have become integrable as a result of the ongoing analysis of
|
|
;; this package.
|
|
(let ((proc (car exp)))
|
|
(if (name-node? proc)
|
|
(let* ((node (make-node (get-operator 'call) exp))
|
|
(new-node (maybe-transform-call proc node env)))
|
|
(if (eq? new-node node)
|
|
(really-simple-call? exp env ret?)
|
|
(simple? (expand new-node env) env ret?)))
|
|
(really-simple-call? exp env ret?)))))
|
|
|
|
(define (really-simple-call? exp env ret?)
|
|
(let ((proc (car exp)))
|
|
(and (require "non-local non-tail call" proc
|
|
(or (and ret? (simple? proc env no-ret)) ;tail calls are ok
|
|
(lexical-node? proc))) ;so are calls to arguments
|
|
(simple-list? exp env))))
|
|
|
|
(define (lexical-node? node)
|
|
(not (node-ref node 'binding)))
|
|
|
|
(define no-ret #f)
|
|
|
|
(define ret #t)
|
|
|
|
(define (simple-literal? x) ;Things that TRANSPORT won't copy.
|
|
(or (integer? x)
|
|
(boolean? x)
|
|
(null? x)
|
|
(char? x)
|
|
(symbol? x)))
|
|
|
|
|
|
; --------------------
|
|
; Once we know that we want something to be inlined, the following things
|
|
; actually makes use of the fact. For procedures for which all
|
|
; arguments can be substituted unconditionally, we make a transform
|
|
; (a macro, really) that performs the substitution.
|
|
|
|
(define (make-inline-transform node type p name)
|
|
(let* ((free (free-top-level-variables node))
|
|
(form (make-substitution-template node p free))
|
|
(aux-names (map (lambda (free)
|
|
(do ((free free (generated-parent-name free)))
|
|
((not (generated? free)) free)))
|
|
free)))
|
|
(make-transform (inline-transform form aux-names)
|
|
p ;env ?
|
|
type
|
|
`(inline-transform ',form ',aux-names)
|
|
name)))
|
|
|
|
; Create something that can be passed to SUBSTITUTE. Must be valid as
|
|
; a quotation.
|
|
|
|
(define (make-substitution-template node p free)
|
|
(let ((env (package->environment p)))
|
|
(clean-node node
|
|
(map (lambda (free)
|
|
(cons free (name->qualified free env)))
|
|
free))))
|
|
|
|
; This routine is obligated to return an S-expression.
|
|
; It's better not to rely on the constancy of node id's, so
|
|
; the output language is a sort of quasi-Scheme. Any form that's a list
|
|
; has an operator name in its car.
|
|
|
|
(define (clean-node node env)
|
|
(cond ((name-node? node)
|
|
(clean-lookup env (node-form node)))
|
|
((quote-node? node)
|
|
`(quote ,(cadr (node-form node))))
|
|
((lambda-node? node)
|
|
(clean-lambda node env))
|
|
((call-node? node)
|
|
(cons 'call
|
|
(map (lambda (node) (clean-node node env))
|
|
(node-form node))))
|
|
((loophole-node? node) ;Uck
|
|
(let ((args (cdr (node-form node))))
|
|
`(loophole ,(schemify (car args))
|
|
,(clean-node (cadr args) env))))
|
|
;; LETREC had better not occur, since we ain't prepared for it
|
|
((pair? (node-form node))
|
|
(cons (operator-name (node-operator node))
|
|
(map (lambda (subnode)
|
|
(clean-node subnode env))
|
|
(cdr (node-form node)))))
|
|
(else (node-form node)))) ;literal
|
|
|
|
(define quote-node? (node-predicate 'quote))
|
|
(define call-node? (node-predicate 'call))
|
|
|
|
|
|
(define (clean-lambda node env)
|
|
(let* ((exp (node-form node))
|
|
(formals (cadr exp))
|
|
(env (append (map (lambda (name)
|
|
(cons name
|
|
(unused-name env name)))
|
|
(normalize-formals formals))
|
|
env)))
|
|
`(lambda ,(let recur ((foo formals))
|
|
(cond ((name? foo) (clean-lookup env foo))
|
|
((pair? foo)
|
|
(cons (recur (car foo))
|
|
(recur (cdr foo))))
|
|
(else foo)))
|
|
,(clean-node (caddr exp) env))))
|
|
|
|
(define (clean-lookup env name)
|
|
(cdr (assq name env))) ;Must be there.
|
|
|
|
; I'm aware that this is pedantic.
|
|
|
|
(define (unused-name env name)
|
|
(let ((sym (if (generated? name)
|
|
(generated-symbol name)
|
|
name)))
|
|
(do ((i 0 (+ i 1))
|
|
(name sym
|
|
(string->symbol (string-append (symbol->string sym)
|
|
(number->string i)))))
|
|
((not (assq name env)) name))))
|
|
|
|
|
|
(define (free-top-level-variables node)
|
|
(let recur ((node node) (vars '()))
|
|
(cond ((quote-node? node) vars)
|
|
((name-node? node)
|
|
(if (node-ref node 'binding)
|
|
(let ((var (node-form node)))
|
|
(if (memq var vars) vars (cons var vars)))
|
|
vars))
|
|
;; lambda, letrec shouldn't occur
|
|
(else
|
|
(let ((form (node-form node)))
|
|
(if (pair? form)
|
|
(reduce (lambda (node vars)
|
|
(if (node? node)
|
|
(recur node vars)
|
|
vars))
|
|
vars
|
|
(if (call-node? node)
|
|
form
|
|
(cdr form)))
|
|
vars))))))
|
|
|
|
|
|
; --------------------
|
|
; debugging hack
|
|
|
|
(define (require reason id x)
|
|
(if (and *debug?* (not x))
|
|
(begin (write id)
|
|
(display " lost because ")
|
|
(display reason)
|
|
(display " failed")
|
|
(newline)))
|
|
x)
|
|
|
|
(define *debug?* #f)
|
|
|
|
; utility
|
|
|
|
(define (package-lookup-type p name)
|
|
(let ((probe (package-lookup p name)))
|
|
(if (binding? probe)
|
|
(binding-type probe)
|
|
#f)))
|
|
|
|
; --------------------
|
|
|
|
|
|
;(define (foo f p)
|
|
; (analyze-forms (alpha-forms (scan-file f p) p)))
|
|
;
|
|
;
|
|
;(define (tst e p)
|
|
; (inlinable-rhs? (alpha e p) #f))
|
|
;
|
|
;(define b (make-compiler-base))
|
|
;
|
|
;(define p (make-simple-package (list b) eval #f))
|
|
;
|
|
;; (define b-stuff (alpha-structure b))
|
|
;
|