
;; Implements a source-to-source optimizer

;; The src-to-src transformation currently drops
;;  properties, which is bad. The 'mzc-cffi,
;;  'method-arity-error, and 'inferred-name properties are
;;  specially preserved for `lambda' expressions.

(module src2src scheme/base
  (require mzlib/class
	   syntax/kerncase
	   syntax/primitives
	   mzlib/etc
	   mzlib/list
           (for-syntax scheme/base))

  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Optimizer
  ;; classes representing syntax with methods for optimization steps
  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  ;; Maximum number of times to inline while processing a call site
  (define max-fuel 0)
  (define fuel (make-parameter max-fuel))

  (define foldable-prims '(void
			   + - * / arithmetic-shift
			   < <= = > >=
			   number? positive? negative? zero?
			   real? complex?
			   string-ref))

  (define effectless-prims '(list list* cons vector))

  ;; The following primitives either invoke functions, or
  ;;  install functions that can be used later.
  (define (non-valueable-prims) (procedure-calling-prims))

  (define recert-insp (current-code-inspector))

  (define (keep-mzc-property stx-out stx)
    (let ([v (syntax-property stx 'mzc-cffi)]
	  [v2 (syntax-property stx 'method-arity-error)]
	  [v3 (syntax-property stx 'inferred-name)])
      (let ([stx-out2 (if v
			  (syntax-property stx-out 'mzc-cffi v)
			  stx-out)])
	(let ([stx-out3 (if v2
			    (syntax-property stx-out2 'method-arity-error v2)
			    stx-out2)])
	  (if v3
	      (syntax-property stx-out3 'inferred-name v3)
	      stx-out3)))))
  
  (define-struct context (need indef))
  ;; need = #f => don't need  the value
  ;; need = 'bool => need bool only
  ;; need = 'all => need exact result

  ;; indef = list of binding%s

  (define (need-all ctx)
    (if (eq? 'all (context-need ctx))
	ctx
	(make-context 'all (context-indef ctx))))
  (define (need-none ctx)
    (if (eq? 'none (context-need ctx))
	ctx
	(make-context 'none (context-indef ctx))))
  (define (need-bool ctx)
    (make-context 'bool (context-indef ctx)))

  (define-struct accessor (make-struct-type-expr position))
  (define-struct mutator (make-struct-type-expr position))
  (define-struct ctor (make-struct-type-expr))
  
  (define exp%
    (class object%
      
      (init-field src-stx)
      (when (not (syntax? src-stx))
        (printf "~a~n" src-stx)
        (error 'stx))
      (init-field [cert-stxes (list src-stx)])
      (field (known-value #f))
      
      ;; resets known-value computation, use counts, etc.
      (define/public (reset-varflags)
        (set! known-value #f)
        (for-each (lambda (e) (send e reset-varflags)) (sub-exprs)))
      
      ;; accumulates known-value mappings, use counts on bindings, etc.;
      ;; assumes varflags are reset
      (define/public (set-known-values)
        (for-each (lambda (e) (send e set-known-values)) (nonbind-sub-exprs)))

      ;; sets `mutable?' flags; set-known-values does that, too,
      ;; but this one only sets mutable flags
      (define/public (set-mutability)
        (for-each (lambda (e) (send e set-mutability)) (nonbind-sub-exprs)))

      ;; for each reference of a binding in the exp, drop one use
      (define/public (drop-uses)
        (for-each (lambda (e) (send e drop-uses)) (nonbind-sub-exprs)))

      ;; any side-effects might be in this expression?
      ;; (return #t if unsure)
      (define/public (no-side-effect?)
        (andmap (lambda (e) (send e no-side-effect?))
                (nonbind-sub-exprs)))

      ;; arity is a number or 'unknown
      (define/public (get-result-arity) 'unknown)

      ;; gets all subexpressions, including binding%s for lambda, etc.
      (define/public (sub-exprs) (append (bind-sub-exprs) (nonbind-sub-exprs)))
      ;; just the binding%s
      (define/public (bind-sub-exprs) null)
      ;; all subexpressions that aren't binding%s
      (define/public (nonbind-sub-exprs) null)

      ;; some default implementations map over nonbind-sub-exprs, 
      ;; the install the results with this method
      (define/public (set-nonbind-sub-exprs x) (void))

      ;; valueable means that evaluating the expression can't access
      ;;  a variable before it is initialized or mutate a
      ;;  variable. It's used, for example, on the RHSs of a letrec
      ;;  to determine known bindings.
      (define/public (valueable?)
        (andmap (lambda (x) (send x valueable?)) (nonbind-sub-exprs)))
      
      ;; ok to duplicate or move the expression?
      ;; (return #f if unsure)
      (define/public (can-dup/move?) #f)
      
      ;; known value is an exp%; usually only binding% objects
      ;; get known-value settings
      (define/public (set-known-value x) (set! known-value x))

      ;; finds the most-specific exp% whose value is the
      ;; same this this expression's value
      (define/public (get-value) (or known-value this))

      ;; helper:
      (define/private (subexp-map! f)
        (set-nonbind-sub-exprs (map f (nonbind-sub-exprs)))
        this)

      ;; main optimization method:
      (define/public (simplify ctx)
        (subexp-map! (lambda (x) 
                       (send x simplify (need-all ctx)))))
	
      (define/public (escape)
        (subexp-map! (lambda (x) (send x escape))))

      (define/public (stack-allocate)
        (subexp-map! (lambda (x) (send x stack-allocate))))
      
      ;; not an optimizations, but exposes info (epsecially to mzc)
      (define/public (reorganize)
        (subexp-map! (lambda (x) (send x reorganize))))
      ;; reverses reorganize
      (define/public (deorganize)
        (subexp-map! (lambda (x)
                       (send x deorganize))))

      ;; substitution of lexical refs for global variables
      (define/public (global->local env)
        (subexp-map! (lambda (x)
                       (send x global->local env))))
      
      ;; substitution of lexical refs for either lex or global vars
      (define/public (substitute env)
        (subexp-map! (lambda (x)
                       (send x substitute env))))

      ;; creates a copy, used for inling; don't try to preserve
      ;;  analysis, because we'll just re-compute it
      (define/public (clone env)
        (error 'clone "unimplemented: ~a" this))

      ;; gets stx object, usually for src info
      (define/public (get-stx) src-stx)

      ;; convert back to a syntax object
      (define/public (sexpr) 
	(recertify src-stx))

      ;; adds certifications back to result of sexp, when needed
      (define/public (recertify stx)
	(let loop ([cert-stxes cert-stxes][stx stx])
	  (cond
	   [(null? cert-stxes) stx]
	   [else (loop (cdr cert-stxes)
		       (syntax-recertify stx 
					 (car cert-stxes)
					 recert-insp 
					 #f))])))

      ;; returns cert stxes
      (define/public (get-cert-stxes)
	cert-stxes)
      
      ;; merges cert info from another expression
      (define/public (merge-certs exp)
	(set! cert-stxes 
	      (append (filter (lambda (i) (not (memq i cert-stxes)))
			      (send exp get-cert-stxes))
		      cert-stxes)))
      
      ;; list of body exprs (avoids redundant `begin', just for
      ;; readability)
      (define/public (body-sexpr) (list (sexpr)))

      (super-instantiate ())))

  (define (get-sexpr o) (send o sexpr))
  (define (get-body-sexpr o) (send o body-sexpr))

  (define-struct bucket (mutated? inited-before-use?) #:mutable)

  (define (global-bucket table stx)
    (let ([l (hash-ref table (syntax-e stx) (lambda () null))])
      (let ([s (ormap (lambda (b)
			(and (free-identifier=? stx (car b))
			     (cdr b)))
		      l)])
	(if s
	    s
	    (let ([s (make-bucket #f #f)])
	      (hash-set! table (syntax-e stx) (cons (cons stx s) l))
	      s)))))

  (define-struct tables (global-ht et-global-ht))

  (define global%
    (class exp% 
      (init-field trans? tables needs-top?)
      (super-instantiate ())
      (inherit-field src-stx) ;; The identifier
      (inherit-field cert-stxes) ;; The identifier
      
      (define mbind #f)
      (define bucket (global-bucket ((if trans? tables-et-global-ht tables-global-ht) tables) src-stx))
      (define/private (get-mbind!)
        (unless mbind
          (set! mbind ((if trans?
                           identifier-transformer-binding 
                           identifier-binding)
                       src-stx))))
      (define/public (orig-name)
        (get-mbind!)
        (if (pair? mbind)
            (cadr mbind)
            (syntax-e src-stx)))

      (define/public (is-kernel?)
        (get-mbind!)
        (and (pair? mbind)
             (eq? (car mbind) '#%kernel)))

      (define/public (is-trans?) trans?)

      (define/public (is-mutated?) (bucket-mutated? bucket))

      (define/override (no-side-effect?)
        ;; If not built in, could raise exn
	(is-kernel?))

      (define/override (get-result-arity) 1)

      (define/override (valueable?) (or (bucket-inited-before-use? bucket)
                                        (is-kernel?)))

      (define/override (can-dup/move?) (valueable?))

      (define/override (clone env) (make-object global% trans? tables needs-top? src-stx cert-stxes))

      (define/override (global->local env)
        (or (ormap (lambda (e)
                     (and (free-identifier=? (car e) src-stx)
                          (make-object ref% (cdr e) src-stx cert-stxes)))
                   env)
            this))

      (inherit recertify)
      (define/override (sexpr)
	(recertify 
	 (if needs-top?
	     (with-syntax ([stx src-stx])
	       (syntax (#%top . stx)))
	     src-stx)))
      
      (define/public (set-mutated) (set-bucket-mutated?! bucket #t))
      (define/public (set-inited) (set-bucket-inited-before-use?! bucket #t))))

  (define binding% 
    (class exp% 
      (init-field always-inited?)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      (define value #f)
      (define used 0)
      (define mutated? #f)
      (define inited? always-inited?)
      (define escape #f)
      
      (define/public (is-used?) (positive? used))
      (define/public (is-mutated?) mutated?)
      (define/public (is-inited?) inited?)
      (define/public (get-use-count) used)

      (define/public (set-mutated) (set! mutated? #t))
      (define/public (set-inited) (set! inited? #t))
      (define/public (set-value v) (set! value v))

      (define/public (escapes?) escape)
      (define/public (set-escapes x) (set! escape #t))
      
      (define/public (clone-binder env) 
        (make-object binding% 
          always-inited?
          (datum->syntax
           #f
           (gensym (syntax-e src-stx))
           src-stx
	   cert-stxes)))


      (define/override (reset-varflags)
        (set! used 0)
        (set! mutated? #f)
        (set! inited? always-inited?))
      (define/override (set-known-values)
        (set! used (add1 used))
        (unless inited?
          (set! mutated? #t)))

      (define/override (valueable?) (and inited? (not mutated?)))

      (define/override (drop-uses) (set! used (sub1 used)))

      (define/override (get-value) 
        (and (not mutated?)
             value
             (send value get-value)))
      
      (inherit recertify)
      (define/override (sexpr)
        ;; `(==lexical== ,name ,used ,mutated? ,inited? ,(get-value))
        (recertify src-stx)
        )
      (define/public (orig-name)
        (syntax-e src-stx))))

  (define ref% 
    (class exp% 
      (init-field binding)
      (super-instantiate ())
      (inherit-field src-stx) ;; The identifier
      
      
      (define/public (is-used?) (send binding is-used?))
      (define/public (is-mutated?) (send binding is-mutated?))
      (define/public (is-inited?) (send binding is-inited?))
      
      (define/public (get-use-count) (send binding get-use-count))

      (define/public (set-mutated) (send binding set-mutated))
      (define/public (set-inited) (send binding set-inited))
      (define/public (set-value v) (send binding set-value v))

      (define/override (set-known-values) (send binding set-known-values))

      (define/override (valueable?) (send binding valueable?))
      (define/override (can-dup/move?) (valueable?))

      (define/override (drop-uses) (send binding drop-uses))

      (define/override (get-result-arity) 1)

      (define/override (get-value) (send binding get-value))

      (define/override (escape) 
        (send binding set-escape))
      
      (define/override (simplify ctx)
        (if (context-need ctx)
            (let ([v (get-value)])
              (if (and v (send v can-dup/move?))
                  (begin
                    (drop-uses)
                    (send v simplify ctx))
                  this))
            (begin
              (drop-uses)
              (make-object void% src-stx))))

      (define/override (clone env) (lookup-clone binding this env))
      (define/override (substitute env) (lookup-clone binding this env))

      (inherit recertify)
      (define/override (sexpr) 
        (let ([x (send binding sexpr)])
	  (recertify (datum->syntax
		      x
		      (syntax-e x)
		      src-stx))))

      (define/public (get-binding) binding)
      (define/public (orig-name) (send binding orig-name))))
      

  (define begin% 
    (class exp%
      (init-field subs)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      (inherit merge-certs)
      
      (define/override (nonbind-sub-exprs) subs)
      (define/override (set-nonbind-sub-exprs s) (set! subs s))

      (define/override (get-result-arity)
        (if (null? subs)
            'unknown
            (let loop ([subs subs])
              (if (null? (cdr subs))
                  (send (car subs) get-result-arity)
                  (loop (cdr subs))))))
      
      (define/override (simplify ctx)
        (set! subs
              (let loop ([subs subs])
                (cond
                  [(null? subs) null]
                  [(null? (cdr subs))
                   (list (send (car subs) simplify ctx))]
                  [else
                   (let ([r (send (car subs) simplify (need-none ctx))]
                         [rest (loop (cdr subs))])
                     (cond
                       [(send r no-side-effect?)
                        (send r drop-uses)
                        rest]
                       [(is-a? r begin%)
			(merge-certs r)
                        (append (send r nonbind-sub-exprs)
                                rest)]
                       [else (cons r rest)]))])))
        (if (and (pair? subs)
                 (null? (cdr subs)))
            (let ([v (car subs)])
	      (send v merge-certs this)
	      v)
            this))

      (define/override (clone env)
        (make-object begin% 
          (map (lambda (x) (send x clone env)) 
               subs)
          src-stx
	  cert-stxes))

      (inherit recertify)
      (define/override (sexpr)
	(with-syntax ([(body ...) (body-sexpr)])
	  (syntax/loc src-stx (begin body ...))))

      (define/override (body-sexpr)
	(map (lambda (e) (recertify (get-sexpr e))) subs))))
  
  (define top-def% 
    (class exp% 
      (init-field formname varnames expr tables)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      (define globals #f)
      
      (define/override (nonbind-sub-exprs) (list expr))
      (define/override (set-nonbind-sub-exprs s) (set! expr (car s)))

      (define/override (get-result-arity) 1)

      (define/override (no-side-effect?) #f)
      (define/override (valueable?) #f)
      
      (define/override (clone env) (make-object top-def% 
                                     formname
                                     varnames 
                                     (send expr clone env)
                                     tables
                                     src-stx
				     cert-stxes))

      (inherit recertify)
      (define/override (sexpr)
        (with-syntax ([formname formname]
                      [(varname ...) varnames]
                      [rhs (get-sexpr expr)])
	  (recertify
	   (syntax/loc src-stx (formname (varname ...) rhs)))))

      (define/public (get-vars) varnames)
      (define/public (get-rhs) expr)

      ;; Like get-vars, but return global% objects, instead.
      ;; Useful because the global% object has the global variable bucket info.
      (define/public (get-globals)
        (unless globals
          (set! globals
                (map (lambda (v)
                       (make-object global% #f tables #f v))
                     varnames)))
        globals)))

  (define variable-def% 
    (class top-def% 
      (init varnames expr tables stx)
      
      (super-instantiate ((quote-syntax define-values) varnames expr tables stx))))

  (define syntax-def% 
    (class top-def% 
      (init varnames expr tables stx)
      (super-instantiate ((quote-syntax define-syntaxes) varnames expr tables stx))))

  (define for-syntax-def% 
    (class top-def% 
      (init varnames expr tables stx)
      (super-instantiate ((quote-syntax define-values-for-syntax) varnames expr tables stx))))

  (define (install-values vars expr)
    (when (= 1 (length vars))
      (send (car vars) set-value expr)))

  (define constant% 
    (class exp% 
      (init-field val)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      
      (define/public (get-const-val) val)

      (define/override (get-value) this)
      
      (define/override (valueable?) #t)

      (define/override (can-dup/move?)
        (or (number? val)
            (boolean? val)
            (char? val)
            (symbol? val)
            (void? val)))

      (define/override (get-result-arity) 1)

      (define/override (simplify ctx)
        (cond
          [(eq? 'bool (context-need ctx))
           (if (boolean? val)
               this
               (make-object constant% #t src-stx))]
          [(context-need ctx)
           (cond
             [(eq? val (void))
              (make-object void% src-stx)]
             [else this])]
          [else (make-object void% src-stx)]))
      
      (define/override (clone env) (make-object constant% val src-stx cert-stxes))
      
      (inherit recertify)
      (define/override (sexpr)
	(let ([vstx (datum->syntax (quote-syntax here) val src-stx)])
	  (cond
	   [(or (number? val)
		(string? val)
		(boolean? val)
		(char? val))
	    vstx]
	   [(syntax? val)
	    (with-syntax ([vstx vstx])
	      (recertify
	       (syntax (quote-syntax vstx))))]
	    [else
	     (with-syntax ([vstx vstx])
	       (syntax (quote vstx)))])))))

  (define void%
    (class constant% 
      (init stx)
      (super-instantiate ((void) stx))
      (inherit-field src-stx cert-stxes)

      (define/override (sexpr) (quote-syntax (#%plain-app void)))

      (define/override (simplify ctx)
        (if (eq? 'bool (context-need ctx))
            (make-object constant% #t src-stx)
            this))

      (define/override (clone env) (make-object void% src-stx cert-stxes))))


  (define app%
    (class exp% 
      (init-field rator rands tables)
      (super-instantiate ())
      (inherit-field src-stx
		     cert-stxes)
      (inherit merge-certs)

      (define known-single-result? #f)

      (inherit set-known-value)
      
      (define/private (known-single-result v)
        (set! known-single-result? #t)
        (set-known-value v)
        v)

        
      (define/override (nonbind-sub-exprs) (cons rator rands))
      (define/override (set-nonbind-sub-exprs s) 
        (set! rator (car s))
        (set! rands (cdr s)))

      (define/override (no-side-effect?)
        ;; Note: get-result-arity assumes #t result => single value
        ;;
        ;; Some prims are known to be side-effect-free (including no errors)
        ;; get-result-arity assumes 1 when this returns #t
        (or known-single-result?
            (and (rator . is-a? . global%)
                 (send rator is-kernel?)
                 (memq (send rator orig-name) effectless-prims)
                 (andmap (lambda (rand) (send rand no-side-effect?))
                         rands))))

      (define/override (valueable?)
        (and (rator . is-a? . global%)
             (send rator is-kernel?)
             (not (memq (send rator orig-name)
                        (non-valueable-prims)))
             (super valueable?)))

      (define/override (get-result-arity)
        (if (or known-single-result? (no-side-effect?))
            1
            'unknown))

      (define/override (escape)
        (send rator escape)
        (cond
          ((bound-identifier=? #'vector-ref (send rator get-stx)) (void))
          ((and (bound-identifier=? #'vector-set! (send rator get-stx))
                (not (null? rands)))
           (map (lambda (x) (send x escape)) (cdr rands)))
          (else
           (map (lambda (x) (send x escape)) rands))))

      (define/override (simplify ctx)
        (super simplify ctx)
        (cond
          ;; ((lambda (a ...) ...) v ...) => (let ([a v] ...) ...)
          [(and (is-a? rator lambda%)
                (send rator can-inline?))
	   (if (send rator arg-body-exists? (length rands))
	       (begin
		 (send rator drop-other-uses (length rands))
		 (let-values ([(vars body) (send rator arg-vars-and-body (length rands))])
		   (for-each (lambda (var rand)
			       (install-values (list var) rand))
			     vars rands)
		   (let ([let-form (make-object let%
						(map list vars)
						rands
						body
						src-stx
						cert-stxes)])
		     (send let-form merge-certs this)
		     (send let-form merge-certs rator)
		     (send let-form simplify ctx))))
	       (begin
		 (unless (send rator arg-count-ok? (length rands))
		   (warning "immediate procedure called with wrong number of arguments"
			    this))
		 this))]
          
          ;; constant folding
          [(and (is-a? rator global%)
                (memq (send rator orig-name) foldable-prims)
                (send rator is-kernel?)
                (andmap (lambda (x) (is-a? x constant%)) rands))
           (if (eq? (send rator orig-name) 'void)
               (make-object void% src-stx)
               (let ([vals (map (lambda (x) (send x get-const-val)) rands)]
                     [f (dynamic-require 'mzscheme (send rator orig-name))])
                 (with-handlers ([exn:fail? (lambda (x) 
					      (fprintf (current-error-port)
						       "constant calculation error: ~a~n"
						       (exn-message x))
					      this)])
                   (known-single-result
                    (send (make-object constant% (apply f vals) src-stx)
                          simplify ctx)))))]
          
          ;; (+ x 1) => (add1 x)
          [(and (is-a? rator global%)
                (send rator is-kernel?)
                (eq? (send rator orig-name) '+)
                (= 2 (length rands))
                (or (and (is-a? (car rands) constant%)
                         (eq? 1 (send (car rands) get-const-val)))
                    (and (is-a? (cadr rands) constant%)
                         (eq? 1 (send (cadr rands) get-const-val)))))
           (make-object app% 
             (make-object global% (send rator is-trans?) tables #f (quote-syntax add1))
             (list
              (if (and (is-a? (car rands) constant%)
                       (eq? 1 (send (car rands) get-const-val)))
                  (cadr rands)
                  (car rands)))
             tables
             src-stx
	     cert-stxes)]
          ;; (- x 1) => (sub1 x)
          [(and (is-a? rator global%)
                (send rator is-kernel?)
                (eq? (send rator orig-name) '-)
                (= 2 (length rands))
                (and (is-a? (cadr rands) constant%)
                     (eq? 1 (send (cadr rands) get-const-val))))
           (make-object app% 
             (make-object global%  (send rator is-trans?) tables #f (quote-syntax sub1))
             (list (car rands))
             tables
             src-stx
	     cert-stxes)]
          
          ;; (car x) where x is known to be a list construction
          [(and (is-a? rator global%)
                (send rator is-kernel?)
                (let-values ([(pos len) (case (send rator orig-name) 
                                          [(car) (values 0 1)]
                                          [(cadr) (values 1 1)]
                                          [(caddr) (values 2 1)]
                                          [(cadddr) (values 3 1)]
                                          [(list-ref) (values (and (= 2 (length rands))
                                                                   (let ([v (send (cadr rands) get-value)])
                                                                     (and (v . is-a? . constant%)
                                                                          (send v get-const-val))))
                                                              2)]
                                          [else (values #f #f)])])
                  (and (number? pos)
                       (= len (length rands))
                       (and ((car rands) . is-a? . ref%)
                            (let ([val (send (car rands) get-value)])
                              (and (val . is-a? . app%)
                                   (send val get-list-ref pos)))))))
           =>
           (lambda (val)
             (send (car rands) drop-uses)
             (known-single-result val))]
          
          ;; (memv x '(c ...)) in a boolean context => (if (eq[v]? x 'c) ...)
          ;; relevant to the output of `case'
          [(and (eq? (context-need ctx) 'bool) 
                (is-a? rator global%)
                (send rator is-kernel?)
                (eq? (send rator orig-name) 'memv)
                (= 2 (length rands))
                (is-a? (car rands) ref%)
                (is-a? (cadr rands) constant%)
                (list? (send (cadr rands) get-const-val)))
           (let ([xformed
                  (let ([l (send (cadr rands) get-const-val)]
                        [l-stx (send (cadr rands) get-stx)]
                        [false (make-object constant% #f (datum->syntax #f #f))]
                        [true (make-object constant% #t (datum->syntax #f #t))])
                    (if (null? l)
                        false
                        (let loop ([l l])
                          (let ([test
                                 (make-object app%
                                   (make-object global%
                                     (send rator is-trans?)
                                     tables
                                     #f
                                     (let ([a (car l)])
                                       (if (or (symbol? a)
                                               (and (number? a)
                                                    (exact? a)
                                                    (integer? a)
                                                    ;; fixnums:
                                                    (<= (- (expt 2 29))
                                                        a
                                                        (expt 2 29))))
                                           (quote-syntax eq?)
                                           (quote-syntax eqv?))))
                                   (list
                                    (car rands)
                                    (make-object constant% 
                                      (car l)
                                      l-stx))
                                   tables
                                   src-stx
				   cert-stxes)])
                            (cond
                              [(null? (cdr l)) test]
                              [else (let ([rest (loop (cdr l))])
                                      ;; increment use count:
                                      (send (car rands) set-known-values)
                                      (make-object if%
                                        test
                                        true
                                        rest
                                        src-stx
					cert-stxes))])))))])
	     (send xformed merge-certs this)
             (send xformed simplify ctx))]
          
          ;; (values e) where e has result arity 1
          [(and (is-a? rator global%)
                (send rator is-kernel?)
                (eq? 'values (send rator orig-name))
                (= 1 (length rands))
                (equal? 1 (send (car rands) get-result-arity)))
	   (send (car rands) merge-certs this)
           (known-single-result (car rands))]
          
          ;; Check arity of other calls to primitives
          [(and (is-a? rator global%)
                (send rator is-kernel?))
	   (let ([f (dynamic-require 'mzscheme (send rator orig-name))])
	     (cond
	      [(not (procedure? f))
	       (warning "call of non-procedure" this)]
	      [(not (procedure-arity-includes? f (length rands)))
	       (warning "primitive called with wrong number of arguments" this)]))
           this]

          ;; inlining
          [(and #f ;; disabled!
		(> (fuel) 0)
                (or (is-a? rator ref%) (is-a? rator global%))
                (is-a? (send rator get-value) lambda%)
                (not (send (send rator get-value) get-simplifying-body)))
           (let ([f (send (send rator get-value) clone null)])
             (send rator drop-uses)
             (set! rator f)
             (send f set-known-values)
             ;; Now we have ((lambda ...) ...). Go again.
             (fuel (sub1 (fuel)))
             (if (= (fuel) (sub1 max-fuel))
                 (begin0
                   (simplify ctx)
                   (fuel max-fuel))
                 (simplify ctx)))]

          ;; Check arity of a call to a known (non-primitive) function
          [(and (or (is-a? rator ref%) (is-a? rator global%))
                (is-a? (send rator get-value) lambda%))
           (let ([f (send rator get-value)])
	     (unless (send f arg-count-ok? (length rands))
	       (warning "procedure called with wrong number of arguments"
			this))
	     this)]
          
          [else this]))
      
      (define/override (clone env) (make-object app%
                                     (send rator clone env)
                                     (map (lambda (rand)
                                            (send rand clone env))
                                          rands)
                                     tables
                                     src-stx
				     cert-stxes))

      (inherit recertify)
      (define/override (sexpr)
	(recertify
	 (keep-mzc-property
	  (with-syntax ([rator (get-sexpr rator)]
			[(rand ...) (map get-sexpr rands)])
	    (syntax/loc src-stx (#%plain-app rator rand ...)))
	  src-stx)))

      ;; Checks whether the expression is an app of `values'
      ;; to a particular set of bindings.
      (define/public (is-values-of? args)
        (and (rator . is-a? . global%)
             (send rator is-kernel?)
             (eq? (send rator orig-name) 'values)
             (= (length rands) (length args))
             (andmap
              (lambda (rand arg)
                (and (rand . is-a? . ref%)
                     (eq? arg (send rand get-binding))))
              rands args)))

      ;; If app constructs a list and the nth element can be
      ;;  safely extracted, then extract it.
      (define/public (get-list-ref n)
        (and (rator . is-a? . global%)
             (send rator is-kernel?)
             (eq? 'list (send rator orig-name))
             ((length rands) . > . n)
             (let ([i (list-ref rands n)])
               (if (send i can-dup/move?)
                   i
                   #f))))))

  (define lambda% 
    (class exp% 
      (init-field varss normal?s bodys)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      (define simplifying-body #f)
      
      
      (inherit drop-uses)
      
      
      (define/private (multarity-ize l)
        (if (null? (cdr l))
            (car l)
            (cons (car l)
                  (multarity-ize (cdr l)))))

      (define/public (get-simplifying-body) simplifying-body)
      
      (define/public (multi?) (or (null? bodys)
                                  (pair? (cdr bodys))))
      
      (define/public (arg-body-exists? n)
        (ormap (lambda (vs n?) (and n? (= n (length vs))))
               varss normal?s))
      (define/public (arg-count-ok? n)
        (ormap (lambda (vs n?) (or (and n? (= n (length vs)))
				   (and (not n?) (n . >= . (sub1 (length vs))))))
               varss normal?s))
      (define/public (arg-vars-and-body n)
        (let loop ([varss varss][normal?s normal?s][bodys bodys])
          (if (and (car normal?s)
                   (= (length (car varss)) n))
              (values (car varss) (car bodys))
              (loop (cdr varss) (cdr normal?s) (cdr bodys)))))
      
      (define/public (drop-other-uses n)
        (let loop ([n n][varss varss][normal?s normal?s][bodys bodys])
          (unless (null? varss)
            (let ([n (if (and (car normal?s)
                              (= (length (car varss)) n))
                         -1
                         (begin
                           (send (car bodys) drop-uses)
                           n))])
              (loop n (cdr varss) (cdr normal?s) (cdr bodys))))))
      
      (define/public (can-inline?)
        (not (syntax-property src-stx 'mzc-cffi)))
      
      (define/override (bind-sub-exprs) (apply append varss))
      (define/override (nonbind-sub-exprs) bodys)
      (define/override (set-nonbind-sub-exprs s) (set! bodys s))
      
      (define/override (no-side-effect?) #t)
      (define/override (get-result-arity) 1)
      
      (define/override (valueable?) #t)
	
      (define/override (simplify ctx)
        (if (eq? 'bool (context-need ctx))
            (begin
              (drop-uses)
              (make-object constant% #t src-stx))
            (begin
              (set! simplifying-body #t)
              (begin0
                (super simplify ctx)
                (set! simplifying-body #f)))))
      
      (define/override (clone env)
        (let ([varss+bodys
               (let loop ([varss varss][bodys bodys])
                 (if (null? varss)
                     null
                     (let* ([vars (car varss)]
                            [new-vars (map (lambda (v) (send v clone-binder env))
                                           vars)])
                       (cons
                        (cons new-vars
                              (send (car bodys)
                                    clone (append (map cons vars new-vars)
                                                  env)))
                        (loop (cdr varss) (cdr bodys))))))])
          (make-object lambda%
            (map car varss+bodys)
            normal?s
            (map cdr varss+bodys)
            src-stx
	    cert-stxes)))

      (inherit recertify)
      (define/override (sexpr)
        (with-syntax ([(vars ...)
                       (map (lambda (vars normal?)
                              (let ([vs (map get-sexpr vars)])
                                (if normal?
                                    vs
                                    (multarity-ize vs))))
                            varss normal?s)]
                      [(body ...)
                       (map (lambda (body)
                              (get-body-sexpr body))
                            bodys)])
	  (recertify
	   (keep-mzc-property
	    (if (multi?)
		(syntax/loc src-stx
		  (case-lambda
		   [vars . body] ...))
		(with-syntax ([body (car (syntax->list (syntax (body ...))))])
		  (syntax/loc src-stx
		    (#%plain-lambda vars ... . body))))
	    src-stx))))))

  (define local% 
    (class exp% 
      (init-field form varss rhss body)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      (inherit merge-certs)
      
      (define/public (get-rhss) rhss)
      (define/public (get-varss) varss)
      (define/public (get-body) body)

      (define/override (bind-sub-exprs) (apply append varss))
      (define/override (nonbind-sub-exprs) (cons body rhss))
      (define/override (set-nonbind-sub-exprs s) 
        (set! body (car s))
        (set! rhss (cdr s)))

      (define/override (get-result-arity) (send body get-result-arity))
      
      (define/override (simplify ctx)
        (set! rhss (map (lambda (rhs vars) 
                          (send rhs simplify 
                                (make-context 'all
                                              (append vars (context-indef ctx)))))
                        rhss varss))
        (set! body (send body simplify ctx))
        
        ;; Drop unused constant bindings
        (set!-values (varss rhss)
                     (let loop ([varss varss][rhss rhss])
                       (cond
                         [(null? varss) (values null null)]
                         [else (let-values ([(rest-vss rest-rhss)
                                             (loop (cdr varss) (cdr rhss))])
                                 (if (and (andmap (lambda (var) (not (send var is-used?)))
                                                  (car varss))
                                          (equal? (send (car rhss) get-result-arity)
                                                  (length (car varss)))
                                          (send (car rhss) no-side-effect?))
                                     (begin
                                       (send (car rhss) drop-uses)
                                       (values rest-vss rest-rhss))
                                     (values (cons (car varss) rest-vss)
                                             (cons (car rhss) rest-rhss))))])))
        
        (cond
          ;; (let-values ([(x) e]) (if e ... ...))
          ;;  is a pattern created by `or'
          [(and (is-a? body if%)
                (let ([t (send body get-if-test)])
                  (and (is-a? t binding%)
                       (= 1 (length varss))
                       (= 1 (length (car varss)))
                       (eq? (caar varss) t)
                       (= 1 (send t get-use-count)))))
           (make-object if%
             (car rhss)
             (send body get-if-then)
             (send body get-if-else)
             src-stx
	     cert-stxes)]
          [(null? varss)
	   (send body merge-certs this)
           (send body simplify ctx)]
          ;; (let-values [(x) y] ...) whether y is inited, and
          ;;  neither x nor y is mutated => replace x by y
          [(and (andmap (lambda (vars) (= 1 (length vars))) varss)
                (send (caar varss) valueable?)
                (andmap (lambda (rhs) (and (or (rhs . is-a? . ref%)
                                               (rhs . is-a? . global%))
                                           (send rhs valueable?)))
                        rhss))
	   (send body merge-certs this)
           (send body substitute
                 (map (lambda (vars rhs) (cons (car vars) 
                                               (if (rhs . is-a? . ref%)
                                                   (send rhs get-binding)
                                                   rhs)))
                      varss rhss))]
          
          [else
           this]))

      (define/override (clone env)
        (let* ([new-varss
                (map (lambda (vs)
                       (map (lambda (v) (send v clone-binder env))
                            vs))
                     varss)]
               [body-env (append
                          (map cons
                               (apply append varss)
                               (apply append new-varss))
                          env)]
               [letrec? (eq? form 'letrec-values)])
          (make-object (if letrec? letrec% let%)
            new-varss
            (map (lambda (rhs) 
                   (send rhs clone (if letrec? body-env env)))
                 rhss)
            (send body clone body-env)
            src-stx
	    cert-stxes)))

      (define/override (get-value) (send body get-value))
	
      (inherit recertify)
      (define/override (sexpr)
        (with-syntax ([form form]
                      [(vars ...)
                       (map (lambda (vars)
                              (map get-sexpr vars))
                            varss)]
                      [(rhs ...)
                       (map get-sexpr rhss)]
                      [(body ...) (get-body-sexpr body)])
	  (recertify
	   (syntax/loc src-stx
	     (form ([vars rhs] ...) 
		   body ...)))))))
    
  (define let%
    (class local% 
      (init -varss -rhss -body -stx -cert-stxes)
      (inherit get-varss get-rhss get-body)
      
      (define/override (set-known-values)
        (for-each (lambda (vars rhs) (install-values vars rhs))
                  (get-varss) (get-rhss))
        (super set-known-values))
      
      (super-instantiate ((quote-syntax let-values) -varss -rhss -body -stx -cert-stxes))))

  (define letrec%
    (class local% 
      (init -varss -rhss -body -stx -cert-stxes) 
      (inherit get-varss get-rhss)
      
      (define/override (set-known-values)
        (let loop ([varss (get-varss)][rhss (get-rhss)])
          (unless (null? varss)
            (when (send (car rhss) valueable?)
              (for-each (lambda (var) (send var set-inited))
                        (car varss))
              (loop (cdr varss) (cdr rhss)))))
        (for-each install-values (get-varss) (get-rhss))
        (super set-known-values))

      (super-instantiate ((quote-syntax letrec-values) -varss -rhss -body -stx -cert-stxes))))

  (define set!%
    (class exp% 
      (init-field var val)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      
      (define/override (nonbind-sub-exprs) (list var val))
      (define/override (set-nonbind-sub-exprs s) 
        (set! var (car s))
        (set! val (cadr s)))

      (define/override (no-side-effect?) #f)
      (define/override (valueable?) #f)
      (define/override (get-result-arity) 1)
	
      (define/override (set-known-values)
        (send var set-mutated)
        (send var set-known-values) ; increments use
        (send val set-known-values))
	
      (define/override (set-mutability)
        (send var set-mutated)
	(super set-mutability))
	
      (define/override (clone env)
        (make-object set!% 
          (send var clone env)
          (send val clone env)
          src-stx
	  cert-stxes))

      (inherit recertify)
      (define/override (sexpr)
	(with-syntax ([var (get-sexpr var)]
		      [val (get-sexpr val)])
	  (recertify
	   (syntax/loc src-stx
	     (set! var val)))))))

  (define if%
    (class exp% 
      (init-field test then else)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      
      (define/public (get-if-test) test)
      (define/public (get-if-then) then)
      (define/public (get-if-else) else)

      (define/override (nonbind-sub-exprs) (list test then else))
      (define/override (set-nonbind-sub-exprs s)
        (set! test (car s))
        (set! then (cadr s))
        (set! else (caddr s)))
	
      (define/override (get-result-arity)
        (let ([t (send then get-result-arity)]
              [e (send else get-result-arity)])
          (if (equal? t e)
              t
              'unknown)))
      
      (define/override (simplify ctx)
        (set! test (send test simplify (need-bool ctx)))
        (set! then (send then simplify ctx))
        (set! else (send else simplify ctx))

        ;; (if xvar xvar y) when need bool
        ;;   => (if xvar #t y)
        (when (and (eq? 'bool (context-need ctx))
                   (is-a? test binding%)
                   (eq? test then))
          (send then drop-uses)
          (set! then (make-object constant% #t src-stx)))
        (when (and (eq? 'bool (context-need ctx))
                   (eq? test else)
                   (is-a? test binding%))
          (send else drop-uses)
          (set! else (make-object constant% #f src-stx)))
        
        
        (cond
          ;; Constant switch
          [(is-a? test constant%)
           (if (eq? (send test get-const-val) #f)
               (begin
                 (send test drop-uses)
                 (send then drop-uses)
                 else)
               (begin
                 (send test drop-uses)
                 (send else drop-uses)
                 then))]
          
          ;; (if (if x y #f) a (void))
          ;;    => (if x (if y a (void)) (void))
          [(and (is-a? test if%)
                (is-a? else void%)
                (let ([c (send test get-if-else)])
                  (and (is-a? c constant%)
                       (eq? #f (send c get-const-val)))))
           (send
            (make-object if%
              (send test get-if-test)
              (make-object if%
                (send test get-if-then)
                then
                (make-object void% src-stx)
                src-stx
		cert-stxes)
              (make-object void% src-stx)
              src-stx
	      cert-stxes)
            simplify ctx)]
          
          [else this]))

      (define/override (clone env) 
        (make-object if%
          (send test clone env)
          (send then clone env)
          (send else clone env)
          src-stx
	  cert-stxes))
      
      (inherit recertify)
      (define/override (sexpr)
        (with-syntax ([test (get-sexpr test)]
                      [then (get-sexpr then)])
	  (recertify
	   (if (else . is-a? . void%)
	       (syntax/loc src-stx
		 (if test then))
	       (with-syntax ([else (get-sexpr else)])
		 (syntax/loc src-stx
		   (if test then else)))))))))

  (define begin0%
    (class exp% 
      (init-field first rest)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)
      
      (define/override (nonbind-sub-exprs) (list first rest))
      (define/override (set-nonbind-sub-exprs s)
        (set! first (car s))
        (set! rest (cadr s)))
	
      (define/override (get-result-arity) (send first get-result-arity))

      (define/override (simplify ctx)
        (set! first (send first simplify ctx))
        (set! rest (send rest simplify (need-none ctx)))
        (if (send rest no-side-effect?)
            (begin
              (send rest drop-uses)
	      (send first merge-certs this)
              first)
            this))
	
      (define/override (clone env) 
        (make-object begin0%
          (send first clone env)
          (send rest clone env)
	  src-stx
	  cert-stxes))
	
      (inherit recertify)
      (define/override (sexpr)
	(with-syntax ([first (get-sexpr first)]
		      [(rest ...) (get-body-sexpr rest)])
	  (recertify
	   (syntax/loc src-stx
	     (begin0 first rest ...)))))))

  (define wcm%
    (class exp% 
      (init-field key val body)
      (super-instantiate ())
      (inherit-field src-stx cert-stxes)

      (define/override (nonbind-sub-exprs) (list key val body))
      (define/override (set-nonbind-sub-exprs s)
        (set! key (car s))
        (set! val (cadr s))
        (set! body (caddr s)))

      (define/override (get-result-arity) (send body get-result-arity))
	
      (define/override (clone env) 
        (make-object wcm%
          (send key clone env)
          (send val clone env)
          (send body clone env)
	  src-stx
	  cert-stxes))

      (inherit recertify)
      (define/override (sexpr)
        (with-syntax ([key (get-sexpr key)]
                      [val (get-sexpr val)]
                      [body (get-sexpr body)])
	  (recertify
	   (syntax/loc src-stx
	     (with-continuation-mark key val body)))))))

  (define module%
    (class exp% 
      (init-field body et-body name init-req req-prov tables src-module-begin-stx)
      (super-instantiate ())
      (inherit-field src-stx)
      
      (define/override (reset-varflags)
        (for-each (lambda (e) (send e reset-varflags)) body)
        (for-each (lambda (e) (send e reset-varflags)) et-body))
      (define/override (set-known-values)
        ;; Assumes varflags are reset
        (for-each (lambda (e) (send e set-known-values)) (nonbind-sub-exprs)))

      (define/override (drop-uses)
        ;; Assumes varflags are reset
        (for-each (lambda (e) (send e drop-uses)) (nonbind-sub-exprs)))
	
      (define/override (no-side-effect?) #f)
      (define/override (valueable?) #f)

      (define/override (get-result-arity) 'unknown)

      (define/override (sub-exprs) (append (append et-body body)))
      (define/override (bind-sub-exprs) null)
      (define/override (nonbind-sub-exprs) (sub-exprs))
      (define/override (set-nonbind-sub-exprs l)
        (let-values ([(etb b)
                      (let loop ([l l][etb et-body][accum null])
                        (cond
                          [(null? etb)
                           (values (reverse accum) l)]
                          [else (loop (cdr l) (cdr etb) (cons (car l)
                                                              accum))]))])
          (set! body body)
          (set! et-body etb)))
      
      ;; expose known bindings by converting a sequence of top-level
      ;;  expressions into a letrec:
      ;;   (define-values (a ...) body) ...
      ;;   => (define-values (a ... ...) 
      ;;          (letrec-values ([(a ...) body] ...) (values a ... ...)))
      (define/override (reorganize)
        (let ([-body (map (lambda (x) (send x reorganize)) body)]
              [-et-body (map (lambda (x) (send x reorganize)) et-body)])
          (let loop ([l -body][defs null])
            (cond
              [(and (pair? l) 
                    ((car l) . is-a? . variable-def%)
                    (not (ormap (lambda (v) (send v is-mutated?))
                                (send (car l) get-globals)))
                    (send (send (car l) get-rhs) valueable?))
               (for-each (lambda (g) (send g set-inited))
                         (send (car l) get-globals))
               (loop (cdr l)
                     (cons (car l) defs))]
              [else
               (if (null? defs)
                   (void) ; no reorganization
                   (let* ([defs (reverse defs)]
                          [varss
                           (map (lambda (def) (send def get-vars)) defs)]
                          [rhss
                           (map (lambda (def) (send def get-rhs)) defs)]
                          [lex-varss (map (lambda (vars)
                                            (map (lambda (var)
                                                   (make-object binding%
                                                     #t
                                                     (datum->syntax
                                                      #f
                                                      (syntax-e var)
                                                      var)))
                                                 vars))
                                          varss)]
                          [vars (apply append varss)]
                          [lex-vars (apply append lex-varss)]
                          [env (map cons vars lex-vars)])
                     (set! -body
                           (cons
                            (make-object variable-def%
                              vars
                              (make-object letrec%
                                lex-varss
                                (map (lambda (rhs)
                                       (send rhs global->local env))
                                     rhss)
                                (make-object app%
                                  (make-object global%
                                    #f
                                    tables
                                    #f
                                    (quote-syntax values))
                                  (map (lambda (var lex-var)
                                         (make-object ref% lex-var var))
                                       vars
                                       lex-vars)
                                  tables 
                                  (send (car defs) get-stx))
                                (send (car defs) get-stx)
				(send (car defs) get-cert-stxes))
                              tables
                              (send (car defs) get-stx))
                            l))))])
            (set! body -body)
            (set! et-body -et-body)))
        this)

      (define/override (deorganize)
        ;; Check for
        ;;   (define-values (a ... ...) 
        ;;       (letrec-values ([(a ...) body] ...) (values a ... ...)))
        ;;   => (define-values (a ...) body) ...
        (when (and (pair? body)
                   (let ([first (car body)])
                     (and (first . is-a? . variable-def%)
                          (let ([rhs (send first get-rhs)])
                            (and (rhs . is-a? . letrec%)
                                 (let ([lbody (send rhs get-body)]
                                       [lvarss (send rhs get-varss)])
                                   (and (lbody . is-a? . app%)
                                        (send lbody is-values-of? 
                                              (apply append lvarss)))))))))
          (let ([vars (send (car body) get-vars)]
                [bindingss (send (send (car body) get-rhs) get-varss)]
                [bodys (send (send (car body) get-rhs) get-rhss)])
            ;; split vars into varss:
            (let ([varss (let loop ([bindingss bindingss][vars vars])
                           (if (null? bindingss)
                               null
                               (let loop2 ([bindings (car bindingss)][vars vars][accum null])
                                 (if (null? bindings)
                                     (cons (reverse accum)
                                           (loop (cdr bindingss) vars))
                                     (loop2 (cdr bindings) (cdr vars) (cons (car vars) accum))))))]
                  [bindings (apply append bindingss)])
              (let ([env (map cons bindings 
                              (map (lambda (var) 
                                     (make-object global% #f tables #f var)) 
                                   vars))])
                (set! body
                      (append
                       (map (lambda (vars body)
                              (make-object variable-def%
                                vars
                                (send body substitute env)
                                tables
                                src-stx))
                            varss bodys)
                       (cdr body)))))))
        (super deorganize))

      (inherit recertify)
      (define/override (sexpr)
	(with-syntax ([name name]
		      [init-req init-req]
		      [(body ...) (map get-sexpr body)]
		      [(et-body ...) (map get-sexpr et-body)]
		      [(req-prov ...) (map get-sexpr req-prov)])
          (with-syntax ([body 
                         (syntax-recertify #'(#%plain-module-begin
                                              req-prov ...
                                              body ...
                                              et-body ...)
                                           src-module-begin-stx
                                           recert-insp
                                           #f)])
            (recertify
             (syntax/loc src-stx
               (module name init-req body))))))
      (define/override (body-sexpr)
	(list (sexpr)))))

  ;; requires and provides should really be ignored:
  (define require/provide%
    (class exp% 

      (define/override (valueable?) #f)
      (define/override (no-side-effect?) #f)
      (super-instantiate ())))
  
  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Warning reporting
  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (print-warning msg exp)
    (let ([stx (send exp get-stx)])
      (when (syntax-source stx)
	(fprintf (current-output-port) "~a:" (syntax-source stx))
	(cond
	 [(syntax-column stx)
	  (fprintf (current-output-port) "~a:~a:" 
		   (syntax-line stx)
		   (syntax-column stx))]
	 [(syntax-position stx)
	  (fprintf (current-output-port) ":~a:" 
		   (syntax-position stx))])
	(fprintf (current-output-port) " "))
      (fprintf (current-output-port) 
	       "~a: ~e~n"
	       msg
	       (syntax->datum (send exp sexpr)))))

  (define (warning msg exp)
    ; (print-warning msg exp)
    (void))

  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Parser
  ;; converts a syntax object to an exp%
  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (parse-args env args)
    (let-values ([(norm? ids)
		  (syntax-case args ()
		    [id
		     (identifier? (syntax id))
		     (values #f (list (syntax id)))]
		    [(id ...)
		     (values #t (syntax->list args))]
		    [_else (values #f
				   (let loop ([args args])
				     (syntax-case args ()
				       [id (identifier? args) (list args)]
				       [(id . rest)
					(cons (syntax id) (loop (syntax rest)))])))])])
      (let ([bindings (map (lambda (id) (make-object binding% #t id)) ids)])
	(values
	 (append (map cons ids bindings) env)
	 bindings
	 norm?))))

  (define (parse-let % rec? stx env loop)
    (syntax-case stx ()
      [(_ ([vars rhs] ...) . body)
       (let* ([varses (syntax->list (syntax (vars ...)))]
	      [rhses (syntax->list (syntax (rhs ...)))]
	      [var-objses (map (lambda (vars)
				 (map (lambda (var)
					(make-object binding% (not rec?) var))
				      (syntax->list vars)))
			       varses)]
	      [body-env (append
			 (apply
			  append
			  (map (lambda (var-objs vars)
				 (map cons
				      (syntax->list vars)
				      var-objs))
			       var-objses
			       varses))
			 env)])
	 (make-object %
           var-objses
           (map (lambda (rhs)
                  (loop rhs (if rec? body-env env)))
                rhses)
           (loop (syntax (begin . body)) body-env)
           stx
	   (list stx)))]))

  (define (stx-bound-assq ssym l)
    (ormap (lambda (p)
	     (and (bound-identifier=? ssym (car p))
		  p))
	   l))

  (define (lookup-clone binding var env)
    (let ([s (assq binding env)])
      (if s
	  (let ([b (cdr s)])
	    (if (b . is-a? . binding%)
		(make-object ref% b (send var get-stx) (send var get-cert-stxes))
		;; it's a global%:
		b))
	  var)))

  (define dummy 'dummy) ; for #%variable-reference

  (define (make-parse top?)
    (lambda (stx env trans? in-module? tables)
      (kernel-syntax-case stx trans?
	[id
	 (identifier? stx)
	 (let ([a (stx-bound-assq stx env)])
	   (if a
	       (make-object ref% (cdr a) stx)
	       (make-object global% trans? tables #f stx)))]

	[(#%top . id)
	 (make-object global% trans? tables #t (syntax id))]
	
	[(#%variable-reference . val)
	 (make-object constant% (#%variable-reference dummy) stx)]

	[(define-values names rhs)
	 (make-object variable-def% 
           (syntax->list (syntax names))
           (parse (syntax rhs) env #f in-module? tables)
           tables
           stx)]
	
	[(define-syntaxes names rhs)
	 (make-object syntax-def% 
           (syntax->list (syntax names))
           (parse (syntax rhs) env #t in-module? tables)
           tables
           stx)]
	
	[(define-values-for-syntax names rhs)
	 (make-object for-syntax-def% 
           (syntax->list (syntax names))
           (parse (syntax rhs) env #t in-module? tables)
           tables
           stx)]
	
	[(begin . exprs)
	 (make-object begin%
           (map (lambda (e) ((if top? parse-top parse) e env trans? in-module? tables))
                (syntax->list (syntax exprs)))
           stx)]

	[(begin0 expr . exprs)
	 (make-object begin0%
           (parse (syntax expr) env trans? in-module? tables)
           (parse (syntax (begin . exprs)) env trans? in-module? tables)
           stx)]

	[(quote expr)
	 (make-object constant% (syntax->datum (syntax expr)) stx)]

	[(quote-syntax expr)
	 (make-object constant% (syntax expr) stx)]

	[(#%plain-lambda args . body)
	 (let-values ([(env args norm?) (parse-args env (syntax args))])
	   (make-object lambda%
             (list args)
             (list norm?)
             (list (parse (syntax (begin . body)) env trans? in-module? tables))
             stx))]

	[(case-lambda [args . body] ...)
	 (let-values ([(envs argses norm?s)
		       (let ([es+as+n?s
			      (map
			       (lambda (args)
				 (let-values ([(env args norm?) (parse-args env args)])
				   (cons env (cons args norm?))))
			       (syntax->list (syntax (args ...))))])
			 (values
			  (map car es+as+n?s)
			  (map cadr es+as+n?s)
			  (map cddr es+as+n?s)))])
	   (make-object lambda%
             argses
             norm?s
             (map (lambda (env body)
                    (with-syntax ([body body])
                      (parse (syntax (begin . body)) env trans? in-module? tables)))
                  envs
                  (syntax->list (syntax (body ...))))
             stx))]
	
	[(let-values . _)
	 (parse-let let% #f stx env
		    (lambda (b env) (parse b env trans? in-module? tables)))]
	[(letrec-values . _)
	 (parse-let letrec% #t stx env
		    (lambda (b env) (parse b env trans? in-module? tables)))]

	[(set! var rhs)
	 (make-object set!% 
           (parse (syntax var) env trans? in-module? tables)
           (parse (syntax rhs) env trans? in-module? tables)
           stx)]

	[(if test then else)
	 (make-object if%
           (parse (syntax test) env trans? in-module? tables)
           (parse (syntax then) env trans? in-module? tables)
           (parse (syntax else) env trans? in-module? tables)
           stx)]
	
	[(with-continuation-mark k v body)
	 (make-object wcm% 
           (parse (syntax k) env trans? in-module? tables)
           (parse (syntax v) env trans? in-module? tables)
           (parse (syntax body) env trans? in-module? tables)
           stx)]
	
	[(#%plain-app)
	 (make-object constant% null stx)]
	
	[(#%plain-app func . args)
	 (make-object app% 
           (parse (syntax func) env trans? in-module? tables)
           (map (lambda (v) (parse v env trans? in-module? tables)) (syntax->list (syntax args)))
           tables
           stx)]

	[(module name init-require (#%plain-module-begin . body))
	 (let* ([body (map (lambda (x)
			     (parse x env #f #t tables))
			   (syntax->list (syntax body)))]
		[et-body
		 (filter (lambda (x) (or (x . is-a? . syntax-def%)
					 (x . is-a? . for-syntax-def%)))
			 body)]
		[rt-body
		 (filter (lambda (x) (not (or (x . is-a? . syntax-def%)
					      (x . is-a? . for-syntax-def%)
					      (x . is-a? . require/provide%))))
			 body)]
		[req-prov
		 (filter (lambda (x) (x . is-a? . require/provide%))
			 body)])
	   (make-object module%
             rt-body
             et-body
             (syntax name)
             (syntax init-require)
             req-prov
             tables 
             (syntax-case stx ()
               [(m n ir mb) #'mb])
             stx))]
        
	[(#%require . i) (make-object require/provide% stx)]
	[(#%provide i ...) (make-object require/provide% stx)]

        [(#%expression e)
         (parse (syntax e) env trans? in-module? tables)]

	[else 
         (error 'parse "unknown expression: ~a" (syntax->datum stx))])))

  (define parse (make-parse #f))
  (define parse-top (make-parse #t))

  (define (create-tables)
    (make-tables (make-hasheq) (make-hasheq)))

  
  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Optimizer
  ;; the driver function
  ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define optimize 
    (opt-lambda (e [for-mzc? #f])
      (let ([p (parse-top e null #f #f (create-tables))])
	(send p set-mutability)
        (send p reorganize)
	(send p set-known-values)
	(let ([p (send p simplify (make-context 'all null))])
	  (let ([v (get-sexpr (if for-mzc?
				  p
				  (send p deorganize)))])
	    v)))))
	
  (provide optimize))
