在上一篇文章中,jjcc2
函數實現了對setq
這個語句的編譯。這麼一來,即可以將加減乘除運算中的嵌套表達式都替換爲變量了。好比,將git
(+ (+ 1 2) 3)
中的嵌套的表達式(+ 1 2)
用一個變量G564
代替,變成github
(PROGN (SETQ #:G564 (+ 1 2)) (+ #:G564 3))
PS:上面的結果中的#:G564
只是打印出來的時候長這個樣子而已,實際地輸入這段代碼的話,兩個#:G564
實際上是不一樣的符號,會致使未綁定的變量的錯誤的。segmentfault
言歸正傳。既然如此,如今就要來支持編譯(+ #:G564 3)
這樣的表達式了。其實這個真的是太簡單了,只須要將這個符號塞入到jjcc2
的第二個參數的globals
中,而後在生成的「彙編指令」的S表達式中,嵌入這個符號便可。app
我剛開始的時候也是這麼想的,後來發現這樣出來的代碼編譯不過,哭函數
折騰了一小段時間後,才知道原來有一種叫作「RIP-relative」的東西——好吧,個人X64的彙編語言知識也是趕鴨子上架的,遇到什麼問題就放狗搜,因此徹底不成體系——總之,我找到了解決辦法,就是將本來放入一個符號的操做數,替換爲相似於下面這樣的內容rest
G564(%RIP)
因此對於操做數,實際上還須要先判斷一下其類型。若是是整數,就按照原來的方式原樣輸出;若是是符號,就生成像上面這樣的RIP-relative的結構。這部分太常常出現了,因而提煉出了一個專門處理四則運算的操做數的輔助函數get-operand
code
(defun get-operand (expr n) "從EXPR中提取出第N個操做數,操做數的下標從0開始計算" (check-type expr list) (check-type n integer) (let ((e (nth (1+ n) expr))) (etypecase e (integer e) (symbol (format nil "~A(%RIP)" e)))))
藉助它重寫jjcc2
,結果以下orm
(defun jjcc2 (expr globals) "支持兩個數的四則運算的編譯器" (check-type globals hash-table) (cond ((eq (first expr) '+) `((movl ,(get-operand expr 0) %eax) (movl ,(get-operand expr 1) %ebx) (addl %ebx %eax))) ((eq (first expr) '-) `((movl ,(get-operand expr 0) %eax) (movl ,(get-operand expr 1) %ebx) (subl %ebx %eax))) ((eq (first expr) '*) ;; 將兩個數字相乘的結果放到第二個操做數所在的寄存器中 ;; 由於約定了用EAX寄存器做爲存放最終結果給continuation用的寄存器,因此第二個操做數應當爲EAX `((movl ,(get-operand expr 0) %eax) (movl ,(get-operand expr 1) %ebx) (imull %ebx %eax))) ((eq (first expr) '/) `((movl ,(get-operand expr 0) %eax) (cltd) (movl ,(get-operand expr 1) %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 ,(get-operand expr 0)))) globals))))
如今,若是運行下面的這個example1
函數字符串
(defun example1 () "驗證jjcc2確實能夠處理含有變量的加減乘除運算" (let ((ht (make-hash-table))) (setf (gethash 'a ht) 1) (let ((asm (jjcc2 '(+ a a) ht))) (stringify asm ht))))
即可以獲得下面這段彙編代碼了get
.data A: .long 1 .section __TEXT,__text,regular,pure_instructions .globl _main _main: MOVL A(%RIP), %EAX MOVL A(%RIP), %EBX ADDL %EBX, %EAX movl %eax, %edi movl $0x2000001, %eax syscall
全文完。