git.fiddlerwoaroof.com
backend/optimize.scm
4e987026
 ;;; 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)