這篇的東西比較多。html
首先要處理一下inside-out/aux
和inside-out
這兩個函數。以前的inside-out/aux
其實一直不支持對progn
的處理,須要先補充;而inside-out
則能夠優化一下,避免在只有一個表達式的狀況下,也用progn
將其包裹起來。修改後的inside-out/aux
和inside-out
分別以下git
(defun inside-out/aux (expr result) "將嵌套的表達式EXPR由內而外地翻出來" (check-type expr list) ;; 出於簡單起見,暫時只處理加法運算 (cond ((member (first expr) '(+ - * / _exit > exit)) (let ((operands '())) ;; 對參數列表中的全部表達式都遞歸地進行【外翻】處理 (dolist (arg (rest expr)) (if (listp arg) (let ((var (gensym))) (setf result (inside-out/aux arg result)) (let ((val (pop result))) (push `(setq ,var ,val) result) (push var operands))) (push arg operands))) (push (cons (first expr) (nreverse operands)) result) result)) ((eq (first expr) 'if) (push `(if ,(inside-out (second expr)) ,(inside-out (third expr)) ,(inside-out (fourth expr))) result) result) ((eq (first expr) 'progn) (dolist (e (rest expr)) (push (inside-out e) result)) result) (t (push expr result) result))) (defun inside-out (expr) (let ((forms (nreverse (inside-out/aux expr '())))) (if (> (length forms) 1) (cons 'progn forms) (car forms))))
實際上能夠更進一步:inside-out/aux
和inside-out
大能夠合併到一塊兒,結果以下github
(defun inside-out (expr) "將嵌套的表達式EXPR由內而外地翻出來" (check-type expr list) (cond ((eq (first expr) 'if) `(if ,(inside-out (second expr)) ,(inside-out (third expr)) ,(inside-out (fourth expr)))) ((eq (first expr) 'progn) (cons 'progn (mapcar #'inside-out (rest expr)))) (t (let ((assignments '()) (operands '())) ;; 對參數列表中的全部表達式都遞歸地進行【外翻】處理 (dolist (arg (rest expr)) (if (listp arg) (let ((val (inside-out arg)) (var (gensym))) (push `(setq ,var ,val) assignments) (push var operands)) (push arg operands))) (if (null assignments) expr `(progn ,@(nreverse assignments) (,(first expr) ,@(nreverse operands))))))))
好了,接下來纔是本文的重點:如何編譯全部的函數調用表達式。ide
儘管我在上面誇下海口,說要編譯「全部」的函數調用表達式,但事實上,如今我還作不到——我只能把全部的函數調用表達式,都映射到對C標準庫中的函數的調用。所以,若是想要調用C標準庫中的putchar
函數,那麼必須寫下以下的代碼函數
(|_putchar| #.(char-code #\A))
這裏用了雙豎線的語法來確保這個符號的symbol-name
是全小寫的putchar
,開始的下劃線是由於在macOS中,調用C函數的時候必需要加上這個前綴的下劃線。#.
是個Common Lisp中的reader macro
,可讓後面的表達式在讀取期被求值,這樣我就不須要手寫字母A的code-point啦——好吧,是在炫技。優化
要編譯這種函數調用表達式,只須要模仿一下此前對_exit
的處理就能夠啦。首先,是求值函數調用表達式中的各個參數,而後將它們放入恰當的位置中——有的要放入寄存器中,有的要壓棧。做爲一個野路子的編譯器愛好者,我固然是沒有正兒八經地看過牙膏廠或者按摩店出品的ABI手冊的,我看的是這一份資料:https://www3.nd.edu/~dthain/c...rest
因此我瞭解到的是:code
RDI
、RSI
、RDX
、RCX
、R8
,以及R9
這些寄存器中的;而後因爲macOS的任性要求,在調用前還須要將RSP
寄存器對齊到16字節的內存地址。我在這裏折騰了好久,最後才發現,原來我要在函數調用結束以後,把修改過的RSP
寄存器恢復原狀才行_(:з」∠)_orm
因此,這一部分的代碼是這樣子的(精簡了一下)htm
(defun jjcc2 (expr globals) "支持兩個數的四則運算的編譯器" (check-type globals hash-table) (cond (t ;; 這裏省卻了不少其它狀況下的代碼,歡迎讀者自行腦補 ;; 按照這裏(https://www3.nd.edu/~dthain/courses/cse40243/fall2015/intel-intro.html)所給的函數調用約定來傳遞參數 (let ((instructions '()) (registers '(%rdi %rsi %rdx %rcx %r8 %r9))) (dotimes (i (length (rest expr))) (if (nth i registers) (push `(movq ,(get-operand expr i) ,(nth i registers)) instructions) (push `(pushq ,(get-operand expr i)) instructions))) ;; 通過一番嘗試後,我發現必須在完成函數調用後恢復RSP寄存器纔不會致使段錯誤 `(,@(nreverse instructions) (pushq %rsp) (and ,(format nil "$0x~X" #XFFFFFFFFFFFFFFF0) %rsp) (call ,(first expr)) (popq %rsp))))))
先用pushq
把RSP
保存起來,待call
指令結束返回以後,再popq
出來恢復它XD
到這裏爲止,就能夠來寫經典的Hello World了,代碼以下
(fb `(progn ,@(mapcar #'(lambda (c) `(|_putchar| ,(char-code c))) (coerce "Hello, world!" 'list)) (_exit 0)))
生成的彙編代碼以下
.data .section __TEXT,__text,regular,pure_instructions .globl _main _main: MOVQ $72, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $101, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $108, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $108, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $111, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $44, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $32, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $119, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $111, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $114, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $108, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $100, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVQ $33, %RDI PUSHQ %RSP AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _putchar POPQ %RSP MOVL $0, %EDI AND $0xFFFFFFFFFFFFFFF0, %RSP CALL _exit
使用GAS編譯上述代碼,並藉助gcc連接後,運行它就能夠看到Hello, world!
了
全文完