この演習では、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です。ただし、関数に引数を渡すために使われる場合は除きます。
たとえば次のプログラムでは、rdiが10、rsiが-20で初期化され、それ以外のレジスタはすべて0です。
program(10, -20)
プログラムの戻り値は常にraxに格納されます。
レジスタ名は_大文字と小文字を区別しません_。
つまり、rax、RAX、rAxはすべて同じレジスタを指します。
アセンブリのコードの各行は、次の2つの形式のいずれかです。
何らかの命令で変更されない限り、プログラムの実行の流れは上から順に進みます。 つまり、ある行の実行が完全に終わってから、その次の行が実行されます。
ラベルは次の構文で書きます。
<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 |
これらに加えて、2オペランドのcmp命令も扱えるようにします。
この命令はどのレジスタも変更しません。代わりにdestinationとsourceのオペランドを比較し、プログラムの内部状態を次の3つのいずれかに設定します。
destination > sourceのとき、より大きい
destination == sourceのとき、等しい
destination < sourceのとき、より小さい
この状態を内部的にどう保持するかは自由です。
プログラムの流れを変え、実行をコードの別の位置に移す命令があります。 これらは_ジャンプ_命令と呼ばれます。
いずれもオペランドを1つだけ取ります。これはジャンプ先、つまり実行を続けるコード上の位置を示すラベルです。
<opcode> <label>
jmp命令は_常に_、対象のラベルへジャンプします。
その他のジャンプ命令は、プログラムの内部状態が特定の値に設定されているときだけジャンプします。
そのため、通常はcmp命令の後に続きます。
プログラムが扱えるようにするジャンプ命令は次のとおりです。
| 命令 | 実行される操作 |
|---|---|
jmp |
無条件ジャンプ。常にジャンプします |
je |
内部状態が_等しい_ときにジャンプします |
jl |
内部状態が_より小さい_ときにジャンプします |
jg |
内部状態が_より大きい_ときにジャンプします |
命令のオペコードは、2オペランドでも1オペランドでも、大文字と小文字を区別しません。
つまり、add、ADD、aDdはすべて同じ命令を指します。