產生組合語言指令
Last updated
Last updated
在 x86-64 我們使用cmp
指令來進行比較。從堆疊裡彈出2個整數進行比較,如果相同的話就在 RAX 設定1否則設為0,組合語言程式如下:
這段程式雖然短,但內涵有點豐富,我們來一步一步看下去:
開頭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
來實作。
>
和>=
不需要指令產生器支援。請在分析器讀取時把左右換過來改成<
和<=
就可以了。
參考實作: 6ddba4be5f633886
x86-64 不把數值比較的結果存在普通的整數暫存器,而是默默存在特殊的暫存器的作法,可能一開始會覺得不是很好理解。實際上,有的 RISC 處理器不喜歡設置特殊暫存器,所以有把數值比較結果設定在普通暫存器的指令。像 RISC-V 就是這樣的指令集。
但是,從硬體實作的角度來看,想要直覺地實作狀態暫存器是非常簡單的。只要在進行整數運算時,把其結果的線分一條出來另外接到額外的邏輯迴路上,確認其結果是不是0(所有的線是不是都為0)、結果是不是負數(最高位是不是1)等等,並設定到狀態暫存器的各個位元就好了。有狀態暫存器的 CPU 就是這樣實作的,每次只要進行整數運算就會同時更新狀態暫存器。
這個設計下,不只是cmp
,連add
或sub
都會更新到狀態暫存器。實際上,cmp
真實的身份就是為了更新狀態暫存器的特殊sub
指令。如果執行像sub rax, rdi
指令,雖然可以知道 RAX 和 RDI 的大小關係,但是 RAX 會被更新,所以準備了cmp
這個不會寫入整數暫存器的sub
指令。
軟體的角度來看的話,「順便算某某東西」總是會多花時間,但是硬體的話,只要多拉一條線多用一些電晶體,並不會有時間上的損失,所以如果是如上直覺版的硬體實作的話,每次都更新狀態暫存器的作法並不會有額外的成本。