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中预分配寄存器,在指令的汇编期间,当寄存器首次露面时,你能在那个时候分配寄存器。
