git.fiddlerwoaroof.com
Raw Blame History
;;; strictness.scm -- strictness analyzer
;;;
;;; author :  Sandra Loosemore
;;; date   :  28 May 1992
;;;
;;; The algorithm used here follows Consel, "Fast Strictness Analysis
;;; Via Symbolic Fixpoint Interation".
;;;
;;; The basic idea is to do a traversal of the flic structure, building
;;; a boolean term that represents the strictness of each subexpression.
;;; The boolean terms are composed of ands & ors of the argument variables
;;; to each function.  After traversing the body of the function, we can
;;; determine which argument variables are strict by examining the 
;;; corresponding term, and then we can update the strictness attribute
;;; of the var that names the function.
;;;
;;; Another traversal needs to be done to attach strictness properties
;;; to locally bound variables.  


;;; Here's the main entry point.

(define (strictness-analysis-top big-let)
  (fun-strictness-walk big-let)
  (var-strictness-walk big-let '() '())
  ;; *** This probably belongs somewhere else?
  (do-box-analysis big-let '() '() '#t)
  big-let)


;;;======================================================================
;;; Function strictness analyzer code walk
;;;======================================================================

;;; This actually involves two code walkers.  The first merely traverses
;;; structure and identifies function definitions.  The second traverses
;;; the definitions of the functions to compute their strictness.


;;; Fun-strictness-walk is the walker to find function definitions.
;;; This is trivial for everything other than flic-let.

(define-flic-walker fun-strictness-walk (object))

(define-fun-strictness-walk flic-lambda (object)
  (fun-strictness-walk (flic-lambda-body object)))

(define-fun-strictness-walk flic-let (object)
  (if (flic-let-recursive? object)
      (fun-strictness-walk-letrec object)
      (fun-strictness-walk-let* object))
  (dolist (v (flic-let-bindings object))
    (fun-strictness-walk (var-value v)))
  (fun-strictness-walk (flic-let-body object)))

(define-fun-strictness-walk flic-app (object)
  (fun-strictness-walk (flic-app-fn object))
  (for-each (function fun-strictness-walk) (flic-app-args object)))

(define-fun-strictness-walk flic-ref (object)
  (declare (ignore object))
  '#f)

(define-fun-strictness-walk flic-pack (object)
  (declare (ignore object))
  '#f)

(define-fun-strictness-walk flic-const (object)
  (declare (ignore object))
  '#f)

(define-fun-strictness-walk flic-case-block (object)
  (for-each (function fun-strictness-walk) (flic-case-block-exps object)))

(define-fun-strictness-walk flic-return-from (object)
  (fun-strictness-walk (flic-return-from-exp object)))

(define-fun-strictness-walk flic-and (object)
  (for-each (function fun-strictness-walk) (flic-and-exps object)))

(define-fun-strictness-walk flic-if (object)
  (fun-strictness-walk (flic-if-test-exp object))
  (fun-strictness-walk (flic-if-then-exp object))
  (fun-strictness-walk (flic-if-else-exp object)))

(define-fun-strictness-walk flic-sel (object)
  (fun-strictness-walk (flic-sel-exp object)))

(define-fun-strictness-walk flic-is-constructor (object)
  (fun-strictness-walk (flic-is-constructor-exp object)))

(define-fun-strictness-walk flic-con-number (object)
  (fun-strictness-walk (flic-con-number-exp object)))

(define-fun-strictness-walk flic-void (object)
  (declare (ignore object))
  '#f)



;;; Here is the magic for let bindings of function definitions.
;;; Sequential bindings are easy.  For recursive bindings, we must 
;;; keep track of mutually recursive functions.
;;; If a function binding has a strictness annotation attached,
;;; do not mess with it further.

(define (fun-strictness-walk-let* object)
  (dolist (var (flic-let-bindings object))
    (let ((val  (var-value var)))
      (when (is-type? 'flic-lambda val)
	(if (var-strictness var)
	    (mark-argument-strictness
	      (var-strictness var) (flic-lambda-vars val))
	    (compute-function-strictness var val '())))
      )))

(define (fun-strictness-walk-letrec object)
  (let ((stack   '()))
    (dolist (var (flic-let-bindings object))
      (let ((val  (var-value var)))
	(if (and (is-type? 'flic-lambda val) (not (var-strictness var)))
	    (setf stack (add-recursive-function-1 var (init-var-env) stack)))))
    (dolist (var (flic-let-bindings object))
      (let ((val  (var-value var)))
	(when (is-type? 'flic-lambda val)
	  (if (var-strictness var)
	      (mark-argument-strictness
	        (var-strictness var) (flic-lambda-vars val))
	      (compute-function-strictness var val stack)))
	))))

(define (compute-function-strictness var val stack)
  (let* ((vars  (flic-lambda-vars val))
	 (env   (add-var-binding-n vars (map (function list) vars)
				   (init-var-env)))
	 (term  (compute-strictness-walk (flic-lambda-body val) env stack)))
    (when (eq? term '#t)
      (signal-infinite-loop-function var)
      (setf (flic-lambda-body val)
	    (make-infinite-loop-error
	      (format '#f "Function ~s has an infinite loop." var))))
    (setf (var-strictness var) (munge-strictness-terms term vars))))


(define (signal-infinite-loop-function var)
  (recoverable-error 'infinite-loop-function
    "Function ~s has an infinite loop."
    var))

(define (make-infinite-loop-error msg)
  (make-flic-app
    (make-flic-ref (core-symbol "error"))
    (list (make-flic-const msg))
    '#t))

  
;;; compute-strictness-walk is the traversal to compute strictness
;;; terms.
;;; The purpose of the env is to map locally bound variables onto 
;;; strictness terms which are expressed as lists of argument variables
;;; to the function being analyzed.
;;; The purpose of the stack is to keep track of recursive function
;;; walks and recognize when we've reached a fixed point.

(define-flic-walker compute-strictness-walk (object env stack))


;;; Making a function never forces anything.

(define-compute-strictness-walk flic-lambda (object env stack)
  (declare (ignore object env stack))
  '#f)


;;; For let, add bindings to environment and get strictness of body.

(define-compute-strictness-walk flic-let (object env stack)
  (let ((bindings    (flic-let-bindings object))
	(body        (flic-let-body object))
	(recursive?  (flic-let-recursive? object)))
    (if recursive?
	;; Must add stuff to env and stack before traversing anything.
	(begin
	  (dolist (var bindings)
	    (setf env (add-var-binding-1 var '#f env)))
	  (dolist (var bindings)
	    (let ((val  (var-value var)))
	      (when (is-type? 'flic-lambda val)
		(setf stack (add-recursive-function-1 var env stack)))))
	  (dolist (var bindings)
	    (let ((val  (var-value var)))
	      (set-var-env var env (compute-strictness-walk val env stack)))))
	;; Otherwise just do things sequentially.
	;; Note that even though there is no possibility of recursion
	;; here, we must add stuff to the stack anyway so that we can
	;; walk calls in the correct env.
	(dolist (var bindings)
	  (let ((val  (var-value var)))
	    (when (is-type? 'flic-lambda val)
	      (setf stack (add-recursive-function-1 var env stack)))
	    (setf env
		  (add-var-binding-1
		    var (compute-strictness-walk val env stack) env)))))
    (compute-strictness-walk body env stack)))


;;; Treat explicit, saturated calls to named functions specially.

(define-compute-strictness-walk flic-app (object env stack)
  (let ((fn         (flic-app-fn object))
	(args       (flic-app-args object))
	(saturated? (flic-app-saturated? object)))
    (cond ((and (is-type? 'flic-ref fn) saturated?)
	   ;; Special handling for named functions.
	   (compute-application-strictness
	     (flic-ref-var fn)
	     args env stack))
	  ((and (is-type? 'flic-pack fn) saturated?)
	   ;; Similarly for constructor applications, but we always
	   ;; know which arguments are strict in advance.
	   (compute-application-strictness-aux
	      (con-slot-strict? (flic-pack-con fn))
	      args env stack))
	  (else
	   ;; Otherwise, we know that the function expression is going to
	   ;; be forced, but all of its arguments are lazy.  So ignore the
	   ;; arguments in computing the strictness of the whole expression.
	   (compute-strictness-walk fn env stack)))))


(define (compute-application-strictness var args env stack)
  (let* ((strictness          (var-strictness var))
	 (info                '#f)
	 (arg-strictness-list '#f))
    (cond ((eq? var (core-symbol "error"))
	   ;; This expression will return bottom no matter what.
	   'error)
	  (strictness
	   ;; We've already completed the walk for this function and
	   ;; determined which of its arguments are strict.
	   ;; The strictness expression for the application is the
	   ;; OR of the strictness of its non-lazy arguments.
	   (compute-application-strictness-aux strictness args env stack))
	  ((get-recursive-function-trace
	     (setf arg-strictness-list
		   (map (lambda (a) (compute-strictness-walk a env stack))
			args))
	     (setf info (get-recursive-function var stack)))
	   ;; We're already tracing this call.  Return true to
	   ;; terminate the fixpoint iteration.
	   '#t)
	  (else
	   ;; Otherwise, begin a new trace instance.
	   ;; Add stuff to the saved var-env to map references to
	   ;; the argument variables to the strictness terms for
	   ;; the actual arguments at this call site.
	   ;; References to closed-over variables within the function
	   ;; use the strictness values that were stored in the env
	   ;; at the point of function definition.
	   (let* ((env      (get-recursive-function-env info))
		  (lambda   (var-value var))
		  (body     (flic-lambda-body lambda))
		  (vars     (flic-lambda-vars lambda))
		  (result   '#f))
	     (push-recursive-function-trace arg-strictness-list info)
	     (setf result
		   (compute-strictness-walk
		     body
		     (add-var-binding-n vars arg-strictness-list env)
		     stack))
	     (pop-recursive-function-trace info)
	     result))
	  )))


(define (compute-application-strictness-aux strictness args env stack)
  (make-or-term
    (map (lambda (strict? arg)
	   (if strict? (compute-strictness-walk arg env stack) '#f))
	 strictness args)))


;;; For a reference, look up the term associated with the variable in env.
;;; If not present in the environment, ignore it; the binding was established
;;; outside the scope of the function being analyzed.

(define-compute-strictness-walk flic-ref (object env stack)
  (declare (ignore stack))
  (get-var-env (flic-ref-var object) env))
	

;;; References to constants or constructors never fail.

(define-compute-strictness-walk flic-const (object env stack)
  (declare (ignore object env stack))
  '#f)

(define-compute-strictness-walk flic-pack (object env stack)
  (declare (ignore object env stack))
  '#f)


;;; The first clause of a case-block is the only one that is always
;;; executed, so it is the only one that affects the strictness of
;;; the overall expression.

(define-compute-strictness-walk flic-case-block (object env stack)
  (compute-strictness-walk (car (flic-case-block-exps object)) env stack))


;;; Return-from fails if its subexpression fails.

(define-compute-strictness-walk flic-return-from (object env stack)
  (compute-strictness-walk (flic-return-from-exp object) env stack))


;;; For and, the first subexpression is the only one that is always
;;; executed, so it is the only one that affects the strictness of
;;; the overall expression.

(define-compute-strictness-walk flic-and (object env stack)
  (compute-strictness-walk (car (flic-and-exps object)) env stack))


;;; The strictness of an IF is the strictness of the test OR'ed
;;; with the AND of the strictness of its branches.

(define-compute-strictness-walk flic-if (object env stack)
  (make-or-term-2
    (compute-strictness-walk (flic-if-test-exp object) env stack)
    (make-and-term-2
      (compute-strictness-walk (flic-if-then-exp object) env stack)
      (compute-strictness-walk (flic-if-else-exp object) env stack))))


;;; Selecting a component of a data structure causes it to be forced,
;;; so propagate the strictness of the subexpression upwards.

(define-compute-strictness-walk flic-sel (object env stack)
  (compute-strictness-walk (flic-sel-exp object) env stack))


;;; Is-constructor and con-number force their subexpressions.

(define-compute-strictness-walk flic-is-constructor (object env stack)
  (compute-strictness-walk (flic-is-constructor-exp object) env stack))

(define-compute-strictness-walk flic-con-number (object env stack)
  (compute-strictness-walk (flic-con-number-exp object) env stack))

(define-compute-strictness-walk flic-void (object env stack)
  (declare (ignore object env stack))
  '#f)



;;;======================================================================
;;; Utilities for managing the env
;;;======================================================================

;;; The env is just an a-list.

(define (init-var-env)
  '())

(define (add-var-binding-1 var binding env)
  (cons (cons var binding) env))

(define (add-var-binding-n vars bindings env)
  (if (null? vars)
      env
      (add-var-binding-n (cdr vars) (cdr bindings)
			 (cons (cons (car vars) (car bindings)) env))))

(define (get-var-env var env)
  (let ((stuff  (assq var env)))
    (if stuff
	(cdr stuff)
	'#f)))

(define (set-var-env var env new-value)
  (let ((stuff  (assq var env)))
    (if stuff
	(setf (cdr stuff) new-value)
	(error "Can't find binding for ~s in environment." var))))
  


;;;======================================================================
;;; Utilities for managing the stack
;;;======================================================================

;;; For now, the stack is just an a-list too.
;;; Some sort of hashing scheme could also be used instead of a linear
;;; search, but if the iteration depth for the fixpoint analysis is
;;; small, it's probably not worth the trouble.

(define (add-recursive-function-1 var env stack)
  (cons (list var env '()) stack))

(define (get-recursive-function var stack)
  (or (assq var stack)
      (error "Can't find entry for ~s in stack." var)))

(define (get-recursive-function-env entry)
  (cadr entry))

(define (push-recursive-function-trace new-args entry)
  (push new-args (caddr entry)))

(define (pop-recursive-function-trace entry)
  (pop (caddr entry)))

(define (get-recursive-function-trace args entry)
  (get-recursive-function-trace-aux args (caddr entry)))

(define (get-recursive-function-trace-aux args list)
  (cond ((null? list)
	 '#f)
	((every (function term=) args (car list))
	 '#t)
	(else
	 (get-recursive-function-trace-aux args (cdr list)))))


;;;======================================================================
;;; Utilities for boolean terms
;;;======================================================================


;;; A term is either #t, #f, the symbol 'error, or a list of variables 
;;; (which are implicitly or'ed together).
;;; #t and 'error are treated identically, except that #t indicates
;;; failure because of infinite recursion and 'error indicates failure
;;; due to a call to the error function.
;;; In general, AND terms add nothing to the result, so to reduce
;;; needless computation we generally reduce (and a b) to #f.

;;; Make an OR term.  First look for some obvious special cases as an
;;; efficiency hack, otherwise fall through to more general code.

(define (make-or-term terms)
  (cond ((null? terms)
	 '#f)
	((null? (cdr terms))
	 (car terms))
	((eq? (car terms) '#t)
	 '#t)
	((eq? (car terms) 'error)
	 'error)
	((eq? (car terms) '#f)
	 (make-or-term (cdr terms)))
	(else
	 (make-or-term-2 (car terms) (make-or-term (cdr terms))))))

(define (make-or-term-2 term1 term2)
  (cond ((eq? term2 '#t)
	 '#t)
	((eq? term2 'error)
	 'error)
	((eq? term2 '#f)
	 term1)
	((eq? term1 '#t)
	 '#t)
	((eq? term1 'error)
	 'error)
	((eq? term1 '#f)
	 term2)
	;; At this point we know both terms are variable lists.
	((implies? term2 term1)
	 term2)
	((implies? term1 term2)
	 term1)
	(else
	 (merge-list-terms term1 term2))))


;;;  Merge the two lists, throwing out duplicate variables.

(define (merge-list-terms list1 list2)
  (cond ((null? list1)
	 list2)
	((null? list2)
	 list1)
	((eq? (car list1) (car list2))
	 (cons (car list1) (merge-list-terms (cdr list1) (cdr list2))))
	((var< (car list1) (car list2))
	 (cons (car list1) (merge-list-terms (cdr list1) list2)))
	(else
	 (cons (car list2) (merge-list-terms list1 (cdr list2))))))


;;; Helper function: does term1 imply term2?
;;; True if every subterm of term2 is also included in term1.

(define (implies? term1 term2)
  (every (lambda (v2) (memq v2 term1)) term2))


;;; Make an AND term.  Because we don't want to build up arbitrarily
;;; complex AND expressions, basically just compute an OR list that 
;;; represents the intersection of the subterms.

(define (make-and-term terms)
  (cond ((null? terms)
	 '#f)
	((null? (cdr terms))
	 (car terms))
	((eq? (car terms) '#t)
	 (make-and-term (cdr terms)))
	((eq? (car terms) 'error)
	 (make-and-term (cdr terms)))
	((eq? (car terms) '#f)
	 '#f)
	(else
	 (make-and-term-2 (car terms) (make-and-term (cdr terms))))))

(define (make-and-term-2 term1 term2)
  (cond ((eq? term2 '#t)
	 term1)
	((eq? term2 'error)
	 term1)
	((eq? term2 '#f)
	 '#f)
	((eq? term1 '#t)
	 term2)
	((eq? term1 'error)
	 term2)
	((eq? term1 '#f)
	 '#f)
	;; At this point we know both terms are variable lists.
	((implies? term2 term1)
	 term1)
	((implies? term1 term2)
	 term2)
	(else
	 (let ((result  '()))
	   (dolist (v term1)
	     (if (memq v term2)
		 (push v result)))
	   (if (null? result)
	       '#f
	       (nreverse result))))
	))


;;; Subterms of an and/or term are always sorted, so that to compare
;;; two terms we can just compare subterms componentwise.

(define (term= term1 term2)
  (or (eq? term1 term2)
      (and (pair? term1)
	   (pair? term2)
	   (eq? (car term1) (car term2))
	   (term= (cdr term1) (cdr term2)))))


;;; Variables within an OR-list are sorted alphabetically by names.

(define (var< var1 var2)
  (string<? (symbol->string (def-name var1))
	    (symbol->string (def-name var2))))


;;; Determine which of the vars are present in the term.

(define (munge-strictness-terms term vars)
  (map (lambda (v)
	 (setf (var-strict? v)
	       (cond ((var-force-strict? v)
		      '#t)
		     ((eq? term '#t)
		      '#t)
		     ((eq? term 'error)
		      '#t)
		     ((eq? term '#f)
		      '#f)
		     ((memq v term)
		      '#t)
		     (else
		      '#f))))
       vars))

(define (mark-argument-strictness strictness vars)
  (map (lambda (s v) (setf (var-strict? v) s)) strictness vars))



;;;======================================================================
;;; Variable strictness propagation code walk
;;;======================================================================

;;; Walk the code, marking any vars found in strict contexts as strict.
;;; Locally bound variables are consed onto the varlist.  This is
;;; used to determine which variables can be marked as strict when they
;;; appear in strict contexts.
;;; When walking something that does not appear in a strict context
;;; or that is not always evaluated, reinitialize varlist to the empty
;;; list.
;;; The stack is used to keep track of variables that have not been
;;; initialized yet, so that we can detect some kinds of infinite loops.
;;; When walking something that is not always evaluated, reset this to 
;;; the empty list.

(define-flic-walker var-strictness-walk (object varlist stack))



;;; Since the body of the lambda might not be evaluated, reset
;;; both varlist and stack.

(define-var-strictness-walk flic-lambda (object varlist stack)
  (declare (ignore varlist stack))
  (var-strictness-walk (flic-lambda-body object) '() '()))


;;; The basic idea for let is to find the variables that are strict in 
;;; the body first, and propagate that information backwards to the 
;;; binding initializers.

(define-var-strictness-walk flic-let (object varlist stack)
  (let ((bindings  (flic-let-bindings object)))
    (var-strictness-walk-let-aux
      bindings
      (flic-let-body object)
      (append bindings varlist)
      (append bindings stack)
      (flic-let-recursive? object))))

(define (var-strictness-walk-let-aux bindings body varlist stack recursive?)
  (if (null? bindings)
      (var-strictness-walk body varlist stack)
      (begin
	(var-strictness-walk-let-aux
	  (cdr bindings) body varlist (cdr stack) recursive?)
	(let* ((var  (car bindings))
	       (val  (var-value var)))
	  (cond ((var-strict? var)
		 ;; Recursive variables have to be set back to unstrict
		 ;; because the value form might contain forward references.
		 ;; The box analyzer will set them to strict again if the
		 ;; value forms are safe.
		 (when recursive? (setf (var-strict? var) '#f))
		 ;; Detect x = 1 + x circularities here
		 (var-strictness-walk val varlist stack))
		((flic-exp-strict-result? val)
		 ;; The val is going to be wrapped in a delay.
		 (var-strictness-walk val '() '()))
		(else
		 ;; Watch out for x = x and x = cdr x circularities.
		 ;; *** I am still a little confused about this.  It
		 ;; *** seems like the stack should be passed through
		 ;; *** when walking already-boxed values that appear as
                 ;; *** non-strict function arguments as well, but doing
		 ;; *** so generates some apparently bogus complaints
		 ;; *** about infinite loops.  So maybe doing it here
		 ;; *** is incorrect too, and we just haven't run across
		 ;; *** a test case that triggers it???
		 (var-strictness-walk val '() stack))
		)))))


(define (flic-exp-strict-result? val)
  (cond ((is-type? 'flic-ref val)
	 (var-strict? (flic-ref-var val)))
	((is-type? 'flic-sel val)
	 (list-ref (con-slot-strict? (flic-sel-con val)) (flic-sel-i val)))
	(else
	 '#t)))

(define-var-strictness-walk flic-app (object varlist stack)
  (let ((fn           (flic-app-fn object))
	(args         (flic-app-args object))
	(saturated?   (flic-app-saturated? object)))
    (cond ((and saturated? (is-type? 'flic-ref fn))
	   ;; Strictness of function should be stored on var
	   (do-var-strictness-flic-app-aux
	     (var-strictness (flic-ref-var fn))
	     fn args varlist stack))
	  ((and saturated? (is-type? 'flic-pack fn))
	   ;; Strictness of constructor should be stored on con
	   (do-var-strictness-flic-app-aux
	     (con-slot-strict? (flic-pack-con fn))
	     fn args varlist stack))
	  (else
	   ;; All arguments are non-strict
	   (var-strictness-walk fn varlist stack)
	   (dolist (a args)
	     (var-strictness-walk a '() '()))))))

(define (do-var-strictness-flic-app-aux strictness fn args varlist stack)
  (when (not strictness)
    (error "Can't find strictness for function ~s." fn))
  (dolist (a args)
    (if (pop strictness)
	(var-strictness-walk a varlist stack)
	(var-strictness-walk a '() '()))))


(define-var-strictness-walk flic-ref (object varlist stack)
  (let ((var  (flic-ref-var object)))
    (cond ((memq var stack)
	   ;; Circular variable definition detected.
	   (signal-infinite-loop-variable var)
	   (setf (var-value var)
		 (make-infinite-loop-error
		   (format '#f "Variable ~s has an infinite loop." var))))
	  ((memq var varlist)
	   (setf (var-strict? var) '#t))
	  (else
	   '#f))))

(define (signal-infinite-loop-variable var)
  (recoverable-error 'infinite-loop-variable
    "Variable ~s has an infinite loop."
    var))

(define-var-strictness-walk flic-const (object varlist stack)
  (declare (ignore object varlist stack))
  '#f)

(define-var-strictness-walk flic-pack (object varlist stack)
  (declare (ignore object varlist stack))
  '#f)

(define-var-strictness-walk flic-case-block (object varlist stack)
  (var-strictness-walk (car (flic-case-block-exps object)) varlist stack)
  (dolist (exp (cdr (flic-case-block-exps object)))
    (var-strictness-walk exp '() '())))

(define-var-strictness-walk flic-return-from (object varlist stack)
  (var-strictness-walk (flic-return-from-exp object) varlist stack))

(define-var-strictness-walk flic-and (object varlist stack)
  (var-strictness-walk (car (flic-and-exps object)) varlist stack)
  (dolist (exp (cdr (flic-and-exps object)))
    (var-strictness-walk exp '() '())))

(define-var-strictness-walk flic-if (object varlist stack)
  (var-strictness-walk (flic-if-test-exp object) varlist stack)
  (var-strictness-walk (flic-if-then-exp object) '() '())
  (var-strictness-walk (flic-if-else-exp object) '() '()))

(define-var-strictness-walk flic-sel (object varlist stack)
  (var-strictness-walk (flic-sel-exp object) varlist stack))

(define-var-strictness-walk flic-is-constructor (object varlist stack)
  (var-strictness-walk (flic-is-constructor-exp object) varlist stack))

(define-var-strictness-walk flic-con-number (object varlist stack)
  (var-strictness-walk (flic-con-number-exp object) varlist stack))

(define-var-strictness-walk flic-void (object varlist stack)
  (declare (ignore object varlist stack))
  '#f)



;;;======================================================================
;;; Printer support
;;;======================================================================

(define (strictness-analysis-printer big-let)
  (print-strictness big-let 0))

(define (print-strictness-list list depth)
  (dolist (o list)
    (print-strictness o depth)))

(define (print-strictness-indent depth)
  (dotimes (i (* 2 depth))
    (declare (ignorable i))
    (write-char #\space)))

(define (strictness-string bool)
  (if bool "#t" "#f"))

(define-flic-walker print-strictness (object depth))

(define-print-strictness flic-lambda (object depth)
  (print-strictness-indent depth)
  (format '#t "In anonymous function:~%")
  (print-strictness (flic-lambda-body object) (1+ depth)))

(define-print-strictness flic-let (object depth)
  (dolist (var (flic-let-bindings object))
    (let ((val  (var-value var)))
      (if (is-type? 'flic-lambda val)
	  (begin
	    (print-strictness-indent depth)
	    (format '#t "Function ~s has argument strictness ~a.~%"
		    var
		    (map (function strictness-string) (var-strictness var)))
	    (print-strictness (flic-lambda-body val) (1+ depth)))
	  (begin
	    (print-strictness-indent depth)
	    (format '#t "Variable ~s has strictness ~a.~%"
		    var
		    (strictness-string (var-strict? var)))
	    (print-strictness val depth)))))
  (print-strictness (flic-let-body object) depth))

(define-print-strictness flic-app (object depth)
  (print-strictness (flic-app-fn object) depth)
  (print-strictness-list (flic-app-args object) depth))

(define-print-strictness flic-ref (object depth)
  (declare (ignore object depth))
  '#f)

(define-print-strictness flic-const (object depth)
  (declare (ignore object depth))
  '#f)

(define-print-strictness flic-pack (object depth)
  (declare (ignore object depth))
  '#f)

(define-print-strictness flic-case-block (object depth)
  (print-strictness-list (flic-case-block-exps object) depth))

(define-print-strictness flic-return-from (object depth)
  (print-strictness (flic-return-from-exp object) depth))

(define-print-strictness flic-and (object depth)
  (print-strictness-list (flic-and-exps object) depth))

(define-print-strictness flic-if (object depth)
  (print-strictness (flic-if-test-exp object) depth)
  (print-strictness (flic-if-then-exp object) depth)
  (print-strictness (flic-if-else-exp object) depth))

(define-print-strictness flic-sel (object depth)
  (print-strictness (flic-sel-exp object) depth))

(define-print-strictness flic-is-constructor (object depth)
  (print-strictness (flic-is-constructor-exp object) depth))

(define-print-strictness flic-con-number (object depth)
  (print-strictness (flic-con-number-exp object) depth))

(define-print-strictness flic-void (object depth)
  (declare (ignore object depth))
  '#f)