In questo esercizio definirai la sintassi e il modello di esecuzione di un piccolo linguaggio ispirato al linguaggio assembly x86-64.
L'obiettivo è formalizzare come il codice viene scritto, analizzato ed eseguito in un ambiente limitato.
Il tuo codice assembly sarà scritto all'interno di un costrutto speciale:
let program := assemble!(
-- assembly code here
)
Questo costrutto deve produrre un programma, che simula una funzione che esegue il codice assembly e restituisce un valore.
Questo programma viene poi chiamato usando la seguente sintassi, dove a, b, c, ecc. sono argomenti:
let result := program(a, b, c, ...)
Tutti gli argomenti e il valore restituito sono interi con segno a 64 bit. È consentito un massimo di 6 argomenti.
Il valore restituito e tutti gli argomenti vengono passati nei registri. Il tuo linguaggio deve supportare i seguenti registri:
| registro | ruolo |
|---|---|
rdi |
1º argomento |
rsi |
2º argomento |
rdx |
3º argomento |
rcx |
4º argomento |
r8 |
5º argomento |
r9 |
6º argomento |
rax |
valore restituito |
I registri partono con un valore predefinito di 0, a meno che non vengano usati per passare argomenti alla funzione.
Ad esempio, nel seguente programma, tutti i registri hanno un valore di 0, fatta eccezione per rdi, inizializzato con 10, e rsi, inizializzato con -20:
program(10, -20)
Il valore restituito dal programma è sempre memorizzato in rax.
I nomi dei registri non distinguono maiuscole e minuscole.
Ciò significa che rax, RAX e rAx si riferiscono tutti allo stesso registro.
Ogni riga nel codice assembly può avere due forme:
Se non viene modificato da qualche istruzione, il flusso di esecuzione del programma procede in modo lineare. Ciò significa che una riga precedente viene eseguita completamente prima che venga eseguita la riga successiva.
Le etichette hanno la seguente sintassi:
<label>:
Non alterano il valore di alcun registro e non hanno alcun effetto sul programma se non quello di contrassegnare punti specifici del codice, così che possano essere usati dalle istruzioni.
Le etichette distinguono maiuscole e minuscole.
Ciò significa che Start, start e sTart sono tutte etichette diverse.
La maggior parte delle istruzioni ha la seguente sintassi:
<opcode> <destination>, <source>
L'opcode indica l'operazione eseguita, usando i valori degli operandi destination e source.
Il risultato dell'operazione viene memorizzato nell'operando destination.
Ad esempio, l'istruzione add somma i valori negli operandi destination e source e memorizza questo risultato nell'operando destination.
L'operando destination è sempre un registro, mentre l'operando source può essere un registro o un numero letterale.
Questa è una lista di istruzioni che il tuo programma deve supportare:
| opcode | operazione eseguita |
|---|---|
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 |
Oltre a queste, il tuo programma deve supportare l'istruzione cmp a due operandi.
Questa istruzione non modifica alcun registro, ma confronta gli operandi destination e source e imposta uno stato interno del programma su una delle tre opzioni:
destination > source
destination == source
destination < source
Come tenere traccia internamente di questo stato dipende da te.
Ci sono alcune istruzioni che alterano il flusso del programma, trasferendo l'esecuzione in un altro punto del codice. Si chiamano istruzioni di salto.
Prendono tutte un solo operando, che è un'etichetta che indica la destinazione del salto, cioè il punto del codice in cui l'esecuzione proseguirà:
<opcode> <label>
L'istruzione jmp esegue sempre il salto verso l'etichetta di destinazione.
Le altre istruzioni di salto effettuano il salto solo se lo stato interno del programma è impostato su un valore specifico.
Ciò significa che di solito sono precedute da un'istruzione cmp.
Queste sono le istruzioni di salto che il tuo programma deve supportare:
| istruzione | operazione eseguita |
|---|---|
jmp |
salto incondizionato. Il salto viene sempre eseguito |
je |
salta se lo stato interno è uguale |
jl |
salta se lo stato interno è minore di |
jg |
salta se lo stato interno è maggiore di |
Gli opcode delle istruzioni, siano esse a due operandi o a un operando, non distinguono maiuscole e minuscole.
Ciò significa che add, ADD e aDd si riferiscono tutte alla stessa istruzione.
Iscriviti a Exercism per imparare e padroneggiare Lean con 100 esercizi e il mentoring di persone reali, tutto gratis.