在上一篇文章中,jjcc2
函数实现了对setq
这个语句的编译。这么一来,便可以将加减乘除运算中的嵌套表达式都替换为变量了。比如,将
中的嵌套的表达式(+ 1 2)
用一个变量G564
代替,变成
1 2 3
| (PROGN (SETQ #:G564 (+ 1 2)) (+ #:G564 3))
|
PS:上面的结果中的#:G564
只是打印出来的时候长这个样子而已,实际地输入这段代码的话,两个#:G564
其实是不同的符号,会导致未绑定的变量的错误的。
言归正传。既然如此,现在就要来支持编译(+ #:G564 3)
这样的表达式了。其实这个真的是太简单了,只需要将这个符号塞入到jjcc2
的第二个参数的globals
中,然后在生成的“汇编指令”的S表达式中,嵌入这个符号即可。
我刚开始的时候也是这么想的,后来发现这样出来的代码编译不过,哭
折腾了一小段时间后,才知道原来有一种叫做“RIP-relative”的东西——好吧,我的X64的汇编语言知识也是赶鸭子上架的,遇到什么问题就放狗搜,所以完全不成体系——总之,我找到了解决办法,就是将原本放入一个符号的操作数,替换为类似于下面这样的内容
所以对于操作数,实际上还需要先判断一下其类型。如果是整数,就按照原来的方式原样输出;如果是符号,就生成像上面这样的RIP-relative的结构。这部分太经常出现了,于是提炼出了一个专门处理四则运算的操作数的辅助函数get-operand
1 2 3 4 5 6 7 8
| (defun get-operand (expr n) "从EXPR中提取出第N个操作数,操作数的下标从0开始计算" (check-type expr list) (check-type n integer) (let ((e (nth (1+ n) expr))) (etypecase e (integer e) (symbol (format nil "~A(%RIP)" e)))))
|
借助它重写jjcc2
,结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| (defun jjcc2 (expr globals) "支持两个数的四则运算的编译器" (check-type globals hash-table) (cond ((eq (first expr) '+) `((movl ,(get-operand expr 0) %eax) (movl ,(get-operand expr 1) %ebx) (addl %ebx %eax))) ((eq (first expr) '-) `((movl ,(get-operand expr 0) %eax) (movl ,(get-operand expr 1) %ebx) (subl %ebx %eax))) ((eq (first expr) '*) `((movl ,(get-operand expr 0) %eax) (movl ,(get-operand expr 1) %ebx) (imull %ebx %eax))) ((eq (first expr) '/) `((movl ,(get-operand expr 0) %eax) (cltd) (movl ,(get-operand expr 1) %ebx) (idivl %ebx))) ((eq (first expr) 'progn) (let ((result '())) (dolist (expr (rest expr)) (setf result (append result (jjcc2 expr globals)))) result)) ((eq (first expr) 'setq) (setf (gethash (second expr) globals) 0) (values (append (jjcc2 (third expr) globals) `((movl %eax ,(get-operand expr 0)))) globals))))
|
现在,如果运行下面的这个example1
函数
1 2 3 4 5 6
| (defun example1 () "验证jjcc2确实可以处理含有变量的加减乘除运算" (let ((ht (make-hash-table))) (setf (gethash 'a ht) 1) (let ((asm (jjcc2 '(+ a a) ht))) (stringify asm ht))))
|
便可以得到下面这段汇编代码了
1 2 3 4 5 6 7 8 9 10 11
| .data A: .long 1 .section __TEXT,__text,regular,pure_instructions .globl _main _main: MOVL A(%RIP), %EAX MOVL A(%RIP), %EBX ADDL %EBX, %EAX movl %eax, %edi movl $0x2000001, %eax syscall
|
全文完。