Dans cet exercice, tu vas définir la syntaxe et le modèle d'exécution d'un petit langage inspiré du langage assembleur x86-64.
L'objectif est de formaliser la façon dont le code est écrit, analysé et exécuté dans un environnement restreint.
Ton code assembleur s'écrit à l'intérieur d'une construction particulière :
let program := assemble!(
-- assembly code here
)
Cette construction doit produire un programme, qui simule une fonction exécutant le code assembleur et renvoyant une valeur.
Ce programme est ensuite appelé à l'aide de la syntaxe suivante, où a, b, c, etc. sont des arguments :
let result := program(a, b, c, ...)
Tous les arguments et la valeur de retour sont des entiers signés sur 64 bits. Un maximum de 6 arguments est autorisé.
La valeur de retour et tous les arguments sont transmis dans des registres. Ton langage doit prendre en charge les registres suivants :
| registre | rôle |
|---|---|
rdi |
1er argument |
rsi |
2e argument |
rdx |
3e argument |
rcx |
4e argument |
r8 |
5e argument |
r9 |
6e argument |
rax |
valeur de retour |
Les registres démarrent avec une valeur par défaut de 0, sauf lorsqu'ils servent à transmettre des arguments à la fonction.
Par exemple, dans le programme suivant, tous les registres valent 0, à l'exception de rdi, qui est initialisé à 10, et de rsi, qui est initialisé à -20 :
program(10, -20)
La valeur de retour du programme est toujours stockée dans rax.
Les noms de registres sont insensibles à la casse.
Cela signifie que rax, RAX et rAx désignent tous le même registre.
Chaque ligne du code assembleur peut prendre deux formes :
Sauf si une instruction en modifie le cours, l'exécution du programme se déroule de manière linéaire. Cela signifie qu'une ligne est entièrement exécutée avant que la ligne suivante ne le soit.
Les étiquettes ont la syntaxe suivante :
<label>:
Elles ne modifient la valeur d'aucun registre et n'ont aucun autre effet sur le programme que de marquer des endroits précis du code pour que les instructions puissent les utiliser.
Les étiquettes sont sensibles à la casse.
Cela signifie que Start, start et sTart sont toutes des étiquettes différentes.
La plupart des instructions ont la syntaxe suivante :
<opcode> <destination>, <source>
L'opcode indique l'opération exécutée, à partir des valeurs des opérandes destination et source.
Le résultat de l'opération est stocké dans l'opérande destination.
Par exemple, l'instruction add additionne les valeurs des opérandes destination et source et stocke ce résultat dans l'opérande destination.
L'opérande destination est toujours un registre, tandis que l'opérande source peut être un registre ou un nombre littéral.
Voici la liste des instructions que ton programme doit prendre en charge :
| opcode | opération effectuée |
|---|---|
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 |
En plus de celles-ci, ton programme doit prendre en charge l'instruction cmp à deux opérandes.
Cette instruction ne modifie aucun registre : elle compare les opérandes destination et source et place un état interne du programme sur l'une des trois options suivantes :
destination > source
destination == source
destination < source
Libre à toi de choisir comment suivre cet état en interne.
Certaines instructions modifient le cours du programme en transférant l'exécution vers un autre point du code. On les appelle les instructions de saut.
Elles ne prennent qu'un seul opérande : une étiquette qui indique la cible du saut, c'est-à-dire le point du code où l'exécution se poursuivra :
<opcode> <label>
L'instruction jmp effectue toujours le saut vers l'étiquette cible.
Les autres instructions de saut n'effectuent le saut que si l'état interne du programme correspond à une valeur précise.
Cela signifie qu'elles sont généralement précédées d'une instruction cmp.
Voici les instructions de saut que ton programme doit prendre en charge :
| instruction | opération effectuée |
|---|---|
jmp |
saut inconditionnel. Le saut est toujours effectué |
je |
saute si l'état interne est égal à |
jl |
saute si l'état interne est inférieur à |
jg |
saute si l'état interne est supérieur à |
Les opcodes d'instruction, qu'ils soient à deux opérandes ou à un opérande, sont insensibles à la casse.
Cela signifie que add, ADD et aDd désignent toutes la même instruction.
Inscris-toi sur Exercism pour apprendre et maîtriser Lean avec 100 exercices, et un vrai mentorat humain, le tout gratuitement.