Laboratório 10 - Convenção de Chamada (ABI) e Integração C/Assembly
Objetivo
Implementar rotinas em Assembly RISC-V (RV32IM) estritamente compatíveis com a Application Binary Interface (ABI) para integração e vinculação (linking) com programas escritos em linguagem C.
Motivação
No desenvolvimento de sistemas de software modernos é comum combinar código de alto nível em C com rotinas de baixo nível escritas em Assembly. Enquanto a linguagem C oferece expressividade para estruturar o fluxo de controle e gerenciar estruturas de dados complexas, o Assembly permite interagir diretamente com o sistema operacional via chamadas de sistema (syscalls) e otimizar rotinas críticas de processamento.
Para que funções escritas em Assembly possam ser chamadas de forma transparente por funções compiladas em C (e vice-versa), ambos os lados devem respeitar um contrato binário padronizado: a Application Binary Interface (ABI). Esse contrato define:
-
Layout de Memória: Como tipos primitivos, vetores e estruturas (
struct) são alinhados e organizados em memória. -
Passagem de Argumentos e Retorno: Como parâmetros inteiros e ponteiros são enviados nos registradores
a0–a7e como o valor de retorno é devolvido ema0. -
Preservação de Registradores: Quais registradores podem ser livremente modificados pela função chamada (caller-saved:
t0–t6,a0–a7) e quais devem obrigatoriamente ter seus valores originais preservados na pilha e restaurados antes do retorno (callee-saved:s0–s11,spe o endereço de retornora).
Neste laboratório, você colocará a ABI do RISC-V em prática implementando uma biblioteca em Assembly que será vinculada diretamente a um programa principal em C (example.c). Para exercitar diferentes padrões de assinatura de função, manipulação de ponteiros, estruturas encadeadas e vetores, a biblioteca é dividida em três módulos práticos:
-
Funções Utilitárias de Entrada/Saída e Conversão: Implementação das rotinas fundamentais da biblioteca padrão C (
puts,gets,atoi,itoaeexit) utilizando syscalls Linux/RISC-V. -
Decodificação com Árvore de Huffman: Manipulação de ponteiros e estruturas (
struct Node) alocadas pelo código em C para percorrer uma árvore binária e descomprimir uma sequência de bits (referência sobre Codificação de Huffman). -
Rotinas Numéricas Auxiliares para o Algoritmo K-Means: Chamadas aninhadas de funções e percorrimento de vetores em memória para cálculo de distância euclidiana (
distancia), busca de índice mínimo (min) e cálculo de centróides por média inteira (means) (referência sobre K-Means).
Convenção de Chamada RISC-V (ABI) e Estruturas de Dados
Resumo dos Registradores na ABI (RV32I)
| Registradores | Nome ABI | Papel na Convenção de Chamada | Preservação |
|---|---|---|---|
x1 |
ra |
Endereço de retorno (Return Address) | Preservado se a função fizer chamadas (call) |
x2 |
sp |
Ponteiro de pilha (Stack Pointer) | Callee-saved (deve ser restaurado ao final) |
x8–x9, x18–x27 |
s0–s11 |
Registradores salvos (Saved registers) | Callee-saved (salvar na pilha antes de usar) |
x10–x11 |
a0–a1 |
Argumentos de função (1º e 2º) e valores de retorno | Caller-saved (temporários) |
x12–x17 |
a2–a7 |
Argumentos de função (3º ao 8º) | Caller-saved (temporários) |
x5–x7, x28–x31 |
t0–t6 |
Registradores temporários | Caller-saved (temporários) |
Representação da Estrutura Node em Memória (32 Bits)
Para o exemplo de decodificação de Huffman, o código em C constrói uma árvore binária composta por nós do tipo Node, onde cada ponteiro ocupa 4 bytes (32 bits):
typedef struct Node {
int val;
struct Node *left, *right;
} Node;
O layout de cada instância de Node em memória possui 12 bytes contíguos:
| Offset +0 (4 bytes) | Offset +4 (4 bytes) | Offset +8 (4 bytes) |
+---------------------+---------------------+---------------------+
| val | *left | *right |
+---------------------+---------------------+---------------------+
-
val(offset0): Inteiro de 32 bits. Vale0para nós internos da árvore; nos nós folha, armazena o código ASCII do caractere decodificado (val != 0). -
left(offset4): Ponteiro de 32 bits para o filho à esquerda (seguido quando o bit de entrada é'0'). -
right(offset8): Ponteiro de 32 bits para o filho à direita (seguido quando o bit de entrada é'1').
Especificação das Funções
Seu arquivo em Assembly deve exportar (via diretiva .globl) todas as nove funções declaradas em biblio.h:
1. Funções Utilitárias da Biblioteca C
void puts(const char *str);- Parâmetros:
a0= endereço de uma string terminada pelo byte nulo ('\0'). - Descrição: Calcula o comprimento da string apontada por
str(sem incluir o' \0') e escreve seus caracteres na saída padrão (stdout, descritor1) seguido obrigatoriamente por um caractere de quebra de linha ('\n', ASCII10), utilizando a syscallwrite(a7 = 64). - Retorno: Nenhum (
void). -
Referência: puts - C++ Reference
-
char *gets(char *str); - Parâmetros:
a0= endereço do buffer em memória a ser preenchido. - Descrição: Lê caracteres da entrada padrão (
stdin, descritor0) um a um utilizando a syscallread(a7 = 63) e armazena-os no buffer até encontrar um caractere de quebra de linha ('\n', ASCII10) ou o fim da entrada. O caractere' \n'não é mantido na string final, sendo substituído pelo byte nulo terminador ('\0', valor0). - Retorno: Retorna em
a0o endereço original do bufferstr. -
Referência: gets - C++ Reference
-
int atoi(const char *str); - Parâmetros:
a0= endereço de uma string terminada por'\0'. - Descrição: Descarta caracteres de espaço em branco iniciais (
' ','\t','\n','\v','\f','\r'), interpreta um sinal opcional ('+'ou'-') e converte a sequência subsequente de dígitos decimais ('0'–'9') em um inteiro com sinal de 32 bits. - Retorno: Retorna em
a0o valor inteiro convertido. -
Referência: atoi - C++ Reference
-
char *itoa(int value, char *str, int base); - Parâmetros:
a0= valor inteiro a ser convertido (value),a1= endereço do buffer de destino (str),a2= base numérica (10ou16). - Descrição: Converte
valuepara sua representação textual nabaseespecificada (10ou16), finalizando a string com'\0'. Para base10, valores negativos devem ser precedidos pelo caractere'-'. Para base16, o valor é tratado como sem sinal (unsigned) utilizando dígitos'0'–'9'e'A'–'F'(os testes não diferenciam maiúsculas de minúsculas). - Retorno: Retorna em
a0o ponteiro original para o bufferstr. -
Referência: itoa - C++ Reference
-
void exit(int code); - Parâmetros:
a0= código de status de encerramento (code). - Descrição: Encerra imediatamente a execução do programa invocando a syscall
exit(a7 = 93). - Retorno: Nenhum (
void).
2. Exemplo de Manipulação de Estruturas: Decodificação de Huffman
void Huffman(const char *input, char *output, Node *root_node);- Parâmetros:
a0(input): Endereço da string contendo a sequência de bits ('0'e'1') terminada em'\0'.a1(output): Endereço do buffer onde a mensagem decodificada deve ser gravada.a2(root_node): Ponteiro para o nó raiz da árvore binária de Huffman.
- Descrição: A partir de
root_node, lê cada caractere da stringinput:- Se o caractere for
'0'(ASCII48), avança para o ponteiroleft(4(node)). - Se o caractere for
'1'(ASCII49), avança para o ponteiroright(8(node)). - A cada passo, verifica o campo
val(0(node)). Enquantoval == 0, o nó é interno e a descida continua com o próximo bit deinput. Quandoval != 0, atingiu-se um nó folha: o byte correspondente avaldeve ser escrito emoutput, o ponteiro de escrita emoutputé incrementado e o percurso na árvore reinicia emroot_nodepara o próximo bit. - Ao encontrar o terminador
'\0'eminput, grava o byte nulo'\0'ao final deoutput.
- Se o caractere for
- Retorno: Nenhum (
void).
3. Exemplo de Chamadas e Vetores: Funções Auxiliares do K-Means
int distancia(int x1, int y1, int x2, int y2);- Parâmetros:
a0 = x1,a1 = y1(coordenadas do primeiro ponto) ea2 = x2,a3 = y2(coordenadas do segundo ponto). - Retorno: Retorna em
a0a distância inteira calculada. -
Descrição: Calcula a distância euclidiana inteira entre os pontos \((x_1, y_1)\) e \((x_2, y_2)\):
d = floor(sqrt((x1 - x2)^2 + (y1 - y2)^2)) -
int min(int *posicoes, int tam); - Parâmetros:
a0= ponteiro para o vetor de inteirosposicoes,a1= número de elementostam. - Retorno: Retorna em
a0o índice (0 <= idx < tam) onde se encontra o menor valor. -
Descrição: Percorre os
tamelementos deposicoes(índices0atam - 1) para localizar o índice do menor valor. Em caso de empate entre dois elementos com o mesmo valor mínimo (posicoes[i] <= menor_atual), atualize o índice para o elemento mais recentei. -
int means(int *valores, int *posicoes, int tam); - Parâmetros:
a0= vetor base de coordenadasvalores,a1= vetorposicoescontendo os índices selecionados para o grupo (cluster),a2= quantidadetamde índices válidos emposicoes. - Retorno: Retorna em
a0a média inteira simples dos valores selecionados. - Descrição: Calcula a média aritmética inteira dos elementos de
valorescujos índices estão armazenados nas primeirastamposições do vetorposicoes:Casomedia = (valores[posicoes[0]] + valores[posicoes[1]] + ... + valores[posicoes[tam - 1]]) / tamtam == 0, a função deve retornar0sem realizar divisão.
Interface de Integração (biblio.h e example.c)
Durante a correção, seu código em Assembly será compilado e vinculado juntamente com o cabeçalho biblio.h e o arquivo de teste em C example.c. Verifique o código completo de example.c para consultar como cada caso de teste invoca as funções implementadas.
biblio.h
typedef struct Node {
int val;
struct Node *left, *right;
} Node;
void puts ( const char *str );
char *gets ( char *str );
int atoi (const char *str);
char *itoa ( int value, char *str, int base );
void exit(int code);
void Huffman(const char *input, char *output, Node *root_node);
int distancia(int x1, int y1, int x2, int y2);
int min(int *posicoes, int tam);
int means(int *valores, int* posicoes, int tam);
Entradas e Saídas
Entrada (stdin):
- 1ª linha: Um inteiro
op(0 <= op <= 6) indicando a operação a ser executada porrun_operation(op). - Linhas seguintes (dependendo de
op): op = 0ouop = 2: Nenhuma linha adicional.op = 1: Uma linha de texto a ser lida porgetse impressa porputs.op = 3: Um número inteiro em base 10 a ser convertido e impresso em hexadecimal (base 16).op = 4: Duas linhas de texto consecutivas a serem lidas e impressas.op = 5: Uma string de bits ('0'e'1') a ser decodificada pela árvore de Huffman.op = 6: Um inteiro indicando o número de iterações do algoritmo K-Means.
Saída (stdout):
- A saída correspondente ao caso de teste selecionado por
op, finalizada com quebra de linha (\n).
Exemplos
Caso 1 — Leitura e Escrita de String (op = 1)
1
Answer with spaces
--------------------------------
Answer with spaces
Caso 3 — Conversão Decimal para Hexadecimal (op = 3)
3
255
--------------------------------
FF
Caso 5 — Decodificação de Huffman (op = 5)
5
010011010110000110110011101101100100011010110110010101111101101
--------------------------------
you shall not pass
Caso 6 — Iterações do K-Means (op = 6)
6
5
--------------------------------
16
Notas e Dicas
-
Símbolos Globais: Todos os nomes de funções exigidos devem ser declarados como globais usando a diretiva
.globl nome_da_funcaopara que o linker consiga resolver as referências chamadas pelo código em C. -
Preservação de Registradores (ABI): O código em C compilado utiliza intensamente os registradores salvos
s0–s11para manter variáveis locais (comok,x,y,tam1,tam2,tam3emrun_operation). Se qualquer uma de suas funções em Assembly modificars0–s11, ela deve salvá-los na pilha no prólogo e restaurá-los antes doret. -
Ponto de Entrada (
_start): Como o ponto de entrada_startjá está definido no arquivo em C (testing.c/example.c), não defina_startno seu arquivo Assembly para evitar erro de símbolo duplicado na compilação. -
Caractere Nulo (
'\0'): O caractere'\0'corresponde ao byte nulo de valor0. Evite usarli a0, '\0', pois alguns montadores interpretam a expressão como o caractere'0'(valor ASCII48); utilizeli a0, 0ou o registradorzero(sb zero, 0(a1)). -
Terminação de Strings: Lembre-se de gravar o byte nulo (
0) ao final das strings produzidas emgets,itoaeHuffmanantes de retornar ao código em C.
Entrega
O arquivo report gerado pelo simulador deve ser submetido no Classroom da disciplina, nomeado como {RA}_lab10.report.
