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
全文完
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。