Common Lisp中的setq
相似於其它語言中的賦值語句,它能夠給一個符號對象設定一個值,相似於將一個值賦值給一個變量同樣。簡單起見,在jjcc2
中,我會將全部的符號都做爲全局的一個label來實現。也就是說,若是代碼中出現了git
(setq a 1)
這樣的代碼,那麼在最後生成的代碼中,就會相應的在.data
段中有一個同名的label,其中存放着數值1。github
既然都是全局變量,那麼只須要準備一個容器來盛這些變量名便可。現階段,暫時認爲全部的變量都是數值類型便可。簡單起見,這個容器直接用Common Lisp內置的HASH-TABLE
來表示。app
當在jjcc2
函數中遭遇到setq
這個符號時,整個表的形態是這樣的函數
(setq var form)
這時候,首先要將var
放入到記錄全局變量的哈希表中。而後,遞歸地調用jjcc2
函數,先編譯form
,獲得一系列的彙編代碼。而後,生成一條mov
語句,將eax
寄存器中的內容放到var
所指的內存位置中。最終的jjcc2
的代碼以下rest
(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
段的聲明。代碼以下code
(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~%"))
弄了一個輔助的函數來方便將jjcc2
和stringify
串起來orm
(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
全文完ip