Combining Instruction Sequences

SICP > Computing with Register Machines > Compilation > Combining Instruction Sequences
Previous: Compiling Combinations Next: An Example of Compiled Code

    This section describes the details on how instruction sequences are represented and combined. Recall from section [*] that an instruction sequence is represented as a list of the registers needed, the registers modified, and the actual instructions. We will also consider a label (symbol) to be a degenerate case of an instruction sequence, which doesn't need or modify any registers. So to determine the registers needed and modified by instruction sequences we use the selectors

    (define (registers-needed s)
      (if (symbol? s) '() (car s)))
    
    (define (registers-modified s)
      (if (symbol? s) '() (cadr s)))
    
    (define (statements s)
      (if (symbol? s) (list s) (caddr s)))
    
    and to determine whether a given sequence needs or modifies a given register we use the predicates
    (define (needs-register? seq reg)
      (memq reg (registers-needed seq)))
    
    (define (modifies-register? seq reg)
      (memq reg (registers-modified seq)))
    
    In terms of these predicates and selectors, we can implement the various instruction sequence combiners used throughout the compiler.

    The basic combiner is append-instruction-sequences. This takes as arguments an arbitrary number of instruction sequences that are to be executed sequentially and returns an instruction sequence whose statements are the statements of all the sequences appended together. The subtle point is to determine the registers that are needed and modified by the resulting sequence. It modifies those registers that are modified by any of the sequences; it needs those registers that must be initialized before the first sequence can be run (the registers needed by the first sequence), together with those registers needed by any of the other sequences that are not initialized (modified) by sequences preceding it.

    The sequences are appended two at a time by append-2-sequences. This takes two instruction sequences seq1 and seq2 and returns the instruction sequence whose statements are the statements of seq1 followed by the statements of seq2, whose modified registers are those registers that are modified by either seq1 or seq2, and whose needed registers are the registers needed by seq1 together with those registers needed by seq2 that are not modified by seq1. (In terms of set operations, the new set of needed registers is the union of the set of registers needed by seq1 with the set difference of the registers needed by seq2 and the registers modified by seq1.) Thus, append-instruction-sequences is implemented as follows:

    (define (append-instruction-sequences . seqs)
      (define (append-2-sequences seq1 seq2)
        (make-instruction-sequence
         (list-union (registers-needed seq1)
                     (list-difference (registers-needed seq2)
                                      (registers-modified seq1)))
         (list-union (registers-modified seq1)
                     (registers-modified seq2))
         (append (statements seq1) (statements seq2))))
      (define (append-seq-list seqs)
        (if (null? seqs)
            (empty-instruction-sequence)
            (append-2-sequences (car seqs)
                                (append-seq-list (cdr seqs)))))
      (append-seq-list seqs))
    

    This procedure uses some simple operations for manipulating sets represented as lists, similar to the (unordered) set representation described in section [*]:

    (define (list-union s1 s2)
      (cond ((null? s1) s2)
            ((memq (car s1) s2) (list-union (cdr s1) s2))
            (else (cons (car s1) (list-union (cdr s1) s2)))))
    
    (define (list-difference s1 s2)
      (cond ((null? s1) '())
            ((memq (car s1) s2) (list-difference (cdr s1) s2))
            (else (cons (car s1)
                        (list-difference (cdr s1) s2)))))
    

    Preserving, the second major instruction sequence combiner, takes a list of registers regs and two instruction sequences seq1 and seq2 that are to be executed sequentially. It returns an instruction sequence whose statements are the statements of seq1 followed by the statements of seq2, with appropriate save and restore instructions around seq1 to protect the registers in regs that are modified by seq1 but needed by seq2. To accomplish this, preserving first creates a sequence that has the required saves followed by the statements of seq1 followed by the required restores. This sequence needs the registers being saved and restored in addition to the registers needed by seq1, and modifies the registers modified by seq1 except for the ones being saved and restored. This augmented sequence and seq2 are then appended in the usual way. The following procedure implements this strategy recursively, walking down the list of registers to be preserved: [*]

    (define (preserving regs seq1 seq2)
      (if (null? regs)
          (append-instruction-sequences seq1 seq2)
          (let ((first-reg (car regs)))
            (if (and (needs-register? seq2 first-reg)
                     (modifies-register? seq1 first-reg))
                (preserving (cdr regs)
                 (make-instruction-sequence
                  (list-union (list first-reg)
                              (registers-needed seq1))
                  (list-difference (registers-modified seq1)
                                   (list first-reg))
                  (append `((save ,first-reg))
                          (statements seq1)
                          `((restore ,first-reg))))
                 seq2)
                (preserving (cdr regs) seq1 seq2)))))
    

    Another sequence combiner, tack-on-instruction-sequence, is used by compile-lambda to append a procedure body to another sequence. Because the procedure body is not ``in line'' to be executed as part of the combined sequence, its register use has no impact on the register use of the sequence in which it is embedded. We thus ignore the procedure body's sets of needed and modified registers when we tack it onto the other sequence.

    (define (tack-on-instruction-sequence seq body-seq)
      (make-instruction-sequence
       (registers-needed seq)
       (registers-modified seq)
       (append (statements seq) (statements body-seq))))
    

    Compile-if and compile-procedure-call use a special combiner called parallel-instruction-sequences to append the two alternative branches that follow a test. The two branches will never be executed sequentially; for any particular evaluation of the test, one branch or the other will be entered. Because of this, the registers needed by the second branch are still needed by the combined sequence, even if these are modified by the first branch.

    (define (parallel-instruction-sequences seq1 seq2)
      (make-instruction-sequence
       (list-union (registers-needed seq1)
                   (registers-needed seq2))
       (list-union (registers-modified seq1)
                   (registers-modified seq2))
       (append (statements seq1) (statements seq2))))
    

    Previous: Compiling Combinations Next: An Example of Compiled Code

      webmaster@arsdigita.org