Tracks
/
Lean
Lean
/
Übungen
/
Assembler
Assembler

Assembler

Schwer

Anleitung

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.

Ausführungsmodell

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.

Register

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.

Note

Bei Registernamen wird nicht zwischen Groß- und Kleinschreibung unterschieden. Das bedeutet, dass rax, RAX und rAx alle dasselbe Register bezeichnen.

Assembler-Code

Jede Zeile im Assembler-Code kann eine von zwei Formen haben:

  • Labels markieren bestimmte Stellen des Programms, die von einigen Anweisungen verwendet werden.
  • Anweisungen stellen Operationen dar, die vom Programm ausgeführt werden.

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

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.

Note

Bei Labels wird nicht zwischen Groß- und Kleinschreibung unterschieden. Das bedeutet, dass Start, start und sTart alle unterschiedliche Labels sind.

Anweisungen mit zwei Operanden

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:

  • größer als, wenn destination > source
  • gleich, wenn destination == source
  • kleiner als, wenn destination < source

Wie du diesen Zustand intern verfolgst, bleibt dir überlassen.

Anweisungen mit einem Operanden

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
Note

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.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Lean Exercism

Bereit, mit Assembler zu starten?

Melde dich bei Exercism an, um Lean mit 100 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.