Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to get a constant into a register

A small immediate, a zeroing idiom, movabs or movz/movk or lui/addi for a big number, a PC-relative address, a GOT load, or a load from read-only data. The size of the value and what it is decide.

Ask what the constant is and how big it is.

  • Zero → the zeroing idiom: xor eax, eax on x86-64 (it changes the flags, so do it before a cmp, never between a cmp and its branch), mov x0, xzr / mov w0, #0 on ARM64, li a0, 0 on RISC-V.
  • A number that fits the instruction's immediate field (x86-64: 32 bits, sign-extended; ARM64 mov: one 16-bit slot; RISC-V addi: −2048 to 2047) → one mov/li.
  • A wider integer → x86-64 movabs (one 10-byte instruction); ARM64 one movz plus up to three movk; RISC-V lui + addi (and more for 64 bits). Let the assembler's li/mov pick when you can.
  • The address of a global or a string → never a fixed number: PC-relative, lea rax, [rip + x], adrp + add :lo12:, or auipc + addi.
  • The address of something in another shared library → load it from the global offset table (GOT).
  • A floating-point constant → there is no FP immediate worth relying on: the compiler puts it in read-only data and loads it PC-relative.

Small numbers, in the instruction itself

mov eax, 42, mov w0, #42, li a0, 42: where the number sits inside the instruction, and how big it may be.

A number written into the instruction itself, like the 42 in mov eax, 42.

Example

In mov eax, 1000 the 1000 is stored in the instruction's own bytes; no memory is read.

Why a 4-byte instruction can only spare 12 or 16 bits for a constant.

Zero, the special case

Setting a register to zero with xor eax, eax instead of mov eax, 0, because it is shorter.

Example

xor eax, eax is 2 bytes against mov eax, 0's 5, because mov carries a 4-byte immediate.

Why compilers zero with xor, and when they can't (between a compare and its branch).

A register that always reads as 0 and ignores writes (xzr on ARM64, x0 on RISC-V).

Example

ARM64 mov x0, xzr and RISC-V mv a0, zero copy a register that always reads 0.

Big numbers, built in pieces

movabs on x86-64, movz then movk on ARM64, lui then addi on RISC-V.

The x86-64 instruction that puts a full 64-bit number into a register in one go.
The ARM64 instruction that drops a 16-bit number into one quarter of a register and keeps the other bits.

Example

movz x0, #0x7788 then movk x0, #0x5566, lsl #16 builds 0x55667788 in two instructions.
The RISC-V instruction that puts a number into the upper part of a register, ready for addi to fill in the low 12 bits.

Example

lui a0, 244 then addi a0, a0, 576 builds 0xF4240 = 1,000,000.
A convenient instruction name the assembler turns into one or more real instructions, like RISC-V's li and ret.

li and ARM64's mov with a big number are written as one line and expanded by the assembler.

Addresses, relative to the code

Naming a piece of data by its distance from the current instruction, so the program works wherever it is loaded in memory.

Example

lea rax, [rip + msg] stores the distance from the next instruction to msg, so the code works wherever it is loaded.
The ARM64 instruction that finds the 4 KB block of memory a global variable lives in, relative to the current instruction; an add then supplies the last 12 bits.

Example

adrp x0, msg finds the 4 KiB block holding msg; add x0, x0, :lo12:msg adds the low 12 bits.

Why addresses are PC-relative everywhere today, and the GOT load for a symbol that lives in another module.

A table of addresses in a program's data that the dynamic linker fills in, so position-independent code can find things in other libraries.

Constants that live in memory

A double constant such as 2.5 is stored in read-only data and loaded PC-relative.

The part of a program that holds constants such as string literals; the program may read it but not change it.