在本练习中,你要为一门受 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,除非它们被用来向函数传递实参。
例如,在下面的程序中,所有寄存器的值都是 0,只有 rdi 和 rsi 例外,前者初始化为 10,后者初始化为 -20:
program(10, -20)
程序的返回值始终存放在 rax 中。
寄存器名不区分大小写。
这意味着 rax、RAX 和 rAx 指的是同一个寄存器。
汇编代码中的每一行都可以是下面两种形式之一:
除非被某条指令改变,程序的执行流程都是线性推进的。 也就是说,上一行会在下一行之前完整执行。
标签的语法如下:
<label>:
除了标记代码中的特定位置以供指令使用之外,标签不会改变任何寄存器的值,也不会对程序产生其他影响。
标签区分大小写。
这意味着 Start、start 和 sTart 都是不同的标签。
大多数指令的语法如下:
<opcode> <destination>, <source>
opcode 表示要执行的操作,它会用到 destination 和 source 两个操作数的值。
操作的结果存放在 destination 操作数中。
例如,add 指令把 destination 和 source 两个操作数中的值相加,并把结果存放在 destination 操作数中。
destination 操作数始终是寄存器,而 source 操作数可以是寄存器,也可以是字面量数字。
你的程序必须支持以下指令:
| opcode | operation performed |
|---|---|
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 指令。
你的程序必须支持以下跳转指令:
| instruction | operation performed |
|---|---|
jmp |
unconditional jump. The jump is always performed |
je |
jumps if the internal state is equal |
jl |
jumps if the internal state is less than |
jg |
jumps if the internal state is greater than |
指令的操作码,无论是双操作数还是单操作数,都不区分大小写。
这意味着 add、ADD 和 aDd 指的是同一条指令。