---
title: Data Movement
module: Machine-Level Programming
moduleNumber: 1
lessonNumber: 2
order: 102
summary: >
  Most instructions a program runs simply move data. We cover the mov family and
  its size suffixes, the three operand forms, the full memory addressing mode
  D(Rb,Ri,S) and its special cases, lea for address arithmetic, and how push and
  pop manipulate the stack pointer %rsp on a stack that grows toward lower
  addresses.
topics: [Machine-Level Programming]
sources:
  - book: Bryant & O'Hallaron
    ref: "CS:APP — §3.4 Accessing Information (Operand Specifiers, Data Movement, Pushing and Popping)"
---

The single most common thing a processor does is copy bytes from one place to
another — register to register, memory to register, register to memory. Before any
arithmetic can happen the operands must be in the right registers, and afterward
the result must be written back. This lesson covers the **mov** family that does
that copying, the **operand forms** that name where data lives, the full memory
**addressing mode**, the address-computing **lea**, and the stack operations
`push` and `pop`.

## The three operand forms

Every operand an instruction reads or writes takes one of three forms, and
learning to classify them at a glance is most of the battle in reading assembly.

- **Immediate** — a literal constant, written with a `$` prefix, as in `$0x1f` or
  `$255`. Immediates can only be sources, never destinations.
- **Register** — the contents of one of the sixteen registers (at any of its four
  widths), written with a `%` prefix: `%rax`, `%edi`, `%al`.
- **Memory** — the bytes **at** a computed address, written with the address in
  parentheses: `(%rax)` means "the bytes at the address held in `%rax`."

$$
% caption: The three operand forms. An immediate is a literal carried in the
% caption: instruction; a register names one of the sixteen slots; a memory operand
% caption: is the bytes at an address. Only memory involves a trip to RAM.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  box/.style={draw, minimum width=24mm, minimum height=12mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box] (imm) at (0,0) {\textbf{immediate}\\$\mathtt{{\char36}0x1f}$\\a literal value};
  \node[box] (reg) at (3.2,0) {\textbf{register}\\$\mathtt{\%rax}$\\its own contents};
  \node[box, draw=acc, fill=acc!8] (mem) at (6.4,0) {\textbf{memory}\\$\mathtt{(\%rax)}$\\bytes at an address};
  \node[font=\scriptsize, align=center] at (0,-1.2) {source only};
  \node[font=\scriptsize, align=center] at (3.2,-1.2) {source or dest};
  \node[font=\scriptsize, align=center, text=acc] at (6.4,-1.2) {source or dest};
\end{tikzpicture}
$$

The one rule that constrains every move: **at most one operand may be a memory
reference.** x86-64 has no instruction that copies memory directly to memory; such
a copy is two instructions, through a register.

## The mov family

The `mov` instruction **copies** the value of its source into its
destination, leaving the source unchanged — register to register, memory to
register, or register to memory. The size suffix fixes how many bytes are copied.

$$
% caption: mov as a copy. The value in the source is duplicated into the
% caption: destination; the source keeps its value. Here movq (%rsi), %rdx copies
% caption: 8 bytes from the memory cell at %rsi into the register %rdx.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  box/.style={draw, minimum width=20mm, minimum height=9mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box, draw=acc, fill=acc!8] (src) at (0,0) {$\mathtt{(\%rsi)}$\\\texttt{0x42}};
  \node[box] (dst) at (5.2,0) {$\mathtt{\%rdx}$\\\texttt{0x42}};
  \node[font=\scriptsize] at (0,-1.0) {memory cell (source)};
  \node[font=\scriptsize] at (5.2,-1.0) {register (dest)};
  \draw[->, very thick, acc] (src.east) -- (dst.west)
    node[midway, above, font=\scriptsize] {copy 8 bytes};
  \node[font=\scriptsize, align=center] at (2.6,-0.75) {$\mathtt{movq\ (\%rsi),\%rdx}$};
\end{tikzpicture}
$$

The basic copy instruction is `mov`, carrying a size suffix that fixes how many
bytes move. `movb` moves one byte, `movw` two, `movl` four, `movq` eight. The
source and destination widths must agree with the suffix.

```asm [movs.s]
movq    $0x4050, %rax     # immediate -> register (8 bytes)
movb    %al, (%rdi)       # register  -> memory   (1 byte)
movl    (%rsi), %edx      # memory    -> register (4 bytes)
movw    %ax, %bx          # register  -> register (2 bytes)
```

Two specialized moves handle width changes when copying a small value into a
larger register. `movz` **zero-extends** the source (fills the high bytes with
zeros) and `movs` **sign-extends** it (replicates the sign bit), each taking a
two-letter suffix naming the source then destination widths.

```asm [extend.s]
movzbq  %al, %rbx         # zero-extend  byte -> quad
movsbl  %al, %edx         # sign-extend  byte -> long
```

The full family is small and follows one naming scheme: `mov` then `z` or `s`,
then the source width, then the destination width. The source is always narrower
than the destination, so the reachable pairs are these.

| Instruction | Source → dest | Extension |
| --- | --- | --- |
| `movzbw` / `movsbw` | byte → word | zero / sign |
| `movzbl` / `movsbl` | byte → long | zero / sign |
| `movzwl` / `movswl` | word → long | zero / sign |
| `movzbq` / `movsbq` | byte → quad | zero / sign |
| `movzwq` / `movswq` | word → quad | zero / sign |
| — / `movslq` | long → quad | sign only |

Two gaps in the table are worth naming. There is no `movzlq` for a
**long-to-quad zero-extend**, because a plain `movl` into the 32-bit destination
already zeroes the upper four bytes: `movl %eax, %eax` is the idiomatic 32-to-64
zero-extend, so a dedicated instruction would be redundant. Sign extension has no
such shortcut, so `movslq` (also spelled `cltq` when the operand is `%eax`) does
exist. Every other combination is a genuine two-width move that the narrow write
alone cannot express.

That `movl` shortcut is the upper-byte-zeroing rule from
[the machine's view](/computer-architecture/machine-level-x86-64/the-machines-view)
put to work: any `l`-suffixed write clears the top four bytes, so the compiler
leans on it wherever an `int`-to-`long` zero-extend is needed.

The zero-versus-sign choice changes the numeric value. Take the
byte `0xFF` sitting in `%al`. As an `unsigned char` that is 255; as a `signed char`
it is $-1$. Widening it to a quad must preserve whichever interpretation the C type
demanded, and the two `mov` variants do exactly that.

$$
% caption: Extending the byte 0xFF from %al to a quad. movzbq fills the new high
% caption: bytes with zeros (value 255); movsbq replicates the sign bit 1 (value -1).
% caption: The source bits are identical; the fill bytes decide the number.
\begin{tikzpicture}[font=\footnotesize,
  cell/.style={draw, minimum width=34mm, minimum height=6.5mm, inner sep=2pt},
  hi/.style={draw=acc, text=acc, minimum width=34mm, minimum height=6.5mm, inner sep=2pt}]
  \definecolor{acc}{HTML}{2348F2}
  \node[anchor=east, font=\scriptsize] at (0.2,0.8) {$\mathtt{movzbq\ \%al,\%rbx}$};
  \node[hi] at (2.6,0.8) {$\mathtt{0000\,0000\,0000\,00FF}$};
  \node[anchor=west, font=\scriptsize] at (4.5,0.8) {$= 255$};
  \node[anchor=east, font=\scriptsize] at (0.2,-0.2) {$\mathtt{movsbq\ \%al,\%rbx}$};
  \node[hi] at (2.6,-0.2) {$\mathtt{FFFF\,FFFF\,FFFF\,FFFF}$};
  \node[anchor=west, font=\scriptsize] at (4.5,-0.2) {$= \text{-}1$};
\end{tikzpicture}
$$

The low byte `FF` is the same in both rows; only the seven new bytes differ, and
that difference is the entire distinction between `unsigned` and `signed` widening.
This is why C's integer-promotion rules compile to one instruction or the other, and
why a stray `movzbl` where the code meant `movsbl` turns a small negative into a
large positive.

## The full addressing mode

A memory operand specifies how to compute an address. The general form combines a
constant displacement, a base register, an index register, and a scale factor, and
the hardware evaluates the address by a single formula.

> **Definition (Memory addressing mode).** The operand `D(Rb,Ri,S)` denotes the
> bytes at address
> $$ \mathrm{Imm} + \mathrm{R}[\mathrm{Rb}] + \mathrm{R}[\mathrm{Ri}] \cdot S, $$
> where `D` is a constant displacement `Imm`, `Rb` is the **base** register, `Ri`
> is the **index** register, and the **scale** $S \in \{1, 2, 4, 8\}$. The address
> is computed first; the operand is the memory at that address.

The scale's allowed values match the sizes of the primitive types — 1, 2, 4,
8 — which is no accident: indexing an array `A[i]` of $S$-byte elements is
precisely `base(,Ri,S)`, computing $\text{base} + i \cdot S$ in one operand.

$$
% caption: The general addressing mode D(Rb,Ri,S). The effective address is the
% caption: displacement plus the base register plus the scaled index; the operand
% caption: is the memory found there.
\begin{tikzpicture}[font=\footnotesize, >=stealth]
  \definecolor{acc}{HTML}{2348F2}
  % the four components on a row
  \node (d)  at (0,0)   {$\mathtt{D}$};
  \node      at (0.55,0) {$\mathtt{(}$};
  \node (rb) at (1.4,0) {$\mathtt{Rb}$};
  \node      at (2.0,0) {\texttt{,}};
  \node (ri) at (2.9,0) {$\mathtt{Ri}$};
  \node      at (3.5,0) {\texttt{,}};
  \node (s)  at (4.3,0) {$\mathtt{S}$};
  \node      at (4.85,0) {$\mathtt{)}$};
  % role labels, alternating above/below to avoid crowding
  \node[text=acc, font=\scriptsize, align=center] at (0,1.0) {displacement\\$\mathrm{Imm}$};
  \draw[acc, ->] (0,0.7) -- (d.north);
  \node[text=acc, font=\scriptsize, align=center] at (1.4,-1.0) {base reg};
  \draw[acc, ->] (1.4,-0.7) -- (rb.south);
  \node[text=acc, font=\scriptsize, align=center] at (2.9,1.0) {index reg};
  \draw[acc, ->] (2.9,0.7) -- (ri.north);
  \node[text=acc, font=\scriptsize, align=center] at (4.3,-1.0) {scale\\1, 2, 4, 8};
  \draw[acc, ->] (4.3,-0.7) -- (s.south);
  % the resulting formula
  \node[anchor=west] at (6.2,0) {$=\ \mathrm{Imm} + \mathrm{R}[\mathrm{Rb}] + \mathrm{R}[\mathrm{Ri}]$ x $S$};
\end{tikzpicture}
$$

Most operands use only part of the form, and the special cases are worth
memorizing because they are what you actually meet in compiled code.

| Form | Effective address | Name |
| --- | --- | --- |
| `(%rax)` | $\mathrm{R}[\mathtt{rax}]$ | indirect |
| `8(%rax)` | $\mathrm{R}[\mathtt{rax}] + 8$ | base + displacement |
| `(%rax,%rcx)` | $\mathrm{R}[\mathtt{rax}] + \mathrm{R}[\mathtt{rcx}]$ | indexed |
| `(%rax,%rcx,4)` | $\mathrm{R}[\mathtt{rax}] + \mathrm{R}[\mathtt{rcx}] \cdot 4$ | scaled indexed |
| `0x40(,%rcx,8)` | $\mathtt{0x40} + \mathrm{R}[\mathtt{rcx}] \cdot 8$ | scaled, no base |

A worked example fixes the arithmetic. Suppose `%rdx` holds `0x100` and `%rcx`
holds `3`. Then `0x8(%rdx,%rcx,4)` addresses
$\mathtt{0x8} + \mathtt{0x100} + 3 \cdot 4 = \mathtt{0x114}$, and a `movq` from it
reads the 8 bytes starting there.

$$
% caption: Resolving 0x8(%rdx,%rcx,4) with %rdx=0x100 and %rcx=3. The three terms
% caption: sum to the effective address 0x114; the operand is the quad word in
% caption: memory at that address.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  term/.style={draw, minimum width=20mm, minimum height=8mm, align=center},
  cell/.style={draw, minimum width=22mm, minimum height=7mm, inner sep=1pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  % the three summands stacked
  \node[term] (d)  at (0,1.2)  {$\mathtt{0x8}$};
  \node[term] (rb) at (0,0.0)  {$\mathtt{\%rdx}=\mathtt{0x100}$};
  \node[term] (ri) at (0,-1.2) {3 x 4 = \texttt{0xC}};
  % sum node
  \node[term, draw=acc, thick] (ea) at (4.2,0.0) {$\mathtt{0x114}$};
  \draw[->, thick] (d.east)  -- (ea.north west);
  \draw[->, thick] (rb.east) -- (ea.west);
  \draw[->, thick] (ri.east) -- (ea.south west);
  % memory column to the right
  \node[cell] (m1) at (8.6,0.7)  {...};
  \node[cell, draw=acc, fill=acc!8] (m2) at (8.6,0.0) {quad word};
  \node[cell] (m3) at (8.6,-0.7) {...};
  \node[anchor=west, text=acc, font=\footnotesize] at (10.0,0.0) {at \texttt{0x114}};
  \draw[->, thick, acc] (ea.east) -- (m2.west) node[midway, above, font=\scriptsize] {address};
\end{tikzpicture}
$$

### A worked memory access, end to end

To see the addressing mode drive an actual load, fix a small memory picture and run
one instruction against it. Suppose an array of 8-byte `long`s begins at `0x1000`,
`%rdx` holds that base `0x1000`, and `%rcx` holds the index `2`. The instruction
`movq (%rdx,%rcx,8), %rax` should load the third element.

The processor evaluates the operand in two phases. First it computes the effective
address from the formula: $\mathrm{R}[\mathtt{rdx}] + \mathrm{R}[\mathtt{rcx}] \cdot 8
= \mathtt{0x1000} + 2 \cdot 8 = \mathtt{0x1010}$. Then it reads the 8 bytes starting
at `0x1010` and copies them into `%rax`. If the `long` stored there is `0x2A`
(decimal 42), `%rax` ends holding `0x2A`; the base register `%rdx` and index `%rcx`
are untouched, since the address arithmetic happens in a hidden adder, not in them.

$$
% caption: movq (%rdx,%rcx,8), %rax with %rdx=0x1000 and %rcx=2. The effective
% caption: address 0x1010 selects the third 8-byte element; its value 0x2A is copied
% caption: into %rax while the base and index registers keep their values.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  cell/.style={draw, minimum width=20mm, minimum height=7mm, inner sep=1pt, align=center},
  reg/.style={draw, minimum width=20mm, minimum height=7mm, inner sep=1pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  % memory column, addresses ascending downward
  \node[font=\scriptsize] at (0,2.4) {memory};
  \node[cell] (m0) at (0,1.6)  {\texttt{0x2200}};
  \node[cell] (m1) at (0,0.8)  {\texttt{0x1111}};
  \node[cell, draw=acc, fill=acc!8] (m2) at (0,0.0) {\texttt{0x2A}};
  \node[cell] (m3) at (0,-0.8) {\texttt{...}};
  \node[anchor=east, font=\scriptsize] at (-1.15,1.6) {\texttt{0x1000}};
  \node[anchor=east, font=\scriptsize] at (-1.15,0.8) {\texttt{0x1008}};
  \node[anchor=east, text=acc, font=\scriptsize] at (-1.15,0.0) {\texttt{0x1010}};
  % address computation
  \node[align=center, font=\scriptsize] (calc) at (5.4,1.2)
    {ef\/f. address $=$ base $+$ 2 x 8\\$= \mathtt{0x1000} + \mathtt{0x10} = \mathtt{0x1010}$};
  \draw[->, thick, acc] (calc.south) -- (2.7,0.0) -- (m2.east)
    node[pos=0.7, above, font=\scriptsize] {select};
  % destination register
  \node[reg, draw=acc, thick] (rax) at (5.4,-0.6) {$\mathtt{\%rax}=\mathtt{0x2A}$};
  \draw[->, thick, acc] (m2.south) .. controls (2.0,-0.8) .. (rax.west)
    node[pos=0.6, below, font=\scriptsize] {copy 8 bytes};
\end{tikzpicture}
$$

The two-phase reading — compute an address, then touch memory once — is the whole
mental model for every memory operand. The next instruction stops after the first
phase.

## lea computes addresses

There is one instruction that uses the addressing-mode syntax but does **not**
touch memory: `lea`, "load effective address." Where a `mov` from `D(Rb,Ri,S)`
fetches the bytes at the computed address, `lea` computes the address and writes
**the address itself** into the destination register.[^lea]

$$
% caption: mov versus lea on the same operand. Both compute the address from the
% caption: operand; mov then loads the bytes living there, while lea stops at the
% caption: address and stores the number itself, never touching memory.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  box/.style={draw, minimum width=18mm, minimum height=8mm, align=center},
  cell/.style={draw, minimum width=18mm, minimum height=8mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box] (op) at (0,0) {$\mathtt{8(\%rax)}$};
  \node[font=\scriptsize] at (0,-0.95) {operand};
  \node[box, draw=acc, thick] (addr) at (3.4,0) {addr \texttt{0x108}};
  \draw[->, thick] (op.east) -- (addr.west) node[midway, above, font=\scriptsize] {compute};
  % lea path: stop at the address
  \node[box, draw=acc, fill=acc!8] (lea) at (7.4,0.9) {$\mathtt{\%rcx}=\mathtt{0x108}$};
  \draw[->, thick, acc] (addr.north) |- (lea.west) node[midway, above, font=\footnotesize] {\texttt{lea}: the address};
  % mov path: load the bytes there
  \node[cell] (cell) at (7.4,-0.9) {bytes \texttt{42}};
  \node[box, draw=acc, fill=acc!8] (mov) at (11.0,-0.9) {$\mathtt{\%rdx}=\mathtt{42}$};
  \draw[->, thick] (addr.south) |- (cell.west) node[pos=0.75, below, font=\footnotesize] {\texttt{mov}: load};
  \draw[->, thick] (cell.east) -- (mov.west);
\end{tikzpicture}
$$

```asm [lea.s]
# %rdi = x.  Compute &A[i] style addresses without a memory access.
leaq    7(%rdi), %rax        # rax = x + 7
leaq    (%rdi,%rdi,2), %rax   # rax = x + 2x = 3x
leaq    0(,%rdi,8), %rax      # rax = 8x
```

Because the address formula is a sum of a constant, a register, and a
scaled register, `lea` doubles as a fast way to compute `Imm + a + b·{1,2,4,8}`
in a single instruction, often more cheaply than the equivalent add-and-multiply.
Compilers reach for it constantly for small multiplications and array-offset math;
we return to this trick in
[arithmetic and logic](/computer-architecture/machine-level-x86-64/arithmetic-and-logic).

> **Definition (lea).** `leaq D(Rb,Ri,S), Rd` sets `Rd` to the effective address
> $\mathrm{Imm} + \mathrm{R}[\mathrm{Rb}] + \mathrm{R}[\mathrm{Ri}] \cdot S$ without
> reading or writing memory. It is `mov`'s addressing syntax used purely as an
> arithmetic expression.

## The stack: push and pop

The run-time **stack** is a region of memory that grows toward **lower** addresses,
with `%rsp` always pointing at the **top** element — the lowest occupied address.
Two instructions maintain it. `pushq S` makes room by decrementing `%rsp` by 8 and
writes `S` to the new top; `popq D` reads the top into `D` and reclaims the space
by incrementing `%rsp` by 8.

$$
% caption: pushq %rax decrements %rsp by 8 and stores the value at the new top;
% caption: the stack grows downward, so the top sits at the lowest address. popq
% caption: reverses this, reading the top and raising %rsp back.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  cell/.style={draw, minimum width=20mm, minimum height=7mm, inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  % ---- before (left column) ----
  \node[font=\footnotesize] at (0,2.5) {before \texttt{pushq}};
  \node[cell] (b2) at (0,1.4) {...};
  \node[cell] (b1) at (0,0.7) {old top};
  \node[cell, draw=acc] (bn) at (0,0.0) {};
  \node[anchor=west, text=acc, font=\scriptsize] at (1.3,0.7) {<- $\mathtt{\%rsp}$};
  \node[anchor=west, font=\scriptsize] at (1.3,0.0) {lower addr};
  % ---- after (right column) ----
  \node[font=\footnotesize] at (5,2.5) {after \texttt{pushq \%rax}};
  \node[cell] (a2) at (5,1.4) {...};
  \node[cell] (a1) at (5,0.7) {old top};
  \node[cell, draw=acc, fill=acc!8] (an) at (5,0.0) {\texttt{\%rax}};
  \node[anchor=west, text=acc, font=\scriptsize] at (6.3,0.0) {<- $\mathtt{\%rsp}$ (-8)};
  % downward growth arrow between columns, label above to avoid the addr text
  \draw[->, thick] (3.2,1.4) -- (3.2,0.0);
  \node[font=\scriptsize] at (3.2,1.85) {grows down};
\end{tikzpicture}
$$

Because the stack lives in ordinary memory, the top element is reachable as
`(%rsp)`, the element below it as `8(%rsp)`, and so on. `pushq %rbp` is therefore
exactly equivalent to the pair `subq $8, %rsp` then `movq %rbp, (%rsp)`, and
`popq %rax` to `movq (%rsp), %rax` then `addq $8, %rsp`. The single instructions
are shorter encodings of those pairs.

A numeric trace nails the pointer arithmetic. Say `%rsp` holds `0x7fffffffe018`
and `%rax` holds `0x9`. Executing `pushq %rax` does two things in order: it
subtracts 8 from `%rsp`, giving `0x7fffffffe010`, then writes `0x9` to the memory
at that new address. A following `pushq %rbx` (with `%rbx = 0x4`) repeats the
pattern, lowering `%rsp` to `0x7fffffffe008` and storing `0x4` there. Now a
`popq %rcx` reads `(%rsp)` — the `0x4` just pushed — into `%rcx` and adds 8 back,
returning `%rsp` to `0x7fffffffe010`. The stack is last-in, first-out precisely
because `push` and `pop` move `%rsp` in opposite directions by the same 8.

$$
% caption: pushq %rax then pushq %rbx then popq %rcx, tracing %rsp (low bytes shown).
% caption: Each push lowers %rsp by 8 and writes; the pop reads the top and raises
% caption: %rsp by 8, so it returns the last value pushed (4) and restores the pointer.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  trace/.style={draw, minimum width=30mm, minimum height=6.5mm, inner sep=2pt, align=left, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \node[trace] (s0) at (0,1.5)  {$\mathtt{\%rsp}=\mathtt{0xe018}$ (start)};
  \node[trace] (s1) at (0,0.75) {$\mathtt{pushq\ \%rax}$: $\mathtt{\%rsp}=\mathtt{0xe010}$, mem $= 9$};
  \node[trace] (s2) at (0,0.0)  {$\mathtt{pushq\ \%rbx}$: $\mathtt{\%rsp}=\mathtt{0xe008}$, mem $= 4$};
  \node[trace, draw=acc, text=acc] (s3) at (0,-0.75) {$\mathtt{popq\ \%rcx}$: $\mathtt{\%rcx}=4$, $\mathtt{\%rsp}=\mathtt{0xe010}$};
\end{tikzpicture}
$$

This same stack is where `call` saves its return address and where procedures keep
their local frames, the subject of
[procedures](/computer-architecture/machine-level-x86-64/procedures). For now the
key facts are the direction of growth and the role of `%rsp`.

## Wider moves and the red zone

The `mov` family CS:APP presents is the integer core, but the same processor moves
data in wider units the compiler reaches for constantly. The SSE and AVX extensions
add 128-, 256-, and 512-bit vector registers (`%xmm0`, `%ymm0`, `%zmm0`) and their
own move instructions: `movdqa`/`movdqu` copy 16 bytes aligned or unaligned,
`vmovups` copies 32. When gcc vectorizes a loop or inlines a small `memcpy`, the
assembly is full of these wide moves rather than a run of `movq`s, so recognizing
`movdqu (%rsi), %xmm0` as "load 16 bytes" is part of reading optimized output. These
sit in the same source lineage — Intel's own architecture manuals document them
alongside the integer moves — and follow the identical operand grammar, only wider.[^intel]

One System V detail CS:APP mentions only in passing is worth stating outright
because it surprises debugger users: the **red zone**. The ABI reserves the 128
bytes _below_ `%rsp` as scratch space a leaf function (one that calls nothing) may
use without moving `%rsp` at all. A short leaf function can therefore keep locals at
`-8(%rsp)`, `-16(%rsp)`, and so on, emitting no `subq $N, %rsp` prologue, because it
is guaranteed nothing — not even an interrupt handler on the same stack — will
clobber that region. This is why some compiled leaf functions appear to write "below
the stack" and are still correct.[^redzone]

> **Takeaway.** Operands are immediates (`$`), registers (`%`), or memory; at most
> one operand is memory. The addressing mode `D(Rb,Ri,S)` computes
> $\mathrm{Imm} + \mathrm{R}[\mathrm{Rb}] + \mathrm{R}[\mathrm{Ri}] \cdot S$ with
> $S \in \{1,2,4,8\}$, and `lea` returns that address itself instead of the memory
> at it. The stack grows toward lower addresses; `pushq`/`popq` adjust `%rsp` by 8
> and move one quad word across the top.

[^lea]: **Bryant & O'Hallaron**, _CS:APP_, §3.5.1 — Load Effective Address: `leaq` computes the effective address of a memory operand and stores it, doubling as compact arithmetic.
[^intel]: **Intel**, _Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 1_ (2023), §10–§15 — the SSE/AVX/AVX-512 register files and their aligned/unaligned move instructions (`movdqa`, `movdqu`, `vmovups`).
[^redzone]: **Matz, Hubička, Jaeger & Mitchell**, _System V Application Binary Interface, AMD64 Architecture Processor Supplement_ (v1.0, 2018), §3.2.2 — the 128-byte red zone below `%rsp` that leaf functions may use without adjusting the stack pointer.
