Adele é uma máquina virtual (virtual machine ou “VM”) do tipo pilha LIFO (last in, first out), portável e desenvolvida na linguagem C (C99). O projeto foi iniciado para oferecer a base comum para os projetos leni e Lygia, desenvolvidos no Laboratório e Observatório de Ontologias Projetuais (Loop) da Universidade Federal do Espírito Santo (Ufes).
O projeto inclui o interpretador da linguagem de montagem Adele Assembly e a API em C para extender as instruções de fábrica da máquina virtual.
A máquina virtual foi batizada em homenagem às cientistas da computação Adele Goldberg (1945-) e Adele Goldstine (1920-1964). Goldberg integrou a equipe que desenvolveu a linguagem Smalltalk no Xerox Parc (1970s) e escreveu o manual clássico que difundiu o padrão Smalltalk-80. Goldstine atuou no projeto do primeiro computador digital (ENIAC), quando escreveu o manual de operação e implementou mecanismos para o armazenamento de programas na memória da máquina.
Adele integra a pesquisa “Da Computação no Design para a Computação do Design” (PPRPG/Ufes n°10256/2020).
A distribuição de Adele inclui:
adelevm
adelec
adelevm.inc
fibonacci.asm
e reduz.asm
Distribuições por plataforma (use por sua conta e risco)
Apoio à edição de código
executa
e volta
adelec
inicio
e fim
)Basta descompactar o conteúdo do pacote zip para qualquer pasta do computador e seguir as definições deste manual.
(Orientações decentes em breve…)
Adele explora diferentes ideias e conceitos da computação para oferecer camadas de abstração em relação às plataformas de execução subjacentes.
O matemático britânico Alan Turing (1912-1954) elaborou o modelo de uma máquina teórico capaz de realizar qualquer tipo de computação (a máquina de Turing) em função de suas características e dos estados que pode assumir. A máquina seria composta por:
Os símbolos que a máquina lê e escreve, incluindo aquele que apaga o conteúdo das células, integram o alfabeto finito que controla o comportamento da máquina. Estes símbolos podem ordenar que o cabeçote de leitura mova-se uma ou mais células à direita ou esquerda; que apague o símbolo na célula atual e escreva outro etc.
A máquina de Turing exemplifica o funcionamento de elementos presentes nos computadores modernos, tais como memória (fita) unidades de processamento central (central processing units, CPUs) e linguagens de programação (símbolos finitos do alfabeto). No entanto, a primeira proposta de Turing é conceitualmente mais próxima de calculadoras digitais comuns, pois têm um único programa controlando seus estados durante a entrada de dados pelo teclado ou recuperadas na memória.
Em sua versão universal, a máquina de Turing também lê na fita sua tabela de transição, o que permite que a máquina seja alimentada por um programa que controlará seus estados. Sendo assim, a máquina universal pode simular o comportamento de outra máquina, desde que programada para tal finalidade. O mesmo dispositivo pode funcionar como calculadora ou como processador de textos.
Linguagens são Turing-completas se e somente se puderem manipular qualquer máquina de Turing de fita única por meio de suas instruções e alterações das posições de memória. Na prática, significa a habilidade de programar a máquina para simular o funcionamento de outras máquinas.
O princípio da máquina universal de Turing foi utilizada pelo cientista húngaro-americano John von Neumann (1903-1957) para desenvolver a arquitetura do computador com programa armazenado, atualmente conhecida como arquitetura de von Neumann. A proposta foi originalmente composta por:
TODO: Desenvolver fetch->decode->execute…
As linguagens leni e Lygia, que utilizarão Adele como VM, foram desenvolvidas utilizando respectivamente C e HTML/CSS/Javascript. O código de Lygia é carregado via web e executado no computador do usuário como se fosse uma página web. HTML, CSS e Javascript são onipresentes, interpretáveis em qualquer computador equipado com navegadores modernos.
Já leni está sendo desenvolvida tendo minimalismo, portabilidade, simplicidade e abertura como princípios. Tais restrições estabeleceram como alvo um interpretador compacto para linha de comando, disponível em toda plataforma capaz de compilar códigos com o GNU C Compiler. O código do interpretador pode gerar executáveis para diversas plataformas com pequenos ajustes.
Em síntese, antes de Adele, os processos de desenvolvimento de leni e Lygia precisavam considerar no mínimo:
Também há interesse de oferecer o interpretador de leni para plataformas de computação física como Arduino, que não empregam sistemas operacionais para executar programas.
A introdução de Adele no processo descrito busca reduzir o número de variáveis de projeto, abstraindo as camadas do sistema operacional e do hardware que executam os programas. Na prática, as implementações de Adele são indiferentes aos sistemas operacionais e hardware que podem executar a VM. Em cada contexto, a máquina virtual traduz o mesmo conjunto de instruções para a plataforma destino de maneira transparente para o programador.
Este processo de tradução tem consequências importantes para o desempenho da VM que, de forma geral, espera-se que seja mais lenta comparada à execução do mesmo conjunto de instruções diretamente pela plataforma destino (sem traduções). No entanto, a redução da complexidade no processo, os objetivos acadêmicos e de portabilidade de Adele justificam perdas aceitáveis de desempenho.
VMs são artefatos interessantes para o estudo e compreensão do funcionamento de computadores, de linguagens de programação e suas ferramentas, tais como compiladores, interpretadores e ligadores.
A pilha ou stack é uma estrutura linear de dados cujo funcionamento é baseado na sobreposição ou remoção contínuas de elementos em coleções, sempre a partir do topo. A tabela abaixo ilustra uma pilha com cinco elementos numerados de 1 (posição mais baixa) a 5 (topo).
Posição | Nome | Ponteiro |
---|---|---|
5 | Goldberg | <- |
4 | Goldstine | |
3 | Clark | |
2 | Adele | |
1 | Lygia |
Nas operações, altera-se a posição do ponteiro, que sempre indica o topo da pilha. No exemplo, o ponteiro encontra-se na posição 5 (Goldberg) e a inserção de novos elementos na coleção é antecedida pelo incremento do ponteiro.
Para inserir o novo elemento Loop, deve-se colocá-lo acima de Goldberg, que está na posição 5. Esta operação se chama push
e resultará no aumento do tamanho da pilha exemplificada para 6.
Posição | Nome | Ponteiro | Operações |
---|---|---|---|
6 | Loop | <- |
|
5 | Goldberg | push "Loop" |
|
4 | Goldstine | ||
… | … | … | … |
Para remover o elemento Clark (posição 3), faz-se necessário remover antes os itens Goldberg (5) e Goldstine (4). Estas operações são denominadas pop
e resultam na diminuição do tamanho da pilha. No exemplo a seguir, as linhas riscadas sinalizam os elementos que foram removidos na pilha, que passou do tamanho cinco (5) para três (3).
Posição | Nome | Ponteiro | Operações |
---|---|---|---|
pop |
|||
pop |
|||
3 | Clark | <- |
|
2 | Adele | ||
1 | Lygia |
Por fim, cabe mencionar que pilhas são frequentemente representadas horizontalmente, indicando o crescimento por meio da inserção de itens (push
) da esquerda para a direita e a remoção (pop
) no sentido inverso.
Lygia Adele Clark Goldstine Goldberg
--------
ponteiro
Aplica-se a instrução push "Loop"
:
Lygia Adele Clark Goldstine Goldberg Loop
----
ponteiro
Aplica-se a instrução pop
três vezes:
Lygia Adele Clark
------
ponteiro
A realização de operações matemáticas com elementos de pilhas segue lógica distinta daquela observada em linguagens de programação populares. Os pseudocódigos a seguir ilustram operações de soma:
exemplo de código 1
-
01 // atribui resultado da soma '5 + 10' a 'total'
02 total = 5 + 10
Obs: Os números à esquerda indicam o número da linha do programa e as barras duplas (//
) sinalizam comentários.
O primeiro exemplo de soma emprega operandos 5
e 10
e operador de soma +
para que o interpretador da linguagem possa avaliar a expressão e calcular seu resultado.
exemplo de código 2
-
01 // atribui valores a 'x' e 'y'
02 x = 2, y = 3
03 // atribui a 'total' o resultado da soma 'x' (2) + 'y' (3)
04 total = x + y
O segundo exemplo utiliza as variáveis x
e y
como operandos e o operador +
. Esta notação é chamada infixa e tem a forma geral “operando operador operando”.
Nas operações aritméticas de Adele, adota-se a notação denominada polonesa inversa. Esta notação posiciona o operador após os operandos, alterando a forma geral anterior para “operando operando operador”:
exemplo de código 3
-
01 // somar 5 e 10
02 total = 5 10 +
ou
exemplo de código 4
-
01 // somar x e y
02 total = x y +
Apesar do estranhamento, esta notação é útil para a realização de operações em pilhas, uma vez que deve-se manipular sempre o topo da estrutura linear indicado pelo ponteiro. Nesse sentido, programas escritos para Adele são elaborados de acordo com as regras da pilha, resultando em programas com outra estrutura:
exemplo de código 5
-
01 ; início do programa, ponteiro na posição '0' (pilha vazia)
02 inicio
03 ; move ponteiro para a posição '1' e insere '5' na pilha
04 insere 5
05 ; move ponteiro para a posição '2' e insere '10' na pilha
06 insere 10
07 ; move ponteiro para a posição '3' e soma!
08 soma
09 fim
Obs: Comentários nos programas de Adele são sinalizados por ponto e vírgula (;
).
O interpretador, ao identificar a instrução soma
(linha 08) na pilha, realiza as seguintes operações:
As etapas descritas utilizariam outras instruções para armazenar e recuperar os operandos na memória da VM, bem como para realizar a operação de soma propriamente dita. O conjunto dessas instruções estaria encapsulado na instrução de mais alto nível soma
, cujo encerramento retorna o resultado das computações para o topo da pilha.
A linguagem de programação nativa da VM, denominada Adele Assembly, é composta por instruções pré-definidas como as do exemplo de código 5. Os usuários podem criar funções a partir da combinação das instruções pré-definidas ou pela manipulação direta da pilha utilizando a API em C.
Adele adota, na implementação principal, palavras de 32 bits, o que quer dizer que as instruções que definem o comportamento da máquina são codificadas em cadeias de até 32 números zeros e uns na base 2.
cadeia de 32 bits
-
00000000 00000000 00000000 00000000
^ ^
bit 31 bit 0
Na linguagem C, por exemplo, cadeias de bits são utilizados para representar diferentes tipos de dados. Caracteres (tipo char
) utilizam 8 bits e números inteiros (int
) utilizam 32 bits.
caractere 'a' (código 65 na tabela ASCII)
-
65 01000001 = 2^6 + 2^0 = 64 + 1 = 65
^ ^
bits 7 0
A conversão de números decimais em binários procede por divisões sucessivas pela base 2
. Quando há resto, adiciona-se 1
ao número; quando não, adiciona-se 0
.
número decimal 9 representado em binário
-
divisão quociente resto bit #
9 / 2 = 4 sim 1º 1
4 / 2 = 2 não 2º 0
2 / 2 = 1 não 3º 0
1 / 2 = 0 sim 4º 1
representação binária de 9: 1001
A conversão inversa, de binário para decimal, pode ser feita pela soma dos resultados da potenciação de 2 de acordo com a posição do bit ativo:
número binário 1001
-
bits 1001
^ ^
3 0 = 2^3 + 2^0 = 8 + 1 = 9
A capacidade de codificar números binários dobra com a adição de mais posições na cadeia, o que pode ajudar a compreender o ganho de performance com a evolução de sistemas de 8 para 16, 32 e 64 bits.
representação da informação por bits
-
bits capacidade números representações
1 2^1 0-1 (2) 0, 1
2 2^2 0-3 (4) 00, 01, 10, 11
3 2^3 0-7 (8) 000, 001, 010, 011, 100, 101, 110, 111
...
Em sistemas com 32 bits (0-31), pode-se representar números ainda maiores, de 0
a 4.294.967.295
(sem sinal):
número 200
-
00000000 00000000 00000000 11001000 = 2^7 + 2^6 + 2^3 = 200
^ ^ ^ ^ ^
bits 31 23 15 7 0
número 2.020
-
00000000 00000000 00000111 11100100
^ ^ ^ ^ ^
bits 31 23 15 7 0
número 987.654.321
-
00111010 11011110 01101000 10110001
^ ^ ^ ^ ^
bits 31 23 15 7 0
Pode-se observar que a representação de 200
utilizou apenas a primeira cadeia de 8 bits, enquanto 2.020
exigiu a primeira cadeia mais três bits da segunda e 987.654.321
usou bits de todas as cadeias. Se o tipo escolhido for representar apenas números positivos (sem sinal, unsigned int
em C), o número 200
deixaria 24
bits sem uso (8-31) e 2.020
deixaria 16
bits sem uso (16-31).
No funcionamento de Adele, utilizamos a cadeia dos primeiro 8
bits (0-7) para representar instruções e os 24 bits restantes (16-31) para selecionar registradores. Estes são espaços de memória pré-alocados e de fácil acesso, utilizados para armazenar dados durante as computações.
Na sintaxe de Adele Assembly, os registradores são indicados pela letra r
seguida do tipo de registrador (detalhes a seguir e do número do registrador — p.ex. ri1
ou rt255
. Caso o registrador não seja fornecido, a VM armazenará o dado no próximo registrador livre.
exemplo de código 7
-
gravareg ri0, 5 ; carrega o número '5' para o registrador inteiro nº0
copiapilha ri0 ; lê conteúdo do registrador inteiro nº0 para a pilha
A forma de representar informações descrita resulta nas seguintes propriedades da VM:
ri
endereçados pelos bits 8-15;rr
endereçados pelos bits 15-23;rt
(strings) endereçados pelos bits 24-31. O comprimento das 256 cadeias pode ser configurado pelo usuário na inicialização da VM. O valor padrão é 255
.As cadeias a seguir ilustram a representação da informação nas instruções de Adele.
instruções de adele (32 bits)
-
bits informação
0-7 opcode da instrução
8-15 número do registrador para inteiros - ri0 a ri255
16-23 número do registrador para reais - rr0 a rr255
24-31 número do registrador para textos - rt0 a rt255
textos reais inteiros opcode
00000000 00000000 00000000 00000000
^ ^ ^ ^ ^
31 23 15 7 0
A VM carrega instruções sequenciais em preparadas pelo montador (“assembler”) do compilador adelec. Tais instruções realizam operações elementares manipulando itens na pilha da máquina e todas as funções de mais alto nível da linguagem são combinações e concatenações daquelas operações.
Adele Assembly é Turing-completa: pode manipular qualquer entrada arbitrária de dados pelo uso de condicionais e alterar posições na memória da VM, simulando o funcionamento de outras máquinas.
Instrução | Opcode | Descrição | Argumentos | Exemplo |
---|---|---|---|---|
inicio | 1 | Coloca a VM no estado inicial, com pilha limpa e ponteiro = -1 |
Nenhum. | inicio |
insere | 2 | Incrementa ponteiro e insere argumento no topo da pilha. |
Valor a ser inserido. | insere 3.14 |
remove | 3 | Remove elemento do topo da pilha e decrementa ponteiro . |
Nenhum. | remove |
fim | 4 | Interrome funcionamento da VM. | Nenhum. | fim |
copiareg | 5 | Copia valor em ponteiro para registrador indicado. |
Registrador. | copiareg ri0 |
copiapilha | 6 | Incrementa ponteiro e move valor do registrador indicado para o topo da pilha. |
Registrador. | copiapilha ri0 |
gravareg | 7 | Armazena valor no registrador específico indicado. | Registrador e valor a ser armazenado. | gravareg ri0, 5 |
soma | 8 | Soma ponteiro e ponteiro-1 , remove ambos da pilha, atualiza a posição de ponteiro e insere o resultado da operação no topo da pilha. |
Nenhum. | soma |
subtrai | 9 | Subtrai ponteiro-1 de ponteiro , remove ambos da pilha, atualiza a posição de ponteiro e insere o resultado da operação no topo da pilha. |
Nenhum. | subtrai |
multiplica | 10 | Multiplica ponteiro e ponteiro-1 , remove ambos da pilha, atualiza a posição de ponteiro e insere o resultado da operação no topo da pilha. |
Nenhum. | multiplica |
divide | 11 | Divide ponteiro-1 por ponteiro , remove ambos da pilha, atualiza a posição de ponteiro e insere o resultado da operação no topo da pilha. |
Nenhum. | divide |
potencia | 12 | Eleva ponteiro-1 a ponteiro , remove ambos da pilha, atualiza a posição de ponteiro e insere o resultado da operação no topo da pilha. |
Nenhum. | potencia |
resto | 13 | Divide ponteiro-1 por ponteiro , remove ambos da pilha, atualiza a posição de ponteiro e insere o resto da divisão no topo da pilha. |
Nenhum. | resto |
vaipara | 14 | Salta para a posição indicada do programa, que pode ser um rótulo ou número de linha. | Rótulo. | vaipara calcula |
vaiseigual | 15 | Salta para a posição indicada do programa se ponteiro for igual ao valor. |
Rótulo e valor para comparação. | vaiseigual 3.14, calcula |
vaisemaior | 16 | Salta para a posição indicada do programa se ponteiro for maior que o valor. |
Rótulo e valor para comparação. | vaisemaior 5, calcula |
vaisemenor | 17 | Salta para a posição indicada do programa se ponteiro menor que o valor. |
Rótulo e valor para comparação. | vaisemenor 1, calcula |
volta | 18 | Volta para a posição anterior ao salto realizado. | Nenhum. | volta |
imprime | 19 | Imprime valor em ponteiro . |
Nenhum. | imprime |
pilha | 20 | Imprime estado da pilha e posição de ponteiro . |
Nenhum. | pilha |
executa | 21 | Executa função criada previamente pelo usuário. | Nome da função. | executa _fibonacci |
A lógica dos programas em Adele Assembly é controlada por saltos condicionais. Rótulos e são precedidos pelo sinal _
(sublinhado) e terminados por dois pontos :
.
exemplo de código 8
-
01 ; saltos infinitos
02 inicio
03 insere 10
04 _vourepetir: ; definição do rótulo
05 insere 1
06 soma
07 imprime
08 vaipara _vourepetir ; volta para a linha 04
09 fim ; o programa nunca chegará aqui!
O programa acima rodará indefinidamente, adicionando unidades ao número 10
até que seja interrompido. A versão a seguir interromperá o programa assim que a condição da linha 08 (o valor da pilha for menor que 20
) não for mais verdadeira.
exemplo de código 9
-
01 ; saltos condicionais
02 inicio
03 insere 10
04 _vourepetir: ; define o rótulo
05 insere 1
06 soma
07 imprime
08 vaisemenor 20,_vourepetir ; volta para a linha 04 enquanto < 20
09 fim
As instruções de controle condicional estão indicadas pelos opcodes 14 a 17.
Na prática, funções em Adele Assembly são rótulos com retorno, indicados pela instrução volta
. Todas as funções devem ser declaradas antes de sua invocação, que é realizada por meio da instrução executa
.
Funções devem obrigatoriamente ser encerradas pela instrução volta
. O compilador acusará a ausência do encerramento, embora em alguns casos o efeito pode ser a execução do bloco de instruções imediatamente subsequente.
exemplo de código 10
-
01 _dobra: ; declaração da função
02 insere 2
03 multiplica
04 volta ; retorno da função
05
06 inicio
07 insere 5
08 executa _dobra ; invocação da função
09 imprime ; o valor de imprime será 10
10 fim
Importante: a mesma pilha da VM manipulada pelas funções básicas é manipulada pelas funções do usuário.
O processo de desenvolvimento para Adele começa (1) pela programação do código fonte (p.ex. script.asm
) em Adele Assembly, utilizando as instruções pré-definidas ou criadas pelo usuário.
As instruções a serem utilizadas pelo compilador adelec
(2) são previamente geradas pela aplicação da máquina virtual adelevm
(4). O arquivo adelevm.inc
(1) nomeia as instruções implementadas na VM e indica seu opcode e número de argumentos.
O resultado da compilação de script.asm
e adelevm.inc
é uma imagem em formato binário (3, p.ex. script.img
) que alimentará a aplicação adelevm
(4). O nome da imagem gerada é idêntico ao do script fornecido, com a extensão alterada de .asm
para .img
e gravada na mesma pasta.
O processo é circular, pois a máquina informa previamente o compilador quais instruções são válidas em sua operação. Novas funções criadas pelo usuário para a VM deverão ser novamente exportadas para utilização pelo compilador ou não serão reconhecidas.
Em desenvolvimento…
Programas para Adele são basicamente scripts (arquivos de texto) produzidos no editor de sua preferência. As instruções são sequenciais e assumem implicitamente a existência da pilha na VM. Espera-se que o programador compreenda as operações aritméticas envolvidas.
exemplo de código 6
-
01 inicio ; inicia máquina
02 insere 3.14 ; insere 3.14 no topo da pilha
03 insere 12 ; insere 12 no topo da pilha
04 soma ; soma
05 fim ; encerra o programa
Comentários são sinalizados por ponto e vírgula ;
e serão ignorados pelo compilador. As instruções pré-definidas inicio
(iniciar máquina) e fim
(suspender) no exemplo de código 6 são importantes para:
Programas sem as instruções inicio
e fim
resultarão em erro do compilador e da máquina virtual.
A máquina virtual requer a imagem do programa a ser executado para iniciar seu funcionamento. Para exemplificar o uso de Adele, a distribuição acompanha diversas imagens exemplo que podem ser testadas antes de compreender o uso do compilador.
A execução das imagens é feita por meio do comando:
adelevm nomedaimagem.img
O arquivo com a imagem resulta da compilação de scripts no compilador adelec com as configurações de adelevm.inc
. Este arquivo contém as instruções válidas da máquina virtual e deve ser gerado utilizando o comando:
adelevm exporta
Todo script Adele Assembly será compilado contra arquivos de configuração gerados pelo comando acima. Diferentes VMs podem implementar o mesmo conjunto de instruções de maneiras diversas, e ainda assim a imagem será executável, desde que os opcodes das instruções sejam mantidos.
Para saber mais sobre o arquivo adelevm.inc
, verifique a respectiva seção desta documentação. Consulte o processo de programação da VM para relembrar a relação entre o compilador, o arquivo de configurações adelevm.inc
e a imagem gerada.
O compilador adelec
é responsável pela análise dos scripts e transformação subsequente das instruções no arquivo de imagem:
adelevm.inc
ou similar) foram corretamente utilizadas no script;inicio
e fim
no script;A linha de comando mais frequente do compilador consiste em:
adelec script.asm
A execução do compilador informando apenas o nome do script .asm
buscará o arquivo adelevm.inc
na mesma pasta e gerará o arquivo saida.img
como imagem. Para personalizar o nome da imagem de saída deve-se utilizar o argumento --imagem
ou -img
.
adelec script.asm -img script.img
Outros arquivos de configuração da VM podem ser informados ao compilador:
adelec script.asm -img script.img -c maquina.inc
Para conhecer as demais opções de execução do compilador, basta invocar adelec
sem argumentos. Consulte o processo de programação da VM para relembrar a relação entre o compilador, o arquivo de configurações adelevm.inc
e a imagem gerada.
A lista de instruções geradas pela VM tem a estrutura a seguir:
adelevm.inc exemplo
-
01 ; gerado por adele vm 0.01 (vm-teste)
02 inicio,1,0
03 insere,2,0
04 remove,3,0
05 fim,4,0
...
A primeira linha é um comentário que informa a versão da VM que gerou o arquivo e o nome da máquina correspondente (definida pelo usuário). As linhas seguintes declaram o nome das instruções, o respectivo opcode e número de argumentos.
Apesar da estrutura simples, recomenda-se que o arquivo não seja gerado manualmente, pois há instruções pré-definidas na VM que podem ser esquecidas pelo usuário e gerarão erros de compilação.
Desde a versão 0.013
, a indicação do arquivo adelevm.inc
é opcional. O compilador procurará este arquivo na mesma pasta do script .asm
e emitirá mensagem de erro caso não exista. Pode-se fornecer diferentes configurações utilizando o argumento --config
ou -c
seguido do nome do arquivo .inc
.
adelec script.asm -c meuarquivo.inc -img script.img
O resultado do processamento das instruções gerará uma lista com a instruções registradas para uso no compilador.
adele : máquina virtual (vm)
(c) hugo cristo sant'anna / loop-ufes, 2020+
v. 0.013 (14/05/2020)
+ adele-vm vm-teste 1.0 inicializada.
> pilha: 10, programa máximo: 50, máximo de instruções: 256
> instrução 1 'inicio' registrada com opcode '1' e uso obrigatório nos scripts.
> instrução 2 'insere' registrada com opcode '2' e '1' argumentos.
> instrução 3 'remove' registrada com opcode '3' e '0' argumentos.
> instrução 4 'fim' registrada com opcode '4' e uso obrigatório nos scripts.
...
Caso haja problemas na utilização do arquivo, pode-se utilizar o argumento --debug
(ou -d
) na invocação do compilador para exibir o registro da importação e processamento.
adelec script.asm -img script.img -c adelevm.inc -d
Consulte o processo de programação da VM para relembrar a relação entre o compilador, o arquivo de configurações adelevm.inc
e a imagem gerada.
O formato .img
gerado pelo compilador é composto pelas seguintes informações:
int
) na plataforma utilizada na geração da imagem;float
) na plataforma utilizada na geração da imagem;char
) para o tipo de instrução (ver tabela a seguir) e 4 bytes para a instrução;int
) para cada instrução, empilhadas na mesma ordem em que foram processados pelo analisador sintático.Os tipos facilitam a decodificação de cada linha de instrução, permitindo a evolução gradual do conteúdo da imagem sem mudanças substanciais nas rotinas de leitura.
tipo | instrução seguinte | aplicações na biblioteca |
---|---|---|
1 | opcode | instruções sem argumentos — ex: inicio e soma . |
2 | opcode + inteiro | instruções com um (1) argumento inteiro — ex: insere 10 . |
3 | opcode + real | instruções com um (1) argumento real — ex: insere 3.14 . |
4 | opcode + nº registrador + inteiro | instruções com dois (2) argumentos, registrador + inteiro — ex: gravareg ri0, 10 . |
5 | opcode + nº registrador + real | instruções com dois (2) argumentos, registrador + real — ex: gravareg rr0, 3.14 . |
6 | opcode + nº registrador inteiro | instruções com um (1) argumento referente a registradores inteiros — ex: copiapilha ri0 . |
7 | opcode + nº registrador real | instruções com um (1) argumento referente a registradores reais — ex: copiapilha rr0 . |
8 | opcode + nº registrador texto | instruções com um (1) argumento referente a registradores de texto — ex: copiapilha rt0 . |
9 | opcode + inteiro (linha da instrução) | instruções com um (1) argumento referente ao rótulo de salto do programa — ex: vaipara _repete . |
10 | opcode + inteiro (linha da instrução) + inteiro | instruções com um (2) argumentos utilizados em comparações: inteiro a ser comparado e rótulo destino — ex: vaisemaior 10,_repete . |
11 | opcode + inteiro (linha da instrução) + real | instruções com um (2) argumentos utilizados em comparações: real a ser comparado e rótulo destino — ex: vaisemenor 3.14,_repete . |
O arquivo fornecido à VM será carregado como programa e executado de acordo com as instruções implementadas. Não há conferência, pela VM, se o a imagem fornecida é compatível com o conjunto de instruções. A responsabilidade é deixada ao programador, que deverá compilar o script junto com o arquivo adelevm.inc
adequado.
Em desenvolvimento.
Os scripts a seguir ilustram usos combinados das instruções da VM em algoritmos conhecidos, demonstrando o potencial de Adele Assembly e o desempenho de Adele.
O primeiro exemplo demonstra como inserir números na pilha e realizar operações aritméticas sucessivas controladas por condicionais. O número 999999
será divido por 2
e o quociente da divisão será novamente dividido enquanto o valor resultante for maior que 0.01
.
; divide número por 2 valor até mínimo
inicio
insere 999999
_reduz:
insere 2
divide
imprime
vaisemaior 0.01,_reduz
fim
A sequência de Fibonacci é uma sucessão de números inteiros que pode ser implementada por diversos algoritmos. Começando pelos números zero (0) e (1), os termos subsequentes resultam da soma dos dois números anteriores:
0, 1, 2, 3, 5, 8, 13, 21 ...
A implementação da sequência de Fibonacci em Adele Assembly utiliza operações na pilha, registradores e saltos para construir a sequência.
; fibonacci.asm
inicio
insere 0 ; insere 0 na pilha
insere 1 ; insere 1 (primeiro incremento)
copiareg rr0 ; copia primeiro incremento para rr0
_repete:
soma ; soma
copiareg rr1 ; copia resultado da soma para rr1 (volta como soma)
imprime ; imprime
remove ; remove resultado da soma
copiapilha rr0 ; copia incremento anterior para pilha
copiapilha rr1 ; copia resultado da soma para a pilha
copiareg rr0 ; transforma resultado da soma no próximo incremento
vaisemenor 100, _repete ; repete todos < 100
pilha ; exibe pilha
fim
O exemplo as seguir reestrutura o cálculo da sequência de Fibonnaci para números menores que 100 utilizando funções.
01 ; fibo-func.asm
02
03 ; declara função
04 _fibo:
05 soma ; soma
06 copiareg rr1 ; copia resultado da soma para rr1 (volta como soma)
07 imprime ; imprime
08 remove ; remove resultado da soma
09 copiapilha rr0 ; copia incremento anterior para pilha
10 copiapilha rr1 ; copia resultado da soma para a pilha
11 copiareg rr0 ; transforma resultado da soma no próximo incremento
12 vaisemenor 100, _fibo ; condicional: se o valor for < 100, executa novamente a função
13 volta
14
15 ; programa principal
16 inicio
17 insere 0 ; insere 0 na pilha
18 insere 1 ; insere 1 (primeiro incremento)
19 copiareg rr0 ; copia primeiro incremento para rr0
20 executa _fibo
21 pilha
22 fim
Atenção (05/04/2020): esta parte está desatualizada e passou por mudanças substanciais desde a versão zero da API. Retorne mais tarde!
A interface de programação de Adele permite extender as funções básicas e alterar as propriedades das VMs com facilidade.
…
Para incluir Adele sem seus projetos, basta incluir o cabeçalho adele-vm.h
e indicar o arquivo-fonte adele-vm.c
para o ligador. O código foi testado com o GNU C Compiler para Windows (MinGW), MS-DOS (DJGPP), Linux e macOS (GCC).
As constantes a seguir são utilizadas como retorno em todas as funções da API. A execução da VM atual é encerrada quanto o código VM_PARADA
é encontrado pelo interpretador. Por razões de compatibilidade futura, sugere-se sempre utilizar os nomes em vez de os valores das constantes.
Constante | Valor | Descrição |
---|---|---|
VM_ERRO | -1 | Estado erro para retorno das instruções. |
VM_OK | 1 | Estado sem erros para retorno das instruções. |
VM_PARADA | 2 | Estado de parada da VM. |
Cada instância da VM é declarada como estrutura adele_VM
que contém todas as propriedades, instruções, memória da pilha e dos programas que executa. A API não utiliza variáveis globais, permitindo a integração sem riscos com outras bibliotecas.
Estrutura | Descrição | Membros |
---|---|---|
adele_VM_i | Conjunto de instruções válidas para a instância atual da VM. | unsigned int opcode unsigned int opargs char *op int ftipo_i int (*fp)() |
adele_VM | Instância da VM com suas configurações, pilha, memória para o programa e instruções válidas. | char *nome int ini int *stack int sp int xs unsigned int *prog int pp int xp int lp adele_VM_i *inst int ic int xi |
A API oferece funções pré-definidas para incializar, testar, encerrar, executar e exibir estados da VM na saída padrão.
Função | Descrição | Argumentos e retorno |
---|---|---|
int adele_ini(adele_VM *VM , char *vm_nome , int vm_xs , int vm_xp , int vm_xi ); |
||
int adele_fim(adele_VM *VM ); |
||
int adele_testa(adele_VM *VM ); |
||
void adele_erro(char *mensagem ); |
||
int adele_mensagem(const char *format , …); |
||
int adele_executa(adele_VM *VM ); |
A extensão da linguagem Adele Assembler é realizada por meio do registro de novas instruções. O usuário pode elaborar novas instruções em C e registrá-las com a função adele_inst_registra()
, cujos argumentos incluem identificadores da instrução, número de argumentos e o ponteiro para a função que será executada pela VM.
A variável opcode
(int) é de uso fundamental na aplicação das instruções registradas. Este número é gerado no registro da instrução e é argumento para a recuperação posterior de todas as suas propriedades.
Função | Descrição | Argumentos e retorno |
---|---|---|
int adele_inst_registra(adele_VM *VM , char *op , unsigned int opargs , int (*fp)() ); |
||
char * adele_inst_nome(adele_VM *VM , unsigned int opcode ); |
||
int adele_inst_args(adele_VM *VM , unsigned int opcode ); |
||
int adele_inst_indice(adele_VM *VM , unsigned int opcode ); |
A VM executa apenas programas carregados para sua memória. API inclui funções básicas para a exibição do código-fonte carregado e para recuperar a próxima instrução.
Função | Descrição | Argumentos e retorno |
---|---|---|
int adele_prog_carrega(adele_VM *VM , unsigned int *prog , int lp ); |
||
int adele_prog_lista(adele_VM *VM ); |
||
int adele_prog_fetch(adele_VM *VM );; |
A manipulação da pilha pode ser feita diretamente pela API, dispensando o uso repetitivo das instruções básicas PSH
e POP
. Além das funções a seguir, as propriedades da pilha, do programa e da máquina podem ser alteradas diretamente na estrutura adele_VM
.
Função | Descrição | Argumentos e retorno |
---|---|---|
int adele_pilha_carrega(adele_VM *VM ); |
||
int adele_pilha_lista(adele_VM *VM ); |
||
int adele_pilha_topo(adele_VM *VM ); |
||
int adele_pilha_push(adele_VM *VM , int item ); |
A API inclui instruções básicas para o funcionamento elementar da VM. Estas são reservadas e registradas automaticamente na inicialização de Adele. A alteração das instruções pré-definidas deve ser feita diretamente no código-fonte da função adele_ini()
.
Função | Descrição | Argumentos e retorno |
---|---|---|
int adele_i_ini(adele_vm *VM ); |
||
int adele_i_ins(adele_vm *VM ); |
||
int adele_i_fim(adele_vm *VM ); |
||
int adele_i_rmv(adele_vm *VM ); |
Copyright (c) 2020 Hugo Cristo Sant’Anna
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”, to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Última atualização: 15/05/2020 - hugo.santanna@ufes.br