1. 程式人生 > >5.2.3 為了指令生成可執行程式

5.2.3 為了指令生成可執行程式

5.2.3 為了指令生成可執行程式
彙編器呼叫make-execution-procedure 來生成指令的執行程式。
像4.1.7部分中的直譯器中的  analyze程式一樣,這個分發程式
根據指令的型別來生成合適的執行程式。

(define  (make-execution-procedure  inst  labels  machine 
                                                           pc flag stack ops)
         (cond  ((eq?  (car inst)  'assign) 
                      (make-assign  inst machine  labels ops pc))  
                    ((eq?  (car inst)  'test) 
                      (make-test  inst machine  labels ops flag pc))  
                    ((eq?  (car inst)  'branch) 
                     (make-branch  inst  machine labels  flag  pc)) 
                    ((eq?  (car inst)  'goto) 
                      (make-goto  inst  machine  labels  pc))  
                    ((eq?  (car inst)  'save) 
                       (make-save  inst machine  stack pc))  
                    ((eq?  (car inst)  'restore) 
                       (make-restore  inst machine  stack pc)) 
                    ((eq?  (car inst)  'perform) 
                         (make-perform  inst machine  labels  ops  pc)) 
                    (else  (error  "Unknown instruction type ---ASSEMBLE"  inst)))
)

在暫存器機器的語言中對於指令的每一種型別,有一個生成器來構建一個合適的執行程式。
這些程式的細節確定了在暫存器機器語言中的獨立的指令的語法和意義。我們使用資料抽象
來把暫存器機器的表示式的詳細的語法與通用的執行機制分離開,正如我們為4.1.2部分中的直譯器所做的那樣,通過使用語法程式來抽取和分類指令的部分。

*賦值指令
make-assign程式處理賦值指令:

(define  (make-assign  inst machine labels operations pc)
     (let  ((target  (get-register  machine  (assign-reg-name  inst))) 
             (value-exp  (assign-value-exp  inst))) 
           (let  ((value-proc 
                       (if (operation-exp?  value-exp) 
                             (make-operation-exp  value-exp machine  labels operations) 
                             (make-primitive-exp  (car value-exp)  machine labels)))) 
                  (lambda  () 
                              (set-contents!  target  (value-proc)) 
                              (advance-pc pc))))
)

Make-assign 抽取了目標的暫存器的名稱(是指令的第二個元素)和值的表示式
(形成指令的列表的其它部分),是從assign指令中抽取的。使用如下的選擇子:

(define (assign-reg-name  assign-instruction) 
(cadr  assign-instruction))

(define  (assign-value-exp  assign-instruction) 
(cddr  assign-instruction))

為了生成目標暫存器物件,用get-register查詢暫存器的名稱。如果值是一個操作的結果,
並且為了make-primitive-exp程式,值的表示式被傳遞給了make-operation-exp.
這些程式解析值的表示式和生成值的執行程式。這個一個沒有引數的程式,叫做value-proc,
為了把生成的實際的值賦給暫存器,在模擬期間,它將被解釋。注意的是查詢暫存器名稱的工
作和解釋值的表示式的工作僅被執行一次,在彙編的時候,而不是指令被模擬的每一次時。
這工作的節省就是我們使用執行程式的理由,在4.1.7部分中的直譯器中通過把程式分析與執行分離開來,在工作中我們得到了相應的節省。

make-assign返回的結果是賦值指令的執行程式。當這個程式被呼叫時(通過機器模型中的執行程式),通過執行value-proc,它設定目標暫存器的值為得到的結果。然後讓pc指向下一條指令,通過執行如下的程式:

(define   (advance-pc  pc)
   (set-contents!  pc  (cdr  (get-contents pc)))
)

除了分支和goto指令,advance-pc為了其它的指令的正常的終止處。

*測試,分支和GOTO指令
make-test以相似的方式來處理測試指令。它抽取被測試的指定的條件的表示式,並且生成了一個執行程式。在模擬的時候,條件的程式被呼叫,結果被賦值給了標誌的暫存器和程式計數器:

(define  (make-test  inst  machine  labels  operations flag pc)
    (let   ((condition   (test-condition  inst))) 
           (if  (operation-exp?   condition) 
                (let  ((condition-proc 
                            (make-operation-exp  condition  machine labels  operations))) 
                       (lambda () 
                                     (set-contents!  flag  (condition-proc))  
                                     (advance-pc  pc)))
               (error   "Bad  TEST  instruction  --- ASSEMBLE"  inst)
            ))
)

(define   (test-condition  test-instruction)
      (cdr  test-instruction)
)

分支指令的執行程式檢查標誌暫存器的內容,並且把pc的值設定為分支的目的地(如果分支被採用)或者設定pc(如果分支沒有被採用)。 注意的是在一個分支的指令中顯示的目標地址必須是一個標籤,並且Make-branch的程式強制它。注意的是標籤在彙編時被查詢,不是在分支指令每次被模擬的時候。

(define (make-branch inst machine labels flag pc)
  (let ((dest (branch-dest inst)))
    (if (label-exp? dest)
        (let ((insts
               (lookup-label labels (label-exp-label dest))))
          (lambda ()
            (if (get-contents flag)
                (set-contents! pc insts)
                (advance-pc pc))))
        (error "Bad BRANCH instruction -- ASSEMBLE" inst))))
(define (branch-dest branch-instruction)
  (cadr branch-instruction))

一個goto指令與分支指令相似,除了目標地址可能被指定為一個標籤或者是一個暫存器
也沒有條件的檢查,程式總是設定為新的目的地址。

(define (make-goto inst machine labels pc)
  (let ((dest (goto-dest inst)))
    (cond ((label-exp? dest)
           (let ((insts
                  (lookup-label labels
                                (label-exp-label dest))))
             (lambda () (set-contents! pc insts))))
          ((register-exp? dest)
           (let ((reg
                  (get-register machine
                                (register-exp-reg dest))))
             (lambda ()
               (set-contents! pc (get-contents reg)))))
          (else (error "Bad GOTO instruction -- ASSEMBLE"
                       inst)))))
(define (goto-dest goto-instruction)
  (cadr goto-instruction))

*其它指令
棧指令儲存和恢復在使用棧方面,使用目的暫存器和程式計數器是相似的:

(define (make-save inst machine stack pc)
  (let ((reg (get-register machine
                           (stack-inst-reg-name inst))))
    (lambda ()
      (push stack (get-contents reg))
      (advance-pc pc))))

(define (make-restore inst machine stack pc)
  (let ((reg (get-register machine
                           (stack-inst-reg-name inst))))
    (lambda ()
      (set-contents! reg (pop stack))   
      (advance-pc pc))))

(define (stack-inst-reg-name stack-instruction)
  (cadr stack-instruction))

最後的指令的型別,由make-perform來處理,為被執行的動作,
生成一個執行程式。在模擬的時候,動作程式被執行,並且pc被設定。

(define (make-perform inst machine labels operations pc)
  (let ((action (perform-action inst)))
    (if (operation-exp? action)
        (let ((action-proc
               (make-operation-exp
                action machine labels operations)))
          (lambda ()
            (action-proc)
            (advance-pc pc)))
        (error "Bad PERFORM instruction -- ASSEMBLE" inst))))
(define (perform-action inst) (cdr inst))

*子表示式的執行程式
暫存器,標籤或者是常數的表示式的值可能被暫存器的賦值或者是一個
操作的輸入所需要。如下的程式在模擬期間為了這些表示式生成值
而生成執行程式:

(define (make-primitive-exp exp machine labels)
  (cond ((constant-exp? exp)
         (let ((c (constant-exp-value exp)))
           (lambda () c)))
        ((label-exp? exp)
         (let ((insts
                (lookup-label labels
                              (label-exp-label exp))))
           (lambda () insts)))
        ((register-exp? exp)
         (let ((r (get-register machine
                                (register-exp-reg exp))))
           (lambda () (get-contents r))))
        (else
         (error "Unknown expression type -- ASSEMBLE" exp))))

暫存器,標籤或者是常數的表示式的語法由如下的程式決定:

(define (register-exp? exp) (tagged-list? exp 'reg))
(define (register-exp-reg exp) (cadr exp))
(define (constant-exp? exp) (tagged-list? exp 'const))
(define (constant-exp-value exp) (cadr exp))
(define (label-exp? exp) (tagged-list? exp 'label))
(define (label-exp-label exp) (cadr exp))

賦值,執行和測試的指令可能包括了一個機器的有一些運算元的
操作的應用。如下的程式生成了一個有操作的表示式的執行程式,
這個表示式是一個列表包括了操作和運算元表示式:

(define (make-operation-exp exp machine labels operations)
  (let ((op (lookup-prim (operation-exp-op exp) operations))
        (aprocs
         (map (lambda (e)
                (make-primitive-exp e machine labels))
              (operation-exp-operands exp))))
    (lambda ()
      (apply op (map (lambda (p) (p)) aprocs)))))

操作表示式的語法由如下的程式決定:

(define (operation-exp? exp)
  (and (pair? exp) (tagged-list? (car exp) 'op)))
(define (operation-exp-op operation-exp)
  (cadr (car operation-exp)))
(define (operation-exp-operands operation-exp)
  (cdr operation-exp))

注意的是操作表示式的處理是非常像程式應用的處理。在4.1.7部分中的直譯器
中的analyze-application程式為了任何一個運算元生成一個執行程式。在模擬期
間,我們呼叫運算元程式,應用scheme程式,來模擬操作到結果的值。通過
在操作的表格中,查詢操作名稱,模擬程式建立了起來:

(define (lookup-prim symbol operations)
  (let ((val (assoc symbol operations)))
    (if val
        (cadr val)
        (error "Unknown operation -- ASSEMBLE" symbol))))

練習5.9
如上的機器操作的處理驢行它們操作標籤像常數和暫存器的內容一樣。修改
表示式的處理的程式強制條件為操作僅被暫存器和常數使用。

練習5.10
對暫存器機器的指令設計一個新的語法並且修改模擬器來使用你的新的語法。
你能實現你的新的語法沒有修改模擬器的其它部分除了在這部分中的語法
程式嗎?

練習5.11
在5.1.4部分中,我們介紹了儲存和恢復指令時,我們沒有指定如果我們試圖
恢復的暫存器不是最後一個被儲存的,這會發生什麼,如下面的序列:

(save  y)
(save  x)
(restore y)

a.  (restore y) 把棧的頂部的值給y,而不論它來自於哪個暫存器。這是我們的模擬
器的行為方式。顯示如何利用這個行為,來消除5.1.4部分中的斐波那些數的機器的
一個指令。(見圖5.12)

b. (restore y) 把棧的頂部的值給y,但是僅當那個值來自於y,否則它報一個錯誤。
修改模擬器,來實現這種方式。你將不得不修改儲存,把暫存器名稱和值一起加入到
棧中。

c.   (restore y) 把y儲存的最後一個值,給y,而不管其它的暫存器被儲存了,卻沒有
被恢復的情況。修改模擬器,來實現這種方式。你將不得不為每個暫存器關聯一個單獨的棧。
你應該讓初始化棧的操作來初始化所有的暫存器的棧。

練習5.12
模擬器能被用來幫助確定資料路徑,這路徑要求是為了實現一個有給定的控制器的機器。
擴充套件一個彙編器在機器的模型中,來儲存如下的資訊。

所有的指令的列表,重複地刪除,以指令的型別排序

一個暫存器的列表,也是沒有重複的,這些暫存器被用來儲存入口點
(也就是被goto指令引用的暫存器)

一個暫存器的列表,也是沒有重複的,這些暫存器用來作儲存與恢復的。

對於每一個暫存器,一個沒有重複的被賦值的來源的列表(例如,val暫存器的來源
在圖5.11的階乘機器中,是(const 1)和((op  *)  (reg n)   (reg  val))

擴充套件對機器的訊息傳遞的介面來提供這些新資訊的讀取。為了測試你的分析器,定義
圖5.12中的階乘機器,並且檢查你的組裝的列表。

練習5.13
修改你的模擬器,來使用控制器序列,來確定機器有什麼暫存器,而不是需要一個暫存器的列表
作為引數來生成make-machine.代替在make-machine中預分配暫存器,在指令的彙編期間,當暫存器首次露面時,你能在那個時候分配暫存器。