Thumb e Thumb-2: instruções de 16/32 bits
Até agora todas as instruções que vimos tinham 32 bits fixos. O ARM quebrou essa regra em 1994 com o Thumb: instruções de 16 bits que cabem em metade do espaço. Depois, o Thumb-2 misturou 16 e 32 bits no mesmo programa. O resultado: binários menores, melhor uso de cache e, no caso do arm-box, uma nova arquitetura (--arch=thumb2) que você pode exercitar hoje. Vamos ver os bytes reais.
Por que Thumb existe
Instruções de 32 bits ocupam 4 bytes cada. Num microcontrolador com pouca RAM ou num pipeline estreito, isso é caro. O Thumb comprime as instruções mais comuns para 16 bits (2 bytes), perdendo funcionalidade (menos registradores acessíveis, limites de imediato) mas ganhando densidade de código — tipicamente 65% do tamanho do ARM puro para o mesmo programa. Em cache, menos bytes = menos misses.
Thumb-1 vs Thumb-2
- Thumb-1 (ARMv4T, ARMv5TE): só 16 bits. Subconjunto reduzido do ARM — não tem todos os registradores, nem todos os modos de endereçamento.
- Thumb-2 (ARMv6T2, ARMv7): mistura 16 e 32 bits. Quase toda funcionalidade do ARM puro, com densidade do Thumb. É o que o
arm-boxexecuta com--arch=thumb2.
Como o processador sabe qual modo
O bit T (bit 5) do CPSR indica o modo:
CPSR bit 5 (T):
0 = modo ARM (instruções de 32 bits)
1 = modo Thumb (instruções de 16/32 bits) Quando o processador inicia, o T é definido pelo endereço de reset (bit 0 do vetor). A partir daí, o BX (branch exchange) é quem troca: se o bit 0 do endereço-alvo é 1, entra em Thumb (T=1); se é 0, entra em ARM (T=0). É o interworking — misturar ARM e Thumb no mesmo binário.
O exemplo: a mesma função em ARM e Thumb
Montamos a mesma função simples (soma dois números) nos dois modos. Primeiro o ARM (já conhecido):
Agora o Thumb — bytes reais do devkitARM:
Repare: as instruções do _start em Thumb ocupam 2 bytes cada (0x2003, 0x2104, 0x2701, 0xdf00), enquanto as do ARM ocupam 4 bytes. A exceção é o BL — que em Thumb-2 é uma instrução de 32 bits (dois halfwords: 0xf000 + 0xf802).
Decodificando instruções Thumb (16 bits)
Instruções Thumb de 16 bits têm a estrutura diferente do ARM. O código da operação vive nos bits mais altos (15..6 ou 15..9), e os registradores nos bits mais baixos. Exemplos reais:
movs r0, #3 = 0x2003
- O opcode ocupa 5 bits (15..11) — muito menor que os 8 bits do ARM.
- O registrador destino é 3 bits (r0–r7) — só acessa metade dos registradores (diferente do ARM que tem r0–r15).
- O imediato é 8 bits (0–255) — não tem rotação como o ARM.
adds r0, r0, r1 = 0x1840
- O
adds(com S implícito em Thumb) atualiza as flags N/Z/C/V — por isso o processador sabe se o resultado é zero, negativo, etc. - Os três registradores cabem em 3 bits cada — por isso Thumb-1 só acessa r0–r7 nestas formas.
bx lr = 0x4770
- O
Rmestá nos bits 6..3 (não nos bits 2..0 como no ARM). r14 =lr=1110→ bate. - O bit 7 distingue
BX(0) deBLX(1) — oBLXtroca para ARM e salva retorno em LR.
A instrução de 32 bits: BL em Thumb-2
O BL (branch with link) não cabe em 16 bits porque precisa de um offset grande + o bit de link. No Thumb-2, ele ocupa dois halfwords (4 bytes):
- O offset é codificado nos dois halfwords e, como no ARM, o alvo é
(PC + 4) + (offset << 1). No caso:0x8008 + 4 + (offset << 1) = 0x800c. - É por isso que o
BLem Thumb-2 tem o mesmo alcance que o BL em ARM (±16 MB) — os 32 bits dão espaço suficiente.
Tamanho: ARM vs Thumb
A mesma função (add_arm vs add_thumb):
| Modo | Instruções | Tamanho |
|---|---|---|
| ARM | add r0, r0, r1 + bx lr | 2 × 4 = 8 bytes |
| Thumb | adds r0, r0, r1 + bx lr | 2 × 2 = 4 bytes |
Metade do tamanho. Num programa real com centenas de funções, a economia é significativa — e menos código = menos fetch = menos cache misses.
Glossário: instruções e comandos
| Comando / Instrução | O que faz |
|---|---|
.thumb | Diretiva: entra no modo Thumb no assembly. |
.thumb_func | Marca o rótulo como função Thumb (bit 0 = 1 no ELF). |
bl alvo | Chama função (Thumb-2: 32 bits; Thumb-1: via trampoléim). |
bx reg | Troca de modo: bit 0 de reg define ARM/Thumb. |
-march=armv6k -mthumb | Monta em modo Thumb com a toolchain. |
arm-none-eabi-objdump -d -j .text | Mostra os bytes (16/32 bits) para decodificar. |
O que o arm-jitter faz com isso
Quando você passa --arch=thumb2 para o arm-box, o arm-jitter ativa o pipeline Thumb-2: ele lê 2 bytes, verifica se é uma instrução de 16 bits ou se precisa de mais 2 bytes (32 bits), e decoda de acordo. O bit T do CPSR é mantido pelo core emulado — e o BX troca o modo exatamente como no hardware. É por isso que o arm-box consegue rodar binários Thumb-2 reais: o decodificador faz a mesma distinção que o processador faria.
Próximo passo
Com Thumb e Thumb-2 no arsenal, o próximo texto pode explorar instruções condicionais em Thumb (onde quase tudo é condicional sem precisar de branch), ou Thumb-2 completo no arm-box (exercitando --arch=thumb2 com binários reais de GCC). Até lá, tente montar uma função sua em Thumb e compare o tamanho com a versão ARM — a diferença vai falar por si.