Neste exercício, vais definir a sintaxe e o modelo de execução de uma pequena linguagem inspirada na linguagem assembly x86-64.
O objetivo é formalizar a forma como o código é escrito, analisado e executado num ambiente restrito.
O teu código assembly será escrito dentro de uma construção especial:
let program := assemble!(
-- assembly code here
)
Esta construção tem de produzir um programa, que simula uma função que executa o código assembly e devolve um valor.
Este programa é depois invocado com a seguinte sintaxe, em que a, b, c, etc. são argumentos:
let result := program(a, b, c, ...)
Todos os argumentos e o valor devolvido são inteiros com sinal de 64 bits. É permitido um máximo de 6 argumentos.
O valor devolvido e todos os argumentos são passados em registos. A tua linguagem tem de suportar os seguintes registos:
| registo | papel |
|---|---|
rdi |
1.º argumento |
rsi |
2.º argumento |
rdx |
3.º argumento |
rcx |
4.º argumento |
r8 |
5.º argumento |
r9 |
6.º argumento |
rax |
valor devolvido |
Os registos começam com um valor predefinido de 0, a menos que sejam usados para passar argumentos à função.
Por exemplo, no programa seguinte, todos os registos têm o valor 0, com exceção de rdi, que é inicializado com 10, e rsi, que é inicializado com -20:
program(10, -20)
O valor devolvido do programa é sempre armazenado em rax.
Os nomes dos registos não distinguem maiúsculas de minúsculas.
Isto significa que rax, RAX e rAx se referem todos ao mesmo registo.
Cada linha do código assembly pode assumir uma de duas formas:
A menos que seja modificado por alguma instrução, o fluxo de execução do programa prossegue linearmente. Isto significa que uma linha anterior é totalmente executada antes de a linha seguinte ser executada.
As etiquetas têm a seguinte sintaxe:
<label>:
Não alteram o valor de nenhum registo nem têm qualquer efeito sobre o programa, além de marcarem locais específicos do código para poderem ser usados por instruções.
As etiquetas distinguem maiúsculas de minúsculas.
Isto significa que Start, start e sTart são todas etiquetas diferentes.
A maioria das instruções tem a seguinte sintaxe:
<opcode> <destination>, <source>
O opcode indica a operação que está a ser executada, usando os valores dos operandos destination e source.
O resultado da operação é armazenado no operando destination.
Por exemplo, a instrução add soma os valores dos operandos destination e source e armazena esse resultado no operando destination.
O operando destination é sempre um registo, enquanto o operando source pode ser um registo ou um número literal.
Esta é uma lista de instruções que o teu programa tem de suportar:
| código de operação | operação realizada |
|---|---|
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 |
Além destas, o teu programa tem de suportar a instrução cmp de dois operandos.
Esta instrução não modifica nenhum registo, mas compara os operandos destination e source e define um estado interno do programa como uma de três opções:
destination > source
destination == source
destination < source
A forma como controlas internamente este estado é contigo.
Existem algumas instruções que alteram o fluxo do programa, transferindo a execução para outro ponto do código. São chamadas instruções de salto.
Todas recebem apenas um operando, que é uma etiqueta que indica o destino do salto, ou seja, o ponto do código onde a execução vai continuar:
<opcode> <label>
A instrução jmp faz sempre o salto para a etiqueta de destino.
As outras instruções de salto só fazem o salto se o estado interno do programa estiver definido como um valor específico.
Isto significa que são normalmente precedidas por uma instrução cmp.
Estas são as instruções de salto que o teu programa tem de suportar:
| instrução | operação realizada |
|---|---|
jmp |
salto incondicional. O salto é sempre realizado |
je |
salta se o estado interno for igual |
jl |
salta se o estado interno for menor que |
jg |
salta se o estado interno for maior que |
Os códigos de operação das instruções, sejam de dois operandos ou de um operando, não distinguem maiúsculas de minúsculas.
Isto significa que add, ADD e aDd se referem todos à mesma instrução.
Inscreve-te no Exercism para aprenderes e dominares Lean com 100 exercícios, e mentoria humana real, tudo grátis.