summaryrefslogtreecommitdiff
path: root/backend/optimize.scm
diff options
context:
space:
mode:
Diffstat (limited to 'backend/optimize.scm')
-rw-r--r--backend/optimize.scm1986
1 files changed, 1986 insertions, 0 deletions
diff --git a/backend/optimize.scm b/backend/optimize.scm
new file mode 100644
index 0000000..1624e35
--- /dev/null
+++ b/backend/optimize.scm
@@ -0,0 +1,1986 @@
+;;; optimize.scm -- flic optimizer
+;;;
+;;; author : Sandra Loosemore
+;;; date : 7 May 1992
+;;;
+;;;
+;;; The optimizer does these kinds of program transformations:
+;;;
+;;; * remove unreferenced variable bindings.
+;;;
+;;; * constant folding and various other kinds of compile-time
+;;; evaluation.
+;;;
+;;; * beta reduction (replace references to variables bound to simple
+;;; expressions with the expression)
+;;;
+
+
+;;; Since some of the optimizations can make additional transformations
+;;; possible, we want to make multiple iteration passes. But since each
+;;; pass is likely to have diminishing benefits, we don't want to keep
+;;; iterating indefinitely. So establish a fairly arbitrary cutoff point.
+;;; The value is based on empirical results from compiling the prelude.
+
+(define *max-optimize-iterations* 5)
+(define *optimize-foldr-iteration* 0) ; when to inline foldr
+(define *optimize-build-iteration* 0) ; when to inline build
+(define *current-optimize-iteration* 0)
+
+
+;;; Flags for enabling various optimizations
+
+(define *all-optimizers* '(foldr inline constant lisp))
+(define *optimizers* *all-optimizers*)
+
+
+;;; Used to note whether we are doing the various optimizations
+
+(define-local-syntax (do-optimization? o)
+ `(memq ,o (dynamic *optimizers*)))
+
+(define *do-foldr-optimizations* (do-optimization? 'foldr))
+(define *do-inline-optimizations* (do-optimization? 'inline))
+(define *do-constant-optimizations* (do-optimization? 'constant))
+
+
+;;; If the foldr optimization is enabled, bind the corresponding
+;;; variables to these values instead of the defaults.
+
+(define *foldr-max-optimize-iterations* 15)
+(define *foldr-optimize-foldr-iteration* 8)
+(define *foldr-optimize-build-iteration* 5)
+
+
+;;; Some random other variables
+
+(define *structured-constants* '())
+(define *structured-constants-table* '#f)
+(define *lambda-depth* 0)
+(define *local-bindings* '())
+
+
+;;; This is for doing some crude profiling.
+;;; Comment out the body of the macro to disable profiling.
+
+;;; Here are current counts from compiling the prelude:
+;;; (LET-REMOVE-UNUSED-BINDING . 5835)
+;;; (REF-INLINE-SINGLE-REF . 2890)
+;;; (REF-INLINE . 2692)
+;;; (LET-EMPTY-BINDINGS . 2192)
+;;; (APP-LAMBDA-TO-LET . 1537)
+;;; (APP-MAKE-SATURATED . 416)
+;;; (LET-HOIST-RETURN-FROM . 310)
+;;; (CASE-BLOCK-IDENTITY . 273)
+;;; (CASE-BLOCK-DEAD-CODE . 234)
+;;; (CASE-BLOCK-TO-IF . 212)
+;;; (SEL-FOLD-VAR . 211)
+;;; (APP-HOIST-LET . 190)
+;;; (LET-HOIST-LAMBDA . 181)
+;;; (FOLDR-INLINE . 176)
+;;; (AND-UNARY . 172)
+;;; (LAMBDA-COMPRESS . 168)
+;;; (APP-FOLD-SELECTOR . 141)
+;;; (BUILD-INLINE-LAMBDA . 134)
+;;; (LET-COMPRESS . 134)
+;;; (IF-FOLD . 128)
+;;; (INTEGER-TO-INT-CONSTANT-FOLD . 124)
+;;; (AND-COMPRESS . 94)
+;;; (APP-COMPRESS . 93)
+;;; (FOLDR-CONS-IDENTITY . 69)
+;;; (IF-COMPRESS-TEST . 65)
+;;; (IF-HOIST-LAMBDA . 61)
+;;; (APP-HOIST-STRUCTURED-CONSTANT . 60)
+;;; (FOLDR-PRIM-APPEND-INLINE . 55)
+;;; (FOLDR-BUILD-IDENTITY . 40)
+;;; (CASE-BLOCK-DISCARD-REDUNDANT-TEST . 37)
+;;; (FOLDR-NIL-IDENTITY . 36)
+;;; (LET-HOIST-INVARIANT-ARGS . 30)
+;;; (FOLDR-HOIST-LET . 28)
+;;; (CON-NUMBER-FOLD-TUPLE . 21)
+;;; (FOLDR-CONS-NIL-IDENTITY . 15)
+;;; (AND-CONTAINS-TRUE . 14)
+;;; (IF-IDENTITY-INVERSE . 8)
+;;; (IF-HOIST-RETURN-FROM . 7)
+;;; (CASE-BLOCK-HOIST-LET . 7)
+;;; (INTEGER-TO-INT-IDENTITY . 7)
+;;; (APP-PACK-IDENTITY . 2)
+;;; (CON-NUMBER-FOLD . 2)
+;;; (IF-IDENTITY . 2)
+;;; (INT-TO-INTEGER-CONSTANT-FOLD . 2)
+;;; (LET-HOIST-STRUCTURED-CONSTANT . 1)
+
+
+(define-local-syntax (record-hack type . args)
+ (declare (ignore args))
+ `',type
+; `(record-hack-aux ,type ,@args)
+ )
+
+(define *hacks-done* '())
+
+(define (record-hack-aux type . args)
+ ;; *** debug
+ ;; (format '#t "~s ~s~%" type args)
+ (declare (ignore args))
+ (let ((stuff (assq type (car (dynamic *hacks-done*)))))
+ (if stuff
+ (incf (cdr stuff))
+ (push (cons type 1) (car (dynamic *hacks-done*))))))
+
+(define (total-hacks)
+ (let ((totals '()))
+ (dolist (alist *hacks-done*)
+ (dolist (entry alist)
+ (let ((stuff (assq (car entry) totals)))
+ (if stuff
+ (setf (cdr stuff) (+ (cdr stuff) (cdr entry)))
+ (push (cons (car entry) (cdr entry)) totals)))))
+ totals))
+
+
+;;; This is the main entry point.
+
+(define (optimize-top object)
+ (dynamic-let ((*structured-constants* '())
+ (*structured-constants-table* (make-table))
+ (*lambda-depth* 0)
+ (*local-bindings* '())
+ (*do-inline-optimizations*
+ (do-optimization? 'inline))
+ (*do-constant-optimizations*
+ (do-optimization? 'constant))
+ (*max-optimize-iterations*
+ (if (do-optimization? 'foldr)
+ (dynamic *foldr-max-optimize-iterations*)
+ (dynamic *max-optimize-iterations*)))
+ (*optimize-foldr-iteration*
+ (if (do-optimization? 'foldr)
+ (dynamic *foldr-optimize-foldr-iteration*)
+ (dynamic *optimize-foldr-iteration*)))
+ (*optimize-build-iteration*
+ (if (do-optimization? 'foldr)
+ (dynamic *foldr-optimize-build-iteration*)
+ (dynamic *optimize-build-iteration*))))
+ (setf *hacks-done* '())
+ (dotimes (i (dynamic *max-optimize-iterations*))
+ (dynamic-let ((*current-optimize-iteration* i))
+;; debug (*duplicate-object-table* (make-table)))
+ (when (memq 'optimize-extra (dynamic *printers*))
+ (format '#t "~%Optimize pass ~s:" i)
+ (pprint object))
+ (push '() *hacks-done*)
+ (setf object (optimize-flic-let-aux object '#t))))
+ (setf (flic-let-bindings object)
+ (nconc (nreverse (dynamic *structured-constants*))
+ (flic-let-bindings object))))
+ (install-uninterned-globals (flic-let-bindings object))
+ (postoptimize object)
+ object)
+
+
+(define-flic-walker optimize (object))
+
+;;; debugging stuff
+;;;
+;;; (define *duplicate-object-table* (make-table))
+;;;
+;;; (define (new-optimize object)
+;;; (if (table-entry (dynamic *duplicate-object-table*) object)
+;;; (error "Duplicate object ~s detected." object)
+;;; (begin
+;;; (setf (table-entry (dynamic *duplicate-object-table*) object) '#t)
+;;; (old-optimize object))))
+;;;
+;;; (lisp:setf (lisp:symbol-function 'old-optimize)
+;;; (lisp:symbol-function 'optimize))
+;;; (lisp:setf (lisp:symbol-function 'optimize)
+;;; (lisp:symbol-function 'new-optimize))
+
+(define (optimize-list objects)
+ (optimize-list-aux objects)
+ objects)
+
+(define (optimize-list-aux objects)
+ (if (null? objects)
+ '()
+ (begin
+ (setf (car objects) (optimize (car objects)))
+ (optimize-list-aux (cdr objects)))))
+
+
+;;; Compress nested lambdas. This hack is desirable because saturating
+;;; applications within the lambda body effectively adds additional
+;;; parameters to the function.
+
+;;; *** Maybe this should look for hoistable constant lambdas too.
+
+(define-optimize flic-lambda (object)
+ (let ((vars (flic-lambda-vars object)))
+ (dynamic-let ((*lambda-depth* (1+ (dynamic *lambda-depth*)))
+ (*local-bindings* (cons vars (dynamic *local-bindings*))))
+ (dolist (var vars)
+ (setf (var-referenced var) 0))
+ (let ((new-body (optimize (flic-lambda-body object))))
+ (setf (flic-lambda-body object) new-body)
+ (cond ((is-type? 'flic-lambda new-body)
+ (record-hack 'lambda-compress)
+ (setf (flic-lambda-vars object)
+ (nconc (flic-lambda-vars object)
+ (flic-lambda-vars new-body)))
+ (setf (flic-lambda-body object) (flic-lambda-body new-body)))
+ (else
+ '#f))
+ object))))
+
+
+;;; For let, first mark all variables as unused and check for "simple"
+;;; binding values that permit beta reduction. Then walk the subexpressions.
+;;; Finally discard any bindings that are still marked as unused.
+;;; *** This fails to detect unused recursive variables.
+
+(define-optimize flic-let (object)
+ (optimize-flic-let-aux object '#f))
+
+(define (optimize-flic-let-aux object toplevel?)
+ (let ((bindings (flic-let-bindings object))
+ (recursive? (flic-let-recursive? object)))
+ ;; *** This handling of *local-bindings* isn't quite right since
+ ;; *** it doesn't account for the sequential nature of bindings
+ ;; *** in a non-recursive let, but it's close enough. We won't
+ ;; *** get any semantic errors, but it might miss a few optimizations.
+ (dynamic-let ((*local-bindings*
+ (if (and recursive? (not toplevel?))
+ (cons bindings (dynamic *local-bindings*))
+ (dynamic *local-bindings*))))
+ (optimize-flic-let-bindings bindings recursive? toplevel?)
+ (dynamic-let ((*local-bindings*
+ (if (and (not recursive?) (not toplevel?))
+ (cons bindings (dynamic *local-bindings*))
+ (dynamic *local-bindings*))))
+ (setf (flic-let-body object) (optimize (flic-let-body object))))
+ ;; Check for unused bindings and other rewrites.
+ ;; Only do this for non-toplevel lets.
+ (if toplevel?
+ object
+ (optimize-flic-let-rewrite object bindings recursive?)))))
+
+(define (optimize-flic-let-bindings bindings recursive? toplevel?)
+ ;; Initialize
+ (dolist (var bindings)
+ (setf (var-referenced var) 0)
+ (setf (var-fn-referenced var) 0)
+ (when (is-type? 'flic-lambda (var-value var))
+ (dolist (v (flic-lambda-vars (var-value var)))
+ (setf (var-arg-invariant? v) '#t)
+ (setf (var-arg-invariant-value v) '#f))))
+ ;; Traverse value subforms
+ (do ((bindings bindings (cdr bindings)))
+ ((null? bindings) '#f)
+ (let* ((var (car bindings))
+ (val (var-value var)))
+ (if (and (is-type? 'flic-app val)
+ (dynamic *do-constant-optimizations*)
+ (let ((fn (flic-app-fn val))
+ (args (flic-app-args val)))
+ (if recursive?
+ (structured-constant-app-recursive?
+ fn args bindings (list var))
+ (structured-constant-app? fn args))))
+ ;; Variable is bound to a structured constant. If this
+ ;; isn't already a top-level binding, replace the value
+ ;; of the constant with a reference to a top-level variable
+ ;; that is in turn bound to the constant expression.
+ ;; binding to top-level if this is a new constant.
+ ;; *** Maybe we should also look for variables bound
+ ;; *** to lambdas, that can also be hoisted to top level.
+ (when (not toplevel?)
+ (multiple-value-bind (con args cvar)
+ (enter-structured-constant-aux val '#t)
+ (record-hack 'let-hoist-structured-constant)
+ (if cvar
+ (setf (var-value var) (make-flic-ref cvar))
+ (add-new-structured-constant var con args))))
+ (begin
+ ;; If this is a function that's a candidate for foldr/build
+ ;; optimization, stash the value away prior to
+ ;; inlining the calls.
+ ;; *** We might try to automagically detect functions
+ ;; *** that are candidates for these optimizations here,
+ ;; *** but have to watch out for infinite loops!
+ (when (and (var-force-inline? var)
+ (eqv? (the fixnum
+ (dynamic *current-optimize-iteration*))
+ (the fixnum
+ (dynamic *optimize-build-iteration*)))
+ (is-type? 'flic-lambda val)
+ (or (is-foldr-or-build-app? (flic-lambda-body val))))
+ (setf (var-inline-value var) (copy-flic-top val)))
+ ;; Then walk value normally.
+ (let ((new-val (optimize val)))
+ (setf (var-value var) new-val)
+ (setf (var-simple? var)
+ (or (var-force-inline? var)
+ (and (not (var-selector-fn? var))
+ (can-inline?
+ new-val
+ (if recursive? bindings '())
+ toplevel?))))))
+ ))))
+
+
+(define (is-foldr-or-build-app? exp)
+ (typecase exp
+ (flic-app
+ (let ((fn (flic-app-fn exp)))
+ (and (is-type? 'flic-ref fn)
+ (or (eq? (flic-ref-var fn) (core-symbol "foldr"))
+ (eq? (flic-ref-var fn) (core-symbol "build"))))))
+ (flic-let
+ (is-foldr-or-build-app? (flic-let-body exp)))
+ (flic-ref
+ (let ((val (var-value (flic-ref-var exp))))
+ (and val (is-foldr-or-build-app? val))))
+ (else
+ '#f)))
+
+
+(define (optimize-flic-let-rewrite object bindings recursive?)
+ ;; Delete unused variables from the list.
+ (setf bindings
+ (list-delete-if
+ (lambda (var)
+ (cond ((var-toplevel? var)
+ ;; This was a structured constant hoisted to top-level.
+ '#t)
+ ((eqv? (the fixnum (var-referenced var)) (the fixnum 0))
+ (record-hack 'let-remove-unused-binding var)
+ '#t)
+ ((eqv? (the fixnum (var-referenced var)) (the fixnum 1))
+ (setf (var-single-ref var) (dynamic *lambda-depth*))
+ '#f)
+ (else
+ (setf (var-single-ref var) '#f)
+ '#f)))
+ bindings))
+ ;; Add extra bindings for reducing functions with invariant
+ ;; arguments. Hopefully some of the extra bindings will go
+ ;; away in future passes!
+ (setf (flic-let-bindings object)
+ (setf bindings (add-stuff-for-invariants bindings)))
+ ;; Look for other special cases.
+ (cond ((null? bindings)
+ ;; Simplifying the expression by getting rid of the LET may
+ ;; make it possible to do additional optimizations on the
+ ;; next pass.
+ (record-hack 'let-empty-bindings)
+ (flic-let-body object))
+ ((is-type? 'flic-return-from (flic-let-body object))
+ ;; Hoist return-from outside of LET. This may permit
+ ;; further optimizations by an enclosing case-block.
+ (record-hack 'let-hoist-return-from)
+ (let* ((body (flic-let-body object))
+ (inner-body (flic-return-from-exp body)))
+ (setf (flic-return-from-exp body) object)
+ (setf (flic-let-body object) inner-body)
+ body))
+ ((and (not recursive?)
+ (is-type? 'flic-let (flic-let-body object))
+ (not (flic-let-recursive? (flic-let-body object))))
+ ;; This is purely to produce more compact code.
+ (record-hack 'let-compress)
+ (let ((body (flic-let-body object)))
+ (setf (flic-let-bindings object)
+ (nconc bindings (flic-let-bindings body)))
+ (setf (flic-let-body object) (flic-let-body body))
+ object))
+ ((is-type? 'flic-lambda (flic-let-body object))
+ ;; Hoist lambda outside of LET. This may permit
+ ;; merging of nested lambdas on a future pass.
+ (record-hack 'let-hoist-lambda)
+ (let* ((body (flic-let-body object))
+ (inner-body (flic-lambda-body body)))
+ (setf (flic-lambda-body body) object)
+ (setf (flic-let-body object) inner-body)
+ body))
+ (else
+ object))
+ )
+
+;;; Look for constant-folding and structured constants here.
+
+(define-optimize flic-app (object)
+ (optimize-flic-app-aux object))
+
+(define (optimize-flic-app-aux object)
+ (let ((new-fn (optimize (flic-app-fn object)))
+ (new-args (optimize-list (flic-app-args object))))
+ (typecase new-fn
+ (flic-ref
+ ;; The function is a variable.
+ (let* ((var (flic-ref-var new-fn))
+ (val (var-value var))
+ (n (length new-args))
+ (arity (guess-function-arity var)))
+ (cond ((and arity (< (the fixnum n) (the fixnum arity)))
+ ;; This is a first-class call that is not fully saturated.
+ ;; Make it saturated by wrapping a lambda around it.
+ (setf new-fn
+ (do-app-make-saturated object new-fn new-args arity n))
+ (setf new-args '()))
+ ((var-selector-fn? var)
+ ;; This is a saturated call to a selector. We might
+ ;; be able to inline the call.
+ (multiple-value-bind (fn args)
+ (try-to-fold-selector var new-fn new-args)
+ (setf new-fn fn)
+ (setf new-args args)))
+ ((and (not (var-toplevel? var))
+ (is-type? 'flic-lambda val))
+ ;; This is a saturated call to a local function.
+ ;; Increment its reference count and note if any of
+ ;; the arguments are invariant.
+ (incf (var-fn-referenced var))
+ (note-invariant-args new-args (flic-lambda-vars val)))
+ (else
+ (let ((magic (magic-optimize-function var)))
+ (when magic
+ (multiple-value-bind (fn args)
+ (funcall magic new-fn new-args)
+ (setf new-fn fn)
+ (setf new-args args)))))
+ )))
+ (flic-lambda
+ ;; Turn application of lambda into a let.
+ (multiple-value-bind (fn args)
+ (do-lambda-to-let-aux new-fn new-args)
+ (setf new-fn fn)
+ (setf new-args args)))
+ (flic-pack
+ (let ((con (flic-pack-con new-fn))
+ (temp '#f))
+ (when (eqv? (length new-args) (con-arity con))
+ (cond ((and (dynamic *do-constant-optimizations*)
+ (every-1 (function structured-constant?) new-args))
+ ;; This is a structured constant that
+ ;; can be replaced with a top-level binding.
+ (setf (flic-app-fn object) new-fn)
+ (setf (flic-app-args object) new-args)
+ (record-hack 'app-hoist-structured-constant object)
+ (setf new-fn (enter-structured-constant object '#t))
+ (setf new-args '()))
+ ((and (setf temp (is-selector? con 0 (car new-args)))
+ (is-selector-list? con 1 temp (cdr new-args)))
+ ;; This is an expression like (cons (car x) (cdr x)).
+ ;; Replace it with just plain x to avoid reconsing.
+ (record-hack 'app-pack-identity new-fn)
+ (setf new-fn (copy-flic-top temp))
+ (setf new-args '()))
+ ))))
+ (flic-let
+ ;; Hoist let to surround entire application.
+ ;; Simplifying the function being applied may permit further
+ ;; optimizations on next pass.
+ ;; (We might try to hoist lets in the argument expressions, too,
+ ;; but I don't think that would lead to any real simplification
+ ;; of the code.)
+ (record-hack 'app-hoist-let)
+ (setf (flic-app-fn object) (flic-let-body new-fn))
+ (setf (flic-app-args object) new-args)
+ (setf new-args '())
+ (setf (flic-let-body new-fn) object)
+ )
+ (flic-app
+ ;; Try to compress nested applications.
+ ;; This may make the call saturated and permit further optimizations
+ ;; on the next pass.
+ (record-hack 'app-compress)
+ (setf new-args (nconc (flic-app-args new-fn) new-args))
+ (setf new-fn (flic-app-fn new-fn)))
+ )
+ (if (null? new-args)
+ new-fn
+ (begin
+ (setf (flic-app-fn object) new-fn)
+ (setf (flic-app-args object) new-args)
+ object))
+ ))
+
+(define (guess-function-arity var)
+ (or (let ((value (var-value var)))
+ (and value
+ (is-type? 'flic-lambda value)
+ (length (flic-lambda-vars value))))
+ (var-arity var)))
+
+(define (do-app-make-saturated app fn args arity nargs)
+ (declare (type fixnum arity nargs))
+ (record-hack 'app-make-saturated fn args)
+ (let ((newvars '())
+ (newargs '()))
+ (dotimes (i (- arity nargs))
+ (declare (type fixnum i))
+ (let ((v (init-flic-var (create-temp-var 'arg) '#f '#f)))
+ (push v newvars)
+ (push (make-flic-ref v) newargs)))
+ (setf (flic-app-fn app) fn)
+ (setf (flic-app-args app) (nconc args newargs))
+ (make-flic-lambda newvars app)))
+
+
+
+;;; If the function is a selector applied to a literal dictionary,
+;;; inline it.
+
+(define (try-to-fold-selector var new-fn new-args)
+ (let ((exp (car new-args)))
+ (if (or (and (is-type? 'flic-ref exp)
+ ;; *** should check that var is top-level?
+ (is-bound-to-constructor-app? (flic-ref-var exp)))
+ (and (is-type? 'flic-app exp)
+ (is-constructor-app-prim? exp)))
+ (begin
+ (record-hack 'app-fold-selector)
+ (setf new-fn (copy-flic-top (var-value var)))
+ (do-lambda-to-let-aux new-fn new-args))
+ (values new-fn new-args))))
+
+
+;;; Various primitive functions have special optimizer functions
+;;; associated with them, that do constant folding and certain
+;;; other identities. The optimizer function is called with the
+;;; function expression and list of argument expressions (at least
+;;; as many arguments as the arity of the function) and should return
+;;; the two values.
+
+;;; *** This should really use some kind of hash table, but we'd
+;;; *** have to initialize the table dynamically because core-symbols
+;;; *** aren't defined when this file is loaded.
+
+(define (magic-optimize-function var)
+ (cond ((eq? var (core-symbol "foldr"))
+ (function optimize-foldr-aux))
+ ((eq? var (core-symbol "build"))
+ (function optimize-build))
+ ((eq? var (core-symbol "primIntegerToInt"))
+ (function optimize-integer-to-int))
+ ((eq? var (core-symbol "primIntToInteger"))
+ (function optimize-int-to-integer))
+ ((eq? var (core-symbol "primRationalToFloat"))
+ (function optimize-rational-to-float))
+ ((eq? var (core-symbol "primRationalToDouble"))
+ (function optimize-rational-to-double))
+ ((or (eq? var (core-symbol "primNegInt"))
+ (eq? var (core-symbol "primNegInteger"))
+ (eq? var (core-symbol "primNegFloat"))
+ (eq? var (core-symbol "primNegDouble")))
+ (function optimize-neg))
+ (else
+ '#f)))
+
+
+;;; Foldr identities for deforestation
+
+(define (optimize-foldr fn args)
+ (multiple-value-bind (fn args)
+ (optimize-foldr-aux fn args)
+ (maybe-make-app fn args)))
+
+(define (optimize-foldr-aux fn args)
+ (let ((k (car args))
+ (z (cadr args))
+ (l (caddr args))
+ (tail (cdddr args)))
+ (cond ((and (is-type? 'flic-pack k)
+ (eq? (flic-pack-con k) (core-symbol ":"))
+ (is-type? 'flic-pack z)
+ (eq? (flic-pack-con z) (core-symbol "Nil")))
+ ;; foldr (:) [] l ==> l
+ ;; (We arrange for build to be inlined before foldr
+ ;; so that this pattern can be detected.)
+ (record-hack 'foldr-cons-nil-identity)
+ (values l tail))
+ ((and (is-type? 'flic-app l)
+ (is-type? 'flic-ref (flic-app-fn l))
+ (eq? (flic-ref-var (flic-app-fn l))
+ (core-symbol "build"))
+ (null? (cdr (flic-app-args l))))
+ ;; foldr k z (build g) ==> g k z
+ (record-hack 'foldr-build-identity)
+ (values
+ (car (flic-app-args l))
+ (cons k (cons z tail))))
+ ((and (is-type? 'flic-pack l)
+ (eq? (flic-pack-con l) (core-symbol "Nil")))
+ ;; foldr k z [] ==> z
+ (record-hack 'foldr-nil-identity)
+ (values z tail))
+ ((short-string-constant? l)
+ ;; If the list argument is a string constant, expand it inline.
+ ;; Only do this if the string is fairly short, though.
+ (optimize-foldr-aux
+ fn
+ (cons k (cons z (cons (expand-string-constant l) tail)))))
+ ((and (is-type? 'flic-app l)
+ (is-type? 'flic-pack (flic-app-fn l))
+ (eq? (flic-pack-con (flic-app-fn l)) (core-symbol ":"))
+ (eqv? (length (flic-app-args l)) 2))
+ ;; foldr k z x:xs ==> let c = k in c x (foldr c z xs)
+ (record-hack 'foldr-cons-identity)
+ (let ((x (car (flic-app-args l)))
+ (xs (cadr (flic-app-args l))))
+ (values
+ (if (can-inline? k '() '#f)
+ (do-foldr-cons-identity k z x xs)
+ (let ((cvar (init-flic-var (create-temp-var 'c) k '#f)))
+ (make-flic-let
+ (list cvar)
+ (do-foldr-cons-identity (make-flic-ref cvar) z x xs)
+ '#f)))
+ tail)))
+ ((is-type? 'flic-let l)
+ ;; foldr k z (let bindings in body) ==>
+ ;; let bindings in foldr k z body
+ (record-hack 'foldr-hoist-let)
+ (setf (flic-let-body l)
+ (optimize-foldr fn (list k z (flic-let-body l))))
+ (values l tail))
+ ((not (eqv? (the fixnum (dynamic *current-optimize-iteration*))
+ (the fixnum (dynamic *optimize-foldr-iteration*))))
+ ;; Hope for more optimizations later.
+ (values fn args))
+ ((and (is-type? 'flic-pack k)
+ (eq? (flic-pack-con k) (core-symbol ":")))
+ ;; Inline to special case, highly optimized append primitive.
+ ;; Could also look for (++ (++ l1 l2) l3) => (++ l1 (++ l2 l3))
+ ;; here, but I don't think that happens very often.
+ (record-hack 'foldr-prim-append-inline)
+ (values
+ (make-flic-ref (core-symbol "primAppend"))
+ (cons l (cons z tail))))
+ (else
+ ;; Default inline.
+ (record-hack 'foldr-inline k z)
+ (let ((new-fn
+ (copy-flic-top (var-value (core-symbol "inlineFoldr")))))
+ (if (is-type? 'flic-lambda new-fn)
+ (do-lambda-to-let-aux new-fn args)
+ (values new-fn args))))
+ )))
+
+
+;;; Mess with compile-time expansion of short string constants.
+
+(define-integrable max-short-string-length 3)
+
+(define (short-string-constant? l)
+ (and (is-type? 'flic-const l)
+ (let ((string (flic-const-value l)))
+ (and (string? string)
+ (<= (the fixnum (string-length string))
+ (the fixnum max-short-string-length))))))
+
+(define (expand-string-constant l)
+ (let* ((string (flic-const-value l))
+ (length (string-length string)))
+ (expand-string-constant-aux string 0 length)))
+
+(define (expand-string-constant-aux string i length)
+ (declare (type fixnum i length))
+ (if (eqv? i length)
+ (make-flic-pack (core-symbol "Nil"))
+ (make-flic-app
+ (make-flic-pack (core-symbol ":"))
+ (list (make-flic-const (string-ref string i))
+ (expand-string-constant-aux string (+ 1 i) length))
+ '#f)))
+
+
+;;; Helper function for the case of expanding foldr applied to cons call.
+
+(define (do-foldr-cons-identity c z x xs)
+ (make-flic-app
+ c
+ (list x
+ (optimize-foldr
+ (make-flic-ref (core-symbol "foldr"))
+ (list (copy-flic-top c) z xs)))
+ '#f))
+
+
+
+;;; Short-circuit build inlining for the usual case where the
+;;; argument is a lambda. (It would take several optimizer passes
+;;; for this simplification to fall out, otherwise.)
+
+(define (optimize-build fn args)
+ (let ((arg (car args)))
+ (cond ((not (eqv? (dynamic *current-optimize-iteration*)
+ (dynamic *optimize-build-iteration*)))
+ (values fn args))
+ ((is-type? 'flic-lambda arg)
+ (record-hack 'build-inline-lambda)
+ (do-lambda-to-let-aux
+ arg
+ (cons (make-flic-pack (core-symbol ":"))
+ (cons (make-flic-pack (core-symbol "Nil"))
+ (cdr args)))))
+ (else
+ (record-hack 'build-inline-other)
+ (let ((new-fn
+ (copy-flic-top (var-value (core-symbol "inlineBuild")))))
+ (if (is-type? 'flic-lambda new-fn)
+ (do-lambda-to-let-aux new-fn args)
+ (values new-fn args))))
+ )))
+
+
+;;; Various simplifications on numeric functions.
+;;; *** Obviously, could get much fancier about this.
+
+(define (optimize-integer-to-int fn args)
+ (let ((arg (car args)))
+ (cond ((is-type? 'flic-const arg)
+ (record-hack 'integer-to-int-constant-fold)
+ (if (is-type? 'integer (flic-const-value arg))
+ (let ((value (flic-const-value arg)))
+ (when (not (is-type? 'fixnum value))
+ ;; Overflow is a user error, not an implementation error.
+ (phase-error 'int-overflow
+ "Int overflow in primIntegerToInt: ~s"
+ value))
+ (values arg (cdr args)))
+ (error "Bad argument ~s to primIntegerToInt." arg)))
+ ((and (is-type? 'flic-app arg)
+ (is-type? 'flic-ref (flic-app-fn arg))
+ (eq? (flic-ref-var (flic-app-fn arg))
+ (core-symbol "primIntToInteger"))
+ (null? (cdr (flic-app-args arg))))
+ (record-hack 'integer-to-int-identity)
+ (values (car (flic-app-args arg)) (cdr args)))
+ (else
+ (values fn args)))))
+
+(define (optimize-int-to-integer fn args)
+ (let ((arg (car args)))
+ (cond ((is-type? 'flic-const arg)
+ (record-hack 'int-to-integer-constant-fold)
+ (if (is-type? 'integer (flic-const-value arg))
+ (values arg (cdr args))
+ (error "Bad argument ~s to primIntToInteger." arg)))
+ ((and (is-type? 'flic-app arg)
+ (is-type? 'flic-ref (flic-app-fn arg))
+ (eq? (flic-ref-var (flic-app-fn arg))
+ (core-symbol "primIntegerToInt"))
+ (null? (cdr (flic-app-args arg))))
+ (record-hack 'int-to-integer-identity)
+ (values (car (flic-app-args arg)) (cdr args)))
+ (else
+ (values fn args)))))
+
+(predefine (prim.rational-to-float-aux n d)) ; in prims.scm
+(predefine (prim.rational-to-double-aux n d)) ; in prims.scm
+
+(define (optimize-rational-to-float fn args)
+ (let ((arg (car args)))
+ (cond ((is-type? 'flic-const arg)
+ (record-hack 'rational-to-float-constant-fold)
+ (if (is-type? 'list (flic-const-value arg))
+ (let ((value (flic-const-value arg)))
+ (setf (flic-const-value arg)
+ (prim.rational-to-float-aux (car value) (cadr value)))
+ (values arg (cdr args)))
+ (error "Bad argument ~s to primRationalToFloat." arg)))
+ (else
+ (values fn args)))))
+
+(define (optimize-rational-to-double fn args)
+ (let ((arg (car args)))
+ (cond ((is-type? 'flic-const arg)
+ (record-hack 'rational-to-double-constant-fold)
+ (if (is-type? 'list (flic-const-value arg))
+ (let ((value (flic-const-value arg)))
+ (setf (flic-const-value arg)
+ (prim.rational-to-double-aux (car value) (cadr value)))
+ (values arg (cdr args)))
+ (error "Bad argument ~s to primRationalToDouble." arg)))
+ (else
+ (values fn args)))))
+
+(define (optimize-neg fn args)
+ (let ((arg (car args)))
+ (cond ((is-type? 'flic-const arg)
+ (record-hack 'neg-constant-fold)
+ (if (is-type? 'number (flic-const-value arg))
+ (begin
+ (setf (flic-const-value arg) (- (flic-const-value arg)))
+ (values arg (cdr args)))
+ (error "Bad argument ~s to ~s." arg fn)))
+ (else
+ (values fn args)))))
+
+
+
+;;; Convert lambda applications to lets.
+;;; If application is not saturated, break it up into two nested
+;;; lambdas before doing the transformation.
+;;; It's better to do this optimization immediately than hoping
+;;; the call will become fully saturated on the next pass.
+;;; Maybe we could also look for a flic-let with a flic-lambda as
+;;; the body to catch the cases where additional arguments can
+;;; be found on a later pass.
+
+(define (do-lambda-to-let new-fn new-args)
+ (multiple-value-bind (fn args)
+ (do-lambda-to-let-aux new-fn new-args)
+ (maybe-make-app fn args)))
+
+(define (maybe-make-app fn args)
+ (if (null? args)
+ fn
+ (make-flic-app fn args '#f)))
+
+(define (do-lambda-to-let-aux new-fn new-args)
+ (let ((vars (flic-lambda-vars new-fn))
+ (body (flic-lambda-body new-fn))
+ (matched '()))
+ (record-hack 'app-lambda-to-let)
+ (do ()
+ ((or (null? new-args) (null? vars)))
+ (let ((var (pop vars))
+ (arg (pop new-args)))
+ (setf (var-value var) arg)
+ (setf (var-simple? var) (can-inline? arg '() '#t))
+ (if (eqv? (var-referenced var) 1)
+ (setf (var-single-ref var) (dynamic *lambda-depth*)))
+ (push var matched)))
+ (setf matched (nreverse matched))
+ (if (not (null? vars))
+ (setf body (make-flic-lambda vars body)))
+ (setf new-fn (make-flic-let matched body '#f))
+ (values new-fn new-args)))
+
+
+;;; For references, check to see if we can beta-reduce.
+;;; Don't increment reference count for inlineable vars, but do
+;;; traverse the new value expression.
+
+(define-optimize flic-ref (object)
+ (optimize-flic-ref-aux object))
+
+(define (optimize-flic-ref-aux object)
+ (let ((var (flic-ref-var object)))
+ (cond ((var-single-ref var)
+ ;; (or (eqv? (var-single-ref var) (dynamic *lambda-depth*)))
+ ;; *** The lambda-depth test is too conservative to handle
+ ;; *** inlining of stuff necessary for foldr/build optimizations.
+ ;; Can substitute value no matter how hairy it is.
+ ;; Note that this is potentially risky; if the single
+ ;; reference detected on the previous pass appeared as
+ ;; the value of a variable binding that is being inlined
+ ;; on the current pass, it might turn into multiple
+ ;; references again!
+ ;; We copy the value anyway to avoid problems with shared
+ ;; structure in the multiple reference case.
+ (record-hack 'ref-inline-single-ref var)
+ (optimize (copy-flic-top (var-value var))))
+ ((and (var-inline-value var) (dynamic *do-inline-optimizations*))
+ ;; Use the previously saved value in preference to the current
+ ;; value of the variable.
+ (record-hack 'ref-inline-foldr-hack)
+ (optimize (copy-flic-top (var-inline-value var))))
+ ((and (var-simple? var)
+ (or (dynamic *do-inline-optimizations*)
+ (not (var-toplevel? var))))
+ ;; Can substitute, but must copy.
+ (record-hack 'ref-inline var)
+ (optimize (copy-flic-top (var-value var))))
+ ((eq? var (core-symbol "foldr"))
+ ;; Magic stuff for deforestation
+ (if (> (the fixnum (dynamic *current-optimize-iteration*))
+ (the fixnum (dynamic *optimize-foldr-iteration*)))
+ (begin
+ (record-hack 'ref-inline-foldr)
+ (optimize (make-flic-ref (core-symbol "inlineFoldr"))))
+ object))
+ ((eq? var (core-symbol "build"))
+ ;; Magic stuff for deforestation
+ (if (> (the fixnum (dynamic *current-optimize-iteration*))
+ (the fixnum (dynamic *optimize-build-iteration*)))
+ (begin
+ (record-hack 'ref-inline-build)
+ (optimize (make-flic-ref (core-symbol "inlineBuild"))))
+ object))
+ ((var-toplevel? var)
+ object)
+ (else
+ (incf (var-referenced var))
+ object))))
+
+
+;;; Don't do anything exciting with constants.
+
+(define-optimize flic-const (object)
+ object)
+
+(define-optimize flic-pack (object)
+ object)
+
+
+
+;;; Various simplifications on and
+
+(define-optimize flic-and (object)
+ (maybe-simplify-and
+ object
+ (optimize-and-exps (flic-and-exps object) '())))
+
+(define (maybe-simplify-and object exps)
+ (cond ((null? exps)
+ (record-hack 'and-empty)
+ (make-flic-pack (core-symbol "True")))
+ ((null? (cdr exps))
+ (record-hack 'and-unary)
+ (car exps))
+ (else
+ (setf (flic-and-exps object) exps)
+ object)))
+
+(define (optimize-and-exps exps result)
+ (if (null? exps)
+ (nreverse result)
+ (let ((exp (optimize (car exps))))
+ (typecase exp
+ (flic-pack
+ (cond ((eq? (flic-pack-con exp) (core-symbol "True"))
+ ;; True appears in subexpressions.
+ ;; Discard this test only.
+ (record-hack 'and-contains-true)
+ (optimize-and-exps (cdr exps) result))
+ ((eq? (flic-pack-con exp) (core-symbol "False"))
+ ;; False appears in subexpressions.
+ ;; Discard remaining tests as dead code.
+ ;; Can't replace the whole and expression with false because
+ ;; of possible strictness side-effects.
+ (record-hack 'and-contains-false)
+ (nreverse (cons exp result)))
+ (else
+ ;; Should never happen.
+ (error "Non-boolean con ~s in and expression!" exp))))
+ (flic-and
+ ;; Flatten nested ands.
+ (record-hack 'and-compress)
+ (optimize-and-exps
+ (cdr exps)
+ (nconc (nreverse (flic-and-exps exp)) result)))
+ (else
+ ;; No optimization possible.
+ (optimize-and-exps (cdr exps) (cons exp result)))
+ ))))
+
+
+;;; Case-block optimizations. These optimizations are possible because
+;;; of the restricted way this construct is used; return-froms are
+;;; never nested, etc.
+
+(define-optimize flic-case-block (object)
+ (let* ((sym (flic-case-block-block-name object))
+ (exps (optimize-case-block-exps
+ sym (flic-case-block-exps object) '())))
+ (optimize-flic-case-block-aux object sym exps)))
+
+(define (optimize-flic-case-block-aux object sym exps)
+ (cond ((null? exps)
+ ;; This should never happen. It means all of the tests were
+ ;; optimized away, including the failure case!
+ (error "No exps left in case block ~s!" object))
+ ((and (is-type? 'flic-and (car exps))
+ (is-return-from-block?
+ sym
+ (car (last (flic-and-exps (car exps))))))
+ ;; The first clause is a simple and. Hoist it out of the
+ ;; case-block and rewrite as if/then/else.
+ (record-hack 'case-block-to-if)
+ (let ((then-exp (car (last (flic-and-exps (car exps))))))
+ (setf (flic-case-block-exps object) (cdr exps))
+ (make-flic-if
+ (maybe-simplify-and
+ (car exps)
+ (butlast (flic-and-exps (car exps))))
+ (flic-return-from-exp then-exp)
+ (optimize-flic-case-block-aux object sym (cdr exps)))))
+ ((is-return-from-block? sym (car exps))
+ ;; Do an identity reduction.
+ (record-hack 'case-block-identity)
+ (flic-return-from-exp (car exps)))
+ ((is-type? 'flic-let (car exps))
+ ;; The first clause is a let. Since this clause is going
+ ;; to be executed anyway, hoisting the bindings to surround
+ ;; the entire case-block should not change their strictness
+ ;; properties, and it may permit some further optimizations.
+ (record-hack 'case-block-hoist-let)
+ (let* ((exp (car exps))
+ (body (flic-let-body exp)))
+ (setf (flic-let-body exp)
+ (optimize-flic-case-block-aux
+ object sym (cons body (cdr exps))))
+ exp))
+ (else
+ (setf (flic-case-block-exps object) exps)
+ object)
+ ))
+
+
+(define (optimize-case-block-exps sym exps result)
+ (if (null? exps)
+ (nreverse result)
+ (let ((exp (optimize (car exps))))
+ (cond ((is-return-from-block? sym exp)
+ ;; Any remaining clauses are dead code and should be removed.
+ (if (not (null? (cdr exps)))
+ (record-hack 'case-block-dead-code))
+ (nreverse (cons exp result)))
+ ((is-type? 'flic-and exp)
+ ;; See if we can remove redundant tests.
+ (push (maybe-simplify-and
+ exp
+ (look-for-redundant-tests (flic-and-exps exp) result))
+ result)
+ (optimize-case-block-exps sym (cdr exps) result))
+ (else
+ ;; No optimization possible.
+ (optimize-case-block-exps sym (cdr exps) (cons exp result)))
+ ))))
+
+
+;;; Look for case-block tests that are known to be either true or false
+;;; because of tests made in previous clauses.
+;;; For now, we only look at is-constructor tests. Such a test is known
+;;; to be true if previous clauses have eliminated all other possible
+;;; constructors. And such a test is known to be false if a previous
+;;; clause has already matched this constructor.
+
+(define (look-for-redundant-tests exps previous-clauses)
+ (if (null? exps)
+ '()
+ (let ((exp (car exps)))
+ (cond ((and (is-type? 'flic-is-constructor exp)
+ (constructor-test-redundant? exp previous-clauses))
+ ;; Known to be true.
+ (record-hack 'case-block-discard-redundant-test)
+ (cons (make-flic-pack (core-symbol "True"))
+ (look-for-redundant-tests (cdr exps) previous-clauses)))
+
+ ((and (is-type? 'flic-is-constructor exp)
+ (constructor-test-duplicated? exp previous-clauses))
+ ;; Known to be false.
+ (record-hack 'case-block-discard-duplicate-test)
+ (list (make-flic-pack (core-symbol "False"))))
+ (else
+ ;; No optimization.
+ (cons exp
+ (look-for-redundant-tests (cdr exps) previous-clauses)))
+ ))))
+
+
+;;; In looking for redundant/duplicated tests, only worry about
+;;; is-constructor tests that have an argument that is a variable.
+;;; It's too hairy to consider any other cases.
+
+(define (constructor-test-duplicated? exp previous-clauses)
+ (let ((con (flic-is-constructor-con exp))
+ (arg (flic-is-constructor-exp exp)))
+ (and (is-type? 'flic-ref arg)
+ (constructor-test-present? con arg previous-clauses))))
+
+(define (constructor-test-redundant? exp previous-clauses)
+ (let ((con (flic-is-constructor-con exp))
+ (arg (flic-is-constructor-exp exp)))
+ (and (is-type? 'flic-ref arg)
+ (every-1 (lambda (c)
+ (or (eq? c con)
+ (constructor-test-present? c arg previous-clauses)))
+ (algdata-constrs (con-alg con))))))
+
+(define (constructor-test-present? con arg previous-clauses)
+ (cond ((null? previous-clauses)
+ '#f)
+ ((constructor-test-present-1? con arg (car previous-clauses))
+ '#t)
+ (else
+ (constructor-test-present? con arg (cdr previous-clauses)))))
+
+
+;;; The tricky thing here is that, even if the constructor test is
+;;; present in the clause, we have to make sure that the entire clause won't
+;;; fail due to the presence of some other test which fails. So look
+;;; for a very specific pattern here, namely
+;;; (and (is-constructor con arg) (return-from ....))
+
+(define (constructor-test-present-1? con arg clause)
+ (and (is-type? 'flic-and clause)
+ (let ((exps (flic-and-exps clause)))
+ (and (is-type? 'flic-is-constructor (car exps))
+ (is-type? 'flic-return-from (cadr exps))
+ (null? (cddr exps))
+ (let* ((inner-exp (car exps))
+ (inner-con (flic-is-constructor-con inner-exp))
+ (inner-arg (flic-is-constructor-exp inner-exp)))
+ (and (eq? inner-con con)
+ (flic-exp-eq? arg inner-arg)))))))
+
+
+
+;;; No fancy optimizations for return-from by itself.
+
+(define-optimize flic-return-from (object)
+ (setf (flic-return-from-exp object)
+ (optimize (flic-return-from-exp object)))
+ object)
+
+
+
+;;; Obvious simplification on if
+
+(define-optimize flic-if (object)
+ (let ((test-exp (optimize (flic-if-test-exp object)))
+ (then-exp (optimize (flic-if-then-exp object)))
+ (else-exp (optimize (flic-if-else-exp object))))
+ (cond ((and (is-type? 'flic-pack test-exp)
+ (eq? (flic-pack-con test-exp) (core-symbol "True")))
+ ;; Fold constant test
+ (record-hack 'if-fold)
+ then-exp)
+ ((and (is-type? 'flic-pack test-exp)
+ (eq? (flic-pack-con test-exp) (core-symbol "False")))
+ ;; Fold constant test
+ (record-hack 'if-fold)
+ else-exp)
+ ((and (is-type? 'flic-is-constructor test-exp)
+ (eq? (flic-is-constructor-con test-exp) (core-symbol "True")))
+ ;; Remove redundant is-constructor test.
+ ;; Doing this as a general is-constructor identity
+ ;; backfires because it prevents some of the important case-block
+ ;; optimizations from being recognized, but it works fine here.
+ (record-hack 'if-compress-test)
+ (setf (flic-if-test-exp object) (flic-is-constructor-exp test-exp))
+ (setf (flic-if-then-exp object) then-exp)
+ (setf (flic-if-else-exp object) else-exp)
+ object)
+ ((and (is-type? 'flic-is-constructor test-exp)
+ (eq? (flic-is-constructor-con test-exp) (core-symbol "False")))
+ ;; Remove redundant is-constructor test, flip branches.
+ (record-hack 'if-compress-test)
+ (setf (flic-if-test-exp object) (flic-is-constructor-exp test-exp))
+ (setf (flic-if-then-exp object) else-exp)
+ (setf (flic-if-else-exp object) then-exp)
+ object)
+ ((and (is-type? 'flic-return-from then-exp)
+ (is-type? 'flic-return-from else-exp)
+ (eq? (flic-return-from-block-name then-exp)
+ (flic-return-from-block-name else-exp)))
+ ;; Hoist return-from outside of IF.
+ ;; This may permit further case-block optimizations.
+ (record-hack 'if-hoist-return-from)
+ (let ((return-exp then-exp))
+ (setf (flic-if-test-exp object) test-exp)
+ (setf (flic-if-then-exp object) (flic-return-from-exp then-exp))
+ (setf (flic-if-else-exp object) (flic-return-from-exp else-exp))
+ (setf (flic-return-from-exp return-exp) object)
+ return-exp))
+ ((and (is-type? 'flic-pack then-exp)
+ (is-type? 'flic-pack else-exp)
+ (eq? (flic-pack-con then-exp) (core-symbol "True"))
+ (eq? (flic-pack-con else-exp) (core-symbol "False")))
+ ;; This if does nothing useful at all!
+ (record-hack 'if-identity)
+ test-exp)
+ ((and (is-type? 'flic-pack then-exp)
+ (is-type? 'flic-pack else-exp)
+ (eq? (flic-pack-con then-exp) (core-symbol "False"))
+ (eq? (flic-pack-con else-exp) (core-symbol "True")))
+ ;; Inverse of previous case
+ (record-hack 'if-identity-inverse)
+ (make-flic-is-constructor (core-symbol "False") test-exp))
+ ((or (is-type? 'flic-lambda then-exp)
+ (is-type? 'flic-lambda else-exp))
+ ;; Hoist lambdas to surround entire if. This allows us to
+ ;; do a better job of saturating them.
+ (record-hack 'if-hoist-lambda)
+ (multiple-value-bind (vars then-exp else-exp)
+ (do-if-hoist-lambda then-exp else-exp)
+ (setf (flic-if-test-exp object) test-exp)
+ (setf (flic-if-then-exp object) then-exp)
+ (setf (flic-if-else-exp object) else-exp)
+ (make-flic-lambda vars object)))
+ (else
+ ;; No optimization possible
+ (setf (flic-if-test-exp object) test-exp)
+ (setf (flic-if-then-exp object) then-exp)
+ (setf (flic-if-else-exp object) else-exp)
+ object)
+ )))
+
+
+
+;;; Try to pull as many variables as possible out to surround the entire
+;;; let.
+
+(define (do-if-hoist-lambda then-exp else-exp)
+ (let ((vars '())
+ (then-args '())
+ (else-args '()))
+ (do ((then-vars (if (is-type? 'flic-lambda then-exp)
+ (flic-lambda-vars then-exp)
+ '())
+ (cdr then-vars))
+ (else-vars (if (is-type? 'flic-lambda else-exp)
+ (flic-lambda-vars else-exp)
+ '())
+ (cdr else-vars)))
+ ((and (null? then-vars) (null? else-vars)) '#f)
+ (let ((var (init-flic-var (create-temp-var 'arg) '#f '#f)))
+ (push var vars)
+ (push (make-flic-ref var) then-args)
+ (push (make-flic-ref var) else-args)))
+ (values
+ vars
+ (if (is-type? 'flic-lambda then-exp)
+ (do-lambda-to-let then-exp then-args)
+ (make-flic-app then-exp then-args '#f))
+ (if (is-type? 'flic-lambda else-exp)
+ (do-lambda-to-let else-exp else-args)
+ (make-flic-app else-exp else-args '#f)))))
+
+
+
+;;; Look for (sel (pack x)) => x
+
+(define-optimize flic-sel (object)
+ (optimize-flic-sel-aux object))
+
+(define (optimize-flic-sel-aux object)
+ (let ((new-exp (optimize (flic-sel-exp object))))
+ (setf (flic-sel-exp object) new-exp)
+ (typecase new-exp
+ (flic-ref
+ ;; Check to see whether this is bound to a pack application
+ (let ((val (is-bound-to-constructor-app? (flic-ref-var new-exp))))
+ (if val
+ ;; Yup, it is. Now extract the appropriate component,
+ ;; provided it is inlineable.
+ (let* ((i (flic-sel-i object))
+ (args (flic-app-args val))
+ (newval (list-ref args i)))
+ (if (can-inline? newval '() '#t)
+ (begin
+ (record-hack 'sel-fold-var)
+ (optimize (copy-flic-top newval)))
+ object))
+ ;; The variable was bound to something else.
+ object)))
+ (flic-app
+ ;; The obvious optimization.
+ (if (is-constructor-app-prim? new-exp)
+ (begin
+ (record-hack 'sel-fold-app)
+ (list-ref (flic-app-args new-exp) (flic-sel-i object)))
+ object))
+ (else
+ object))))
+
+
+
+
+;;; Do similar stuff for is-constructor.
+
+(define-optimize flic-is-constructor (object)
+ (let ((con (flic-is-constructor-con object))
+ (exp (optimize (flic-is-constructor-exp object)))
+ (exp-con '#f))
+ (cond ((algdata-tuple? (con-alg con))
+ ;; Tuples have only one constructor, so this is always true
+ (record-hack 'is-constructor-fold-tuple)
+ (make-flic-pack (core-symbol "True")))
+ ((setf exp-con (is-constructor-app? exp))
+ ;; The expression is a constructor application.
+ (record-hack 'is-constructor-fold)
+ (make-flic-pack
+ (if (eq? exp-con con)
+ (core-symbol "True")
+ (core-symbol "False"))))
+ (else
+ ;; No optimization possible
+ (setf (flic-is-constructor-exp object) exp)
+ object)
+ )))
+
+
+(define-optimize flic-con-number (object)
+ (let ((exp (flic-con-number-exp object))
+ (type (flic-con-number-type object)))
+ ;; ***Maybe ast-to-flic should look for this one.
+ (if (algdata-tuple? type)
+ (begin
+ (record-hack 'con-number-fold-tuple)
+ (make-flic-const 0))
+ (let* ((new-exp (optimize exp))
+ (con (is-constructor-app? new-exp)))
+ (if con
+ (begin
+ (record-hack 'con-number-fold)
+ (make-flic-const (con-tag con)))
+ (begin
+ (setf (flic-con-number-exp object) new-exp)
+ object)))
+ )))
+
+(define-optimize flic-void (object)
+ object)
+
+
+;;;===================================================================
+;;; General helper functions
+;;;===================================================================
+
+
+;;; Lucid's built-in every function seems to do a lot of unnecessary
+;;; consing. This one is much faster.
+
+(define (every-1 fn list)
+ (cond ((null? list)
+ '#t)
+ ((funcall fn (car list))
+ (every-1 fn (cdr list)))
+ (else
+ '#f)))
+
+
+
+;;; Equality predicate on flic expressions
+
+(define (flic-exp-eq? a1 a2)
+ (typecase a1
+ (flic-const
+ (and (is-type? 'flic-const a2)
+ (equal? (flic-const-value a1) (flic-const-value a2))))
+ (flic-ref
+ (and (is-type? 'flic-ref a2)
+ (eq? (flic-ref-var a1) (flic-ref-var a2))))
+ (flic-pack
+ (and (is-type? 'flic-pack a2)
+ (eq? (flic-pack-con a1) (flic-pack-con a2))))
+ (flic-sel
+ (and (is-type? 'flic-sel a2)
+ (eq? (flic-sel-con a1) (flic-sel-con a2))
+ (eqv? (flic-sel-i a1) (flic-sel-i a2))
+ (flic-exp-eq? (flic-sel-exp a1) (flic-sel-exp a2))))
+ (else
+ '#f)))
+
+
+
+;;; Predicates for testing whether an expression matches a pattern.
+
+(define (is-constructor-app? exp)
+ (typecase exp
+ (flic-app
+ ;; See if we have a saturated call to a constructor.
+ (is-constructor-app-prim? exp))
+ (flic-ref
+ ;; See if we can determine anything about the value the variable
+ ;; is bound to.
+ (let ((value (var-value (flic-ref-var exp))))
+ (if value
+ (is-constructor-app? value)
+ '#f)))
+ (flic-let
+ ;; See if we can determine anything about the body of the let.
+ (is-constructor-app? (flic-let-body exp)))
+ (flic-pack
+ ;; See if this is a nullary constructor.
+ (let ((con (flic-pack-con exp)))
+ (if (eqv? (con-arity con) 0)
+ con
+ '#f)))
+ (else
+ '#f)))
+
+(define (is-return-from-block? sym exp)
+ (and (is-type? 'flic-return-from exp)
+ (eq? (flic-return-from-block-name exp) sym)))
+
+(define (is-constructor-app-prim? exp)
+ (let ((fn (flic-app-fn exp))
+ (args (flic-app-args exp)))
+ (if (and (is-type? 'flic-pack fn)
+ (eqv? (length args) (con-arity (flic-pack-con fn))))
+ (flic-pack-con fn)
+ '#f)))
+
+(define (is-bound-to-constructor-app? var)
+ (let ((val (var-value var)))
+ (if (and val
+ (is-type? 'flic-app val)
+ (is-constructor-app-prim? val))
+ val
+ '#f)))
+
+(define (is-selector? con i exp)
+ (or (and (is-type? 'flic-ref exp)
+ (is-selector? con i (var-value (flic-ref-var exp))))
+ (and (is-type? 'flic-sel exp)
+ (eq? (flic-sel-con exp) con)
+ (eqv? (the fixnum i) (the fixnum (flic-sel-i exp)))
+ (flic-sel-exp exp))
+ ))
+
+(define (is-selector-list? con i subexp exps)
+ (declare (type fixnum i))
+ (if (null? exps)
+ subexp
+ (let ((temp (is-selector? con i (car exps))))
+ (and (flic-exp-eq? subexp temp)
+ (is-selector-list? con (+ 1 i) subexp (cdr exps))))))
+
+
+
+;;;===================================================================
+;;; Inlining criteria
+;;;===================================================================
+
+;;; Expressions that can be inlined unconditionally are constants, variable
+;;; references, and some functions.
+;;; I've made some attempt here to arrange the cases in the order they
+;;; are likely to occur.
+
+(define (can-inline? exp recursive-vars toplevel?)
+ (typecase exp
+ (flic-sel
+ ;; Listed first because it happens more frequently than
+ ;; anything else.
+ ;; *** Inlining these is an experiment.
+ ;; *** This transformation interacts with the strictness
+ ;; *** analyzer; if the variable referenced is not strict, then
+ ;; *** it is probably not a good thing to do since it adds extra
+ ;; *** forces.
+ ;; (let ((subexp (flic-sel-exp exp)))
+ ;; (and (is-type? 'flic-ref subexp)
+ ;; (not (memq (flic-ref-var subexp) recursive-vars))))
+ '#f)
+ (flic-lambda
+ ;; Do not try to inline lambdas if the fancy inline optimization
+ ;; is disabled.
+ ;; Watch for problems with infinite loops with recursive variables.
+ (if (dynamic *do-inline-optimizations*)
+ (simple-function-body? (flic-lambda-body exp)
+ (flic-lambda-vars exp)
+ recursive-vars
+ toplevel?)
+ '#f))
+ (flic-ref
+ ;; We get into infinite loops trying to inline recursive variables.
+ (not (memq (flic-ref-var exp) recursive-vars)))
+ ((or flic-pack flic-const)
+ '#t)
+ (else
+ '#f)))
+
+
+;;; Determining whether to inline a function is difficult. This is
+;;; very conservative to avoid code bloat. What we need to do is
+;;; compare the cost (in program size mainly) of the inline call with
+;;; an out of line call. For an out of line call, we pay for one function
+;;; call and a setup for each arg. When inlining, we pay for function
+;;; calls in the body and for args referenced more than once. In terms of
+;;; execution time, we win big when a functional parameter is called
+;;; since this `firstifies' the program.
+
+;;; Here's the criteria:
+;;; An inline function gets to reference no more that 2 non-parameter
+;;; values (including constants and repeated parameter references).
+;;; For non-toplevel functions, be slightly more generous since the
+;;; fixed overhead of binding the local function would go away.
+
+(define (simple-function-body? exp lambda-vars recursive-vars toplevel?)
+ (let ((c (if toplevel? 2 4)))
+ (>= (the fixnum (simple-function-body-1 exp lambda-vars recursive-vars c))
+ 0)))
+
+
+;;; I've made some attempt here to order the cases by how frequently
+;;; they appear.
+
+(define (simple-function-body-1 exp lambda-vars recursive-vars c)
+ (declare (type fixnum c))
+ (if (< c 0)
+ (values c '())
+ (typecase exp
+ (flic-ref
+ (let ((var (flic-ref-var exp)))
+ (cond ((memq var lambda-vars)
+ (values c (list-remove-1 var lambda-vars)))
+ ((memq var recursive-vars)
+ (values -1 '()))
+ (else
+ (values (the fixnum (1- c)) lambda-vars)))))
+ (flic-app
+ (simple-function-body-1/l
+ (cons (flic-app-fn exp) (flic-app-args exp))
+ lambda-vars recursive-vars c))
+ (flic-sel
+ (simple-function-body-1
+ (flic-sel-exp exp)
+ lambda-vars recursive-vars (the fixnum (1- c))))
+ (flic-is-constructor
+ (simple-function-body-1
+ (flic-is-constructor-exp exp)
+ lambda-vars recursive-vars (the fixnum (1- c))))
+ ((or flic-const flic-pack)
+ (values (the fixnum (1- c)) lambda-vars))
+ (else
+ ;; case & let & lambda not allowed.
+ (values -1 '())))))
+
+(define (list-remove-1 item list)
+ (cond ((null? list)
+ '())
+ ((eq? item (car list))
+ (cdr list))
+ (else
+ (cons (car list) (list-remove-1 item (cdr list))))
+ ))
+
+(define (simple-function-body-1/l exps lambda-vars recursive-vars c)
+ (declare (type fixnum c))
+ (if (or (null? exps) (< c 0))
+ (values c lambda-vars)
+ (multiple-value-bind (c-1 lambda-vars-1)
+ (simple-function-body-1 (car exps) lambda-vars recursive-vars c)
+ (simple-function-body-1/l
+ (cdr exps) lambda-vars-1 recursive-vars c-1))))
+
+
+
+;;;===================================================================
+;;; Constant structured data detection
+;;;===================================================================
+
+
+;;; Look to determine whether an object is a structured constant,
+;;; recursively examining its components if it's an app. This is
+;;; necessary in order to detect constants with arbitrary circular
+;;; reference to the vars in recursive-vars.
+
+(define (structured-constant-recursive? object recursive-vars stack)
+ (typecase object
+ (flic-const
+ '#t)
+ (flic-ref
+ (let ((var (flic-ref-var object)))
+ (or (memq var stack)
+ (var-toplevel? var)
+ (and (memq var recursive-vars)
+ (structured-constant-recursive?
+ (var-value var) recursive-vars (cons var stack))))))
+ (flic-pack
+ '#t)
+ (flic-app
+ (structured-constant-app-recursive?
+ (flic-app-fn object)
+ (flic-app-args object)
+ recursive-vars
+ stack))
+ (flic-lambda
+ (lambda-hoistable? object))
+ (else
+ '#f)))
+
+(define (structured-constant-app-recursive? fn args recursive-vars stack)
+ (and (is-type? 'flic-pack fn)
+ (eqv? (length args) (con-arity (flic-pack-con fn)))
+ (every-1 (lambda (a)
+ (structured-constant-recursive? a recursive-vars stack))
+ args)))
+
+
+;;; Here's a non-recursive (and more efficient) version of the above.
+;;; Instead of looking at the whole structure, it only looks one level
+;;; deep. This can't detect circular constants, but is useful in
+;;; contexts where circularities cannot appear.
+
+(define (structured-constant? object)
+ (typecase object
+ (flic-ref
+ (var-toplevel? (flic-ref-var object)))
+ (flic-const
+ '#t)
+ (flic-pack
+ '#t)
+ (flic-lambda
+ (lambda-hoistable? object))
+ (else
+ '#f)))
+
+(define (structured-constant-app? fn args)
+ (and (is-type? 'flic-pack fn)
+ (eqv? (length args) (con-arity (flic-pack-con fn)))
+ (every-1 (function structured-constant?) args)))
+
+
+;;; Determine whether a lambda can be hoisted to top-level.
+;;; The main purpose of this code is to mark structured constants
+;;; containing simple lambdas to permit later folding of sel expressions
+;;; on those constants. Since the latter expression is permissible
+;;; only on inlinable functions, stop if we hit an expression that
+;;; would make the function not inlinable.
+
+(define (lambda-hoistable? object)
+ (and (can-inline? object '() '#t)
+ (lambda-hoistable-aux
+ (flic-lambda-body object)
+ (flic-lambda-vars object))))
+
+(define (lambda-hoistable-aux object local-vars)
+ (typecase object
+ (flic-ref
+ (or (var-toplevel? (flic-ref-var object))
+ (memq (flic-ref-var object) local-vars)))
+ ((or flic-const flic-pack)
+ '#t)
+ (flic-sel
+ (lambda-hoistable-aux (flic-sel-exp object) local-vars))
+ (flic-is-constructor
+ (lambda-hoistable-aux (flic-is-constructor-exp object) local-vars))
+ (flic-app
+ (and (lambda-hoistable-aux (flic-app-fn object) local-vars)
+ (every-1 (lambda (x) (lambda-hoistable-aux x local-vars))
+ (flic-app-args object))))
+ (else
+ '#f)))
+
+
+;;; Having determined that something is a structured constant,
+;;; enter it (and possibly its subcomponents) in the hash table
+;;; and return a var-ref.
+
+(define (enter-structured-constant value recursive?)
+ (multiple-value-bind (con args var)
+ (enter-structured-constant-aux value recursive?)
+ (when (not var)
+ (setf var (create-temp-var 'constant))
+ (add-new-structured-constant var con args))
+ (make-flic-ref var)))
+
+(define (enter-structured-constant-aux value recursive?)
+ (let* ((fn (flic-app-fn value))
+ (con (flic-pack-con fn))
+ (args (if recursive?
+ (map (function enter-structured-constant-arg)
+ (flic-app-args value))
+ (flic-app-args value))))
+ (values con args (lookup-structured-constant con args))))
+
+(define (enter-structured-constant-arg a)
+ (if (is-type? 'flic-app a)
+ (enter-structured-constant a '#t)
+ a))
+
+(define (lookup-structured-constant con args)
+ (lookup-structured-constant-aux
+ (table-entry *structured-constants-table* con) args))
+
+(define (lookup-structured-constant-aux alist args)
+ (cond ((null? alist)
+ '#f)
+ ((every (function flic-exp-eq?) (car (car alist)) args)
+ (cdr (car alist)))
+ (else
+ (lookup-structured-constant-aux (cdr alist) args))))
+
+(define (add-new-structured-constant var con args)
+ (push (cons args var) (table-entry *structured-constants-table* con))
+ (setf (var-toplevel? var) '#t)
+ (setf (var-value var) (make-flic-app (make-flic-pack con) args '#t))
+ (push var *structured-constants*)
+ var)
+
+
+
+;;;===================================================================
+;;; Invariant argument stuff
+;;;===================================================================
+
+
+;;; When processing a saturated call to a locally defined function,
+;;; note whether any of the arguments are always passed the same value.
+
+(define (note-invariant-args args vars)
+ (when (and (not (null? args)) (not (null? vars)))
+ (let* ((arg (car args))
+ (var (car vars))
+ (val (var-arg-invariant-value var)))
+ (cond ((not (var-arg-invariant? var))
+ ;; This argument already marked as having more than one
+ ;; value.
+ )
+ ((and (is-type? 'flic-ref arg)
+ (eq? (flic-ref-var arg) var))
+ ;; This is a recursive call with the same argument.
+ ;; Don't update the arg-invariant-value slot.
+ )
+ ((or (not val)
+ (flic-exp-eq? arg val))
+ ;; Either this is the first call, or a second call with
+ ;; the same argument.
+ (setf (var-arg-invariant-value var) arg))
+ (else
+ ;; Different values for this argument are passed in
+ ;; different places, so we can't mess with it.
+ (setf (var-arg-invariant? var) '#f)))
+ (note-invariant-args (cdr args) (cdr vars)))))
+
+
+;;; After processing a let form, check to see if any of the bindings
+;;; are for local functions with invariant arguments.
+;;; Suppose we have something like
+;;; let foo = \ x y z -> <fn-body>
+;;; in <let-body>
+;;; and y is known to be invariant; then we rewrite this as
+;;; let foo1 = \ x z -> let y = <invariant-value> in <fn-body>
+;;; foo = \ x1 y1 z1 -> foo1 x1 z1
+;;; in <let-body>
+;;; The original foo binding is inlined on subsequent passes and
+;;; should go away. Likewise, the binding of y should be inlined also.
+;;; *** This is kind of bogus because of the way it depends on the
+;;; *** magic force-inline bit. It would be better to do a code walk
+;;; *** now on the entire let expression to rewrite all the calls to foo.
+
+(define (add-stuff-for-invariants bindings)
+ (if (null? bindings)
+ '()
+ (let* ((var (car bindings))
+ (val (var-value var)))
+ (setf (cdr bindings)
+ (add-stuff-for-invariants (cdr bindings)))
+ (if (and (is-type? 'flic-lambda val)
+ ;; Don't mess with single-reference variable bindings,
+ ;; or things we are going to inline anyway.
+ (not (var-single-ref var))
+ (not (var-simple? var))
+ ;; All references must be in saturated calls to do this.
+ (eqv? (var-referenced var) (var-fn-referenced var))
+ ;; There is at least one argument marked invariant.
+ (some (function var-arg-invariant?) (flic-lambda-vars val))
+ ;; Every argument marked invariant must also be hoistable.
+ (every-1 (function arg-hoistable?) (flic-lambda-vars val)))
+ (hoist-invariant-args
+ var
+ val
+ bindings)
+ bindings))))
+
+(define (arg-hoistable? var)
+ (if (var-arg-invariant? var)
+ (or (not (var-arg-invariant-value var))
+ (flic-invariant? (var-arg-invariant-value var)
+ (dynamic *local-bindings*)))
+ '#t))
+
+(define (hoist-invariant-args var val bindings)
+ (let ((foo1-var (copy-temp-var (def-name var)))
+ (foo1-def-vars '())
+ (foo1-app-args '())
+ (foo1-let-vars '())
+ (foo-def-vars '()))
+ (push foo1-var bindings)
+ (dolist (v (flic-lambda-vars val))
+ (let ((new-v (copy-temp-var (def-name v))))
+ (push (init-flic-var new-v '#f '#f) foo-def-vars)
+ (if (var-arg-invariant? v)
+ (when (var-arg-invariant-value v)
+ (push (init-flic-var
+ v (copy-flic-top (var-arg-invariant-value v)) '#f)
+ foo1-let-vars))
+ (begin
+ (push v foo1-def-vars)
+ (push (make-flic-ref new-v) foo1-app-args))
+ )))
+ (setf foo1-def-vars (nreverse foo1-def-vars))
+ (setf foo1-app-args (nreverse foo1-app-args))
+ (setf foo1-let-vars (nreverse foo1-let-vars))
+ (setf foo-def-vars (nreverse foo-def-vars))
+ (record-hack 'let-hoist-invariant-args var foo1-let-vars)
+ ;; Fix up the value of foo1
+ (init-flic-var
+ foo1-var
+ (let ((body (make-flic-let foo1-let-vars (flic-lambda-body val) '#f)))
+ (if (null? foo1-def-vars)
+ ;; *All* of the arguments were invariant.
+ body
+ ;; Otherwise, make a new lambda
+ (make-flic-lambda foo1-def-vars body)))
+ '#f)
+ ;; Fix up the value of foo and arrange for it to be inlined.
+ (setf (flic-lambda-vars val) foo-def-vars)
+ (setf (flic-lambda-body val)
+ (if (null? foo1-app-args)
+ (make-flic-ref foo1-var)
+ (make-flic-app (make-flic-ref foo1-var) foo1-app-args '#t)))
+ (setf (var-simple? var) '#t)
+ (setf (var-force-inline? var) '#t)
+ ;; Return modified list of bindings
+ bindings))
+
+
+
+;;;===================================================================
+;;; Install globals
+;;;===================================================================
+
+
+;;; The optimizer, CFN, etc. can introduce new top-level variables that
+;;; are not installed in the symbol table. This causes problems if
+;;; those variables are referenced in the .hci file (as in the inline
+;;; expansion of some other variables). So we need to fix up the
+;;; symbol table before continuing.
+
+(define (install-uninterned-globals vars)
+ (dolist (v vars)
+ (let* ((module (locate-module (def-module v)))
+ (name (def-name v))
+ (table (module-symbol-table module))
+ (def (table-entry table name)))
+ (cond ((not def)
+ ;; This def was not installed. Rename it if it's a gensym
+ ;; and install it.
+ (when (gensym? name)
+ (setf name (rename-gensym-var v name table)))
+ (setf (table-entry table name) v))
+ ((eq? def v)
+ ;; Already installed.
+ '#t)
+ (else
+ ;; Ooops! The symbol installed in the symbol table isn't
+ ;; this one!
+ (error "Duplicate defs ~s and ~s in symbol table for ~s!"
+ v def module))
+ ))))
+
+
+(define (rename-gensym-var var name table)
+ (setf name (string->symbol (symbol->string name)))
+ (if (table-entry table name)
+ ;; This name already in use; gensym a new one!
+ (rename-gensym-var var (gensym (symbol->string name)) table)
+ ;; OK, no problem
+ (setf (def-name var) name)))
+
+
+
+;;;===================================================================
+;;; Postoptimizer
+;;;===================================================================
+
+;;; This is another quick traversal of the structure to determine
+;;; whether references to functions are fully saturated or not.
+;;; Also makes sure that reference counts on variables are correct;
+;;; this is needed so the code generator can generate ignore declarations
+;;; for unused lambda variables.
+
+(define-flic-walker postoptimize (object))
+
+(define-postoptimize flic-lambda (object)
+ (dolist (var (flic-lambda-vars object))
+ (setf (var-referenced var) 0))
+ (postoptimize (flic-lambda-body object)))
+
+(define-postoptimize flic-let (object)
+ (dolist (var (flic-let-bindings object))
+ (setf (var-referenced var) 0)
+ (let ((val (var-value var)))
+ (setf (var-arity var)
+ (if (is-type? 'flic-lambda val)
+ (length (flic-lambda-vars val))
+ 0))))
+ (dolist (var (flic-let-bindings object))
+ (postoptimize (var-value var)))
+ (postoptimize (flic-let-body object)))
+
+(define-postoptimize flic-app (object)
+ (let ((fn (flic-app-fn object)))
+ (typecase fn
+ (flic-ref
+ (let* ((var (flic-ref-var fn))
+ (arity (var-arity var)))
+ (if (not (var-toplevel? var)) (incf (var-referenced var)))
+ (when (not (eqv? arity 0))
+ (postoptimize-app-aux object var arity (flic-app-args object)))))
+ (flic-pack
+ (let* ((con (flic-pack-con fn))
+ (arity (con-arity con)))
+ (postoptimize-app-aux object '#f arity (flic-app-args object))))
+ (else
+ (postoptimize fn)))
+ (dolist (a (flic-app-args object))
+ (postoptimize a))))
+
+(define (postoptimize-app-aux object var arity args)
+ (declare (type fixnum arity))
+ (let ((nargs (length args)))
+ (declare (type fixnum nargs))
+ (cond ((< nargs arity)
+ ;; not enough arguments
+ (when var (setf (var-standard-refs? var) '#t)))
+ ((eqv? nargs arity)
+ ;; exactly the right number of arguments
+ (when var (setf (var-optimized-refs? var) '#t))
+ (setf (flic-app-saturated? object) '#t))
+ (else
+ ;; make the fn a nested flic-app
+ (multiple-value-bind (arghead argtail)
+ (split-list args arity)
+ (setf (flic-app-fn object)
+ (make-flic-app (flic-app-fn object) arghead '#t))
+ (setf (flic-app-args object) argtail)
+ (when var (setf (var-optimized-refs? var) '#t))
+ (dolist (a arghead)
+ (postoptimize a))))
+ )))
+
+(define-postoptimize flic-ref (object)
+ (let ((var (flic-ref-var object)))
+ (if (not (var-toplevel? var)) (incf (var-referenced var)))
+ (setf (var-standard-refs? var) '#t)))
+
+(define-postoptimize flic-const (object)
+ object)
+
+(define-postoptimize flic-pack (object)
+ object)
+
+(define-postoptimize flic-and (object)
+ (for-each (function postoptimize) (flic-and-exps object)))
+
+(define-postoptimize flic-case-block (object)
+ (for-each (function postoptimize) (flic-case-block-exps object)))
+
+(define-postoptimize flic-if (object)
+ (postoptimize (flic-if-test-exp object))
+ (postoptimize (flic-if-then-exp object))
+ (postoptimize (flic-if-else-exp object)))
+
+(define-postoptimize flic-return-from (object)
+ (postoptimize (flic-return-from-exp object)))
+
+(define-postoptimize flic-sel (object)
+ (postoptimize (flic-sel-exp object)))
+
+(define-postoptimize flic-is-constructor (object)
+ (postoptimize (flic-is-constructor-exp object)))
+
+(define-postoptimize flic-con-number (object)
+ (postoptimize (flic-con-number-exp object)))
+
+(define-postoptimize flic-void (object)
+ object)