# 產生組合語言指令

在 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 的值才行。

![](https://416395721-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LjokrNdcE1H2E8hE2Dc%2F-LqZK-Bl2vWYu6oIrmB_%2F-LqZNCGJIhVg7X9inFwf%2Findex.svg?alt=media\&token=8fbd9cba-2a6c-4467-a67b-108071d1e361)

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

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

> 參考實作： [6ddba4be5f633886](https://github.com/rui314/chibicc/commit/6ddba4be5f63388607fc77fd786267b9ddcb14c9)

{% hint style="info" %}
## 小知識：狀態暫存器與硬體

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

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

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

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