LDR vs. LDUR in AArch64

In AArch64 (ARMv8-A), the main difference between LDR and LDUR is how they handle the immediate offset from the base address.

  • LDR (Load Register): Uses a scaled positive immediate offset. It is the standard instruction for loading data from validly aligned structures and arrays.
  • LDUR (Load Register Unscaled): Uses an unscaled signed immediate offset. It is used for accessing data at negative offsets or unaligned addresses that LDR cannot reach.

Here is a detailed breakdown of the differences:

1. Offset Scaling

  • LDR (Scaled): The immediate value you provide is automatically multiplied (scaled) by the size of the data you are loading (4 for 32-bit, 8 for 64-bit).
    • Example: LDR X0, [X1, #8] — The encoded immediate is actually 1. The CPU calculates Base + (1 * 8).
    • Constraint: The offset must be a multiple of the data size (e.g., you cannot use an offset of #3 for a 64-bit load).
  • LDUR (Unscaled): The immediate value is treated exactly as written, in bytes.
    • Example: LDUR X0, [X1, #3] — The encoded immediate is 3. The CPU calculates Base + 3.
    • Benefit: The offset does not need to be a multiple of the data size, allowing access to unaligned data.

2. Offset Range & Sign

  • LDR: Supports a large, unsigned (positive only) range.
    • 32-bit load: 0 to 16,380 bytes (must be multiple of 4).
    • 64-bit load: 0 to 32,760 bytes (must be multiple of 8).
  • LDUR: Supports a small, signed range.
    • Range: -256 to +255 bytes.
    • Benefit: This allows you to load data stored before the base register address (negative offsets).

3. Assembler “Magic” (Aliasing)

In practice, you rarely need to type LDUR manually. Most AArch64 assemblers are smart enough to choose the correct opcode for you based on the offset you write.

If you write:

LDR X0, [X1, #-8]   ; Negative offset

The assembler sees the negative sign, realizes the standard LDR encoding cannot handle it, and automatically compiles it into the machine code for LDUR.

Similarly, if you write:

LDR X0, [X1, #3]    ; Unaligned offset

The assembler detects that #3 is not a multiple of 8 (for a 64-bit load) and emits an LDUR instruction.

Summary Table

FeatureLDR (Immediate)LDUR
Full NameLoad RegisterLoad Register (Unscaled)
Offset TypeScaled (Multiplied by transfer size)Unscaled (Raw byte count)
Offset SignPositive (Unsigned)Signed (Positive or Negative)
Offset RangeLarge (0 to ~32KB)Small (-256 to +255 bytes)
AlignmentOffset must be aligned to data sizeOffset can be unaligned
Primary UseArray/Struct accessNegative offsets, unaligned fields

Note: Do not confuse LDUR (Unscaled) with LDTR, which stands for “Load Register Unprivileged.” LDTR is used to perform a load as if the CPU were in a lower exception level (e.g., User mode), usually for permission checking in OS kernels.


STUR (Store Register Unscaled)

It is the direct counterpart to LDUR.

  • Purpose: Stores a register to memory using an unscaled, signed immediate offset.
  • When to use: It is used when you need to store data at an address offset that is not a multiple of the data size (unaligned) or is negative.
  • Assembler behavior: Just like with LDR/LDUR, you rarely type STUR manually. If you write STR X0, [X1, #-5], the assembler automatically converts it to STUR because standard STR cannot handle unaligned or negative immediate offsets in that specific way.

Leave a Reply

Your email address will not be published. Required fields are marked *