How to add numeric .equ constants in ARM assembly?

2026-08-28

Stack Overflow: View Question

Tags: assembly, arm, literals, gnu-assembler

Score: 3 | Views: 101

The asker is targeting the Raspberry Pi Pico with arm-none-eabi-gcc and wants to load the address of an atomic-set alias register into r0. The RP2040 exposes atomic set/clear/xor aliases at fixed offsets from each peripheral base, so composing RESET_BASE + ATOMIC_SET at assembly time is idiomatic. This works:

ldr r0, =RESET_BASE + 0x2000
.equ RESET_BASE, 0x4000c000

But swapping the literal 0x2000 for a second .equ throws a syntax error, even with parentheses. Both symbols are absolute constants — why does one form work and the other not?

Why it's interesting: This looks like a GNU as quirk but actually reveals how the assembler classifies expressions. The ldr r0, =expr pseudo-instruction asks the assembler to (a) evaluate expr, (b) stuff the result into a literal pool, and (c) rewrite the instruction as a PC-relative load. Step (a) has to happen early enough that step (b) can size the pool. When both operands are forward-referenced .equ symbols, older/some builds of GAS refuse to fold them into a single absolute constant — the parser sees "undefined + undefined" and bails before the pass that resolves them. A literal on the right side sidesteps that because at least one operand is immediately concrete.

The direction I'd try first:

Gotchas: The Cortex-M0+ on the Pico has no movw/movt, so on that core the literal-pool route is mandatory — reorder the .equ instead. Also, GAS's error message here ("syntax error") is famously unhelpful; the real complaint is expression classification, not tokenization. Finally, if the file gets preprocessed by cpp (.S extension), #define RESET_BASE 0x4000c000 avoids the whole mess.

The challenge: ldr =expr looks like a simple immediate load, but it hides a literal-pool-sizing pass that trips over compound expressions of forward-referenced absolute symbols.

All newsletters