2026-08-28
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:
.equ lines above the ldr. Symbol resolution in GAS is nominally two-pass, but =expr literal-pool sizing behaves as if single-pass for compound expressions of absolutes. Defining both symbols first almost always fixes it..data. .equ emits no bytes — it just binds a symbol — so putting it under .data is misleading and can confuse the assembler about the symbol's section. Put constants at file scope or in a dedicated .section block before .text..set instead of .equ. They're near-synonyms, but .set allows redefinition and is sometimes handled differently by the expression evaluator.movw r0, #:lower16:(RESET_BASE + ATOMIC_SET) / movt r0, #:upper16:(...). This avoids the literal pool entirely and uses relocations the linker resolves cleanly.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.
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.
