支持四則運算中的變量

上一篇文章中,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-operandcode

(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

全文完。

閱讀原文

相關文章
相關標籤/搜索