本文很水。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
全文完。