31

如何编译setq?

 4 years ago
source link: https://www.tuicool.com/articles/UnmA3eJ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

全文完


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK