ADRP
Address of 4KB page at a PC-relative offset.
ADRL
Load a PC-relative address into a register. It is similar to the ADR
instruction. ADRL can load a wider range of addresses than ADR because
it generates two data processing instructions.
Specifically,
ADRL assembles to two instructions, an ADRP followed by ADD. If the
assembler cannot construct the address in two instructions, it
generates a relocation. The linker then generates the correct offsets.
ADRL produces position-independent code, because the address is
calculated relative to PC.
What do ADRP
and ADRL
instructions do? More importantly, how does and ADRP
followed by an ADD
construct a PC-relative address?
ADR
The root question is that all ARMv7 / ARMv8 instructions are 4 bytes long.
This simplifies a lot of things, but it has one unfortunate implication: you cannot encode full addresses (4 / 8 bytes) in a single instruction, since we need some bits to encode the instruction itself.
This is where the ADR instruction comes in. Even though we cannot store full addresses, we can refer to some of them (those that fit into the encoding) by the relative address to the PC, which is often enough for many applications.
The ADR instruction uses a 21-bit immediate for the offset, which allows for +-1MiB jumps (20-bits + 1 for the sign).
The rationale here is analogous to that of the ldr =
pseudo instruction: Why use LDR over MOV (or vice versa) in ARM assembly?
ADR can sometimes be achieved with ADD and SUB with PC as documented on the ARMv7 DDI 0406C.d manual D9.4 "Explicit use of the PC in ARM instructions":
Some forms of the ADR instruction can be expressed as forms of ADD or SUB, with the PC as Rn. Those forms of ADD and SUB are permitted, and
not deprecated.
TODO when can it not be achieved with ADD
? GNU GAS suggests that ADR is just a pseudo-op that always assembles into ADD or SUB: https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes
This instruction will load the address of label into the indicated register. The instruction will evaluate to a PC relative ADD or SUB instruction depending upon where the label is located. If the label is out of range, or if it is not defined in the same file (and section) as the ADR instruction, then an error will be generated. This instruction will not make use of the literal pool.
Note that on ARMv8, the PC cannot be used in every instruction like a general purpose register, therefore ADR is actually important there and has a separate encoding: Howto write PC relative adressing on arm asm?
Code sample:
adr r0, label
ldr r1, =label
label:
/* r0 == r1 */
On GitHub with runnable assertion.
ADRP
ADRP is similar to ADR, but it zeroes out the 12 lower bits and shifts pages relative to the current PC page instead of just bytes.
This way, we can jump much further (+-4GiB), at the cost of needing to do an extra ADD after ADRP to set the lower 12-bits. The ARMv8 manual says:
The ADR instruction adds a signed, 21-bit immediate to the value of the program counter that fetched this instruction, and then writes the result to a general-purpose register. This permits the calculation of any byte address within ±1MB of the current PC.
The ADRP instruction shifts a signed, 21-bit immediate left by 12 bits, adds it to the value of the program counter with the bottom 12 bits cleared to zero, and then writes the result to a general-purpose register. This permits the calculation of the address at a 4KB aligned memory region. In conjunction with an ADD (immediate) instruction, or a Load/Store instruction with a 12-bit immediate offset, this allows for the calculation of, or access to, any address within ±4GB of the current PC.
ADRP only exists in ARMv8, not in ARMv7.
The ARMv8 DDI 0487C.a manual says that Page is just a mnemonic for 4KB, and does not reflect the actual page size, which is configurable to other sizes. C3.3.5 "PC-relative address calculation":
The term page used in the ADRP description is short-hand for the 4KB memory region, and is not related to the virtual
memory translation granule size.
Code sample:
adrp x0, label
adr x1, label
/* Clear the lower 12 bits. */
bic x1, x1, #0xFF
bic x1, x1, #0xF00
/* x0 == x1 */
label:
On GitHub with runnable assertion.
ADRL
ADRL is is not an actual instruction, just a pseudo-instruction.
As such, it is not mentioned in the v7 manual, and there is just one mention on the v8 manual at "Instructions that read the PC", but I can't find anywhere in the manual that explains it, so maybe it is just a documentation mistake?
I will therefore focus on the GNU AS implementation which documents it at https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes under ARM specific features::
adrl <register> <label>
This instruction will load the address of label into the indicated register. The instruction will evaluate to one or two PC relative ADD or SUB instructions depending upon where the label is located. If a second instruction is not needed a NOP instruction will be generated in its place, so that this instruction is always 8 bytes long.
Therefore it appears to be able to expand to multiple ADD / SUB, presumably to allow for a larger jump from the PC.
Objdump confirms what the GNU manual says for short addresses:
adr r0, label
10478: e28f0008 add r0, pc, #8
adrl r2, label
10480: e28f2000 add r2, pc, #0
10484: e1a00000 nop ; (mov r0, r0)
TODO: example of long addresses. What is the maximum length? Just 2x that of ADD / ADR or more?
Trying to use it on aarch64 fails, since it is an ARMv7 specific features according to the GNU GAS manual. The error message on GNU GAS is 2.29.1 is:
Error: unknown mnemonic `adrl' -- `adrl r6,.Llabel'
The Linux kernel has also defined a macro called adr_l
at https://patchwork.kernel.org/patch/9883301/ TODO understand rationale.
Alternatives
One main alternative for when the PC offset is too long to encode into the instruction either, is to use movk / movw / movt, see: What is the difference between =label (equals sign) and [label] (brackets) in ARMv6 assembly?