adele docs v0.022

Hugo Cristo Sant’Anna -

1 Sobre o projeto

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).

2 Downloads e histórico de versões

A distribuição de Adele inclui:

Distribuições por plataforma (use por sua conta e risco)

Apoio à edição de código

2.1 Versão 0.013

2.2 Versão 0.012

2.3 Versão 0.011

2.4 Como utilizar

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…)

3 Definições

Adele explora diferentes ideias e conceitos da computação para oferecer camadas de abstração em relação às plataformas de execução subjacentes.

3.1 Máquinas universais de Turing

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:

  1. Uma fita teoricamente infinita dividida em células, nas quais estão registrados símbolos que orientam a conduta da máquina;
  2. Um cabeçote que se move para a esquerda ou direita sobre a fita, podendo ler, escrever ou apagar o conteúdo das células;
  3. Um registrador dos estados da máquina, que são finitos;
  4. Uma tabela de transição ou função parcial, que define as ações que a máquina deve realizar após a leitura do símbolo atual da fita.

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.

3.2 Arquitetura de von Neumann

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:

  1. Memória, equivalente à fita;
  2. Unidade lógica e aritmética (ALU), capaz de realizar operações aritméticas com números inteiros, operações lógicas (conjunção, negação, disjunção e disjunção exclusiva) e deslocamento de bits;
  3. Unidade de controle, que desempenha o papel da tabela de transição, inclui o contador do programa e registradores utilizados para armazenar dados de acesso rápido durantre as computações.
  4. Entrada de dados;
  5. Saída de dados.

TODO: Desenvolver fetch->decode->execute…

3.3 Máquina Virtual (VM)

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.

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.

3.4 Máquinas de pilha

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
5 Goldberg pop
4 Goldstine 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

3.5 Aritmética de pilhas

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:

  1. Decrementa o ponteiro do topo da pilha (3) para a posição 2;
  2. Armazena o operando 5 (posição 2) na memória, remove-o do topo da pilha e decremenenta o ponteiro para a posição 1;
  3. Armazena o operando 10 (posição 1) na memória, remove-o do topo da pilha e decremenenta o ponteiro para a posição 0;
  4. Soma os dois operandos armazenados na memória, incrementa o ponteiro da pilha para a posição 1 e insere o resultado da soma no topo da pilha.

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.

3.6 Representação da informação

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:

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
       

4 A linguagem Adele Assembly

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.

4.1 Instruções básicas

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

4.2 Condicionais

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.

4.3 Funções

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.

5 Programando a VM

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.

5.1 Definições da VM

Em desenvolvimento…

5.2 A estrutura dos programas

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:

  1. Colocar a máquina no estado inicial antes de executar as demais operações;
  2. Encerrar seu funcionamento, liberando blocos de memória e concluindo todas as operações (arquivos abertos etc).

Programas sem as instruções inicio e fim resultarão em erro do compilador e da máquina virtual.

5.3 Execução da VM

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.

5.4 O compilador

O compilador adelec é responsável pela análise dos scripts e transformação subsequente das instruções no arquivo de imagem:

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.

5.5 O arquivo adelevm.inc

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.

5.6 A imagem

O formato .img gerado pelo compilador é composto pelas seguintes informações:

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.

5.7 Incorporando Adele a outros projetos

Em desenvolvimento.

6 Aplicações exemplo

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.

6.1 reduz.asm

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

6.2 fibonacci.asm

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

6.3 fibo-func.asm

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

7 API em C

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).

7.1 Constantes

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.

7.2 Estruturas 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

7.3 Controle e depuração

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);

7.4 Instruções

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);

7.5 Programas

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);;

7.6 Pilha

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);

7.7 Instruções pré-definidas

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);

8 Licença

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