; 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)) ;