共计 2080 个字符,预计需要花费 6 分钟才能阅读完成。
在上一篇文章中,jjcc2
函数实现了对 setq
这个语句的编译。这么一来,便可以将加减乘除运算中的嵌套表达式都替换为变量了。比如,将
(+ (+ 1 2) 3)
中的嵌套的表达式 (+ 1 2)
用一个变量 G564
代替,变成
(PROGN
(SETQ #:G564 (+ 1 2))
(+ #:G564 3))
PS:上面的结果中的 #:G564
只是打印出来的时候长这个样子而已,实际地输入这段代码的话,两个 #:G564
其实是不同的符号,会导致未绑定的变量的错误的。
言归正传。既然如此,现在就要来支持编译 (+ #:G564 3)
这样的表达式了。其实这个真的是太简单了,只需要将这个符号塞入到 jjcc2
的第二个参数的 globals
中,然后在生成的“汇编指令”的 S 表达式中,嵌入这个符号即可。
我刚开始的时候也是这么想的,后来发现这样出来的代码编译不过,哭
折腾了一小段时间后,才知道原来有一种叫做“RIP-relative”的东西——好吧,我的 X64 的汇编语言知识也是赶鸭子上架的,遇到什么问题就放狗搜,所以完全不成体系——总之,我找到了解决办法,就是将原本放入一个符号的操作数,替换为类似于下面这样的内容
G564(%RIP)
所以对于操作数,实际上还需要先判断一下其类型。如果是整数,就按照原来的方式原样输出;如果是符号,就生成像上面这样的 RIP-relative 的结构。这部分太经常出现了,于是提炼出了一个专门处理四则运算的操作数的辅助函数get-operand
(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
,结果如下
(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) '*)
;; 将两个数字相乘的结果放到第二个操作数所在的寄存器中
;; 因为约定了用 EAX 寄存器作为存放最终结果给 continuation 用的寄存器,所以第二个操作数应当为 EAX
`((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)
;; 编译赋值语句的方式比较简单,就是将被赋值的符号视为一个全局变量,然后将 eax 寄存器中的内容移动到这里面去
;; TODO: 这里 expr 的 second 的结果必须是一个符号才行
;; FIXME: 不知道应该赋值什么比较好,先随便写个 0 吧
(setf (gethash (second expr) globals) 0)
(values (append (jjcc2 (third expr) globals)
;; 为了方便 stringify 函数的实现,这里直接构造出 RIP-relative 形式的字符串
`((movl %eax ,(get-operand expr 0))))
globals))))
现在,如果运行下面的这个 example1
函数
(defun example1 ()
"验证 jjcc2 确实可以处理含有变量的加减乘除运算"
(let ((ht (make-hash-table)))
(setf (gethash 'a ht) 1)
(let ((asm (jjcc2 '(+ a a) ht)))
(stringify asm ht))))
便可以得到下面这段汇编代码了
.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
全文完。
阅读原文
正文完