一個簡陋的四則運算編譯器實現

本文很水。git

有一天,我心血來潮想要寫一個將Common Lisp編譯成彙編(x64那種)的編譯器。我喜歡Common Lisp這門語言,它很是好玩,有許多有趣的特性(宏、condition system等),而且它的生態很貧瘠,有不少造輪子的機會。由於我懂的還不夠多,因此無法從源代碼一步到位生成可執行文件,只好先輸出彙編代碼,再利用現成的彙編器(好比as、nasm)從這些輸出內容生成可執行文件。至於這東西是否是真的算是編譯器,我也不是很在乎。github

好了,我要開始表演了。shell

你可能看過龍書,或者其它比較經典的編譯原理和實踐方面的書。那你應該會知道,編譯器還蠻複雜的。但我水平有限,把持不住工業級的產品那麼精妙的結構和代碼,因此個人編譯器很簡陋——簡陋到起碼這個版本一眼就看到盡頭了。函數

儘管簡陋,但身爲一名業餘愛好者,嘗試開發這麼一個玩具仍是很excited的。因爲編譯器自己也是用Common Lisp寫的,因此就偷個懶不寫front end的部分了,聚焦於從CL代碼到彙編代碼的實現。code

先從最簡單的一種狀況——二元整數的加法入手,好比下面這段代碼orm

(+ 1 2)

對於加法,能夠輸出ADDL指令,兩個參數則隨便找兩個寄存器放進去就行了。一段簡單得不能再簡單的代碼一會兒就寫出來了ci

(defun jjcc2 (expr)
  "支持兩個數的四則運算的編譯器"
  (cond ((eq (first expr) '+)
         `((movl ,(second expr) %eax)
           (movl ,(third expr) %ebx)
           (addl %eax %ebx)))))

(defun stringify (asm)
  "根據jjcc2產生的S表達式生成彙編代碼字符串"
  (format t "        .section __TEXT,__text,regular,pure_instructions~%")
  (format t "        .globl _main~%")
  (format t "_main:~%")
  (dolist (ins asm)
    (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))))
  (format t "        movl %ebx, %edi~%")
  (format t "        movl $0x2000001, %eax~%")
  (format t "        syscall~%"))

在 REPL 中像下面這樣運行開發

(stringify (jjcc2 '(+ 1 2)))

它會輸出這些內容字符串

.section __TEXT,__text,regular,pure_instructions
        .globl _main
_main:
        MOVL $1, %EAX
        MOVL $2, %EBX
        ADDL %EAX, %EBX
        movl %ebx, %edi
        movl $0x2000001, %eax
        syscall

把上面這段彙編代碼保存到名爲 jjcc.s 的文件,再運行下列的命令,就能夠獲得一個能跑的 a.out 文件了get

as -o jjcc.o jjcc.s
gcc jjcc.o

運行以後,再輸出上一個命令的退出碼,就能夠看到結果3了。

.section那一行太長,其實能夠用.text來代替;指令和寄存器的名字大小寫混用;stringify函數中對第二第三個操做數的處理代碼很冗餘,等等,都是能夠吐槽的問題XD

全文完。

閱讀原文

相關文章
相關標籤/搜索