用Scheme写Scheme编译器(三):一元运算
对于一元运算,我们现在指的还不是函数(完成函数会在以后讲到),而是一般语言中内置的一元运算,比如Scheme中的add1,sub1,integer->char, char->integer, fixnum? ,boolean? ,null? ,char? 等的运算.
实现这种一元运算的原理和关键就在于用汇编语言完成这些函数的运算后,我们可以使用%eax寄存器将运算的结果进行返回.
1 修改Scheme编译主程序
我们要将编译程序调整为适应一元运算
(define (compile-program expr) (emit-head) (emit-expr expr) (emit-return))
从我们之前的经验大概可以看出来,汇编语句中只有一些是于我们所要编写的程序相关的,所以我们可以把编译主程序改写为由head,expr,return三部分组成的程序.
Head:
(define (emit-head) (emit " .text") (emit " .global _scheme_entry") (emit " .def _scheme_entry; .scl 2; .type 32; .endef") (emit "_scheme_entry:") (emit "LFB0:") (emit " .cfi_startproc") (emit " pushl %ebp") (emit " .cfi_def_cfa_offset 8") (emit " .cfi_offset 5, -8") (emit " movl %esp, %ebp") (emit " .cfi_def_cfa_register 5"))
expr:
(define (emit-expr expr) (cond ((immediate-value? expr) (emit-immediate expr)) ((primcall? expr) (emit-primcall expr)) (else (error 'emit-expr))))
return:
(define (emit-return) (emit " popl %ebp") (emit " .cfi_restore 5") (emit " .cfi_def_cfa 4, 4") (emit " ret") (emit " .cfi_endproc") (emit "LFE0:"))
2 定义用于编写一元运算的宏
为了方便我们编写一元运算的汇编代码,我们可以写一个宏:
(define-syntax define-primitive (syntax-rules () ((_ (op arg* ...) b b* ...) (begin (putprop 'op '*is-prim* #t) (putprop 'op '*arg-length* (length '(arg* ...))) (putprop 'op '*emitter* (lambda (arg* ...) b b* …))))))
3 完成一元运算的汇编代码
(define-primitive (add1 arg) (emit-expr arg) (emit " addl $~a, %eax" (immediate-value-rep 1)))
(define-primitive (sub1 arg) (emit-expr arg) (emit " subl $~a, %eax" (immediate-value-rep 1)))
(define-primitive (char->integer arg) (emit-expr arg) (emit " shll $~s, %eax" (- charshift fxshift)))
字符转换为数字只需要向右移动6位即可,
因为字符的低8位为00001111, 数字 的低8位为xxxxxx00
(define-primitive (integer->char arg) (emit-expr arg) (emit " shll $~s, %eax" (- charshift fxshift)))
(define-primitive (fixnum? arg) (emit-expr arg) (emit " and $~s, %al" fxmask) (emit-cmp fxtag))
(define-primitive (null? arg) (emit-expr arg) (emit-cmp list_nil))
(define-primitive (boolean? arg) (emit-expr arg) (emit " and $~s, %al" bool_mask) (emit-cmp bool_f))
判断是否是布尔值是利用bool_mask(0xbf),将#t转变为#f,然后比较是否等于#f
(define-primitive (char? arg) (emit-expr arg) (emit " and $~s, %al" charmask) (emit-cmp chartag))
最后是判断是否相等的汇编代码:
(define (emit-cmp tag) (emit " cmp $~s, %al" tag) (emit " sete %al") (emit " movzbl %al, %eax") (emit " sal $~s, %al" bool_bit) (emit " or $~s, %al" bool_f))
通过sete我们可以将比较结果放置在%eax中,如果相等,为1,否则为0,
将1左移6位后,与bool_f或运算为bool_t,若为0,或运算后仍为bool_t.