git.fiddlerwoaroof.com
Raw Blame History
;;; 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)