組譯

組譯

困難

說明

在這個練習中,你將定義一門小型語言的語法與執行模型,這門語言受到 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 裡。

Note

暫存器名稱_不區分大小寫_。 這表示 rax、RAX 和 rAx 都指向同一個暫存器。

組合語言程式碼

組合語言程式碼的每一行可以是兩種形式之一:

  • 標籤會標記程式中特定的位置,供某些指令使用。
  • 指令代表程式執行的一項操作。

除非被某些指令改變,否則程式的執行流程是依序線性進行的。 也就是說,前一行會先完整執行完,才會執行下一行。

標籤

標籤的語法如下:

<label>:

除了標記程式碼中的特定位置、讓指令可以使用之外,標籤不會改變任何暫存器的值,也不會對程式造成其他影響。

Note

標籤_區分大小寫_。 這表示 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 若內部狀態為_大於_,則跳躍
Note

指令的運算碼,無論是雙運算元還是單運算元,都_不區分大小寫_。 這表示 add、ADD 和 aDd 都指向同一個指令。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Lean Exercism

準備好開始 組譯 了嗎?

註冊 Exercism,透過 100 個練習 和真人引導來學習並精通 Lean,全部免費。