In dieser Übung definierst du die Syntax und das Ausführungsmodell für eine kleine Sprache, die von der x86-64-Assemblersprache inspiriert ist.
Das Ziel ist, zu formalisieren, wie Code in einer eingeschränkten Umgebung geschrieben, geparst und ausgeführt wird.
Dein Assembler-Code wird in ein spezielles Konstrukt geschrieben:
let program := assemble!(
-- assembly code here
)
Dieses Konstrukt muss ein Programm erzeugen, das eine Funktion simuliert, die den Assembler-Code ausführt und einen Wert zurückgibt.
Dieses Programm wird dann mit der folgenden Syntax aufgerufen, wobei a, b, c usw. Argumente sind:
let result := program(a, b, c, ...)
Alle Argumente und der Rückgabewert sind vorzeichenbehaftete 64-Bit-Ganzzahlen. Es sind maximal 6 Argumente erlaubt.
Der Rückgabewert und alle Argumente werden in Registern übergeben. Deine Sprache muss die folgenden Register unterstützen:
| Register | Rolle |
|---|---|
rdi |
1. Argument |
rsi |
2. Argument |
rdx |
3. Argument |
rcx |
4. Argument |
r8 |
5. Argument |
r9 |
6. Argument |
rax |
Rückgabewert |
Register starten mit dem Standardwert 0, es sei denn, sie werden verwendet, um Argumente an die Funktion zu übergeben.
In dem folgenden Programm haben zum Beispiel alle Register den Wert 0, mit Ausnahme von rdi, das mit 10 initialisiert wird, und rsi, das mit -20 initialisiert wird:
program(10, -20)
Der Rückgabewert des Programms wird immer in rax gespeichert.
Bei Registernamen wird nicht zwischen Groß- und Kleinschreibung unterschieden.
Das bedeutet, dass rax, RAX und rAx alle dasselbe Register bezeichnen.
Jede Zeile im Assembler-Code kann eine von zwei Formen haben:
Sofern keine Anweisung etwas anderes bewirkt, läuft der Ausführungsfluss des Programms linear ab. Das bedeutet, dass eine vorherige Zeile vollständig ausgeführt wird, bevor die darauf folgende Zeile ausgeführt wird.
Labels haben die folgende Syntax:
<label>:
Sie ändern den Wert keines Registers und haben keine andere Wirkung auf das Programm, als bestimmte Stellen des Codes zu markieren, damit sie von Anweisungen verwendet werden können.
Bei Labels wird nicht zwischen Groß- und Kleinschreibung unterschieden.
Das bedeutet, dass Start, start und sTart alle unterschiedliche Labels sind.
Die meisten Anweisungen haben die folgende Syntax:
<opcode> <destination>, <source>
Der opcode gibt die auszuführende Operation an und verwendet dabei die Werte der Operanden destination und source.
Das Ergebnis der Operation wird im Operanden destination gespeichert.
Die Anweisung add zum Beispiel addiert die Werte in den Operanden destination und source und speichert dieses Ergebnis im Operanden destination.
Der Operand destination ist immer ein Register, während der Operand source ein Register oder eine Literalzahl sein kann.
Dies ist eine Liste der Anweisungen, die dein Programm unterstützen muss:
| Opcode | ausgeführte Operation |
|---|---|
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 |
Darüber hinaus muss dein Programm die cmp-Anweisung mit zwei Operanden unterstützen.
Diese Anweisung ändert kein Register, sondern vergleicht die Operanden destination und source und setzt einen internen Zustand des Programms auf eine von drei Möglichkeiten:
destination > source
destination == source
destination < source
Wie du diesen Zustand intern verfolgst, bleibt dir überlassen.
Es gibt einige Anweisungen, die den Ablauf des Programms ändern und die Ausführung an eine andere Stelle des Codes übertragen. Sie werden Sprunganweisungen genannt.
Sie alle nehmen nur einen Operanden, nämlich ein Label, das das Ziel des Sprungs angibt, also die Stelle im Code, an der die Ausführung fortgesetzt wird:
<opcode> <label>
Die jmp-Anweisung führt immer den Sprung zum Ziel-Label aus.
Andere Sprunganweisungen führen den Sprung nur aus, wenn der interne Zustand des Programms auf einen bestimmten Wert gesetzt ist.
Das bedeutet, dass ihnen normalerweise eine cmp-Anweisung vorausgeht.
Dies sind die Sprunganweisungen, die dein Programm unterstützen muss:
| Anweisung | ausgeführte Operation |
|---|---|
jmp |
unbedingter Sprung. Der Sprung wird immer ausgeführt |
je |
springt, wenn der interne Zustand gleich ist |
jl |
springt, wenn der interne Zustand kleiner als ist |
jg |
springt, wenn der interne Zustand größer als ist |
Bei den Opcodes von Anweisungen, ob mit zwei Operanden oder mit einem, wird nicht zwischen Groß- und Kleinschreibung unterschieden.
Das bedeutet, dass add, ADD und aDd alle dieselbe Anweisung bezeichnen.
Melde dich bei Exercism an, um Lean mit 100 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.