Getting an label address to a register on ARM?

There are four ways, three are documented at Sourceware’s Gnu Assembler manual. I guess the label is something like,

 target:
     .long 0xfeadbeef
  1. adr r0,target – pc-relative
  2. adrl r0,target – pc-relative
  3. ldr r0,=target – absolute
  4. sub r0,pc,#(.+8-target) – learning only…

The first two are very similar and generate sub r0,pc,#offset. The 3rd puts a long in a literal pool and loads this via ldr r0,[pc,#offset2] or it may use a mov if the assembler finds it can (usually an aligned label, like at 0x8000).

The last version (4) is to manually calculated it. This is what the adr and adrl versions will windup translating to. It is not recommended, but is just to show how the . operator (current address), the pc and the label along with the convention that the ARM PC is eight ahead of the current executing instruction combine. If it looks confusing, this is why we have adr.

The difference between adr and adrl comes from immediate operands. They are 8bits rotated by a multiple of two. So if the address is far, you may need to perform two instructions, which will usually be faster than the 3rd ldr variant which get a full 32-bits via the data cache or memory.

See also: Relocation in assembler


Thumb2 adds the combination movw and movt, which is absolute addressing. For example,

label:
 ; data
...
movw    r0, :lower16:label - .
movt    r0, :upper16:label - . 

This will put the offset in r0. It is not as useful for PC relative but useful for absolutes or direct loads of constants.


Ironically, the ldr r0,=target is actually using the adr variation to do a PC-relative load. Here is the pseudo-code for the variant.

ldr r0, [pc, target_target]  ; get literal pool value.
 ...
target_target:               ; the literal pool
.long target                 ; an inserted absolute address
target:
.long 0xfeadbeef

The ‘target_target’ is handled automatically by the assembler and is referred to as the literal pool and can be controlled with the .ltorg statement. Many projects will use the ‘ldr rx,=xxxx’ form. This is possibly the best way to get a constant to a register and also works for a label. Hopefully it is clear why adr is better for a label.

‘pc-relative’ is often useful for shared libraries and boot code where the executing address may not be constant. The pc-relative versions will also execute a little faster and the ‘label’ can be a jump table for faster dispatch where you want to do one of several things based on a value.


See: ARM blog on constants

Leave a Comment