; Copyright (c) 1993-1999 by Richard Kelsey. See file COPYING. ; This file contains miscellaneous utilities for accessing and modifying the ; node tree. ; Get the root of the tree containing node. (define (node-base node) (do ((p node (node-parent p))) ((not (node? (node-parent p))) p))) ; Find the procedure node that contains NODE. Go up one parent at a time ; until a lambda node is found, then go up two at a time, skipping the ; intervening call nodes. (define (containing-procedure node) (do ((node (node-parent node) (node-parent node))) ((lambda-node? node) (do ((node node (node-parent (node-parent node)))) ((proc-lambda? node) node))))) ; Trivial calls are those whose parents are call nodes. (define (trivial? call) (call-node? (node-parent call))) (define (nontrivial? call) (lambda-node? (node-parent call))) (define (nontrivial-ancestor call) (let loop ((call call)) (if (or (not (node? (node-parent call))) (nontrivial? call)) call (loop (node-parent call))))) (define (calls-this-primop? call id) (eq? id (primop-id (call-primop call)))) ; Return the variable to which a value is bound by LET or LETREC. (define (bound-to-variable node) (let ((parent (node-parent node))) (case (primop-id (call-primop parent)) ((let) (if (n= 0 (node-index node)) (list-ref (lambda-variables (call-arg parent 0)) (- (node-index node) 1)) #f)) ((letrec2) (if (< 1 (node-index node)) (list-ref (lambda-variables (variable-binder (reference-variable (call-arg parent 1)))) (- (node-index node) 1)) #f)) (else #f)))) ; Return a list of all the reference to lambda-node L's value that call it. ; If not all can be identified then #F is returned. (define (find-calls l) (let ((refs (cond ((bound-to-variable l) => variable-refs) ((called-node? l) (list l)) (else #f)))) (cond ((and refs (every? called-node? refs)) refs) ((calls-known? l) (bug "cannot find calls for known lambda ~S" l)) (else #f)))) ; Walk (or map) a tree-modifying procedure down a variable's references. (define (walk-refs-safely proc var) (for-each proc (copy-list (variable-refs var)))) ; Return #t if the total primop-cost of NODE is less than SIZE. (define (small-node? node size) (let label ((call (lambda-body node))) (set! size (- size (primop-cost call))) (if (>= size 0) (walk-vector (lambda (n) (cond ((lambda-node? n) (label (lambda-body n))) ((call-node? n) (label n)))) (call-args call)))) (>= size 0)) ; True if executing NODE involves side-effects. (define (side-effects? node . permissable) (let ((permissable (cons #f permissable))) (let label ((node node)) (cond ((not (call-node? node)) #f) ((or (n= 0 (call-exits node)) (not (memq (primop-side-effects (call-primop node)) permissable))) #t) (else (let loop ((i (- (call-arg-count node) 1))) (cond ((< i 0) #f) ((label (call-arg node i)) #t) (else (loop (- i 1)))))))))) ; A conservative check - is there only one SET-CONTENTS call for the owner and ; are all calls between CALL and the LETREC call that binds the owner calls to ; SET-CONTENTS? ;(define (single-letrec-set? call) ; (let ((owner (call-arg call set/owner))) ; (and (reference-node? owner) ; (every? (lambda (ref) ; (or (eq? (node-parent ref) call) ; (not (set-reference? ref)))) ; (variable-refs (reference-variable owner)))))) ;(define (set-reference? node) ; (and (eq? 'set-contents ; (primop-id (call-primop (node-parent node)))) ; (= (node-index node) set/owner))) ;------------------------------------------------------------------------------- (define the-undefined-value (list '*undefined-value*)) (define (undefined-value? x) (eq? x the-undefined-value)) (define (undefined-value-node? x) (and (literal-node? x) (undefined-value? (literal-value x)))) (define (make-undefined-literal) (make-literal-node the-undefined-value #f)) ;------------------------------------------------------------------------------- ; Finding the lambda node called by CALL, JUMP, or RETURN (define (called-node? node) (and (node? (node-parent node)) (eq? node (called-node (node-parent node))))) (define (called-node call) (cond ((and (primop-procedure? (call-primop call)) (primop-call-index (call-primop call))) => (lambda (i) (call-arg call i))) (else '#f))) (define (called-lambda call) (get-lambda-value (call-arg call (primop-call-index (call-primop call))))) (define (get-lambda-value value) (cond ((lambda-node? value) value) ((reference-node? value) (get-variable-lambda (reference-variable value))) (else (error "peculiar procedure in ~S" value)))) (define (get-variable-lambda variable) (if (global-variable? variable) (or (variable-known-lambda variable) (error "peculiar procedure variable ~S" variable)) (let* ((binder (variable-binder variable)) (index (node-index binder)) (call (node-parent binder)) (lose (lambda () (error "peculiar procedure variable ~S" variable)))) (case (primop-id (call-primop call)) ((let) (if (= 0 index) (get-lambda-value (call-arg call (+ 1 (variable-index variable)))) (lose))) ((letrec1) (if (= 0 index) (get-letrec-variable-lambda variable) (lose))) ((call) (if (and (= 1 index) (= 0 (variable-index variable))) ; var is a continuation var (get-lambda-value (call-arg call 0)) (lose))) (else (lose)))))) ; Some of the checking can be removed once I know the LETREC code works. (define (get-letrec-variable-lambda variable) (let* ((binder (variable-binder variable)) (call (lambda-body binder))) (if (and (eq? 'letrec2 (primop-id (call-primop call))) (reference-node? (call-arg call 1)) (eq? (car (lambda-variables binder)) (reference-variable (call-arg call 1)))) (call-arg call (+ 1 (variable-index variable))) (error "LETREC is incorrectly organized ~S" (node-parent binder))))) ;(define (get-cell-variable-lambda variable) ; (let ((ref (first set-reference? (variable-refs variable)))) ; (if (and ref ; (eq? 'letrec ; (literal-value (call-arg (node-parent ref) set/type)))) ; (get-lambda-value (call-arg (node-parent ref) set/value)) ; (error "peculiar lambda cell ~S" variable)))) ;------------------------------------------------------------------------------- ; Attaching and detaching arguments to calls ; Make ARGS the arguments of call node PARENT. ARGS may contain #f. (define (attach-call-args parent args) (let ((len (call-arg-count parent))) (let loop ((args args) (i 0)) (cond ((null? args) (if (< i (- len 1)) (bug '"too few arguments added to node ~S" parent)) (values)) ((>= i len) (bug '"too many arguments added to node ~S" parent)) (else (if (car args) (attach parent i (car args))) (loop (cdr args) (+ 1 i))))))) ; Remove all of the arguments of NODE. (define (remove-call-args node) (let ((len (call-arg-count node))) (do ((i 1 (+ i 1))) ((>= i len)) (if (not (empty? (call-arg node i))) (erase (detach (call-arg node i))))) (values))) ; Replace the arguments of call node NODE with NEW-ARGS. (define (replace-call-args node new-args) (let ((len (length new-args))) (remove-call-args node) (if (n= len (call-arg-count node)) (let ((new (make-vector len empty)) (old (call-args node))) (set-call-args! node new))) (attach-call-args node new-args))) ; Remove all arguments to CALL that are EMPTY?. COUNT is the number of ; non-EMPTY? arguments. (define (remove-null-arguments call count) (let ((old (call-args call)) (new (make-vector count empty))) (let loop ((i 0) (j 0)) (cond ((>= j count) (values)) ((not (empty? (vector-ref old i))) (set-node-index! (vector-ref old i) j) (vector-set! new j (vector-ref old i)) (loop (+ i 1) (+ j 1))) (else (loop (+ i 1) j)))) (set-call-args! call new) (values))) ; Remove all but the first COUNT arguments from CALL. (define (shorten-call-args call count) (let ((old (call-args call)) (new (make-vector count empty))) (vector-replace new old count) (do ((i (+ count 1) (+ i 1))) ((>= i (vector-length old))) (erase (vector-ref old i))) (set-call-args! call new) (values))) ; Insert ARG as the INDEXth argument to CALL. (define (insert-call-arg call index arg) (let* ((old (call-args call)) (len (vector-length old)) (new (make-vector (+ 1 len) empty))) (vector-replace new old index) (do ((i index (+ i 1))) ((>= i len)) (vector-set! new (+ i 1) (vector-ref old i)) (set-node-index! (vector-ref old i) (+ i 1))) (set-call-args! call new) (attach call index arg) (values))) ; Remove the INDEXth argument to CALL. (define (remove-call-arg call index) (let* ((old (call-args call)) (len (- (vector-length old) 1)) (new (make-vector len))) (vector-replace new old index) (if (node? (vector-ref old index)) (erase (detach (vector-ref old index)))) (do ((i index (+ i 1))) ((>= i len)) (vector-set! new i (vector-ref old (+ i 1))) (set-node-index! (vector-ref new i) i)) (set-call-args! call new) (if (< index (call-exits call)) (set-call-exits! call (- (call-exits call) 1))) (values))) ; Add ARG to the end of CALL's arguments. (define (append-call-arg call arg) (insert-call-arg call (call-arg-count call) arg)) ; Replace CALL with the body of its continuation. (define (remove-body call) (if (n= 1 (call-exits call)) (bug "removing a call with ~D exits" (call-exits call)) (replace-body call (detach-body (lambda-body (call-arg call 0)))))) ; Avoiding N-Ary Procedures ; These are used in the expansion of the LET-NODES macro. (define (attach-two-call-args node a0 a1) (attach node 0 a0) (attach node 1 a1)) (define (attach-three-call-args node a0 a1 a2) (attach node 0 a0) (attach node 1 a1) (attach node 2 a2)) (define (attach-four-call-args node a0 a1 a2 a3) (attach node 0 a0) (attach node 1 a1) (attach node 2 a2) (attach node 3 a3)) (define (attach-five-call-args node a0 a1 a2 a3 a4) (attach node 0 a0) (attach node 1 a1) (attach node 2 a2) (attach node 3 a3) (attach node 4 a4)) ;------------------------------------------------------------------------------- ; Bind VARS to VALUES using letrec at CALL. If CALL is already a letrec ; call, just add to it, otherwise make a new one. (define (put-in-letrec vars values call) (cond ((eq? 'letrec2 (primop-id (call-primop call))) (let ((binder (node-parent call))) (mark-changed call) (for-each (lambda (var) (set-variable-binder! var binder)) vars) (set-lambda-variables! binder (append (lambda-variables binder) vars)) (for-each (lambda (value) (append-call-arg call value)) values))) (else (move-body call (lambda (call) (let-nodes ((c (letrec1 1 l2)) (l2 ((x #f) . vars) (letrec2 1 l3 (* x) . values)) (l3 () call)) c)))))) ;------------------------------------------------------------------------------- ; Changing lambda-nodes' variable lists (define (remove-lambda-variable l-node index) (remove-variable l-node (list-ref (lambda-variables l-node) index))) (define (remove-variable l-node var) (if (used? var) (bug '"cannot remove referenced variable ~s" var)) (erase-variable var) (let ((vars (lambda-variables l-node))) (if (eq? (car vars) var) (set-lambda-variables! l-node (cdr vars)) (do ((vars vars (cdr vars))) ((eq? (cadr vars) var) (set-cdr! vars (cddr vars))))))) ; Remove all of L-NODES' unused variables. (define (remove-unused-variables l-node) (set-lambda-variables! l-node (filter! (lambda (v) (cond ((used? v) #t) (else (erase-variable v) #f))) (lambda-variables l-node)))) ;------------------------------------------------------------------------------ ; Substituting Values For Variables ; Substitute VAL for VAR. If DETACH? is true then VAL should be detached ; and so can be used instead of a copy for the first substitution. ; ; If VAL is a reference to a variable named V, it was probably introduced by ; the CPS conversion code. In that case, the variable is renamed with the ; name of VAR. This helps considerably when debugging the compiler. (define (substitute var val detach?) (if (and (reference-node? val) (eq? 'v (variable-name (reference-variable val))) (not (global-variable? (reference-variable val)))) (set-variable-name! (reference-variable val) (variable-name var))) (let ((refs (variable-refs var))) (set-variable-refs! var '()) (cond ((not (null? refs)) (for-each (lambda (ref) (replace ref (copy-node-tree val))) (if detach? (cdr refs) refs)) (if detach? (replace (car refs) (detach val)))) (detach? (erase (detach val)))))) ; Walk the tree NODE replacing references to variables in OLD-VARS with ; the corresponding variables in NEW-VARS. Uses VARIABLE-FLAG to mark ; the variables being replaced. (define (substitute-vars-in-node-tree node old-vars new-vars) (for-each (lambda (old new) (set-variable-flag! old new)) old-vars new-vars) (let tree-walk ((node node)) (cond ((lambda-node? node) (walk-vector tree-walk (call-args (lambda-body node)))) ((call-node? node) (walk-vector tree-walk (call-args node))) ((and (reference-node? node) (variable-flag (reference-variable node))) => (lambda (new) (replace node (make-reference-node new)))))) (for-each (lambda (old) (set-variable-flag! old #f)) old-vars)) ; Replaces the call node CALL with VALUE. ; ( . ) => ( ) (define (replace-call-with-value call value) (cond ((n= 1 (call-exits call)) (bug '"can only substitute for call with one exit ~s" call)) (else (let ((cont (detach (call-arg call 0)))) (set-call-exits! call 0) (replace-call-args call (if value (list cont value) (list cont))) (set-call-primop! call (get-primop (enum primop let))))))) ;------------------------------------------------------------------------------ ; Copying Node Trees ; Copy the node-tree NODE. This dispatches on the type of NODE. ; Variables which have been copied have the copy in the node-flag field. (define (copy-node-tree node) (let ((new (cond ((lambda-node? node) (copy-lambda node)) ((reference-node? node) (let ((var (reference-variable node))) (cond ((and (variable-binder var) (variable-flag var)) => make-reference-node) (else (make-reference-node var))))) ((call-node? node) (copy-call node)) ((literal-node? node) (copy-literal-node node))))) new)) ; Copy a lambda node and its variables. The variables' copies are put in ; their VARIABLE-FLAG while the lambda's body is being copied. (define (copy-lambda node) (let* ((vars (map (lambda (var) (if var (let ((new (copy-variable var))) (set-variable-flag! var new) new) #f)) (lambda-variables node))) (new-node (make-lambda-node (lambda-name node) (lambda-type node) vars))) (attach-body new-node (copy-call (lambda-body node))) (set-lambda-protocol! new-node (lambda-protocol node)) (set-lambda-source! new-node (lambda-source node)) (for-each (lambda (var) (if var (set-variable-flag! var #f))) (lambda-variables node)) new-node)) (define (copy-call node) (let ((new-node (make-call-node (call-primop node) (call-arg-count node) (call-exits node)))) (do ((i 0 (+ i 1))) ((>= i (call-arg-count node))) (attach new-node i (copy-node-tree (call-arg node i)))) (set-call-source! new-node (call-source node)) new-node)) ;------------------------------------------------------------------------------ ; Checking the scoping of identifers ; Mark all ancestors of N with FLAG (define (mark-ancestors n flag) (do ((n n (node-parent n))) ((not (node? n)) (values)) (set-node-flag! n flag))) ; Does N have an ancestor with a non-#f flag? (define (marked-ancestor? n) (do ((n n (node-parent n))) ((or (not (node? n)) (node-flag n)) (node? n)))) ; Does N have an ancestor with a #f flag? (define (unmarked-ancestor? n) (do ((n n (node-parent n))) ((or (not (node? n)) (not (node-flag n))) (node? n)))) ; Is ANC? an ancestor of NODE? (define (node-ancestor? anc? node) (set-node-flag! anc? #t) (let ((okay? (marked-ancestor? node))) (set-node-flag! anc? #f) okay?)) ; Find the lowest ancestor of N that has a non-#f flag (define (marked-ancestor n) (do ((n n (node-parent n))) ((or (not (node? n)) (node-flag n)) (if (node? n) n #f)))) ; Mark the ancestors of START with #f, stopping when END is reached (define (unmark-ancestors-to start end) (do ((node start (node-parent node))) ((eq? node end)) (set-node-flag! node #f))) ; Return the lowest node that is above all NODES (define (least-common-ancestor nodes) (mark-ancestors (car nodes) #t) (let loop ((nodes (cdr nodes)) (top (car nodes))) (cond ((null? nodes) (mark-ancestors top #f) top) (else (let ((new (marked-ancestor (car nodes)))) (unmark-ancestors-to top new) (loop (cdr nodes) new)))))) ; Can TO be moved to FROM without taking variables out of scope. ; This first marks all of the ancestors of FROM, and then unmarks all of the ; ancestors of TO. The net result is to mark every node that is above FROM but ; not above TO. Then if any reference-node below FROM references a variable ; with a marked binder, that node, and thus FROM itself, cannot legally be ; moved to TO. ; This is not currently used anywhere, and it doesn't know about trivial ; calls. (define (hoistable-node? from to) (let ((from (if (call-node? from) (node-parent (nontrivial-ancestor from)) from))) (mark-ancestors (node-parent from) #t) (mark-ancestors to #f) (let ((okay? (let label ((n from)) (cond ((lambda-node? n) (let* ((vec (call-args (lambda-body n))) (c (vector-length vec))) (let loop ((i 0)) (cond ((>= i c) #t) ((label (vector-ref vec i)) (loop (+ i 1))) (else #f))))) ((reference-node? n) (let ((b (variable-binder (reference-variable n)))) (or (not b) (not (node-flag b))))) (else #t))))) (mark-ancestors (node-parent from) #f) okay?))) ; Mark all of the lambda nodes which bind variables referenced below NODE. (define (mark-binders node) (let label ((n node)) (cond ((lambda-node? n) (walk-vector label (call-args (lambda-body n)))) ((reference-node? n) (let ((b (variable-binder (reference-variable n)))) (if b (set-node-flag! b #f)))))) (values)) ;------------------------------------------------------------------------------ ; For each lambda-node L this sets (PARENT L) to be the enclosing PROC node ; of L and, if L is a PROC node, sets (KIDS L) to be the lambda nodes it ; encloses. (define (find-scoping lambdas parent set-parent! kids set-kids!) (receive (procs others) (partition-list proc-lambda? lambdas) (for-each (lambda (l) (set-parent! l #f) (set-kids! l '())) procs) (for-each (lambda (l) (set-parent! l #f)) others) (letrec ((set-lambda-parent! (lambda (l) (cond ((parent l) => identity) ((proc-ancestor l) => (lambda (p) (let ((p (if (proc-lambda? p) p (set-lambda-parent! p)))) (set-kids! p (cons l (kids p))) (set-parent! l p) p))) (else #f))))) (for-each set-lambda-parent! lambdas)) (values procs others))) (define (proc-ancestor node) (let ((p (node-parent node))) (if (not (node? p)) #f (let ((node (do ((p p (node-parent p))) ((lambda-node? p) p)))) (do ((node node (node-parent (node-parent node)))) ((proc-lambda? node) node)))))) (define (no-free-references? node) (if (call-node? node) (error "NO-FREE-REFERENCES only works on value nodes: ~S" node)) (let label ((node node)) (cond ((reference-node? node) (let ((b (variable-binder (reference-variable node)))) (or (not b) (node-flag b)))) ((lambda-node? node) (set-node-flag! node #t) (let* ((vec (call-args (lambda-body node))) (res (let loop ((i (- (vector-length vec) 1))) (cond ((< i 0) #t) ((not (label (vector-ref vec i))) #f) (else (loop (- i 1))))))) (set-node-flag! node #f) res)) (else #t)))) (define (node-type node) (cond ((literal-node? node) (literal-type node)) ((reference-node? node) (variable-type (reference-variable node))) ((lambda-node? node) (lambda-node-type node)) ((and (call-node? node) (primop-trivial? (call-primop node))) (trivial-call-return-type node)) (else (error "node ~S does not represent a value" node)))) ;---------------------------------------------------------------- ; Debugging utilities (define (show-simplified node) (let loop ((n node) (r '())) (if (node? n) (loop (node-parent n) (cons (node-simplified? n) r)) (reverse r)))) (define (show-flag node) (let loop ((n node) (r '())) (if (node? n) (loop (node-parent n) (cons (node-flag n) r)) (reverse r)))) (define (reset-simplified node) (let loop ((n node)) (cond ((node? n) (set-node-simplified?! n #f) (loop (node-parent n))))))