1. 程式人生 > >5.5.4 組合指令序列

5.5.4 組合指令序列

5.5.4 組合指令序列
這部分描述了指令序列如何被表示和組合的細節。回憶一下5.5.1部分中的一個指令序列
被表示成需要的暫存器的列表,修改過的暫存器的列表,和實際的指令。我們也考慮一個標籤
成為一個指令序列的退化性的例子,它不需要修改任何的暫存器。所以通過我們使用選擇子的
指令序列來確定需要的和修改的暫存器:

(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)))

並且確定我們在使用判斷式時一個給定的指令序列是否需要或者是修改一個給定的暫存器:

(define (needs-register? seq reg)
  (memq reg (registers-needed seq)))
(define (modifies-register? seq reg)
  (memq reg (registers-modified seq)))

使用這些判斷式和選擇子,我們能夠通過編譯器實現各種各樣的指令的序列的組合子。
基本的組合子是append-instruction-sequences.它以任意數量的被順序性的執行的指令
序列為實際引數,並且返回一個指令序列,這個指令序列是由所有的這些指令序列合併而成。
巧妙之處在於如何確定哪些暫存器被結果序列所需要和修改。它修改那些被序列修改的暫存器;
它需要的那些暫存器必須在第一個序列能夠被執行之前被初始化(暫存器被第一個序列所需要)
結合被其它的序列所需要的暫存器不初始化(修改)通過序列執行它。

在一次執行中append-2-sequences程式把兩個序列合併起來。它以兩個指令序列seq1和seq2
為引數並且返回指令序列,這個指令序列它的語句是由seq1的語句後面是seq2的語句,它修改的暫存器
是seq1或者是seq2修改的暫存器,它需要的暫存器是seq1所需要的,或者是seq2所需要的而且不被seq1
所需要的。(使用集合的操作,所需要的暫存器的新的集合是seq1 和seq2所需要的暫存器的並集)
因此,append-instruction-sequence的實現如下:

(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))

這個程式使用了操作由列表表示的集合一些簡單的操作,與在2.2.3部分中的所描述
的無序集合表示是相似的:

(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 是第二種主要的指令序列的組合子,它以暫存器的列表和兩個指令序列為實際引數。
它返回一個指令序列,第二個指令序列在第一個指令序列的後面,有合適的儲存和恢復指令
圍繞在第一個指令序列的前後面,這可以保護被第一個指令序列修改的,而且第二個指令序列
所需要的暫存器。為了實現這個目的,preserving首先建立了一個序列,它有需要的儲存指令,
序列後面跟著恢復的指令。這個序列需要暫存器除了第一個序列中所需要的暫存器,第一個序列
所修改的暫存器。這個被處理過的序列與第二個序列以普通的方式進行合併。如下的程式遞迴地實現了
這個策略,遍歷了被保留的暫存器的列表:

(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)))))

另一個序列組合子,tack-on-instruction-sequence被compile-lambda程式使用,來把一個程式體
合併到其它的序列中。因為程式體不是內聯的,不是作為組合的序列中的一部分被執行,它的暫存器
使用沒有影響到它所嵌入的序列的暫存器的使用。當我們處理它時,我們因此而忽略程式體中的所需要
的和所修改的暫存器的集合。

(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 和 compile-procedure-call使用一個特殊的組合子,叫做parallel-instruction-sequence,
它把兩個可能的分支合併到一起。兩個分支將根本不能順序性的執行;對於任何特定的測試的解釋,
將進入其中的一個分支中。因為這個原因,被第二個分支所需要的暫存器仍然被組合的序列所需要,即使
它們被第一個分支所修改。

(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))))