Compilando Funções e Procedimentos no MIPS

instrução MIPS LW e SW IF Simples no MIPS

Oi pessoal! No artigo anterior da série MIPS eu mostrei a vocês como compilar uma instrução SWITCH/CASE, em linguagem C, para o Assembly. Notamos nesse artigo que o código MIPS resultante ficou bem mais longo e complexo, fazendo uso de várias instruções já estudadas antes. No artigo de hoje, vamos adicionar um pouco mais de complexidade, pois ao compilarmos procedimentos, teremos de usar registradores da arquitetura MIPS específicos para os endereços de chamadas e retornos do procedimento, entre outras funções já conhecidas. Vamos nos aventurar?

 

 

Como funciona o procedimento no MIPS?

 

Se você não sabe o que é um procedimento, ou função, sugiro que antes de seguir adiante com este artigo, que faça a leitura dos meus artigos sobre eles na série Algoritmos clicando aqui. Depois de compreender bem, volte para este artigo, ok?!

 

Bom, para manipular os procedimentos fazemos uso dos seguintes registradores:

  • $a0 até $a3: são os registradores de argumentos utilizados para a passagem de parâmetros;

  • $v0 e $v1: são os registradores de valor utilizados para o retorno do procedimento;

  • $ra: é o registrador de endereço do retorno do procedimento, utilizado na volta ao ponto de origem da chamada do procedimento.

 

Além dos registradores, também é importante saber os seguintes conceitos:

 

JAL: é uma instrução de salto (jump) utilizada unicamente para os procedimentos (jump and link). Essa instrução desvia para um endereço e, ao mesmo tempo, salva o endereço da instrução seguinte no registrador de endereço de retorno. Nas palavras de Patterson: “Uma instrução que salta para um endereço e simultaneamente salva o endereço da instrução seguinte em um registrador”. Sintaxe da instrução:

 

jal endereco_procedimento

 

Exemplo:

jal fatorial

 

onde fatorial é o LABEL de um bloco de procedimentos e, relembrando, um LABEL é um nome dado a um endereço de memória.

 

JR: é uma instrução de salto, uma instrução de desvio incondicional para o endereço especificado em um registrador, ela volta ao endereço de retorno correto que é armazenado em $ra. Sintaxe da instrução:

 

jr $ra

 

Caller: é o programa que chama o procedimento, fornecendo os valores dos parâmetros. Faz uso dos registradores de $a0 a $a5 para armazenar os parâmetros e também de jal para pular para o procedimento. Importante lembrar que o contador de programa é usado para realizar os cálculos dos endereços corretamente, para quem não lembra, falo como isso funciona neste artigo aqui.

 

Callee: é um procedimento que executa uma série de instruções armazenadas com base nos parâmetros fornecidos pelo Caller e depois retorna o controle para o Caller novamente.

 

Spilled Registers: é o processo de colocar variáveis menos utilizadas na memória (ou variáveis que serão necessárias mais adiante).

 

Pilha: da mesma forma que uma pilha de chamadas e retornos de procedimentos é criada para linguagens como C e Java, aqui no MIPS também faremos uso deste conceito para gerenciar os Spilled Registers. Isto é necessário pois os registradores usados pelo Caller devem ser restaurados aos seus valores anteriores a chamada do procedimento.

 

Stack Pointer: é um valor que indica o endereço alocado mais recentemente em uma pilha, mostrando onde devem ser localizados os valores antigos dos registradores e onde os Spilled Registers devem ser armazenados. O registrador 29 é usado para o $sp.

 

Push: coloca palavras para cada registrador salvo ou restaurando na pilha. Valores são colocados na pilha pela subtração do valor do stack pointer.

 

Pop: remove palavras da pilha. Valores são retirados da pilha pela soma do valor do stack pointer.

 

Assim, procedimentos aqui funcionam da mesma forma que expliquei nos artigos da série Algoritmos, a diferença básica é que aqui devemos informar uma série de passos que não são necessários em linguagens de Médio e Alto nível, isso fica invisível para essas linguagens, mas programando em Assembly, devemos tomar mais cuidado, utilizando as instruções e registradores corretos para a manipulação de procedimentos.

 

 

Compilando um procedimento

 

Observe o trecho de código em linguagem C abaixo:

 

 

Vamos identificar os registradores:

  • $a0 = g
  • $a1 = h
  • $a2 = i
  • $a3 = j
  • $s0 = f

 

O rótulo (label) no MIPS será “exemplo:” que é o nome da função em C. Agora precisamos salvar os registradores temporários usados pelo corpo da função, isto é, $t0 = (g + h ) e $t1 = ( i + j ), que ficará da seguinte forma:

 

 

Os valores antigos são empilhados de forma a criar espaço para três palavras de 12 bytes na pilha. A primeira linha ajusta a pilha criando um espaço para três itens, lembre-se de que quando vamos empilhar precisamos subtrair, pois a pilha cresce de endereços maiores para menores. A segunda linha do código MIPS salva o registrador $t1 para usar depois, o mesmo acontece com a terceira e quarta linha, elas salvam os registradores temporários para uso posterior. Notem que usamos corretamente o alinhamento da memória, pulando de 4 em 4 no endereço. A Figura 1 apresenta o estado da pilha antes do procedimento e a Figura 2 o estado após a chamada do procedimento. Continuando, devemos fazer a compilação do corpo da função:

 

 

Essa parte foi fácil não? Mas qual a diferença? Aqui em vez de usarmos registradores $s e $t para g, h, i e j, nós usamos os registradores de parâmetros $a pois são os parâmetros passados na função que desejamos usar no cálculo. Então, tomem muito cuidado com esse detalhe no momento em que forem fazer a compilação de funções/procedimentos para não confundir os registradores que devem ser utilizados. Agora última coisa que está faltando é o retorno da função e para isso fazemos:

 

 

Copiamos f para um registrador de valor de retorno, neste caso $v0, e antes de retornar para o ponto de origem, os valores antigos devem ser restaurados usando para isto o desempilhamento:

 

 

As três primeiras linhas restauram os registradores para o caller e a última linha ajusta a pilha para excluir os 3 itens. O procedimento deve terminar com:

 

 

Assim encerramos a compilação de uma função em C para o Assembly MIPS. Importante ressaltar que $s0 a $s7 são preservados em uma chamada de procedimento enquanto que registradores de $t0 a $t9 não são. O código completo para o MARS fica da seguinte forma:

 

 

Teste o código no MARS e verifique o resultado. Abaixo seguem prints de tela da execução no meu computador para você se guiar.

 

Compilando Funções e Procedimentos no MIPS - Procedimentos no Mars 1
Procedimentos no Mars 1
Procedimentos no Mars 2
Procedimentos no Mars 2
Procedimentos no Mars 3
Procedimentos no Mars 3
Procedimentos no Mars 4
Procedimentos no Mars 4
Procedimentos no Mars 5
Procedimentos no Mars 5
Procedimentos no Mars 6
Procedimentos no Mars 6
Procedimentos no Mars 7
Procedimentos no Mars 7
Procedimentos no MARS 8
Procedimentos no MARS 8
Procedimentos no MARS 9
Procedimentos no MARS 9
Procedimentos no Mars 10
Procedimentos no Mars 10
Procedimentos no Mars 11
Procedimentos no Mars 11
Procedimentos no Mars 12
Procedimentos no Mars 12
Procedimentos no Mars 13
Procedimentos no Mars 13
Procedimentos no Mars 14
Procedimentos no Mars 14
Procedimentos no Mars 15
Procedimentos no Mars 15
Procedimentos no Mars 16
Procedimentos no Mars 16
Procedimentos no Mars 17
Procedimentos no Mars 17
Procedimentos no Mars 18
Procedimentos no Mars 18

 

 

Conclusão

 

Pelo que pudemos notar aqui, não é assim um bicho de sete cabeças compilar funções e procedimentos, não é mesmo? Conhecemos novas instruções hoje, a jal e a jr, e utilizamos outras já bem conhecidas, pudemos por em praticar o que foi aprendido em outros artigos. No próximo artigo darei continuidade a este assunto, mostrando outros detalhes a respeito da compilação de funções/procedimentos, então não percam. E já sabem, se houver dúvidas, deixe aqui nos comentários que responderei o mais rápido que puder. Até.

 

 

Saiba mais

 

Funções e Procedimentos - Parte 1

Compilando Switch/Case no MIPS

Técnicas de Mapeamento de Memória em Linguagem C

Outros artigos da série

<< Compilando Switch/Case no MIPSCompilando Procedimentos Recursivos e Aninhados no MIPS >>
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Elaine Cecília Gatto
Bacharel em Engenharia de Computação. Mestre em Ciência da Computação. Doutoranda em Ciência da Computação. Co-fundarora e Líder das #GarotasCPBr. Pesquisadora Convidada no Grupo de Pesquisa: "Artes em Tecnologias Emergentes" do Programa de Pós Graduação em Design na UNESP Campus Bauru. Cantora, Docente no Magistério Superior, Geek, Nerd, Otaku e Gamer. Apaixonada por Michael Jackson, Macross, Rocky Balboa, Séries, Filmes, Cervejas e Vinhos. Mais informações sobre mim você encontra em: http://lattes.cnpq.br/8559022477811603.

Deixe um comentário

avatar
 
  Notificações  
Notificar