A Dozen Instructions Cover Most Code

x86-64 has hundreds of instructions, but you do not need them. The overwhelming majority of everyday compiler output is built from about a dozen, and once you know what those do, most listings become readable on sight. This lesson walks through that core set, grouped by the job each one does. Later chapters introduce the rest as they come up, but these are the ones you will see on almost every line.

You have already met some of them in the square and add listings. Here we name them properly.

Moving Data: mov and lea

By far the most common instruction is mov. It copies a value from a source into a destination (remember: in Intel syntax the destination comes first). Despite the name, it copies rather than moves: the source is left unchanged.

mov eax, 5        ; put the number 5 into eax
mov eax, edx      ; copy edx into eax
mov eax, DWORD PTR -4[rbp]   ; load a value from memory into eax

Its close cousin is lea ("load effective address"). It computes an address using the bracket notation, but, unlike mov, it does not touch memory: it just calculates the number and stores it. Compilers love it because it can also do quick arithmetic for free.

lea eax, [rdi+rsi]   ; eax = rdi + rsi  (no memory is read)

The difference is worth fixing now: mov eax, [rbx] reads from memory; lea eax, [rbx] only computes the address rbx and never reads anything.

Doing Arithmetic: add, sub, imul

These do exactly what they say, with the result landing in the destination (the first operand).

add eax, edx      ; eax = eax + edx
sub eax, 1        ; eax = eax - 1
imul eax, edx     ; eax = eax * edx   (signed integer multiply)

You will also run into a few one-operand arithmetic helpers: neg flips a value's sign, and inc / dec add or subtract one. They are just compact forms of what sub and add already do.

Comparing Values: cmp and test

Before code can make a decision it has to compare something. cmp does this. It computes "destination minus source" exactly like sub, but throws the result away and only keeps the side effects, the flags (was it zero? negative? did it overflow?). Those flags are then read by a jump.

cmp edi, 0        ; compare edi against 0, set flags, keep no result

test is the same idea using a bitwise AND instead of subtraction. The classic use is test eax, eax, a compact way to ask "is eax zero?".

test eax, eax     ; set flags from eax (commonly a zero check)

Neither instruction changes its operands. Their whole purpose is to set flags for the instruction that follows.

Jumping Around: jmp and the conditional jumps

A jmp is an unconditional jump: control goes to a label, always. This is how loops restart and how code skips over branches.

jmp .L2           ; always continue at label .L2

The interesting ones are conditional jumps, which jump only if the flags (just set by a cmp or test) say so. The pair you will see constantly is je (jump if equal) and jne (jump if not equal); the signed comparisons are jl, jg, jle, jge (less, greater, and their or-equal forms).

cmp edi, 0
je  .L2           ; jump to .L2 only if edi == 0

So a comparison and a conditional jump almost always travel as a pair: cmp sets the flags, the jump acts on them. That two-line pattern is the whole basis of if statements and loops, which Chapter 3 covers in depth.

Calling Functions: push, pop, call, ret

The stack and function calls lean on four instructions. push puts a value on the stack and pop takes one off:

push rbp          ; save rbp onto the stack
pop  rbp          ; restore it later

call runs a function: it remembers where to come back to and jumps to the function. ret does the reverse: it returns to wherever the matching call was.

call square(int)  ; run square, then resume on the next line
ret               ; hand control back to our caller

That return value, by convention, comes back in eax. The full mechanics of the stack and calling convention are Chapter 4; for now, just recognise this quartet as "the machinery of calling functions".

Key Takeaways

  • A small core of instructions makes up most listings; learn these and the rest is detail.
  • mov copies a value; lea computes an address without touching memory.
  • add, sub, and imul do arithmetic, with the result in the destination operand.
  • cmp and test set flags and keep no result; they exist to feed a following jump.
  • jmp always jumps; conditional jumps (je, jne, jl, jg, ...) jump only when the flags allow, so cmp/jump travel as a pair.
  • push, pop, call, and ret are the machinery of function calls; a function's return value comes back in eax.