Nesta parte vamos ver as instruções de acesso à memória. Como vimos na
parte anterior, as instruções lógicas e aritméticas do ARM trabalham com operandos nos registradores. As instruções de acesso à memória no ARM são primordialmente a carga de um registrador em uma posição de memória e o armazenamento do conteúdo do registrador em uma posição da memória, conforme dita a filosofia RISC.
Estas instruções, denominadas de
Single Data Transfer, são codificadas conforme a figura abaixo:
Note que embora sejam apenas duas instruções assembly,
LDR e
STR, existem uma grande quantidade de opções. Como sempre, o opcode contém uma condição. O bit
L diferencia a instrução de
Load (carga do registrador a partir da memória) da instrução de
Store (armazenamento na memória do conteudo do registrador).
Rd indica o registrador que receberá o valor lido (LDR) ou que contém o valor a ser armazenado (STR). O bit
B indica se estamos manipulando um word (32 bits) ou byte (8 bits); existem instruções separadas para manipular valores de 16 bits (half-words). O resto dos campos do opcode determinam o endereço da posição de memória a ser manipulada.
Este endereço é determinado por duas partes: a
base (que fica no registrador
Rn) e o
offset (que, conforme o bit
I, pode ser um valor imediato de 12 bits ou o conteúdo de um registrador deslocado). O deslocamento do registrador usado para o offset é determinado de forma semelhante ao usado para o valor imediato nas instruções lógicas e aritméticas. O bit
U indica se o offset deve ser somado ou subtraído da base.
O bit
P determina se a combinação da base com o offset deve ser feita antes (
pré-indexação) ou depois (
pós-indexação) do acesso à memória. O bit
W permite atualizar o registrador base com o valor final do endereço. Quando se especifica a pós-indexação, o ARM atualiza sempre o registrador base, para não alterá-lo deve-se colocar um offset de zero. Na prática, isto significa:
- P = 0, W = 0: normalmente tratado pelo ARM como P = 0, W = 1. (para ser preciso, no modo privelegiado em um processador com hardware de gerenciamento de memória esta combinação faz com que o endereço seja tratado como um endereço de usuário ao invés de endereço de sistema).
- P = 1, W = 0: combina base e offset para determinar o endereço, o registrador base fica inalterado. Útil, por exemplo, para acessar um item de um vetor (base é o endereço inicial e o offset é o deslocamento em relação ao início).
- P = 0, W = 1: usa somente a base para determinar o endereço, após o acesso à memória a base é atualizada com a combinação da base com o offset. Permite implementar em uma única instrução a construção *p++ do C (acessa a posição apontada pelo registrador base e o avança para o próximo item).
- P = 1, W = 1: combina base e offset para determinar o endereço, o registrador base é atualizado para o endereço usado. Permite implementar em uma única instrução a construção *++p do C (avança o registrador base para o próximo item e o acessa).
A codificação da instrução em Assembly é a seguinte:
LDR{cond}{B} Rd,<endereço>
STR{cond}{B} Rd,<endereço>
Onde B indica que é um acesso a byte.
<endereço> pode ser
- uma expressão, que resulta em um endereço. O Assembler tenta montar uma instrução que combine um offset ao PC para obter este endereço, se não conseguir gerará um erro. Esta forma é útil para carregar em registradores constantes que estão armazenadas junto ao código.
- uma especificação de endereçamento pré-indexado (combina base e offset antes de acessar a memória):
- [Rn]
- [Rn,#expressão] {!}
- [Rn,{+/-}Rm{,<shift>}] {!}
A presença de ! indica que o registrador base deve ser atualizado (W = 1).
- uma especificação de endereçamento pós-indexado (combina base e offset depois de acessar a memória):
- [Rn],#expressão
- [Rn],{+/-}Rm{,}
nestes casos o registrador base é sempre atualizado.
Alguns exemplos
; coloca valores conhecidos em R1 e R2
MOV R1,#0
MOV R2,#4
; coloca em R0 o conteúdo da posição 0 da memória
LDR R0,[R1]
; coloca em R0 o conteúdo da posição 4 da memória
LDR R0,[R1,R2]
; coloca em R0 o conteúdo da posição 32 da memória
LDR R0,[R1,R2,LSL #3]
; idem
LDR R0,[R1+#32]
; coloca em R0 o conteúdo da posição 4 da memória
; R1 passa a ser 4
LDR R0,[R1,R2]!
; coloca em R0 o conteúdo da posição 4 da memória
; R1 passa a ser 8
LDR R0,[R1],R2
Reparar que não é possível especificar diretamente um endereço qualquer da memória, pois o offset imediato tem 12 bits e um endereço tem 32 bits. Além disso, já vimos que as instruções lógicas e aritméticas são limitadas quanto aos valores imediatos que podem ser usados. Para carregar em um registrador um endereço ou constante qualquer, é preciso armazená-lo em memória em uma posição que possa ser acessada por um LDR. Isto torna frequente a presença de constantes no meio do código:
...
LDR Rd,X ; usa endereçamento relativo ao PC
...
X .long valor ; em algum lugar próximo
Para facilitar isto, o Assembler permite a seguinte construção:
LDR Rd,=valor
Se possível, o assembler gera uma instrução MOV ou MVN para carregar o valor no registrador. Se isto não for possível, o assembler coloca a constante em um trecho próximo e gera uma instrução LDR usando endereçamento relativo ao PC.
Outras Instruções de Acesso à MemóriaAs instruções
LDRH,
LDRSH e
STRH permitem carregar e armazenar valores de 16 bits (
half word). A instrução LDRSH faz a "extensão do sinal", isto é, preenche os 16 bits mais significativos do registrador com o bit mais significativo do valor de 16 bits, de forma a manter o sinal do valor. O mesmo opcode permite também carregar em um registrador um valor de 8 bits com extensão de sinal (
LDRSB). Estas instruções oferecem os mesmos recursos de endereçamento que LDR e STR.
As instruções
LDM e
STM (
Block Data Transfer) permitem carregar ou armazenar um conjunto de registradores em posições contiguas de memória. A codificação destas instruções é apresentada abaixo:
Register list possui 16 bits, um para cada registrador. Os bits com 1 indicam os registradores a serem transferidos. O endereço inicial é obtido a partir de um registrador, que será automaticamente incrementado ou decrementado, antes ou depois de transferir cada registrador. O registrador utilizado pode ou não ser atualizado ao final da transferência.
A codificação em assembly destas instruções é:
LDM{cond}<fd|ed|fa|ea|ia|ib|da|db> Rn{!},<rlist>
STM{cond}<fd|ed|fa|ea|ia|ib|da|db> Rn{!},<rlist>
Onde
- {cond} é a condição em que a instrução será executada.
- FD, ED,..DB determinam os bits P e U. FD, ED, FA e EA são (respectivamente) idênticos a IA, IB, DA e DB; os primeiros são utilizados para documentar quando os registradores estão sendo transferidos para uma pilha.
- ! indica que o registrador Rn deve ser atualizado ao final
Alguns exemplos:
; salva todos os registradores em posições
; consecutivas a partir da apontada por R0
STMIA R0,{R0-R15}
; empilha os registradores (exceto R15/PC)
STMFD SP!,{R0-R15}
; Salva na pilha os registradores R1, R3 e R5
STMFD SP!,{R1, R3, R5}
; desempilha os registradores
LDMED SP!,{R0-R14}