在這個練習中,你將定義一門小型語言的語法與執行模型,這門語言受到 x86-64 組合語言的啟發。
目標是將程式碼在受限環境中如何撰寫、解析與執行的方式形式化。
你的組合語言程式碼會寫在一個特殊的結構裡:
let program := assemble!(
-- assembly code here
)
這個結構必須產生一個程式,用來模擬一個執行組合語言程式碼並回傳值的函式。
接著用以下語法呼叫這個程式,其中 a、b、c 等是引數:
let result := program(a, b, c, ...)
所有引數與回傳值都是_64 位元有號整數_。 最多允許_6 個引數_。
回傳值與所有引數都是透過暫存器傳遞的。 你的語言必須支援以下暫存器:
| 暫存器 | 用途 |
|---|---|
rdi |
第 1 個引數 |
rsi |
第 2 個引數 |
rdx |
第 3 個引數 |
rcx |
第 4 個引數 |
r8 |
第 5 個引數 |
r9 |
第 6 個引數 |
rax |
回傳值 |
暫存器一開始的預設值都是 0,除非它們被用來把引數傳遞給函式。
舉例來說,在以下程式裡,除了初始化為 10 的 rdi 和初始化為 -20 的 rsi 之外,所有暫存器的值都是 0:
program(10, -20)
程式一定會把回傳值存在 rax 裡。
暫存器名稱_不區分大小寫_。
這表示 rax、RAX 和 rAx 都指向同一個暫存器。
組合語言程式碼的每一行可以是兩種形式之一:
除非被某些指令改變,否則程式的執行流程是依序線性進行的。 也就是說,前一行會先完整執行完,才會執行下一行。
標籤的語法如下:
<label>:
除了標記程式碼中的特定位置、讓指令可以使用之外,標籤不會改變任何暫存器的值,也不會對程式造成其他影響。
標籤_區分大小寫_。
這表示 Start、start 和 sTart 都是不同的標籤。
大多數指令的語法如下:
<opcode> <destination>, <source>
opcode 指出要執行的運算,並使用 destination 與 source 兩個運算元的值。
運算結果會存放在 destination 運算元中。
舉例來說,add 指令會把 destination 與 source 運算元的值相加,並把結果存放在 destination 運算元中。
destination 運算元_一定_是暫存器,而 source 運算元可以是暫存器或數字常值。
以下是你的程式必須支援的指令清單:
| 運算碼 | 執行的運算 |
|---|---|
mov |
destination := source |
add |
destination := destination + source |
sub |
destination := destination - source |
mul |
destination := destination * source |
div |
destination := destination / source |
xor |
destination := destination ^^^ source |
and |
destination := destination &&& source |
or |
destination := destination ||| source |
shl |
destination := destination <<< source |
shr |
destination := destination >>> source |
除此之外,你的程式還必須支援雙運算元的 cmp 指令。
這個指令不會修改任何暫存器,而是比較 destination 與 source 運算元,並把程式的內部狀態設為下列三種之一:
destination > source,則為_大於_destination == source,則為_等於_destination < source,則為_小於_你要如何在內部追蹤這個狀態,由你自行決定。
有些指令會改變程式的流程,把執行轉移到程式碼的其他位置。 它們叫做_跳躍_指令。
它們都只接受一個運算元,也就是一個標籤,用來指出跳躍的目標,即程式碼中執行會繼續下去的位置:
<opcode> <label>
jmp 指令_一定_會跳躍到目標標籤。
其他跳躍指令只有在程式的內部狀態被設為特定值時才會跳躍。
這表示它們前面通常會有一個 cmp 指令。
以下是你的程式必須支援的跳躍指令:
| 指令 | 執行的運算 |
|---|---|
jmp |
無條件跳躍。一定會執行跳躍 |
je |
若內部狀態為_等於_,則跳躍 |
jl |
若內部狀態為_小於_,則跳躍 |
jg |
若內部狀態為_大於_,則跳躍 |
指令的運算碼,無論是雙運算元還是單運算元,都_不區分大小寫_。
這表示 add、ADD 和 aDd 都指向同一個指令。