如何编译setq?

Common Lisp中的setq类似于其它语言中的赋值语句,它可以给一个符号对象设定一个值,类似于将一个值赋值给一个变量一样。简单起见,在jjcc2中,我会将所有的符号都作为全局的一个label来实现。也就是说,如果代码中出现了

1
(setq a 1)

这样的代码,那么在最后生成的代码中,就会相应的在.data段中有一个同名的label,其中存放着数值1。

既然都是全局变量,那么只需要准备一个容器来盛这些变量名即可。现阶段,暂时认为所有的变量都是数值类型即可。简单起见,这个容器直接用Common Lisp内置的HASH-TABLE来表示。

当在jjcc2函数中遭遇到setq这个符号时,整个表的形态是这样的

1
(setq var form)

这时候,首先要将var放入到记录全局变量的哈希表中。然后,递归地调用jjcc2函数,先编译form,得到一系列的汇编代码。然后,生成一条mov语句,将eax寄存器中的内容放到var所指的内存位置中。最终的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 ,(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段的声明。代码如下

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
(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~%"))

弄了一个辅助的函数来方便将jjcc2stringify串起来

1
2
3
4
5
(defun test (expr)
(let ((ht (make-hash-table)))
(multiple-value-bind (asm globals)
(jjcc2 expr ht)
(stringify asm globals))))

尝试在SLIME中运行

1
(test '(setq a (+ 1 2)))

最后得到如下的汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
        .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

全文完