在上一篇文章中,jjcc2
函数实现了对setq
这个语句的编译。这么一来,即可以将加减乘除运算中的嵌套表达式都替换为变量了。好比,将git
(+ (+ 1 2) 3)
中的嵌套的表达式(+ 1 2)
用一个变量G564
代替,变成github
(PROGN (SETQ #:G564 (+ 1 2)) (+ #:G564 3))
PS:上面的结果中的#:G564
只是打印出来的时候长这个样子而已,实际地输入这段代码的话,两个#:G564
实际上是不一样的符号,会致使未绑定的变量的错误的。segmentfault
言归正传。既然如此,如今就要来支持编译(+ #:G564 3)
这样的表达式了。其实这个真的是太简单了,只须要将这个符号塞入到jjcc2
的第二个参数的globals
中,而后在生成的“汇编指令”的S表达式中,嵌入这个符号便可。app
我刚开始的时候也是这么想的,后来发现这样出来的代码编译不过,哭函数
折腾了一小段时间后,才知道原来有一种叫作“RIP-relative”的东西——好吧,个人X64的汇编语言知识也是赶鸭子上架的,遇到什么问题就放狗搜,因此彻底不成体系——总之,我找到了解决办法,就是将本来放入一个符号的操做数,替换为相似于下面这样的内容rest
G564(%RIP)
因此对于操做数,实际上还须要先判断一下其类型。若是是整数,就按照原来的方式原样输出;若是是符号,就生成像上面这样的RIP-relative的结构。这部分太常常出现了,因而提炼出了一个专门处理四则运算的操做数的辅助函数get-operand
code
(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
,结果以下orm
(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))))
即可以获得下面这段汇编代码了get
.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
全文完。