如何編譯setq?

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~%"))

弄了一個輔助的函數來方便將jjcc2stringify串起來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

閱讀原文

相關文章
相關標籤/搜索