;;; box.scm -- determine which expressions need to be boxed ;;; ;;; author : Sandra Loosemore ;;; date : 03 Apr 1993 ;;; ;;; ;;; This phase determines whether expressions need to be boxed or unboxed. ;;; ;;; In the case of an expression that needs to be boxed, it determines ;;; whether it can be evaluated eagerly and boxed or whether a delay ;;; must be constructed. ;;; ;;; In the case of an expression that needs to be unboxed, it determines ;;; whether it is already known to have been evaluated and can simply ;;; be unboxed instead of checking for a delay that must be forced. ;;; ;;; This phase may mark previously non-strict variables as strict if their ;;; initializers can be evaluated eagerly. However, doing this evaluation ;;; eagerly never causes any other non-strict variables to be forced, ;;; so there is no need to propagate this strictness information backwards ;;; (as happens in the var-strictness-walk pass). ;;;====================================================================== ;;; Top-level function ;;;====================================================================== ;;; Complexity computation (define-integrable delay-complexity 10) (define-integrable unbox-complexity 1) (define-integrable box-complexity 2) (define-integrable sel-complexity 1) (define-integrable is-constructor-complexity 1) (define-integrable pack-complexity 2) (define-integrable con-number-complexity 1) (define (add-complexity c1 c2) (cond ((not c1) '#f) ((not c2) '#f) (else ;; *** We might want to establish an arbitrary cutoff here. ;; *** e.g., if complexity > N then set it to '#f. (the fixnum (+ (the fixnum c1) (the fixnum c2)))))) ;;; The second argument to the walker is a list of things ;;; that are known to have been forced already. ;;; The third argument is a list of variables that have not yet ;;; been initialized. ;;; Walkers return two values: a new value for already-forced and ;;; the complexity of the expression. ;;; This helper function sets the unboxed? and cheap? bits for the ;;; code generator, and adjusts the basic complexity to account for ;;; forces, boxes, and delays. ;;; ;;; The basic decision tree for the code generator should be: ;;; if unboxed? ;;; then if strict-result? ;;; then generate x (1) ;;; else if cheap? ;;; then generate (unbox x) (2) ;;; else generate (force x) (3) ;;; else if strict-result? ;;; then if cheap? ;;; then generate (box x) (4) ;;; else generate (delay x) (5) ;;; else if cheap? ;;; then generate x (6) ;;; then generate (delay (force x)) (7) ;;; See function do-codegen in codegen.scm. (define (do-box-analysis object already-forced uninitialized unboxed?) (setf (flic-exp-unboxed? object) unboxed?) (multiple-value-bind (result complexity) (box-analysis object already-forced uninitialized) (setf complexity (if unboxed? ;; If the expression returns a boxed value and we want ;; an unboxed one, we may need to do a force. (if (flic-exp-strict-result? object) (begin ; case (1) ;; this flic-exp-cheap? bit is used only by ;; exp-would-be-cheap? below -- not by codegen (setf (flic-exp-cheap? object) (if complexity '#t '#f)) complexity) (if (already-forced? object already-forced) (begin ; case (2) (setf (flic-exp-cheap? object) '#t) (add-complexity complexity unbox-complexity)) (begin ; case (3) (setf (flic-exp-cheap? object) '#f) '#f))) ;; We want a boxed value. If the expression already ;; returns a boxed value, return its complexity directly; ;; otherwise return the cost of either boxing or delaying it. (if (flic-exp-strict-result? object) (if complexity (begin ; case (4) (setf (flic-exp-cheap? object) '#t) (add-complexity complexity box-complexity)) (begin ; case (5) (setf (flic-exp-cheap? object) '#f) delay-complexity)) (if complexity (begin (setf (flic-exp-cheap? object) '#t) ; case (6) complexity) (begin ; case (7) (setf (flic-exp-cheap? object) '#f) delay-complexity))) )) (values (if unboxed? (note-already-forced object result) result) complexity))) ;;;====================================================================== ;;; Code walk ;;;====================================================================== (define *local-function-calls* '()) (define-flic-walker box-analysis (object already-forced uninitialized)) (define-box-analysis flic-lambda (object already-forced uninitialized) (do-box-analysis (flic-lambda-body object) already-forced uninitialized '#t) (values already-forced 0)) (define-box-analysis flic-let (object already-forced uninitialized) (let ((bindings (flic-let-bindings object))) (dynamic-let ((*local-function-calls* (dynamic *local-function-calls*))) (dolist (var bindings) ;; Note local functions (when (and (not (var-toplevel? var)) (is-type? 'flic-lambda (var-value var)) (not (var-standard-refs? var))) (push (cons var '()) (dynamic *local-function-calls*)))) (multiple-value-bind (already-forced complexity) (box-analysis-let-aux object already-forced uninitialized) (dolist (var bindings) ;; Go back and reexamine local functions to see whether ;; we can make more arguments strict, based on the values ;; the function is actually called with. (let ((stuff (assq var (dynamic *local-function-calls*)))) (when stuff (maybe-make-more-arguments-strict var (cdr stuff))))) (values already-forced complexity))))) (define (box-analysis-let-aux object already-forced uninitialized) (let ((recursive? (flic-let-recursive? object)) (bindings (flic-let-bindings object)) (body (flic-let-body object))) (when recursive? (setf uninitialized (append bindings uninitialized))) (dolist (var bindings) (let* ((value (var-value var)) (strict? (var-strict? var)) (result (do-box-analysis value already-forced uninitialized strict?))) (cond (strict? ;; Propagate information about things forced. (setf already-forced result)) ((and (flic-exp-cheap? value) (flic-exp-strict-result? value)) ;; The value expression is cheap unboxed value, so mark ;; the variable as strict. (setf (var-strict? var) '#t) (setf (flic-exp-unboxed? value) '#t)))) (when recursive? (pop uninitialized))) ;; *** Could be smarter about computing complexity. (values (do-box-analysis body already-forced uninitialized '#t) '#f))) (define (maybe-make-more-arguments-strict var calls) (setf (var-strictness var) (maybe-make-more-arguments-strict-aux (flic-lambda-vars (var-value var)) calls))) (define (maybe-make-more-arguments-strict-aux vars calls) (if (null? vars) '() (let ((var (car vars))) ;; If the variable is not already strict, check to see ;; whether it's always called with "cheap" arguments. (when (and (not (var-strict? var)) (every-1 (lambda (call) (exp-would-be-cheap? (car call) var)) calls)) (setf (var-strict? var) '#t) (dolist (call calls) (setf (flic-exp-unboxed? (car call)) '#t))) (cons (var-strict? var) (maybe-make-more-arguments-strict-aux (cdr vars) (map (function cdr) calls)))) )) ;;; Look for one special fixed-point case: argument used as counter-type ;;; variable. Otherwise ignore fixed points. (define (exp-would-be-cheap? exp var) (or (and (flic-exp-cheap? exp) (flic-exp-strict-result? exp)) (and (is-type? 'flic-ref exp) (eq? (flic-ref-var exp) var)) (and (is-type? 'flic-app exp) (is-type? 'flic-ref (flic-app-fn exp)) (var-complexity (flic-ref-var (flic-app-fn exp))) (every-1 (lambda (a) (exp-would-be-cheap? a var)) (flic-app-args exp))) )) (define-box-analysis flic-app (object already-forced uninitialized) (let ((fn (flic-app-fn object)) (args (flic-app-args object)) (saturated? (flic-app-saturated? object))) (cond ((and saturated? (is-type? 'flic-ref fn)) (let* ((var (flic-ref-var fn)) (stuff (assq var (dynamic *local-function-calls*)))) (when stuff (push args (cdr stuff))) (box-analysis-app-aux (var-strictness var) (var-complexity var) args already-forced uninitialized))) ((and saturated? (is-type? 'flic-pack fn)) (box-analysis-app-aux (con-slot-strict? (flic-pack-con fn)) pack-complexity args already-forced uninitialized)) (else ;; The function is going to be forced but all the arguments ;; are non-strict. (dolist (a args) (do-box-analysis a already-forced uninitialized '#f)) (values (do-box-analysis fn already-forced uninitialized '#t) '#f)) ))) ;;; Propagation of already-forced information depends on whether or ;;; not the implementation evaluates function arguments in left-to-right ;;; order. If not, we can still propagate this information upwards. (define (box-analysis-app-aux strictness complexity args already-forced uninitialized) (let ((result already-forced)) (dolist (a args) (let ((strict? (pop strictness))) (multiple-value-bind (new-result new-complexity) (do-box-analysis a already-forced uninitialized strict?) (when strict? (setf result (if left-to-right-evaluation (setf already-forced new-result) (union-already-forced new-result already-forced result)))) (setf complexity (add-complexity complexity new-complexity))))) (values result complexity))) (define-box-analysis flic-ref (object already-forced uninitialized) (values already-forced (if (memq (flic-ref-var object) uninitialized) '#f 0))) (define-box-analysis flic-const (object already-forced uninitialized) (declare (ignore object uninitialized)) (values already-forced 0)) (define-box-analysis flic-pack (object already-forced uninitialized) (declare (ignore object uninitialized)) (values already-forced 0)) ;;; For case-block and and, already-forced information can be propagated ;;; sequentially in the clauses. But only the first expression is ;;; guaranteed to be evaluated, so only it can propagate the information ;;; outwards. (define-box-analysis flic-case-block (object already-forced uninitialized) (values (box-analysis-sequence (flic-case-block-exps object) already-forced uninitialized) '#f)) (define-box-analysis flic-and (object already-forced uninitialized) (values (box-analysis-sequence (flic-and-exps object) already-forced uninitialized) '#f)) (define (box-analysis-sequence exps already-forced uninitialized) (let ((result (setf already-forced (do-box-analysis (car exps) already-forced uninitialized '#t)))) (dolist (e (cdr exps)) (setf already-forced (do-box-analysis e already-forced uninitialized '#t))) (values result already-forced))) (define-box-analysis flic-return-from (object already-forced uninitialized) (values (do-box-analysis (flic-return-from-exp object) already-forced uninitialized '#t) '#f)) ;;; For if, the test propagates to both branches and the result. ;;; Look for an important optimization: ;;; in (if (and e1 e2 ...) e-then e-else), ;;; e-then can inherit already-forced information from all of the ei ;;; rather than only from e1. ;;; *** Could be smarter about the complexity, I suppose.... ;;; *** Also could intersect already-forced results from both ;;; *** branches. (define-box-analysis flic-if (object already-forced uninitialized) (if (is-type? 'flic-and (flic-if-test-exp object)) (box-analysis-if-and-aux object already-forced uninitialized) (box-analysis-if-other-aux object already-forced uninitialized))) (define (box-analysis-if-other-aux object already-forced uninitialized) (setf already-forced (do-box-analysis (flic-if-test-exp object) already-forced uninitialized '#t)) (do-box-analysis (flic-if-then-exp object) already-forced uninitialized '#t) (do-box-analysis (flic-if-else-exp object) already-forced uninitialized '#t) (values already-forced '#f)) (define (box-analysis-if-and-aux object already-forced uninitialized) (let* ((test-exp (flic-if-test-exp object)) (subexps (flic-and-exps test-exp)) (then-exp (flic-if-then-exp object)) (else-exp (flic-if-else-exp object))) (setf (flic-exp-unboxed? test-exp) '#t) (multiple-value-bind (result1 resultn) (box-analysis-sequence subexps already-forced uninitialized) (do-box-analysis then-exp resultn uninitialized '#t) (do-box-analysis else-exp result1 uninitialized '#t) (values result1 '#f)))) (define-box-analysis flic-sel (object already-forced uninitialized) (multiple-value-bind (result complexity) (do-box-analysis (flic-sel-exp object) already-forced uninitialized '#t) (values result (add-complexity sel-complexity complexity)))) (define-box-analysis flic-is-constructor (object already-forced uninitialized) (multiple-value-bind (result complexity) (do-box-analysis (flic-is-constructor-exp object) already-forced uninitialized '#t) (values result (add-complexity is-constructor-complexity complexity)))) (define-box-analysis flic-con-number (object already-forced uninitialized) (multiple-value-bind (result complexity) (do-box-analysis (flic-con-number-exp object) already-forced uninitialized '#t) (values result (add-complexity con-number-complexity complexity)))) (define-box-analysis flic-void (object already-forced uninitialized) (declare (ignore object uninitialized)) (values already-forced 0)) ;;;====================================================================== ;;; Already-forced bookkeeping ;;;====================================================================== ;;; For now, we only keep track of variables that have been forced, ;;; and not data structure accesses. (define (already-forced? object already-forced) (and (is-type? 'flic-ref object) (memq (flic-ref-var object) already-forced))) (define (note-already-forced object already-forced) (if (is-type? 'flic-ref object) (cons (flic-ref-var object) already-forced) already-forced)) (define (union-already-forced new tail result) (cond ((eq? new tail) result) ((memq (car new) result) (union-already-forced (cdr new) tail result)) (else (union-already-forced (cdr new) tail (cons (car new) result))) ))