# Introdução à Arquitetura de Computadores/Suporte à Funções

Funções são ferramentas muito importantes, pois tornam código escrito muito mais usável e torna a tarefa de programar mais produtiva. Elas permitem que um programador se concentre em apeenas uma tarefa de cada vez. Portanto, é essencial que um processador possua suporte á elas. Veremos como isso ocorre no MIPS.

## As Etapas Necessárias para Invocar Funções

Quando queremos chamar uma função, as seguintes etapas devem ser cumpridas:
* O programa principal deve colocar os parâmetros da função em um local que ela possa acessar.
* O programa principal deve ceder o controle para a função.
* A função deve coletar todos oos parâmetros deixados pelo programa principal.
* A função deve executar a tarefa desejada.
* A função deve armazenar seus resultados em um lugar em que o programa principal possa acessar.
* A função deve retornar o fluxo do código para o ponto imediatamente após ser chamada.
Para que estes requisitos sejam cumpridos, as seguintes convenções fora criadas:
* Os registradores $r4, $r5, $r6 e $r7 seriam usados para armazenar parâmetros de funções. Por causa desta funcionalidade, tais registradores podem ser chamados pelos seus "apelidos": $a0, $a1, $a2 e $a3.
* Os registradores $r2 e $r3 seriam usados para as funções armazenarem seus valores de retorno para o programa principal. Por isso, eles costumam ser chamados de $v0 e $v1.
Tais regras são apenas convenções. Nada impede que um programador as desrespeite e use outros registradores para estabelecer comunicação entre funções e programas principais. Entretanto, para que um programa funcione bem em conjunto com todos os demais, é importante quee tais convenções sejam seguidas.
Além dos registradores convencionados acima, o registrador $r31 também tem um papel importante. Ele sempre armazena o endereço de retorno para o qual a última função chamada deve retornar. Por ter uma função tão importante, este registrador é mais conhecido pelo apelido $ra.

## Instruções de Uso de Funções: jal e jr

A instrução que usamos para invocar uma função chama-se jal, ou Jump and Link. Ela é usada da seguinte forma:
Entretanto, só devemos chamar esta função depois de já termos salvo os argumentos nos registradores apropriados.
Depois da função executar todas as operações e salvar os resultados apropriados em $v0 e $v1, podemos usar a instrução jr, ou Jump Register. Normalmente usamos a instrução passando para ela o valor do registrador $ra, que contém o endereço certo para voltarmos:
Como exemplo de como isso pode ser feito, vamos converter para Assembly o seguinte código em C:
O código no Assembly do MIPS ficaria assim:
Entretanto, observe que para realizarmos os cálculos necessários, precisamos usar registradores. Ao fazer isso, existe o grande risco de apagarmos dados importantes do programa principal. Para evitar isso, existe uma convenção que diz que os registradores $r8 até $r15, $r24 e $r25 são registradores temporários. Por isso, eles costumam ser chamados de $t0 até $t9. Após chamar uma função, o programa principal não deve esperar que os valores destes registradores sejam os mesmos. Somente dados que estejam nos registradores $r16 até $r23 são sempre preservados entre funções. Por esta razão, tais registradores permanentes (salvos) são chamados de $s0 até $s7.

## Funções com Muitas Variáveis

Existem funções que podem precisar receber mais do que 4 parâmetros. Entretanto, só temos registradores suficientes para armazenar 4 parâmetros. Nós também podemos querer retornar mais do que os 2 valores permitidos pelos registradores. Ou então, nossa função pode precisar armazenar mais do que 10 valores temporários. O que fazer para resolver isso?
Como o espaço dentro de registradores é bastante limitado, só nos resta apelar para a pilha da memória. Por convenção, o registrador $r29, ou $sp (Stack Pointer) armazena o endereço na pilha de onde novos valores podem ser colocados. Acessando seu endereço e alterando-o podemos guardar valores na memória. O acesso à pilha da memória é muito mais lento que o acesso à registradores, mas este é o único modo de podermos armazenar uma quantidade muito maior de informação.
Por motivos históricos, a pilha "cresce" sempre dos valores de endereços altos para valores menores. Ou seja, para alocar espaço para mais valores, precisamos sempre decrementar o seu valor. Incrementando-o, estamos na verdade removendo os últimos dados da pilha.
Vamos reescrever agora o código Assembly visto acima, desta vez preeservando os valores dos registradores $t0 e $t1 para vermos como ficaria o resultado:

## Funções Recursivas e Sub-Funções

Da mesma forma que o programa principal invocou uma função, uma função também pode invocar outras funções. Se la for recursiva, ela pode até mesmo invocar clones de si mesma para realizar uma tarefa. Como implementar isso sendo que temos apenas um registrador $ra? Se chamarmos outra função, o $ra original é sobrescrito e podemos perder a capacidade de voltar ao programa principal. Além disso, valores temporários que representam variáveis locais podem ser perdidos.
A única forma de evitar isso é enviar para a pilha tudo aquilo que precisa ser salvo - da mesma forma que fizemos com alguns valores no exemplo acima. Para mostrar isso na prática vamos implementar em Assembly a seguinte função em C:
O resultado final é algo como:
Por fim, uma última coisa útil que é interessante comentar é que o Stack Pointer ($sp) pode ser alterado várias vezes ao longo de uma função. Para manter memorizado o valor do endereço da pilha no início da função, costuma-se usar o registrador $r30 como um Frame Pointer ($fp). Nos exemplos acima, isso não foi preciso, pois só mudamos o valor do Stack Pointer no começo e fim de cada função. Mas em alguns programas utilizar este registrador pode ser útil.