Common Lisp中的setq
类似于其它语言中的赋值语句,它可以给一个符号对象设定一个值,类似于将一个值赋值给一个变量一样。简单起见,在jjcc2
中,我会将所有的符号都作为全局的一个label来实现。也就是说,如果代码中出现了
(setq a 1)
这样的代码,那么在最后生成的代码中,就会相应的在.data
段中有一个同名的label,其中存放着数值1。
既然都是全局变量,那么只需要准备一个容器来盛这些变量名即可。现阶段,暂时认为所有的变量都是数值类型即可。简单起见,这个容器直接用Common Lisp内置的HASH-TABLE
来表示。
当在jjcc2
函数中遭遇到setq
这个符号时,整个表的形态是这样的
(setq var form)
这时候,首先要将var
放入到记录全局变量的哈希表中。然后,递归地调用jjcc2
函数,先编译form
,得到一系列的汇编代码。然后,生成一条mov
语句,将eax
寄存器中的内容放到var
所指的内存位置中。最终的jjcc2
的代码如下
(defun jjcc2 (expr globals) "支持两个数的四则运算的编译器" (check-type globals hash-table) (cond ((eq (first expr) '+) `((movl ,(second expr) %eax) (movl ,(third expr) %ebx) (addl %ebx %eax))) ((eq (first expr) '-) `((movl ,(second expr) %eax) (movl ,(third expr) %ebx) (subl %ebx %eax))) ((eq (first expr) '*) ;; 将两个数字相乘的结果放到第二个操作数所在的寄存器中 ;; 因为约定了用EAX寄存器作为存放最终结果给continuation用的寄存器,所以第二个操作数应当为EAX `((movl ,(second expr) %eax) (movl ,(third expr) %ebx) (imull %ebx %eax))) ((eq (first expr) '/) `((movl ,(second expr) %eax) (cltd) (movl ,(third expr) %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 ,(format nil "~A(%RIP)" (second expr))))) globals))))
然后还需要修改stringify
函数,现在它需要处理传给jjcc2
的全局变量的哈希表,将其转化为对应的.data
段的声明。代码如下
(defun stringify (asm globals) "根据jjcc2产生的S表达式生成汇编代码字符串" (check-type globals hash-table) ;; 输出globals中的所有变量 ;; FIXME: 暂时只支持输出数字 (format t " .data~%") (maphash (lambda (k v) (format t "~A: .long ~D~%" k v)) globals) (format t " .section __TEXT,__text,regular,pure_instructions~%") (format t " .globl _main~%") (format t "_main:~%") (dolist (ins asm) (cond ((= (length ins) 3) (format t " ~A ~A, ~A~%" (first ins) (if (numberp (second ins)) (format nil "$~A" (second ins)) (second ins)) (if (numberp (third ins)) (format nil "$~A" (third ins)) (third ins)))) ((= (length ins) 2) (format t " ~A ~A~%" (first ins) (if (numberp (second ins)) (format nil "$~A" (second ins)) (second ins)))) ((= (length ins) 1) (format t " ~A~%" (first ins))))) (format t " movl %eax, %edi~%") (format t " movl $0x2000001, %eax~%") (format t " syscall~%"))
弄了一个辅助的函数来方便将jjcc2
和stringify
串起来
(defun test (expr) (let ((ht (make-hash-table))) (multiple-value-bind (asm globals) (jjcc2 expr ht) (stringify asm globals))))
尝试在SLIME中运行
(test '(setq a (+ 1 2)))
最后得到如下的汇编代码
.dataA: .long 0 .section __TEXT,__text,regular,pure_instructions .globl _main_main: MOVL $1, %EAX MOVL $2, %EBX ADDL %EBX, %EAX MOVL %EAX, A(%RIP) movl %eax, %edi movl $0x2000001, %eax syscall
全文完
阅读原文