Instruções: A Linguagem de Máquina – Aula 02 Professor: André Luis Meneses Silva E-mail/msn: [email protected] Página: www.dcomp.ufs.br/index.php/docentes:Andre Introdução Instruções para tomada de decisões Desvio incondicional Suporte a procedimentos no hardware do computador Endereçamento no mips para operandos e endereços de 32 bits. Endereçamento em desvios condicionais e jumps. Instruções decisões. a tomada de O que distingue um computador de uma calculadora simples? para Capacidade de tomar decisões com base nos dados de entrada e valores calculados. Em linguagens de alto nível, estruturas típicas de decisão são as construções if, algumas vezes acompanhadas de goto e rótulos (labels). O assembly do mips possue duas instruções que dão suporte para tomada de decisões (beq e bnq). Instruções decisões. para a tomada de beq Instrução utilizada quando desejamos comparar se dois valores armazenados em registradores, são iguais. Sintaxe: beq $r1, $r2, L1, Onde $r1, $r2 são os registradores cujos valores armazenados vão ser comparados. Se os valores são iguais, a sequência de execução pula para a instrução que possui o rótulo L1. Instruções decisões. a tomada de bnq Instrução utilizada quando desejamos comparar se dois valores armazenados em registradores, são diferentes. Sintaxe: bnq $r1, $r2, L1, para Onde $r1, $r2 são os registradores cujos valores armazenados vão ser comparados. Se os valores são diferentes, a seqüência de execução pula para a instrução que possui o rótulo L1. beq e bnq são instruções conhecidas como desvios condicionais. Desvios condicionais são instruções que requerem a comparação de dois valores e que leva em conta uma transferência de controle subseqüente para um novo endereço. Desvio incondicional O assembly do Mips também dá suporte a instrução de desvio incondicional. Basicamente, um desvio incondicional é uma instrução que sempre diz que o processador deverá seguir o desvio. A instrução para isso é a instrução j (jump). Sintaxe: j rotulo # pula para a instrução precedida por # rotulo Instruções decisões. para a tomada de Vamos ver estas instruções na prática. Dada a seguinte construção Java, qual o assembly obtido? if ( i == j) f = g + h; else f = g – h; Instruções decisões. para a tomada de Vamos ver estas instruções na prática. Dada a seguinte construção Java, qual o assembly obtido? if ( i == j) f = g + h; else f = g – h; Calma isso ainda não é o assembly Instruções decisões. para Bne $s3, $s4, Else a tomada de Instruções decisões. para Bne $s3, $s4, Else add $s0, $s1, $s2 a tomada de Instruções decisões. para Bne $s3, $s4, Else add $s0, $s1, $s2 J Exit; a tomada de Instruções decisões. para Bne $s3, $s4, Else add $s0, $s1, $s2 J Exit; ELSE:sub $s0, $s1, $s2 a tomada de Instruções decisões. para Bne $s3, $s4, Else add $s0, $s1, $s2 J Exit; ELSE:sub $s0, $s1, $s2 Exit: a tomada de Instruções decisões. para a tomada de Observem que o programador assembly não precisa se preocupar com o cálculo do endereço utilizado nos desvios. E um laço, como seria? Nos restringiremos ao laço while. Os demais laços são bastante semelhantes. Instruções decisões. para a tomada de Seja o seguinte código Java while (save[i] == k) i += 1; Qual seria o assembly correspondente, supondo que os valores de i e k estão nos registradores $s3 e $s5, e a base do array save em $s6? Instruções decisões. para a tomada de 1. Realizar a leitura de save[i] Loop: sll $t1, $s3, 2 # $t1 = 4 * i add $t1, $t1, $s6 # $t1 = endereço de save[i] lw $t0, 0($t1) # $t0 = save[i] 2. Teste do loop, terminando se save[i] != k Bne $t0, $s5, Exit #vá para exit se save[i] != k 3. Senão, adiciona 1 a i e volta para o início. add $s3, $s3, 1 #i=i+1 j Loop Exit: Instruções decisões. para Código fonte while (save[i] == k) i += 1; a tomada de Resultado Final Loop: sll $t1, $s3, 2 add $t1, $t1, $s6 lw $t0, 0($t1) Bne $t0, $s5, Exit add $s3, $s3, 1 j Loop Exit: Instruções decisões. para a tomada de Estas sequências de instruções sem desvios (exceto, possivelmente, no final) e sem destinos de desvio ou rótulos de desvio (exceto, possivelmente, no início) são conhecidas como blocos básicos. Blocos básicos são muito importante e constituem uma das etapas do projeto de compiladores, basicamente, através deles, podemos realizar algumas otimizações no programa. Instruções decisões. para a tomada de As instruções slt e slti Embora o teste de igualdade e desigualdade seja bastante popular, precisamos também de um outro tipo de comparação. A instrução slt é usada quando desejamos verificar se o valor armazenado em um registrador é menor que o valor armazenado em um outro registrador. A instrução slti é usada quando desejamos verificar se o valor armazenado em um registrador é menor que o valor de uma constante literal. Instruções decisões. a tomada de Sintaxe de uso para slt $t1, $r1, $r2 Basicamente, se o valor em $r1 for menor que o valor em $r2, $t1 recebe o valor 1. Caso contrário, $t1 recebe o valor 0. slti $t1, $r1, constante Basicamente, se o valor em $r1 for menor que o valor da constante literal, $t1 recebe o valor 1. Caso contrário, $t1 recebe o valor 0. E se quisermos fazer algo do tipo se i < j faça, tem como? Senão tiver como fazer, porque o MIPS é assim? Instruções decisões. para a tomada de Realmente não temos como fazer isso, mas há uma razão bastante lógica. Se o MIPS tivesse uma instrução que já desempenhasse ambos os papéis ele estaria ferindo sua restrição de projeto (no qual, o processador seria dotado de apenas instruções simples). Logo é preferível quebrar esta instrução mais complexas em duas mais simples e, caso tenhamos necessidade de reproduzirmos um se i < 4 então, utilizamos as instruções apresentadas anteriormente. Instruções decisões. para a tomada de O registrador $zero No MIPS temos um registrador especial que sempre armazena o valor 0. O registrador $zero em conjunto com slt, slti, beq, bne criam todas as condições relativas: igual, diferente, menor que, maior que, menor ou igual e maior ou igual. Instruções decisões. para a tomada de Instruções Case/Switch O modo mais simples de implementá-las é através de uma cadeia de if-then-else. Outra forma de implementá-las é através de uma tabela de endereços de desvio. Para apoiar tais situações, o processador MIPS possui a instrução de desvio incondicional jr (jump register) que pula para o endereço armazenado pelo registrador. Sintaxe: jr $r1 Clareando as idéias Processador Memória de Dados 0 1 2 3 ... 9 00000000 00000100 00001000 00001100 ... 00100100 Memória de Instruções 0 1 2 3 ... 9 00000000 00000100 00001000 00001100 ... 00100100 Clareando as idéias Processador Memória de Dados 0 1 2 3 ... 9 00000000 00000100 00001000 00001100 ... 00100100 Memória de Instruções 0 1 2 3 ... 9 00000000 00000100 00001000 00001100 ... 00100100 Suporte a procedimentos hardware do computador Procedimento ou função é um recurso bastante empregado em linguagens de alto nível para modularizar o código. Podemos ver um procedimento como um espião, pois um espião: no Sai com um plano secreto, adquire recursos, realiza a tarefa, cobre seus rastros e depois retorna ao ponto de origem com o resultado desejado. De forma similar funcionam os procedimentos, vamos ver como isso funciona. Suporte a procedimentos hardware do computador no Na chamada de uma função, alguns registradores do MIPS são reservados para propósito especial, são eles: $a0 - $a3: quatro registradores de argumento, para passar parâmetros $v0-$v1: dois registradores de valor, para valores de retorno $ra: um registrador de endereço de retorno, para retornar ao ponto de origem. Suporte a procedimentos hardware do computador no Além de alocar esses registradores, o assembly do MIPS inclui uma instrução apenas para tratamento de funções/procedimentos. Esta instrução desvia para um endereço e simultaneamente salva o endereço da instrução seguinte no registrador $ra. Esta instrução se chama jal (jump-and-link). Sua sintaxe é a seguinte: jal EndereçoProcedimento Suporte a procedimentos hardware do computador no Existe também um outro registrador de próposito específico denominado PC (program counter). Este registrador armazena o endereço da instrução atual sendo executada. Então qual endereço salvo por jal no registrador $ra? Suporte a procedimentos hardware do computador no Existe também um outro registrador de próposito específico denominado PC (program counter). Este registrador armazena o endereço da instrução atual sendo executada. Então qual endereço salvo por jal no registrador $ra? $PC + 4; Suporte a procedimentos hardware do computador no Então, quando ocorre uma chamada de função: 1. O programa que chama (caller), coloca os valores de parâmetro em $a0 - $a3. 2. Em seguida, caller, utiliza jal X para desviar o procedimento para o procedimento X (o procedimento chamado é denominado callee). 3. O callee, então, realiza os cálculos, coloca os resultados em $v0-$v1. 4. Em seguida o callee retorna o controle para o caller usando jr $ra. Suporte a procedimentos hardware do computador no E se precisarmos de mais argumentos além dos 4 registradores para argumentos e os dois para valores de retorno? O que o callee deve fazer? Suporte a procedimentos hardware do computador no O callee executa um processo denominado spilling registers. A idéia básica é armazenar em memória valores que serão necessários posteriormente para a execução do programa. A estrutura de dados utilizada para fazer este armazenamento é uma pilha. Para controle desta pilha, o MIPS possui um registrador especial denominado stack pointer ($sp). O stack pointer sempre aponta para o último endreço alocado mais recentemente. Suporte a procedimentos hardware do computador Stack Pointer Stack 10000 no Suporte a procedimentos hardware do computador Stack 10000 9996 Valor do registrador $r1 crescimento Stack Pointer no Suporte a procedimentos hardware do computador Stack Pointer no Stack Valor do registrador $r1 9996 Valor do registrador $r2 9992 crescimento 10000 Suporte a procedimentos hardware do computador no Qual seria o código assembly do seguinte procedimento C? int exemplo_folha (int g, int h, int i, int j){ int f; f = (g + h) – (i + j); return f; } Suporte a procedimentos hardware do computador no Fazendo o mapeamento dos elementos da função para registradores, teremos: $a0, $a1, $a2, $a3 int exemplo_folha (int g, int h, int i, int j){ int f; $s0 f = (g + h) – (i + j); return f; } $t0 e $t1 $v0 Suporte a procedimentos hardware do computador no Percebam a existência de registradores conflitantes, ou seja, que já podem estar sendo utilizados ($s0, $t0 e $t1). int exemplo_folha (int g, int h, int i, int j){ int f; $s0 f = (g + h) – (i + j); return f; } $t0 e $t1 Suporte a procedimentos hardware do computador no Alguém chama jal exemplo_folha #Liberando registradores exemplo_folha: addi $sp, $sp, -12 # ajusta a pilha criando #espaço para três itens$sp $t1 $t0 $s0 sw $t1, 8($sp) #salva registradores $t1, $t0 e $s0 sw $t0, 4($sp) # para ser usado depois sw $s0, 0($sp) Suporte a procedimentos hardware do computador add $t0, $a0, $a1 add $t1, $a2, $a3 sub $s0, $t0, $t1 no # $t0 = g+h # $t1 = i + j # $s0 = (g+h) – (i + j), ou seja, f #Retornando o valor de f add $v0, $s0, $zero int exemplo_folha (int g, int h, int i, int j){ int f; f = (g + h) – (i + j); return f; } Suporte a procedimentos hardware do computador no #Restaurando os três valores antigos dos registradores lw $s0, 0($sp) lw $t0, 4($sp) lw $t1, 8($sp) addi $sp, $sp, 12 $t1 $t0 $sp $s0 $sp # E finalmente, o procedimento termina jr $ra # Desvia de volta à rotina que chamou Suporte a procedimentos hardware do computador no No exemplo anterior, salvamos os valores dos registradores $t0 e $t1. Porém existem casos em que o valor de um registrador pode ser descartado sem problemas. Por causa disso, o mips separa 18 dos registradores em dois grupos: $t0-$t9: 10 registradores temporários que não são preservados pelo procedimento chamado (callee) em uma chamada de procedimento. s $s0-$s7: 8 registradores alvos que precisam ser preservados em uma chamada de procedimento (se forem usados, o procedimento chamado os salva e restaura). Suporte a procedimentos hardware do computador no O código mostrado anteriormente é perfeitamente aceitável para funções que não fazem chamadas a outras funções. Funções que não fazem chamadas a outras funções são denominadas folhas. As que chamam outras são denominadas aninhadas. E para funções aninhadas, como seria? int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } Suporte a procedimentos hardware do computador no #Salvando registradores fact: addi $sp, $sp, -8 #ajusta pilha para 2 itens sw $ra, 4($sp) # salva o endereço de retorno sw $a0, 0($sp) # salva o argumento n int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } Suporte a procedimentos hardware do computador #Condição slti $t0, $a0, 1 Beq $t0, $zero, L1 # teste para n < 1 # se n>=1 vai para L1 int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } no Suporte a procedimentos hardware do computador no #Senão for maior que 1, devolve o valor 1. addi $v0, $zero, 1 # retorna 1 addi $sp, $sp, 8 # retira 2 itens da pilha jr $ra #retorna para depois de jal Pessoal, não entendi. Alguém me responda. Porque não restauramos os valores da pilha nesse caso? int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } Suporte a procedimentos hardware do computador no addi $v0, $zero, 1 # retorna 1 addi $sp, $sp, 8 # retira 2 itens da pilha jr $ra #retorna para depois de jal Observem que no caso base, o valor do registrador $ra e $a0 não é alterado, logo não é necessário recuperar os valores dele da memória int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } Suporte a procedimentos hardware do computador #Se for maior que 1 L1: addi $a0, $a0 -1 #arg1 = n – 1; jal fact #chama fact(n-1); int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } no Suporte a procedimentos hardware do computador no #Restaurando registradores. #A próxima instrução é onde fact retorna. lw $a0, 0($sp) #retorna de jal: restaura n lw $ra, 4($sp) #restaura endereço de retorno addi $sp, $sp, 8 #ajusta stack pointer int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } Suporte a procedimentos hardware do computador no #Devolvendo o novo $v0 mul $v0, $a0, $v0 # retorna n * fact( n - 1) jr $ra # retorna para o procedimento que o chamou int fact (int n){ if (n < 1) return (1); else return (n * fact(n – 1)); } Suporte a procedimentos hardware do computador Para resumo de história, quem salva o que na pilha? Caller no Salva os registradores de argumentos. Salva os registradores temporários ($t0-$t9) que serão necessários após a chamada da função. Callee Salva os registradores salvos ($s0-$s8) Salva o registrador que armazena o endereço de retorno ($ra) Suporte a procedimentos hardware do computador Além dos elementos já citados, a pilha também pode ser utilizada para armazenar outros elementos. no Alguns exemplos são variáveis locais (tais como arrays ou estruturas) que não cabem em registradores. Este segmento da pilha relativo a um procedimento é conhecido como frame ou registro de ativação. Em geral os processadores possuem um processador específico para apontar para o início do frame. Este registrador é conhecido como frame pointer ($fp). Suporte a procedimentos hardware do computador no Suporte a procedimentos hardware do computador no O frame pointer, assim como o stack pointer, é utilizado nas instruções que alocam e recuperam dados do frame. Mas se ambos registradores possuem a mesma funcionalidade, então para que termos o frame pointer? Suporte a procedimentos hardware do computador no O frame pointer, assim como o stack pointer, é podem ser utilizados nas instruções que alocam e recuperam dados do frame. Mas se ambos registradores possuem a mesma funcionalidade, então para que termos o frame pointer? O frame pointer, ao contrário do stack pointer, não tem seu valor alterado durante o procedimento. Logo se torna mais fácil recuperar dados utilizando o valor do stack pointer. Suporte a procedimentos hardware do computador O assunto que vimos até o momento, é aplicado apenas para variáveis automáticas (ou seja) locais aos procedimentos. No entanto ainda faltam tratar dois casos: no Variáveis globais (ou estáticas). Estruturas de dados dinâmicas. Estes elementos, quando em memória, não são alocados no stack. Os dados estáticos podem ser acessados através do registrador $gp. Os dados dinâmicos é armazenado em uma região de memória denominada heap. Suporte a procedimentos hardware do computador no Endereçamento no MIPS para operandos e endereços de 32 bits Até o momento, consideramos que endereços de memória já estariam em registradores. Lembrem-se que cada endereço de memória é representado por 32 bits. Lembrando que as instruções com constantes (imediatas) do mips só trabalham com 16 bits. Como fazemos para armazenar endereços de memória em registradores? Endereçamento no MIPS para operandos e endereços de 32 bits Precisamos usar duas instruções; nova instrução “load upper immediate”: Endereçamento no MIPS para operandos e endereços de 32 bits Depois, precisamos acertar os bits de ordem inferior, por exemplo: Endereçamento em condicionais e jumps desvios Já a instrução de desvio condicional jump utiliza um terceiro formato de instrução (tipo j). Nessas instruções temos 26 bits para especificar o local para onde desejamos desviar. Endereçamento em desvios condicionais e jumps No entanto, para desvios condicionais temos um problema: Nosso campo de desvio só possui 16 bits. Fazendo os cálculos, 216 = 65536; E agora pessoal, será que no MIPS só podemos fazer programas de no máximo 64 kbytes? Endereçamento em desvios condicionais e jumps Para evitar este problema, os processadores trabalham com uma técnica denominada de endereçamento relativo. Basicamente a idéia é associar o endereço expresso em uma instrução condicional a um outro elemento (registrador). Qual seria este registrador? Endereçamento em desvios condicionais e jumps Para evitar este problema, os processadores trabalham com uma técnica denominada de endereçamento relativo. Basicamente a idéia é associar o endereço expresso em uma instrução condicional a um outro elemento (registrador). Qual seria este registrador? O registrador utilizado é o PC. Lembrem-se o PC a todo momento se atualiza, armazenando o endereço da instrução atual, sendo executada. Endereçamento em desvios condicionais e jumps Porque isso resolve? Endereçamento em desvios condicionais e jumps Porque isso resolve? Isso resolve porque, em geral, os endereços de desvio não ficam muito longe dos locais onde é solicitado o desvio. Mas e para chamada de funções? Tem algum sentido ela ficar próxima do local do desvio? Como isso é resolvido? Endereçamento em desvios condicionais e jumps Porque isso resolve? Isso resolve porque, em geral, os endereços de desvio não ficam muito longe dos locais onde é solicitado o desvio. Mas e para chamada de funções? Tem algum sentido ela ficar próxima do local do desvio? Como isso é resolvido? Lembrem-se que para funções utilizamos j ou jr que possuem um espaçamento de bits ainda maior (26 bits). Adicionalmente, o MIPS otimiza isso tudo. Seu endereçamento é feito por palavras (endereçamento alinhado), logo o espaço de endereçamento aumenta em mais 4 vezes. Formas de endereçamento. Endereçamento em Registrador Endereçamento imediato Onde o operando está no local de memória cujo endereço é a soma de um registrador e uma constante. lw, sw Endereçamento relativo ao PC Onde o operando é uma constante dentro da própria instrução addi e subi, por exemplo. Endereçamento de base Onde o operando é um registrador add e sub, por exemplo. Onde o endereçamento é a soma do PC e uma constante na instrução bne, beq, por exemplo. Endereçamento pseudodireto. Onde o endereço de jum são os 26 bits da instrução concatenados com os bits mais altos do PC. j e jr. Bibliografia Patterson & Hennessy. Organização e Projeto de Computadores. Seção 2.6 a 2.7 e 2.9