如何编译setq

37次阅读

共计 2465 个字符,预计需要花费 7 分钟才能阅读完成。

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

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

(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

全文完

阅读原文

正文完
 0