← Voltar aos artigos

Anatomia de uma instrução ARM

O esqueleto de 32 bits

Uma instrução de processamento de dados (o ADD, SUB, MOV, CMP, AND... da família) tem este layout:

Instrução de processamento de dados

Decodificando à mão

Compile estas dez instruções e olhe o objdump — anat.s (.elf):

    add   r0, r1, r2            @ registrador
    add   r0, r1, #255          @ imediato pequeno
    add   r0, r1, #0x3F00       @ imediato com rotate
    add   r0, r1, r2, lsl #3    @ registrador deslocado (barrel shifter)
    addeq r0, r1, r2            @ condicional (cond = EQ)
    adds  r0, r1, r2            @ atualiza flags (S = 1)
    ldr   r0, [r1, #4]          @ offset, base inalterada
    ldr   r0, [r1, #4]!         @ pre-index com writeback
    ldr   r0, [r1], #4          @ post-index
    ldrb  r0, [r1]              @ acesso de 1 byte
00010000<_start>:
10000:e0810002addr0, r1, r2
10004:e28100ffaddr0, r1, #255
10008:e2810c3faddr0, r1, #16128
1000c:e0810182addr0, r1, r2, lsl #3
10010:00810002addeqr0, r1, r2
10014:e0910002addsr0, r1, r2
10018:e5910004ldrr0, [r1, #4]
1001c:e5b10004ldrr0, [r1, #4]!
10020:e4910004ldrr0, [r1], #4
10024:e5d10000ldrbr0, [r1]

e0810002 = add r0, r1, r2

CampoBitsValorLeitura
cond1110ALincondicional
classe00—data processing
I0—Operand2 é registrador
opcode0100ADD
S0—não mexe nas flags
Rn0001r1
Rd0000r0
Operand20000 0000 0010r2Rm = r2, sem shift

Junte os nibbles: 1110 0000 1000 0001 0000 0000 0000 0010 = 0xE0810002. Confere.

O que muda em cada variante

EncodingInstruçãoBit(s) que mudou
e28100ffadd r0, r1, #255I = 1; Operand2 = 0x0FF (rotate 0, imm8 0xFF)
e2810c3fadd r0, r1, #0x3F00I = 1; Operand2 = 0xC3F → imm8 0x3Frotacionado à direita por 2×12 = 24 bits
e0810182add r0, r1, r2, lsl #3Operand2 = 0x182 → shift amount 00011 (3), tipo 00 (LSL), Rm 0010 (r2)
00810002addeq r0, r1, r2cond = 0000 (EQ) — só o primeiro nibble
e0910002adds r0, r1, r2S = 1 → bit 20 liga, o nibble vira 9

O Operand2 e o barrel shifter

Doze bits, dois formatos:

Imediato (I = 1):rotate[11:8] + imm8[7:0]. O valor final é imm8 rotacionado à direita por 2 × rotate. É por isso que #0x3F00 cabe (0x3F ror 24) mas #0x3F1 não — não existe rotação par de um byte que produza esse padrão. O montador resolve isso por você ou reclama.

Registrador deslocado (I = 0):shift[11:4] + Rm[3:0]. O shift pode ser por imediato (Rm, LSL #3) ou por outro registrador (Rm, LSL r4). Os tipos:

TipoBits [6:5]EfeitoAfeta C (com S)?
LSL00desloca à esquerda, entra 0sim (último bit que sai)
LSR01desloca à direita lógico, entra 0sim
ASR10desloca à direita aritmético, replica o sinalsim
ROR11rotaciona à direitasim
RRX11 + amount 0rotaciona 1 bit à direita através do Csim

O barrel shifter é um circuito dedicado no caminho de dados: ele aplica esse shift ao operando de graça, no mesmo ciclo da operação. add r0, r1, r2, lsl #3 é uma única instrução — um multiplicar-e-somar embutido.

Load/store: os bits de endereçamento

LDR/STR trocam o Operand2 por um bloco de bits de modo de endereçamento:

LDR/STR
BitNome1 significa
Ppre/postoffset aplicado antes do acesso (pre/offset)
Uup/downoffset somado ao base (senão subtraído)
Bbyteacesso de 1 byte (senão palavra)
Wwritebackgrava o endereço calculado de volta em Rn
Lload/storecarrega (senão armazena)

As três formas do assembly:

AssemblyPWEfeito
ldr r0, [r1, #4] (e5910004)10lê de r1+4; r1 inalterado
ldr r0, [r1, #4]! (e5b10004)11lê de r1+4; r1 ← r1+4
ldr r0, [r1], #4 (e4910004)0—lê de r1; r1 ← r1+4 depois

E ldrb r0, [r1] (e5d10000) é só o bit B ligado — o nibble 9 vira D. Esse post-index ([r1], #4) é o que faz um laço de varredura de vetor andar de 4 em 4 sem uma instrução de incremento separada — exatamente o padrão do artigo de carga e armazenamento.

Espelho no código: ArmDecoder

O decodificador do arm-jitter faz, em Java, o que você acabou de fazer no papel. ArmDecoder.decode(memory, address):

public DecodedInstruction decode(AddressSpace memory, int address) {
    int raw = memory.fetch32(address & ~3);
    Condition condition = decodeCondition(raw >>> 28);   // os 4 bits de cond

    // cond == 0b1111 não é "condição": é um espaço de encoding à parte
    // (NEON, PLD, BLX imediato, CPS, barreiras) desde o ARMv5.
    if ((raw >>> 28) == 0xF) {
        return decodeUnconditional(address, raw, condition);
    }
    // ...
}

O resultado é um record neutro, com os campos que você separou virando propriedades:

public record DecodedInstruction(
        int address,
        int raw,                       // a palavra de 32 bits, crua
        InstructionSet instructionSet,
        Condition condition,           // <- raw >>> 28
        InstructionKind kind,          // <- opcode + subdispatch
        int destinationRegister,       // <- Rd, ou -1
        int sourceRegister,            // <- Rn, ou -1
        int secondSourceRegister,      // <- Rm, ou -1
        int immediate,                 // <- imm8 JÁ rotacionado / offset / nº de SWI
        boolean immediateOperand,      // <- o bit I
        boolean setFlags,              // <- o bit S
        boolean link,                  // <- BL vs B
        int accessSizeBytes,           // <- 1 (bit B) ou 4
        boolean writeback,             // <- o bit W
        boolean postIndexed,           // <- o bit P invertido
        /* ... */) { }

Repare que immediate já vem expandido: o decoder aplica a rotação do imediato de 12 bits ali, para o resto do pipeline não precisar saber do formato rotate|imm8. É a mesma decisão que o montador toma na direção oposta.

Glossário

TermoO que é
Operand2o campo de 12 bits do segundo operando em data processing
Barrel shiftercircuito que aplica LSL/LSR/ASR/ROR ao operando no mesmo ciclo
Imediato rotacionadoimm8 ror 2×rotate — como o ARM cabe constantes de 32 bits em 12 bits
Pre-index / post-indexoffset aplicado antes / depois do acesso de memória
Writebackgravar o endereço calculado de volta no registrador base
Espaço incondicionalcond == 1111: encodings próprios, não uma condição

Comandos

ComandoO que faz
arm-none-eabi-objdump -d x.elfdesmonta (mnemônico + bytes)
arm-none-eabi-objdump --disassemble --show-raw-insnforça a coluna de bytes
printf '%08x\n' $((0xE0810002 & 0x0FF00000))isola campos de um encoding na mão

Próximo passo

Você já sabe ler uma instrução parada. O próximo artigo coloca ela em movimento: o ciclo fetch–decode–execute e como ele vira o laço ArmCore.step() do emulador.

Trilha: Curso de Arquitetura ARM · Módulo 1, lição 3. Ver também: Decodificando instruções ARM à mão.