aarch64-pg
4.1 · registers and immediates

registers and immediates

AArch64 gives you thirty-one general-purpose registers, x0 through x30, each holding 64 bits. The same storage has a 32-bit view, w0 through w30, that reads and writes the low half. A program spends most of its time moving values between these registers and computing on them, so the move instructions are worth knowing well.

An immediate is a constant encoded directly inside an instruction. Immediates are convenient but small: only so many bits remain in a 32-bit instruction word after the opcode and the register fields, so one instruction cannot carry an arbitrary 64-bit constant.

moving values into registers

mov copies a value into a register, either from another register or from a small immediate. When the constant is small enough, mov x0, 42 is all you need and the assembler picks a suitable encoding for you.

Wider constants are built one 16-bit halfword at a time. movz writes a halfword and zeroes the rest of the register, while movk writes a halfword and keeps everything else. Reaching for these by hand is how you place a value a single mov cannot encode.

// the immediate move forms: mov, movz, movk, and add with an immediatedefine(r, x19)        .datafmt:    .string "%lld\n"        .text        .balign 4        .global mainmain:        stp     x29, x30, [sp, -16]!        mov     x29, sp        mov     r, 42                       // a small immediate moves straight in        ldr     x0, =fmt        mov     x1, r        bl      printf        movz    r, 0x0002, lsl 16           // zero the register, set one halfword        movk    r, 0x0003                    // keep that halfword, set the low one        add     r, r, 1                      // add a small immediate        ldr     x0, =fmt        mov     x1, r        bl      printf        mov     w0, 0        ldp     x29, x30, [sp], 16        ret
Open in playground

pitfall

A common slip is to reach for movk first. movk keeps the other halfwords, so if the register still holds an earlier value those bits survive into your constant. Begin with movz to clear the register and set the first halfword, then layer movk on top. The other trap is range: a plain mov immediate is limited, and a constant outside that range has to be built with the movz and movk pair rather than forced into one instruction.

building a wide constant

To place a value that spans more than one halfword, start with movz to set one halfword and clear the register, then use movk with a left shift to fill each remaining halfword. The shift selects which 16-bit slot the immediate lands in: lsl 16 targets bits 16 to 31, lsl 32 targets bits 32 to 47, and so on.

Once the constant is in place, ordinary arithmetic such as add with an immediate adjusts it without touching memory. Run the program below, then change the halfwords and watch the printed value follow.

loading editor...
figure 4.1.1runnable — step it and watch the registersOpen in playground