- Arquitetura de Conjunto de Instruções MIPS
- Primeira Instrução MIPS
- Compilação de Expressões no MIPS
- Convertendo uma instrução com Array no MIPS
- Armazenando um valor em Array no MIPS
- Instruções LW e SW com Array no MIPS
- Instrução IF Simples no MIPS
- Instrução IF Composto no MIPS
- Instrução SLT no MIPS
- Operações Lógicas no MIPS
- Operação Lógica AND no MIPS
- Operação Lógica OR no MIPS
- Operação Lógica NOT no MIPS
- Endereços de Memória no MIPS
- Operandos Imediatos e Constantes no MIPS
- Compilando Arrays com índice variável no MIPS
- Testando as instruções MIPS no MARS
- Executando um Array no MARS para MIPS
- Sinal e Overflow no MIPS
- Compilando instruções com ou sem Sinal, Overflow e Imediato no MIPS
- Compilando While no MIPS
- Compilando Switch/Case no MIPS
- Compilando Funções e Procedimentos no MIPS
- Compilando Procedimentos Recursivos e Aninhados no MIPS
- Detalhamento da Compilação de Procedimentos no MIPS
- MIPS: Instruções de Multiplicação
- MIPS: Instruções de Divisão
- MIPS: Subtração e outras instruções
- Compilando o comando FOR no MIPS
- Ponto Flutuante no MIPS
- Convertendo Código de Máquina em Assembly MIPS – Parte 1
- MIPS: Resolução dos exercícios – Parte 1
Olá pessoal! Hoje vou mostrar para vocês como codificar, em Assembly MIPS, o comando de controle FOR. Primeiro mostro um código em C, o qual será usado como base de raciocínio lógico. No MARS, usaremos essa lógica para a codificação. Neste exemplo vamos colocar em prática praticamente tudo o que já aprendemos durante esses três anos de caminhada! Vou fazer passo a passo para o entendimento ficar mais fácil, comentando blocos de código. Se você tiver dúvidas a respeito das instruções, sugiro consultar os artigos anteriores, para verificar se consegue por eles é possível resolver. Caso não consiga mesmo entender, deixe sua dúvida nos comentários tá bom. Prontos para mais este desafio?
Código em C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> #include <stdlib.h> #include <stdlib.h> int Vetor[10]; int indice, soma = 0, med =0; int main() { soma(Vetor); media(); return 0; } void soma(int Vetor[]){ for(indice=0; indice<10; indice++) { soma = Vetor[indice] + soma; } printf("\nSoma: %d", soma); } void media(){ med = soma / 10; printf("\nMedia: %d", med); } |
Codificando em Assembly MIPS
O primeiro passo é definirmos os dados que vamos usar no programa Assembly:
1 2 3 4 |
.data $LS: .asciiz " a soma é: " $LM: .asciiz " a media é: " vetor: .word 0, 1, 2, 3, 4 |
No espaço de dados defino dois Labels, um LS para o texto que será impresso no console para a soma, e LM para o texto da média. Em seguida defino um vetor de 5 elementos com os valores de 0 a 4. Escolhi assim para ficar mais fácil acompanhar o cálculo na execução do MARS e corrigir possíveis erros. Depois começamos o espaço do código, definindo o programa principal:
1 2 |
.text .globl main |
Feito isto, temos de fazer todas aquelas configurações da pilha do procedimento principal. Então escrevi o label main para identificar o inicio e as quatro linhas seguintes são referentes a pilha.
1 2 3 4 5 6 |
main: # configurações do programa principal subu $sp, $sp, 32 # cria um frame de pilha com 32 bytes sw $ra, 20($sp) # salva o registrador $ra sw $fp, 16($sp) # salva o registrador $fp addiu $sp, $sp, 28 # alinhamento de memória |
As próximas linhas são referentes às chamadas dos procedimentos.
1 2 3 4 5 6 7 8 9 10 11 |
la $a0, vetor # carrega o vetor jal soma # chama o procedimento soma # Move o conteúdo do registrador de retorno ($v0) para o registrador # ($a1), liberando-o para ser usado novamente move $a1, $v0 # chama o procedimento media jal media move $a1, $v0 # libera $v0 |
Carregamos o vetor para o registrador $a0, que é o primeiro parâmetro do procedimento, usando a instrução la. A instrução jal será executada depois que o procedimento soma terminar. Liberando $v0, conseguimos então chamar na sequencia o procedimento media. Novamente precisamos liberar $v0 copiando seu conteúdo para $a1. As próximas linhas são referentes à impressão no console das strings e dos valores inteiros retornados pelos procedimentos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.text # imprimindo a string li $v0, 4 la $a0, $LS syscall # imprimindo o inteiro li $v0, 1 move $a0, $s1 syscall # imprimindo a string li $v0, 4 la $a0, $LM syscall # imprimindo o inteiro li $v0, 1 move $a0, $s2 syscall |
Note que para os dois procedimentos foi necessário replicar código. Para a soma é necessário passar como parâmetro o label $LS e para a média o $LM. Além disso, o resultado da soma será armazenado no registrador $s1, e o da média em $s2, por isso eles são usados na chamada de sistema para impressão de números inteiros. Tome muito cuidado com esses detalhes, é muito fácil confundir labels, registradores, etc. Preste bastante atenção quando estiver fazendo seus próprios códigos. As próximas linhas são referentes à configuração do programa principal.
1 2 3 4 5 |
# configurações do programa principal lw $ra, 20($sp) # restaura valor de $ra lw $fp, 16($sp) # restaura valor de $fp addiu $sp, $sp, 32 # remove o frame de pilha j fim # encerra o programa |
Agora vamos ver o procedimento da soma, que tem um for para fazer a soma de todos os elementos do vetor. Primeiro defini um label para o procedimento, chamado soma, e em seguida fiz todas as configurações da pilha de procedimento. Nunca se esqueça deles!!! Esse trecho de código você sempre pode utiliza-lo como padrão quando usar procedimentos.
1 2 3 4 5 6 7 |
.textsoma: # configurações do procedimento subu $sp, $sp, 32 # reserva o espaço do frame sw $ra, 20($sp) # salva o endereço de retorno sw $fp, 16($sp) # salva o frame pointer addiu $fp, $sp, 28 # prepara o frame pointer sw $a0, 0($fp) # salva o argumento |
Próximo passo é carregar o vetor para ser usado no procedimento, assim como definir um registrador para guardar o número total de elementos ($s3) no vetor e outro que guardará a soma ($s1). Esses registradores serão usados no FOR.
1 2 3 |
la $s4, vetor # indice do vetor li $s3, 5 # número total de elementos no vetor li $s1, 0 # soma |
Chegamos na parte que é novidade, como codificar o FOR no MIPS. Antes de iniciar a parte do código referente ao LOOP, precisamos definir i = 0. O registrador que usarei para isto será o $s0. Assim, movi o valor zero, que está no registrador $zero, para $s0. Pronto, a variável i foi inicializada com zero, lembrando que o nosso i é o índice do vetor.
1 2 |
#FOR move $s0, $zero # i = 0 ($s0 é i) |
Feito isto, comecemos o LOOP. O FOR é uma estrutura de controle como o WHILE e o DO-WHILE. Para escolher para onde ir, vamos precisar usar a instrução SLT juntamente com a BEQ, como já estudamos antes. Essas duas linhas também podem ser usadas como um padrãozinho toda vez que você for codificar um LOOP FOR. A instrução SLT verifica se i >= n, ou $s0>= $s3, que é a condição de parada aqui. Se for verdade, armazena zero em $t0, caso contrário, armazena 1. Bom, mas a instrução SLT, apenas armazena um valor 0 ou 1, ela não toma nenhuma decisão, então, quem é que faz isso? A instrução BEQ é quem faz esse papel, ela decide continuar, ou não, executando o código dentro do LOOP. Se já alcançamos o final do vetor, então, a execução deve parar (EXIT), mas enquanto não atingirmos o fim do vetor, o bloco deve continuar executando. Assim, se i>=n, sai do LOOP, caso contrário, continua executando.
1 2 3 4 |
LOOP: # configurações do FOR slt $t0, $s0, $s3 # t0 = 0 se $s0 >= $s3 ( i >= n), t0 = 1 caso contrário beq $t0, $zero, EXIT # se $s0 >= $s3 ( i >= n) vá para EXIT |
Depois de codificar corretamente a instrução FOR, pelo menos a primeira e a segunda parte dele, devemos agora tratar do ARRAY. Jamais se esqueçam do alinhamento de memória usado no MIPS. Temos de fazer aquele cálculo, padrãozinho também, toda vez que usarmos ARRAYs. Assim, usei a instrução SLL para tornar isso mais fácil, ela já faz o cálculo de 4*i juntamente com o registrador que vamos usar, que neste caso, é o $s0 (i). Em seguida, usei o registrador $s2 para armazenar o resultado final do endereço, que é a soma do endereço do vetor com o (4*i). Depois de tudo isso, podemos fazer o carregamento do elemento que está na posição que queremos, e então soma-lo com o valor que está em $s1 (que será a nossa soma final).
1 2 3 4 5 |
# configuração do ARRAY sll $t1, $s0, 2 # $t1 = 4 * i, ou 4 * $s0 add $t2, $s4, $t1 # t2 = ( vetor + ( 4 * i) ) lw $t3, 0($t2) # $t3 = vetor[i], carregando o elemento do índice i add $s1, $s1, $t3 # somando os elementos (soma = soma + vetor[i] |
Elemento carregado, somado e resultado guardado. Agora é hora de ir para a próxima posição do vetor. Como fazer isso? No FOR usamos um contador, i +=1, mas e no Assembly? Não é tão difícil ou complicado, só é preciso cuidar com os registradores que devem ser utilizados. Usei a instrução ADDI para somar o valor que está no índice i ($s0), com o imediato 1, assim incrementamos o índice e conseguimos passar para a próxima posição. A instrução J volta para o inicio do LOOP e o label EXIT sai desse bloco de código ao término da execução do LOOP.
1 2 3 |
# configurações do FOR addi $s0, $s0, 1 # $s0 = $s0 + 1 (ou i = i + 1) é o contador j LOOP # volta para o LOOP EXIT: |
Finalizando o procedimento soma, temos de terminar as configurações referentes aos registradores. Passei $s1 na instrução ADD pois é este registrador que contem o resultado da soma, e que deve retornar ao programa principal. A instrução JR encerra o procedimento.
1 2 3 |
# configurações do procedimento add $v0, $s1, $zero # retorna para quem chamou. jr $ra |
O procedimento media é exatamente a mesma coisa, só que mais fácil ainda, pois não há um FOR dentro dele a ser codificado. Dessa forma, as configurações do procedimento devem ser codificadas corretamente. Aqui eu usei a instrução DIV para calcular a média, portanto, $s3 é o numero total de elementos no vetor, e $s1 é a soma calculada no procedimento soma. O resultado é armazenado em $s2, o qual é passado na instrução ADD que retorna ao programa principal.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
media: # configurações do procedimento subu $sp, $sp, 32 # reserva o espaço do frame sw $ra, 20($sp) # salva o endereço de retorno sw $fp, 16($sp) # salva o frame pointer addiu $fp, $sp, 28 # prepara o frame pointer sw $a0, 0($fp) # salva o argumento li $s3, 5 # número total de elementos no vetor li $s2, 0 # media div $s2, $s1, $s3 # calcula a média # configurações do procedimento add $v0, $s2, $zero # retorna para quem chamou. jr $ra |
Encerrando nosso “programinha” em Assembly MIPS, precisamo encerrá-lo corretamente:
1 2 3 |
fim: li $v0, 10 syscall |
A seguir você confere o código completo, para facilitar a visualização e também para que você teste em sua máquina.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
.data $LS: .asciiz " a soma é: " $LM: .asciiz " a media é: " vetor: .word 0, 1, 2, 3, 4 .text .globl mainmain: # configurações do programa principal subu $sp, $sp, 32 sw $ra, 20($sp) sw $fp, 16($sp) addiu $sp, $sp, 28 la $a0, vetor # chama o procedimento jal soma # Move o conteúdo do registrador de retorno ($v0) para outro # registrador ($a1), para que seja liberado e usado novamente. move $a1, $v0 jal media move $a1, $v0.text # imprimindo a string li $v0, 4 la $a0, $LS syscall # imprimindo o inteiro li $v0, 1 move $a0, $s1 syscall # imprimindo a string li $v0, 4 la $a0, $LM syscall # imprimindo o inteiro li $v0, 1 move $a0, $s2 syscall # configurações do programa principal lw $ra, 20($sp) lw $fp, 16($sp) addiu $sp, $sp, 32 j fim # encerra o programa .text soma: # configurações do procedimento subu $sp, $sp, 32 # reserva o espaço do frame sw $ra, 20($sp) # salva o endereço de retorno sw $fp, 16($sp) # salva o frame pointer addiu $fp, $sp, 28 # prepara o frame pointer sw $a0, 0($fp) # salva o argumento la $s4, vetor # indice do vetor li $s3, 5 # número total de elementos no vetor li $s1, 0 # soma #FOR move $s0, $zero # i = 0 ($s0 é i) LOOP: # configurações do FOR slt $t0, $s0, $s3 # t0 = 0 se $s0 >= $s3 ( i >= n), t0 = 1 caso contrário beq $t0, $zero, EXIT # se $s0 >= $s3 ( i >= n) vá para EXIT # configuração do ARRAY sll $t1, $s0, 2 # $t1 = 4 * i (4 * $s0) add $t2, $s4, $t1 # t2 = ( vetor + ( 4 * i) ) lw $t3, 0($t2) # $t3 = vetor[i] add $s1, $s1, $t3 # somando os elementos (soma = soma + vetor[i] # configurações do FOR addi $s0, $s0, 1 # $s0 = $s0 + 1 (ou i = i + 1) é o contador j LOOP # volta para o LOOP EXIT: # configurações do procedimento add $v0, $s1, $zero # retorna para quem chamou. jr $ra media: # configurações do procedimento subu $sp, $sp, 32 # reserva o espaço do frame sw $ra, 20($sp) # salva o endereço de retorno sw $fp, 16($sp) # salva o frame pointer addiu $fp, $sp, 28 # prepara o frame pointer sw $a0, 0($fp) # salva o argumento li $s3, 5 # número total de elementos no vetor li $s2, 0 # media div $s2, $s1, $s3 # calcula a média # configurações do procedimento add $v0, $s2, $zero # retorna para quem chamou. jr $ra fim: li $v0, 10 syscall |
Conclusão
E então pessoal? Acharam muito difícil? Se tiverem dúvidas, deixem aqui nos comentários, que responderei o mais breve possível. Para exercitar um pouco, sugiro vocês criarem mais procedimentos usando vetores. É isso galera, até o próximo artigo.