2.4- A visão do software transferencia de dados
Já temos o conhecimento necessário para realizar as operações
aritméticas no MIPS. Entretanto, duas questões ficaram no ar na seção
anterior. Trata-se de como movimentar os dados do par de registradores HI,
LO para o banco de registradores ou como movimentar dados do banco
de/para memória. A primeira questão é bem simples de responder, então
vamos começar por ela
Nós vamos estudar os detalhes da implementação da memória no
Capítulo 6, mas precisamos de um modelo que será utilizado pelo software
como sendo este repositório de dados e programas. Então, o software
enxerga a memória como um grande vetor onde cada posição é indicada por
um endereço. Os endereços são seqüenciais e numerados de 0 a
4.294.967.295. Cada byte ocupa uma destas posições. A Figura 2.1 mostra
como são os endereços e dados em uma memória. Os dados estão
representados em binário e os endereços em hexadecimal.
Um ponto crítico que merece nossa atenção é a alocação de memória
para vetores e matrizes. Um vetor de números inteiros ocupa tantas posições
na memória, quantos forem os seus dados. Por exemplo, vamos ver como a
memória é alocada para as seguintes declarações de dados: um inteiro não
sinalizado, x, inicializado com valor 3, um inteiro y, inicializado com -1 e um
vetor de inteiros n com 10 posições. A Figura 2.6 mostra a alocação
convencional de dados para este exemplo. Veja que os dados foram alocados
em uma região de memória iniciando no endereço 0. Esta região é indiferente
para o programador, já que ele acessa a variável pelo nome, mas, de fato, ela
é controlada pelo módulo carregador do Sistema Operacional.
Ora, a codificação em assembly para esta organização de memória
seria:
lw $8, 4($0) # carrega o valor de y em $8
addi $9, $0, 8 # carrega o endereço base de n em $9
lw $10, 20($9) # carrega o valor de n[5] em $10
mul $8, $8, $10 # multiplica y por n[5]. Resultado em $8
sw $8, 12($9) # guarda valor da multiplicação em n[3]
Neste exemplo utilizamos um registrador que ainda não havíamos
trabalhado: o $0. Este registrador tem uma característica especial, o valor
dele é sempre zero, independente das instruções que operam com ele. Este
registrador é muito importante para calcularmos valores iniciais de
endereços e/ou dados. Este código precisaria ser alterado se a memória fosse
re-endereçada (relocada), ou seja, se o início dos dados não fosse no
endereço 0.
No caso de armazenamento de matrizes, o compilador enxerga uma
matriz como um vetor de vetores, portanto, as linhas são postas
seqüencialmente na memória. Fica como exercício descobrir como endereçar
um elemento n[m,k] de uma matriz de números inteiros.
Até o presente mostramos como transportar dados de/ para memória.
Existe uma outra instrução, que pode ser interpretada também como uma
instrução lógica, que auxilia no cálculo de endereços efetivos. Trata-se da
instrução lui (Load Upper Immediate). Esta instrução carrega na parte
mais significativa (16 bits mais à esquerda) um valor imediato especificado
na própria instrução e zera a parte baixa do registrador de destino. Por
exemplo, desejamos armazenar o endereço base de um vetor que começa em
0f3c0004h. Ora, não existe uma forma de armazenar imediatamente os 32 bits
que formam este endereço, pois todas as instruções que operam com
imediatos só admitem valores de 16 bits.