C編譯器入門~想懂低階系統從自幹編譯器開始~
  • 譯者序
  • 前言
    • 符號與規範
    • 本書的開發環境
    • 關於作者
    • 結束前言之前
  • 機械語言與組譯器
    • CPU 與記憶體
    • 什麼是組譯器
    • C程式和所對應的組合語言
      • 簡單的範例
      • 包含呼叫函式的範例
    • 本章小結
  • 創造計算機等級的語言
    • 第1步:創造能編譯1個整數的語言
    • 第2步:製作可以算加減法的編譯器
    • 第3步:加入標記解析器(tokenizer)
    • 第4步:改良錯誤訊息
    • 文法的記法與遞迴下降分析法
      • 將文法結構表示為樹(tree)
      • 以生成規則定義文法
      • 以 BNF 描述生成規則
      • 簡單的生成規則
      • 以生成規則描述運算子的優先順序
      • 包含遞迴的生成規則
      • 遞迴下降語法分析
    • 堆疊機
      • 堆疊機的概念
      • 編譯成堆疊機指令
      • 以x86-64實作堆疊機的方法
    • 第5步:製作可進行四則運算的編譯器
    • 第6步:單項加與單項減
    • 第7步:比較運算子
      • 修改標記解析器
      • 新的文法
      • 產生組合語言指令
  • 分離編譯與連結
    • 分離編譯
      • 分離編譯與其必要性
      • 標頭檔的必要性與其內容
      • 連結錯誤
      • 全域變數的宣告與定義
    • 第8步:分割檔案與修改 Makefile
      • 分割檔案
      • 修改 Makefile
  • 函式與區域變數
    • 第9步:1個字的區域變數
      • 堆疊上的變數空間
      • 修改標記解析器
      • 修改分析器
      • 左邊值與右邊值
      • 從任意的記憶體位址取得其值
      • 修改指令產生器
      • 修改主函式
    • 第10步:複數文字的區域變數
    • 第11步:return
    • 1973年的C編譯器
Powered by GitBook
On this page

Was this helpful?

  1. 創造計算機等級的語言
  2. 第7步:比較運算子

產生組合語言指令

Previous新的文法Next分離編譯與連結

Last updated 5 years ago

Was this helpful?

在 x86-64 我們使用cmp指令來進行比較。從堆疊裡彈出2個整數進行比較,如果相同的話就在 RAX 設定1否則設為0,組合語言程式如下:

pop rdi
pop rax
cmp rax, rdi
sete al
movzb rax, al

這段程式雖然短,但內涵有點豐富,我們來一步一步看下去:

開頭2行是把值從堆疊彈出。第3行是把他們進行比較(compare),但是比較的結果到哪裡去了呢?x86-64 會把比較指令的結果放到叫「狀態暫存器(FLAGS register)」的一個特別的地方。狀態暫存器是每當進行整數運算或比較就會更新的一個暫存器,其中有代表結果是否為0的位元、代表是否發生溢位(overflow)的位元、代表是否結果小於0的位元等等。

因為狀態暫存器並不是一般的整數暫存器,所以想要把比較的結果放到 RAX 的話,需要把狀態暫存器裡特定的位元複製到 RAX 上才行。這個指令就是sete,sete指令在前一個cmp指令的2個暫存器比較結果相同時,會把特定的暫存器(這裡為 AL)設成1,否則設為0。

AL 這個暫存器名在本書中至今為止是第一次登場,但其實 AL 只是代表 RAX 最後8位元的暫存器別名而已。所以,用sete設定 AL 的值時,RAX 也會自動更新。但是,透過 AL 更新 RAX 的話,首56位元的值並不會改變,所以要把 RAX 的值設為0或1的話,需要把首56位元清成0才行。為此我們使用movzb指令來達成。如果直接用sete直接寫進 RAX 就直接解決了,但是sete定義上只能存取8位元暫存器,所以比較的指令得像這樣用兩個指令來設定 RAX 的值才行。

用其他指令取代sete就可以實作其他比較運算子。<用setl、<=用setle,!=則請使用setne來實作。

>和>=不需要指令產生器支援。請在分析器讀取時把左右換過來改成<和<=就可以了。

小知識:狀態暫存器與硬體

x86-64 不把數值比較的結果存在普通的整數暫存器,而是默默存在特殊的暫存器的作法,可能一開始會覺得不是很好理解。實際上,有的 RISC 處理器不喜歡設置特殊暫存器,所以有把數值比較結果設定在普通暫存器的指令。像 RISC-V 就是這樣的指令集。

但是,從硬體實作的角度來看,想要直覺地實作狀態暫存器是非常簡單的。只要在進行整數運算時,把其結果的線分一條出來另外接到額外的邏輯迴路上,確認其結果是不是0(所有的線是不是都為0)、結果是不是負數(最高位是不是1)等等,並設定到狀態暫存器的各個位元就好了。有狀態暫存器的 CPU 就是這樣實作的,每次只要進行整數運算就會同時更新狀態暫存器。

這個設計下,不只是cmp,連add或sub都會更新到狀態暫存器。實際上,cmp真實的身份就是為了更新狀態暫存器的特殊sub指令。如果執行像sub rax, rdi指令,雖然可以知道 RAX 和 RDI 的大小關係,但是 RAX 會被更新,所以準備了cmp這個不會寫入整數暫存器的sub指令。

軟體的角度來看的話,「順便算某某東西」總是會多花時間,但是硬體的話,只要多拉一條線多用一些電晶體,並不會有時間上的損失,所以如果是如上直覺版的硬體實作的話,每次都更新狀態暫存器的作法並不會有額外的成本。

參考實作:

6ddba4be5f633886