ARM from ADR to LDR
The pseudo addressing mode instruction LDR
allows an immediate data value or the address of a label to be loaded into a register. The ADR
instruction forms address of a label at a PC-relative offset.
ARM Assembly Language: Fundamentals and Techniques, 2nd Edition | Chapter 6: Constants and Literal Pools - 6.5 LOADING ADDRESSES INTO REGISTERS
At some point, you will need to load the address of a label or symbol into a register. Usually you do this to give yourself a starting point of a table, a list, or maybe a set of coefficients that are needed in a digital filter. For example, consider the ARM7TDMI code fragment below.
ADR#
The first two instructions load a known address (called an absolute address, since it doesn't move if you relocate your code in memory) into registers r0
and r3
. The third and fourth instructions are the pseudo-instruction ADR
, which is particularly useful at loading addresses into a register. Why do it this way? Suppose that this section of code was to be used along with other blocks. You wouldn't necessarily know exactly where your data starts once the two sections are assembled, so it's easier to let the assembler calculate the addresses for you. As an example, if image_data
actually started at address 0x8000 in memory, then this address gets moved into register r1
, which we’ve renamed. However, if we change the code, move the image data, or add another block of code that we write later, then this address will change. By using ADR
, we don't have to worry about the address.
Let's examine another example, this time to see how the ADR
pseudo-instruction actually gets converted into real ARM instructions. Again, the code in this example doesn't actually do anything except set up pointers, but it will serve to illustrate how ADR
behaves.
You will note that the program calls a subroutine called func
, using a branch and link operation (BL
). The next instruction is for ending the program, so we really only need to examine what happens after the LTORG
directive. The subroutine begins with a label, func
, and an ADR
pseudo-instruction to load the starting address of our main program into register r0
. The assembler actually creates either an ADD
or SUB
instruction with the Program Counter to do this. Similar to the LDR
pseudo-instruction we saw previously, by knowing the value of the Program Counter at the time when this ADD
or SUB
reaches the execute stage of the pipeline, we can simply take that value and modify it to generate an address. The catch is that the offset must be a particular type of number. For ARM instructions, that number must be one that can be created using a byte value rotated by an even number of bits, exactly as we saw in Section 6.2 (if rejected by the assembler, it will generate an error message to indicate that an offset cannot be represented by 0–255 and a rotation). For 32-bit Thumb instructions, that number must be within ±4095 bytes of a byte, half-word, or word-aligned address. If you notice the second ADR
in this example, the distance between the instruction and the label DataArea is small enough that the assembler will use a simple ADD
instruction to create the constant.
The third ADR
tries to create an address where the label is on the other side of an 8000-byte block of memory. This doesn't work, but there is another pseudo-instruction: ADRL
. Using two operations instead of one, the ADRL
will calculate an offset that is within a range based on the addition of two values now, both created by the byte rotation scheme mentioned above (for ARM instructions). There is a fixed range for 32-bit Thumb instructions of ±1MB. You should note that if you invoke an ADRL
pseudo-instruction in your code, it will generate two operations even if it could be done using only one, so be careful in loops that are sensitive to cycle counts. One other important point worth mentioning is that the label used with ADR
or ADRL
must be within the same code section. If a label is out of range in the same section, the assembler faults the reference. As an aside, if a label is out of range in other code sections, the linker faults the reference.
ADR r2, DataArea + 4300
: ADDimm
is an unsigned immediate, in the range [0, 4095].
LDR#
There is yet another way of loading addresses into registers, and it is exactly the same as the LDR
pseudo-instruction we saw earlier for loading constants. The syntax is
In this instance, the assembler will convert the pseudo-instruction into a load instruction, where the load reads the address from a literal pool that it creates. As with the case of loading constants, you must ensure that a literal pool is within range of the instruction. This pseudo-instruction differs from ADR
and ADRL
in that labels outside of a section can be referenced, and the linker will resolve the reference at link time.
The example below shows a few of the ways the LDR
pseudo-instruction can be used, including using labels with their own offsets.
You can see the first three LDR
statements in the subroutine func1
would actually be PC-relative loads from a literal pool that would exist in memory at the LTORG
statement. Additionally, the first load statement in the second subroutine could use the same literal pool to create a PC-relative offset. As the SPACE
directive has cleared an 8000-byte block of memory, the second load instruction cannot reach the second literal pool, since it must be within 4 kilobytes(12 bits, ±4095).
ADR vs. LDR#
Use the pseudo-instruction
to put an address into a register whenever possible. The address is created by adding or subtracting an offset to/from the PC
, where the offset is calculated by the assembler.
- If the above case fails, use the
ADRL
pseudo-instruction, which will calculate an offset using two separateADD
orSUB
operations. Note that if you invoke anADRL
pseudo-instruction in your code, it will generate two operations even if it could be done using only one.
; EXAMPLE 6.4
ADRL r2, DataArea + 4300 ; => ADD r2, PC, #offset1
; ADD r2, r2, #offset2
; offset1 + offset2 = 4300
Use the pseudo-instruction
if you plan to reference labels in other sections of code, or you know that a literal table will exist and you don't mind the extra cycles used to fetch the literal from memory. Use the same caution with literal pools that you would for the construct
Consult the Assembler User's Guide (ARM 2008a) for more details on the use of ADR
, ADRL
and LDR
for loading addresses.
Copyright clarification
Copyright belongs to the original author. 🫡
Excerpt/quotation for study only, non-commercial.