Since AArch64 uses fixed-size 32-bit instructions, you have to exercise some creativity to load a 64-bit constant.
; move wide with zero ; Rd = imm16 << n ; n can be 0, 16, 32, or 48 movz Rd, #imm16, LSL #n ; move wide with not ; Rd = ~(imm16 << n) ; n can be 0, 16, 32, or 48 movn Rd, #imm16, LSL #n ; move wide with keep ; Rd[n+15:n] = imm16 movk Rd, #imm16, LSL #n
The MOVZ
instruction loads a 16-bit unsigned value into one of the four lanes of a 64-bit destination, or one of the two lanes of a 32-bit destination. All the remaining lanes are set to zero.
The MOVN
instruction does the same thing as MOVZ
, except the whole thing is bitwise negated. (Be careful not to confuse MOVN
with MVN
.)
The MOVK
instruction does the same thing as MOVZ
, except that instead of setting the other lanes to zero, the other lanes are left unchanged.
Loading a 32-bit value can be done in two instructions by using MOVZ
to load 16 bits into half of the register, than the MOVK
into the other half.
movz r0, #0x1234 ; r0 = 0x00001234 movk r0, #0xABCD, LSL #16 ; r0 = 0xABCD1234
This technique can be extended to load a 64-bit value in four steps, but that’s getting quite unwieldy. The compiler is more likely to store the value in the code segment and use a pc-relative addressing mode to load it.
; special syntax for pc-relative loads ldr x0, =0x123456789ABCDEF0 ; load 64-bit value ldr w0, =0x12345678 ; load 32-bit value
As I noted in the discussion of addressing modes, the assembler and disassembler use this special equals-sign notation to represent a pc-relative load. It means that the value is stored in a literal pool in the code segment, and a pc-relative load is being used to fetch it. The assembler batches up all of these literals and emits them between functions. The pc-relative load has a reach of ±1MB, so you are unlikely to run into the problem that you had on AArch32, where the reach was only ±4KB, and you had to find a safe place to dump the literals in the middle of the function.
There are quite a number of instructions that generate constants, and if you use the MOV
pseudo-instruction, the assembler will try to find one that works.
; load up a constant somehow mov Rd, #imm
Instruction | Used for |
---|---|
add Rd, zr, #imm12 |
0x00000000`00000XXX |
add Rd, zr, #imm12, LSL #12 |
0x00000000`00XXX000 |
sub Wd, wzr, #imm12 |
0x00000000`FFFFFXXX |
sub Wd, wzr, #imm12, LSL #12 |
0x00000000`FFXXXFFF |
sub Xd, xzr, #imm12 |
0xFFFFFFFF`FFFFFXXX |
sub Xd, xzr, #imm12, LSL #12 |
0xFFFFFFFF`FFXXXFFF |
movz Rd, #imm16 |
0x00000000`0000XXXX |
movz Rd, #imm16, LSL #16 |
0x00000000`XXXX0000 |
movz Rd, #imm16, LSL #32 |
0x0000XXXX`00000000 |
movz Rd, #imm16, LSL #48 |
0xXXXX0000`00000000 |
movn Wd, #imm16 |
0x00000000`FFFFXXXX |
movn Wd, #imm16, LSL #16 |
0x00000000`XXXXFFFF |
movn Xd, #imm16 |
0xFFFFFFFF`FFFFXXXX |
movn Xd, #imm16, LSL #16 |
0xFFFFFFFF`XXXXFFFF |
movn Xd, #imm16, LSL #32 |
0xFFFFXXXX`FFFFFFFF |
movn Xd, #imm16, LSL #48 |
0xXXXXFFFF`FFFFFFFF |
orr Xd, xzr, #imm |
Value can be expressed as a Bitwise operation constant |
orr Wd, wzr, #imm |
Value can be expressed as lower 32 bits of a Bitwise operation constant |
A common type of sort-of constant is the address of a global variable. It’s a constant whose value isn’t discovered until runtime. We’ll look at those next time.
0 comments