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)))
最后得到如下的汇编代码
.data
A: .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
全文完
阅读原文