; -*- Mode: Lisp -*- Filename: ppeep.s ; Last Revision: 1-Oct-85 1630ct ;--------------------------------------------------------------------------; ; ; ; TI SCHEME -- PCS Compiler ; ; Copyright 1985 (c) Texas Instruments ; ; ; ; David Bartley ; ; ; ; Post-Codegen Optimization ; ; ; ;--------------------------------------------------------------------------; ; ; ; Note: The optimization TEST+JUMP-NULL? ==> JUMP-NOT-TEST has not been ; ; implemented because peep2 can't reliably tell when TEST is dead. ; ; ; ; ; ; Revisions : ; ; 6/1/87 rb - Modified p2-substitute, so as not to monkey with %xesc ; ; 6/3/87 tc - Modified p1 register substitution to understand %xesc ; ; ; ; ; ;--------------------------------------------------------------------------; (define pcs-postgen (lambda (code) (letrec ( ;----! (peep1 (lambda (code) (cond (pcs-permit-peep-1 (p1 code '())) (pcs-permit-peep-2 (reverse! code)) (t code)))) (p1 (lambda (next acc) (if (null? next) (begin (p1-forget-all) acc) (let ((rest (cdr next)) (instr (car next))) (cond ((or (atom? instr) ; label (number? (car instr))) ; label (when (and acc (cdr acc) (not (atom? (car acc))) (eq? (caar acc) 'JUMP) (equal? (cadar acc) instr)) (set! acc (cdr acc))) ; delete "JUMP $+1" (p1-forget-all)) ((memq (car instr) '(JUMP CALL LIVE)) (p1-forget-all)) ((eq? (car instr) 'LOAD) (p1-propagate (cddr instr)) ; src reg (p1-forget (cdr instr)) ; dest reg (p1-remember (cadr instr) ; dest <== src (caddr instr)) ) ((eq? (car instr) '%XESC) ; %xesc assumes the dest reg will be equal tc - 6/3/87 ; to the third operand (cadddr instr) (let ((dest (cadr instr))) (p1-propagate-all (cdr instr)) (p1-forget (cdr instr)) ; dest reg (p1-forget dest) ; old dest reg (p1-remember (cadr instr) ; dest <== src (cadddr instr)) (p1-remember dest ; old dest <== src (cadddr instr)) ) ) ((not (atom? (cdr instr))) (p1-propagate-all (cddr instr)) ; src regs (p1-forget (cdr instr))) ; dest reg (t '())) (set-cdr! next acc) (p1 rest next))))) (p1-propagate (lambda (s*) ; (src ...) (when (not (atom? s*)) (let ((s (car s*))) (when (number? s) (let ((sub (vector-ref reg-table s))) (when sub ; any sub (set-car! s* sub)))))))) (p1-propagate-all (lambda (s*) ; (src ...) (when (not (atom? s*)) (let ((s (car s*))) (when (number? s) (let ((sub (vector-ref reg-table s))) (when (number? sub) ; regs only (set-car! s* sub))))) (p1-propagate-all (cdr s*))))) ; cdr down (p1-remember (lambda (dest src) (when (or (number? src) ; reg? (and (not (atom? src)) ; constant (eq? (car src) 'quote))) (vector-set! reg-table dest src) (set! reg-table-max (max reg-table-max (if (and (number? src)(> src dest)) src dest)))))) (p1-forget (lambda (d*) ; (dest ...) (when (not (atom? d*)) (let ((d (car d*))) (when (number? d) ; reg (vector-set! reg-table d #!false) (p1-forget-uses d)))))) (p1-forget-uses (lambda (reg) (letrec ((loop (lambda (v i reg) (when (not (negative? i)) (if (equal? (vector-ref v i) reg) (vector-set! v i #!false)) (loop v (sub1 i) reg))))) (loop reg-table reg-table-max reg)))) (p1-forget-all (lambda () (vector-fill! reg-table #!false))) ;;; p2 -- peephole optimizer pass 2 ;;; Purposes: ;;; ;;; 1. Destructively reverse the code list (previously reversed by the ;;; first pass), returning it to forward order. ;;; ;;; 2. Eliminate dead code ;;; ;;; Delete instructions whenever the destination register is dead and ;;; there are no side effects. ;;; ;;; Maintain live/dead info: destination registers are dead prior to ;;; assignment, source registers become live. LIVE directives and ;;; arguments to CALLs also control liveness. ;;; ;;; Assumption: every JUMP is immediately preceded by a LIVE. ;;; ;;; 3. Target registers ;;; ;;; Delay register moves (only), such as (LOAD A B). Mark register A ;;; as dead, register B as live. ;;; ;;; Force delayed loads whenever register A is used or a label, CALL, ;;; or JUMP occurs. ;;; ;;; Substitute register A for register B and remove the (LOAD A B) ;;; from the delayed list whenever register B is the destination of ;;; an instruction. ;;; ;;; 4. Other optimizations ;;; ;;; Eliminate no-ops: (LOAD A A) ;;; ;;; Commute operands: (+ A B A) ==> (+ A A B) ;;; ;;; ;;; Data Structures: ;;; ;;; REG-TABLE [0..63] ;;; ;;; Entry I is #!FALSE iff register I is "live" ;;; ;;; DELAY-LIST ;;; ;;; "Delayed" register moves are maintained in the form: ;;; ;;; ((LOAD Ai Bi) ...) ;;; ;;; where each Ai and Bi is a register number, no Ai=Aj, no Ai=Bj, ;;; and no Bi=Bj. The P2-DELAY routine decides whether to delay a ;;; given (LOAD A B), based on the following considerations: ;;; ;;; (= A B) : Can't happen, because P2 previously deletes these ;;; no-ops [p2-dead]. ;;; ;;; (= A Ai) : Can't happen, because Ai is "dead" and P2 would have ;;; deleted this operation [p2-dead]. ;;; ;;; (= A Bi) : Can't happen, because P2 would previously have ;;; substituted the corresponding Ai for A [p2-substitute], making ;;; this (LOAD Ai B), and no Ai=Bj. (???) ;;; ;;; (= B Ai) : Can't happen, because P2 would have forced out any ;;; delayed (LOAD Ai Bi) [p2-sources]. ;;; ;;; (= B Bi) : CAN happen. We modify the current instruction so we ;;; can continue to delay the previous (LOAD Ai Bi), as follows. ;;; ;;; Example: (load 3 5) ... (load 4 5) ;;; ;;; When we see the (LOAD 3 5), we have already delayed the ;;; (LOAD 4 5). Thus, we change (LOAD 3 5) into (LOAD 3 4), ;;; make register 4 "live", and continue to delay (LOAD 4 5). ;;; ;;; B is live : CAN happen. Don't delay the load, since the values ;;; of both A and B are needed. ;;; ;;; otherwise : delay the (LOAD A B). ;;; (peep2 (lambda (code) (cond (pcs-permit-peep-2 (p2 code '())) (pcs-permit-peep-1 (reverse! code)) (t code)))) (p2 (lambda (next acc) (if (null? next) acc (let ((rest (cdr next)) (instr (car next))) (begin (set-cdr! next acc) ; assume we will keep it ;; don't use ACC past here (if (or (atom? instr) (number? (car instr))) (p2 rest (p2-force-all next)) ; label (let ((op (car instr))) (cond ((eq? op 'JUMP) ; JUMP (p2-jump instr rest next)) ((eq? op 'CALL) ; CALL (p2-call instr rest next)) ((eq? op 'LIVE) ; LIVE (p2-live instr rest next)) ((p2-dead? instr) ; result not needed (p2 rest (cdr next))) ; delete it (t (p2-substitute instr) (if (eq? op 'LOAD) (p2-load instr rest next) (begin (let ((dest (cadr instr))) (when (number? dest) (p2-force dest next delay-list '()) (p2-kill dest))) (p2-sources ; make the src regs live (cddr instr) next) (p2-keep rest instr next)))))))))))) ;;; p2-jump -- Process JUMP instructions. (p2-jump (lambda (instr rest next) (p2 rest (p2-sources (cdddr instr) (p2-force-all next))))) ;;; p2-call -- Process CALL instructions. (p2-call (lambda (instr rest next) (vector-fill! reg-table #!true) ; make all regs dead (let ((next (p2-sources (cddr instr) (p2-force-all next)))) ; make src regs live (if (not (atom? (caddr instr))) (p2-make-live 1 (car (caddr instr)))) ; number of args (p2 rest next)))) ;;; p2-live -- Process LIVE directives. (p2-live (lambda (instr rest next) (vector-fill! reg-table #!true) ; make all regs dead (let ((range (cadr instr))) ; then make some live (when (not (null? range)) (p2-make-live (car range)(cdr range)))) (p2 rest next))) (p2-make-live (lambda (lo hi) (when ( >= hi lo) (vector-set! reg-table hi #!false) ; make reg live (p2-make-live lo (sub1 hi))))) ;;; p2-load -- Process LOAD instructions. (p2-load (lambda (instr rest next) (let ((dest (cadr instr)) (src (caddr instr))) (if (equal? dest src) ; no-op? (p2 rest (cdr next)) ; delete it (let ((live-src? (and (number? src) (null? (vector-ref reg-table src))))) (p2-force dest next delay-list '()) (p2-kill dest) (p2-sources (cddr instr) next) (let ((acc (cdr next))) (if (and (not live-src?) (p2-delay next)) ; does (set-cdr! next ...) (p2 rest acc) (p2-keep rest instr next)))))))) ;;; p2-substitute -- Attempt to substitute a delayed register for the ;;; destination of INSTR. If the destination of INSTR is B and a ;;; (LOAD A B) instruction has been delayed, then the destination is ;;; changed to A and the (LOAD A B) is forgotten. ;;; ;;; This substitution cannot be performed on %XESC instructions because ;;; %XESC assumes the destination is the same as the third operand (p2-substitute (lambda (instr) (letrec ((loop (lambda (reg old new) (if (null? old) new (let ((next (cdr old)) (src (caddr (car old)))) (if (and (= reg src) ; don't substitute for %xesc rb - 6/1/87 (not (eq? (car instr) '%xesc))) (begin ; replace the dest opd (p2-kill (cadr instr)) ; kill old dest reg (set-car! (cdr instr) ; subst new dest reg (cadr (car old))) (append! next new)) ; forget it (begin (set-cdr! old new) (loop reg next old)))))))) (if delay-list (let ((dest (cadr instr))) (if (number? dest) (set! delay-list (loop dest delay-list '())))))))) ;;; p2-kill -- Mark the register DEST as "dead". (p2-kill (lambda (dest) (if (number? dest) (vector-set! reg-table dest #!true)))) ;;; p2-sources -- Process the source registers (SS) of an instruction: ;;; 1. Mark each source register as "live". ;;; 2. For each source operand OPD which is a register for which there is ;;; a delayed assignment, force out the load, since this is the last ;;; use of a previous value. ;;; 3. Return the updated code list, NEXT. (p2-sources (lambda (ss next) (if (null? ss) next (let ((opd (car ss))) (if (number? opd) ; register (begin (vector-set! reg-table opd #!false) ; make it live (p2-sources (cdr ss) (p2-force opd next delay-list '()))) (p2-sources (cdr ss) next)))))) ;;; p2-force -- REG is a register which is being used as a source operand ;;; of the instruction which is at the head of CODE-LIST. Thus, we must ;;; force out any delayed load which defines or uses REG, since the source ;;; operand must refer to the old value before reassignment (defines) and ;;; we can't eliminate registers with multiple uses. Returns the updated ;;; CODE-LIST. (p2-force (lambda (reg code-list old new) (if (null? old) (begin (set! delay-list new) code-list) (let ((this (cdr old)) (dest (cadr (car old))) (src (caddr (car old)))) (if (or (= reg dest) (= reg src)) (begin (set-cdr! old (cdr code-list)) (set-cdr! code-list old) (set! delay-list (append! this new)) code-list) (begin (set-cdr! old new) (p2-force reg code-list this old))))))) ;;; p2-force-all -- Force all delayed register assignments out. This is ;;; necessary at all jumps, calls, labels, etc. (p2-force-all (lambda (code-list) (when delay-list (set-cdr! code-list (append! delay-list (cdr code-list))) (set! delay-list '())) code-list)) ;;; p2-delay -- Delay instructions of the form (LOAD reg-A reg-B) (p2-delay (lambda (next) (let ((instr (car next))) (let ((dest (cadr instr)) (src (caddr instr))) (if (number? src) (let ((delayed-load (p2-lookup src delay-list))) (if delayed-load (let ((delayed-dest (cadr delayed-load))) (set-car! (cddr instr) delayed-dest) ; fix this one (p2-make-live delayed-dest delayed-dest) ; keep the other delayed '()) (begin ; delay this one (set-cdr! next delay-list) (set! delay-list next) 't))) '()))))) ; not a reg-reg move (p2-lookup (lambda (src dl) (cond ((null? dl) '()) ((= src (caddr (car dl))) (car dl)) (t (p2-lookup src (cdr dl)))))) ;;; p2-dead? -- Determine whether instruction INSTR may be considered ;;; redundant and thus deleted. If the destination operand is "dead" and ;;; the instruction has no side effects, then the instruction is "dead". (p2-dead? (lambda (instr) (and (eq? (car instr) 'LOAD) ; no side effects (number? (cadr instr)) ; dest reg (or (equal? (cadr instr)(caddr instr)) (not (null? (vector-ref reg-table (cadr instr)))))))) ;;; p2-keep -- Keep the current instruction, INSTR (which is also the first ;;; item in NEXT). If INSTR is a primitive that requires the first source ;;; operand to be the same as the destination register, add an appropriate ;;; LOAD in front and modify the instruction. (p2-keep (lambda (rest instr next) (let ((dest (cadr instr)) (src (and (cddr instr)(caddr instr)))) (cond ((or (not (number? dest)) (not (number? src)) (= dest src) (memq (car instr) funny-primitives)) (p2 rest next)) ((member dest (cdddr instr)) (if (and (memq (car instr) commutative-primops) (equal? dest (cadddr instr))) (begin ; swap source operands (set-car! (cddr instr) dest) (set-car! (cdddr instr) src) (p2 rest next)) (begin (set-cdr! next (cons (list 'LOAD dest 63) (cdr next))) (set-car! (cdr instr) 63) (set-car! (cddr instr) 63) (p2 rest (cons (list 'LOAD 63 src) next))))) (t (set-car! (cddr instr) dest) (p2 rest (cons (list 'LOAD dest src) next))))))) ;;; data (funny-primitives '(LOAD cons car cdr caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr cadddr)) (commutative-primops '(+ * = eq? eqv? equal? max min)) (delay-list '()) (reg-table-max 0) (reg-table (make-vector 64 #!false)) ;----! ) (begin (when pcs-verbose-flag (writeln "Codegen results:") (pcs-princode code) (newline)) (let ((code1 (peep1 code))) (when pcs-verbose-flag (writeln "Pass 1 optimization results:") (set! code1 (reverse! code1)) (pcs-princode code1) (set! code1 (reverse! code1)) (newline)) (let ((code2 (peep2 code1))) (when pcs-verbose-flag (writeln "Pass 2 optimization results:") (pcs-princode code2) (newline)) code2)))))) (define pcs-princode ; PCS-PRINCODE (lambda (code) (letrec ( ;----! (tab " ") (tab2 " ") (nlabels 0) (ninstrs 0) (nfields 0) (pcl (lambda (cl) (newline) (when cl (let ((x (car cl))) (if (or (atom? x) ; label? (number? (car x))) (begin (set! nlabels (add1 nlabels)) (princ tab) (princ x)) ; label (begin (set! ninstrs (add1 ninstrs)) (princ tab2) (pc x tab))) ; instruction (pcl (cdr cl)))))) (pc (lambda (x spacer) (set! nfields (add1 nfields)) (princ (car x)) (when (cdr x) (princ spacer) (pc (cdr x) ", ")))) ;----! ) (pcl code) (writeln " There are " nlabels " labels, " ninstrs " instructions, and " nfields " fields.") )))