產生組合語言指令

在 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 上才行。這個指令就是setesete指令在前一個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來實作。

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

參考實作: 6ddba4be5f633886

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

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

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

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

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

Last updated