---
title: The Machine's View
module: Machine-Level Programming
moduleNumber: 1
lessonNumber: 1
order: 101
summary: >
  The instruction set architecture is the contract a compiler writes against: the
  program counter, sixteen integer registers with their sub-register widths, and
  the condition codes. We follow one C function down through gcc to assembly,
  learn to read an instruction as operation plus operands, and fix the vocabulary
  the rest of the module uses.
topics: [Machine-Level Programming]
sources:
  - book: Bryant & O'Hallaron
    ref: "CS:APP — §3 Machine-Level Representation of Programs (§3.1–§3.4 A Historical Perspective, Program Encodings, Data Formats, Accessing Information)"
---

C presents typed variables, scopes, and structured control. The
processor sees only a stream of **instructions**, a handful of
**registers**, and a flat array of bytes called memory. The seam between those two
views — the precise set of things the hardware promises to do and the state it
exposes — is the **instruction set architecture**, the ISA. This module reads
machine-level x86-64, and this first lesson lays out the state the machine makes
visible and the pipeline that turns your C into it.

## Why read assembly at all

You write in C, and almost never in assembly. So why learn to read the assembly
the compiler emits? Four reasons recur through this module, and each involves
information the C source does not show.

- **To see what the compiler actually produced.** Two source programs that look
  equally fast can compile to very different instructions; the only way to know
  which loop got vectorized, which call got inlined, or which bounds check
  survived is to read the output.
- **To reason about performance.** When a hot loop is slower than it should be,
  the answer lives in the instructions — an extra memory load, a mispredicted
  branch, a division the compiler could not strength-reduce. Assembly is where
  those costs become visible.
- **To understand security.** Buffer overflows, return-address hijacking, and the
  defenses against them are all phenomena of the machine level, invisible in the
  source. The [final lesson](/computer-architecture/machine-level-x86-64/memory-layout-and-buffer-overflows)
  is unreadable without this one.
- **To debug at the metal.** When a debugger drops you into a crash with no source
  — an optimized library, a stripped binary, a corrupted stack — the instructions
  are all you have.

> **Definition (Assembly language).** A human-readable spelling of the machine
> code a CPU executes. Each line names one machine instruction with a short
> mnemonic and its operands; an **assembler** translates that text, almost
> one-to-one, into the raw instruction bytes the processor decodes.

Assembly _is_ the machine
code, written in names instead of bytes. That near one-to-one correspondence is what
makes it worth reading: every line names an operation the hardware performs.

## How to read one line

First, fix the shape of a single instruction. In the
AT&T syntax this module uses (the default in GAS, the GNU assembler), one line is
a **mnemonic** naming the operation, followed by its **operands**, and operands
run **source first, destination last** — the reverse of Intel syntax. Three
sigils tell you what each operand _is_.

$$
% caption: One line of AT&T assembly. The mnemonic names the operation; the sigils
% caption: classify each operand: $ for a literal constant, % for a register, and
% caption: parentheses for the memory at an address. Operands read source then dest.
\begin{tikzpicture}[font=\footnotesize, >=stealth]
  \definecolor{acc}{HTML}{2348F2}
  \node (op)  at (0,0)   {$\mathtt{add}$};
  \node (sfx) at (0.85,0) {$\mathtt{q}$};
  \node (imm) at (2.3,0) {$\mathtt{{\char36}8}$};
  \node       at (3.0,0) {\texttt{,}};
  \node (reg) at (4.2,0) {$\mathtt{\%rax}$};
  \node[text=acc, font=\scriptsize, align=center] at (0.4,1.2) {operation + size};
  \draw[acc, ->] (0.4,0.85) -- (0.4,0.3);
  \node[text=acc, font=\scriptsize, align=center] at (2.3,-1.2) {{\char36} = literal\\(source)};
  \draw[acc, ->] (2.3,-0.85) -- (imm.south);
  \node[text=acc, font=\scriptsize, align=center] at (4.2,-1.2) {\% = register\\(dest)};
  \draw[acc, ->] (4.2,-0.85) -- (reg.south);
\end{tikzpicture}
$$

Read that line as "add the constant 8 into the register `%rax`." The three sigils
are the whole alphabet of operands: **`$`** marks an immediate (a literal number,
like `$8`), **`%`** marks a register (`%rax`), and **parentheses** mark a memory
operand — `(%rax)` means "the bytes _at_ the address held in `%rax`," not the
register itself. A bare name with none of these, like a jump target, is a label.

## A glossary of the core instructions

The entire module uses about fifteen instructions; the lessons introduce each in
turn, with its semantics, before using it. The table below anchors the vocabulary
and serves as a reference to glance back at.

| Mnemonic | What it does (in words) |
| --- | --- |
| `mov` | copy a value from source to destination |
| `lea` | compute an address (or a sum) without touching memory |
| `add` / `sub` | add / subtract source into destination |
| `imul` | multiply destination by source |
| `and` / `or` / `xor` | bitwise AND / OR / XOR into destination |
| `sal` / `shr` / `sar` | shift bits left / right (logical) / right (sign-keeping) |
| `cmp` | subtract, set flags, discard the result |
| `test` | bitwise-AND, set flags, discard the result |
| `jmp` / `jXX` | jump always / jump if a condition holds |
| `push` / `pop` | push a value onto / pop one off the stack |
| `call` / `ret` | call a procedure / return from one |

Each row is one lesson's worth of detail compressed to a line. With the reading
rules and this map in hand, the rest of the lesson can name the state these
instructions act on.

## The ISA is a contract

The ISA is the boundary at which hardware and software agree on a vocabulary. It
fixes the instructions, their byte encodings, the register set, the addressing
modes, and the effect each instruction has on machine state. Everything above it,
your compiler and your program, is written against that contract; everything below
it — the pipeline, the caches, the out-of-order machinery — is unconstrained so
long as the contract's behavior is preserved.[^isa]

> **Definition (Instruction set architecture).** The programmer-visible model of a
> processor: the set of instructions it executes, their encodings, and the
> registers and memory they read and write. The ISA is the abstraction a compiler
> targets; a microarchitecture is one particular hardware implementation of it.

The programmer-visible state of x86-64 is small enough to name on one page: a
**program counter**, sixteen general-purpose **integer registers**, a set of
single-bit **condition codes**, and **memory**. Fix those four and you have fixed
everything an instruction can touch.

- The **program counter** `%rip` holds the address of the next instruction. Almost
  every instruction advances it to the following instruction; jumps and calls
  overwrite it outright.
- The **integer registers** are sixteen 64-bit slots holding values and addresses.
- The **condition codes** record information about the most recent arithmetic
  result, and feed the conditional jumps of a later lesson on
  [control flow](/computer-architecture/machine-level-x86-64/control-flow).
- **Memory** is the byte-addressable array from the
  [first module](/computer-architecture/foundations/bits-bytes-and-words), accessed
  by address.

$$
% caption: The whole programmer-visible state of x86-64 on one page: a program
% caption: counter, sixteen integer registers, four condition-code flags, and a flat
% caption: byte-addressed memory. Every instruction reads and writes only these.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  box/.style={draw, align=center, inner sep=3pt},
  reg/.style={draw, minimum width=9mm, minimum height=5mm, inner sep=0pt, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  % program counter
  \node[box, minimum width=20mm] (pc) at (0,2.2) {$\mathtt{\%rip}$\\program counter};
  % register file
  \node[box, minimum width=34mm, minimum height=22mm] (rf) at (0,0) {};
  \node[anchor=north, font=\scriptsize] at (0,1.05) {16 integer registers};
  \foreach \i in {0,1,2,3} \node[reg] at (-1.1+\i*0.74,0.35) {};
  \foreach \i in {0,1,2,3} \node[reg] at (-1.1+\i*0.74,-0.25) {};
  % condition codes
  \node[box, minimum width=20mm] (cc) at (0,-1.9) {CF\, ZF\, SF\, OF\\condition codes};
  % memory column on the right
  \node[box, minimum width=20mm, minimum height=34mm] (mem) at (5.0,0.15) {};
  \node[anchor=north] at (5.0,1.7) {memory};
  \foreach \y in {1.0,0.45,-0.1,-0.65,-1.2} \draw (4.3,\y) -- (5.7,\y);
  \node[font=\scriptsize, align=center] at (5.0,-2.05) {f\/lat array\\of bytes};
  \draw[<->, thick] (1.75,0) -- (3.95,0) node[midway, above, font=\scriptsize] {loads};
  \draw[<->, thick] (1.75,-0.4) -- (3.95,-0.4) node[midway, below, font=\scriptsize] {stores};
\end{tikzpicture}
$$

## The register file

x86-64 exposes sixteen general-purpose integer registers, each 64 bits wide. The
names are historical baggage made permanent: `%rax` through `%rsp` carry forward
the 8086's accumulator, base, count, and data registers, while `%r8` through
`%r15` were added with the 64-bit extension and have plainly numbered names.

$$
% caption: The sixteen 64-bit integer registers. Two carry dedicated roles —
% caption: %rsp is the stack pointer, %rbp the optional frame pointer — and the
% caption: rest are general-purpose under the calling convention.
\begin{tikzpicture}[font=\footnotesize,
  reg/.style={draw, minimum width=15mm, minimum height=7mm, inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  \foreach \name/\x/\y in {
    {\%rax}/0/0, {\%rbx}/0/-1, {\%rcx}/0/-2, {\%rdx}/0/-3,
    {\%rsi}/2.4/0, {\%rdi}/2.4/-1, {\%r8}/2.4/-2, {\%r9}/2.4/-3,
    {\%r10}/4.8/0, {\%r11}/4.8/-1, {\%r12}/4.8/-2, {\%r13}/4.8/-3,
    {\%r14}/7.2/0, {\%r15}/7.2/-1} {
    \node[reg] at (\x,\y) {$\mathtt{\name}$};
  }
  \node[reg, draw=acc, text=acc, thick] at (7.2,-2) {$\mathtt{\%rbp}$};
  \node[reg, draw=acc, text=acc, thick] at (7.2,-3) {$\mathtt{\%rsp}$};
  \node[anchor=west, text=acc] at (9.0,-2) {frame pointer};
  \node[anchor=west, text=acc] at (9.0,-3) {stack pointer};
\end{tikzpicture}
$$

Two of these registers carry a fixed duty. `%rsp` is the **stack pointer**, always
holding the address of the top of the run-time stack, and is touched by `push`,
`pop`, `call`, and `ret`. `%rbp` is conventionally the **frame pointer**, though
modern compilers often free it for general use. The remaining fourteen are
assigned roles by the calling convention rather than the hardware, a point we make
precise in [procedures](/computer-architecture/machine-level-x86-64/procedures).

### Sub-register widths

Each register can be accessed at four widths, and the name changes with the width.
The low byte, low 16 bits, and low 32 bits of a register all have their own names,
a direct consequence of x86-64's evolution from a 16-bit then 32-bit machine.

$$
% caption: The four access widths of %rax. The same 64-bit slot is named %rax,
% caption: %eax, %ax, or %al depending on how many low bytes the instruction touches.
\begin{tikzpicture}[font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  % full 64-bit bar split into regions
  \draw (0,0) rectangle (8,0.8);
  \draw (4,0) -- (4,0.8);
  \draw (6,0) -- (6,0.8);
  \draw (7,0) -- (7,0.8);
  % width labels above
  \node at (4,1.15) {64 bits};
  \node[text=acc] at (7.5,0.4) {$\mathtt{\%al}$};
  \node[text=acc] at (6.5,0.4) {$\mathtt{\%ax}$};
  \node[text=acc] at (5,0.4) {$\mathtt{\%eax}$};
  \node[text=acc] at (2,0.4) {$\mathtt{\%rax}$};
  % byte-position markers below
  \node[anchor=north] at (8,-0.1) {bit 0};
  \node[anchor=north] at (0,-0.1) {bit 63};
  \node[anchor=north] at (7,-0.1) {8};
  \node[anchor=north] at (6,-0.1) {16};
  \node[anchor=north] at (4,-0.1) {32};
\end{tikzpicture}
$$

Another way to picture it: the smaller names are nested **inside** the larger one,
each naming a prefix of the low bytes. `%al` is the lowest byte, `%ax` the lowest
two, `%eax` the lowest four, and `%rax` the whole 64-bit slot. Naming a width
selects how deep into that nesting an instruction reaches.

$$
% caption: The sub-register names as nested boxes over one 64-bit slot. %al is the
% caption: low byte, %ax the low 2, %eax the low 4, %rax all 8; each name selects a
% caption: prefix of the low bytes, and a write to %eax also clears the upper 4.
\begin{tikzpicture}[font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  % outermost: rax (64 bits)
  \draw (0,0) rectangle (8.4,2.6);
  \node[anchor=north west, text=acc] at (0.1,2.55) {$\mathtt{\%rax}$ (8 bytes)};
  % eax (low 4 bytes)
  \draw[fill=acc!4] (4.2,0.2) rectangle (8.2,1.9);
  \node[anchor=north west, text=acc] at (4.3,1.85) {$\mathtt{\%eax}$ (4)};
  % ax (low 2 bytes)
  \draw[fill=acc!8] (6.2,0.4) rectangle (8.0,1.2);
  \node[anchor=north west, text=acc] at (6.3,1.15) {$\mathtt{\%ax}$};
  % al (low byte)
  \draw[fill=acc!16] (7.1,0.55) rectangle (7.9,0.95);
  \node[text=acc, font=\scriptsize] at (7.5,0.75) {$\mathtt{\%al}$};
  \node[anchor=north, font=\scriptsize] at (8.4,-0.1) {bit 0};
  \node[anchor=north, font=\scriptsize] at (0,-0.1) {bit 63};
\end{tikzpicture}
$$

The four widths line up with the four assembly size suffixes from the
[foundations module](/computer-architecture/foundations/bits-bytes-and-words):
byte, word, double word, quad word. The table fixes the naming for the legacy
registers and the numbered ones.

| 64-bit | 32-bit | 16-bit | 8-bit | Width suffix |
| --- | --- | --- | --- | --- |
| `%rax` | `%eax` | `%ax` | `%al` | `b`/`w`/`l`/`q` |
| `%rbx` | `%ebx` | `%bx` | `%bl` | |
| `%rdi` | `%edi` | `%di` | `%dil` | |
| `%rsi` | `%esi` | `%si` | `%sil` | |
| `%r8` | `%r8d` | `%r8w` | `%r8b` | |

One rule with real consequences: an instruction that writes a register's low **4 bytes**
(an `l`-suffixed operation into `%eax`) **zeroes the upper 4 bytes** of the full
register. Writing the low 1 or 2 bytes (`%al`, `%ax`) leaves the rest unchanged.
This asymmetry is a frequent source of surprise when reading compiled code.

For example, start with
`%rax` holding `0x0011223344556677`, then apply a write at each of the three narrow
widths and compare which upper bytes are preserved.

$$
% caption: Three narrow writes into a register that starts at 0x0011223344556677.
% caption: Writing %al (movb 0xFF) or %ax (movw 0xFFFF) leaves the upper bytes intact
% caption: (shown plain); writing %eax (movl) zeroes the whole upper half (shown accented).
\begin{tikzpicture}[font=\footnotesize,
  keep/.style={draw, minimum width=34mm, minimum height=6.5mm, inner sep=2pt},
  chg/.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,1.5) {start:};
  \node[keep] at (2.4,1.5) {$\mathtt{0011\,2233\,4455\,6677}$};
  \node[anchor=east, font=\scriptsize] at (0.2,0.5) {$\mathtt{movb\ {\char36}0xFF,\%al}$};
  \node[keep] at (2.4,0.5) {$\mathtt{0011\,2233\,4455\,66FF}$};
  \node[anchor=east, font=\scriptsize] at (0.2,-0.5) {$\mathtt{movw\ {\char36}0xFFFF,\%ax}$};
  \node[keep] at (2.4,-0.5) {$\mathtt{0011\,2233\,4455\,FFFF}$};
  \node[anchor=east, font=\scriptsize] at (0.2,-1.5) {$\mathtt{movl\ {\char36}0xFFFFFFFF,\%eax}$};
  \node[chg] at (2.4,-1.5) {$\mathtt{0000\,0000\,FFFF\,FFFF}$};
\end{tikzpicture}
$$

Read the rows top to bottom. `movb` touched only the last byte pair, `movw` the
last two, and both left everything to their left untouched. The `movl` is the odd
one out: it wrote the low four bytes `FFFFFFFF` **and** silently cleared the high
four to zero, so the result is not `0011223344...` with a new low half but a clean
`0x00000000FFFFFFFF`. The reason is worth internalizing now, because it recurs in
every later lesson: 64-bit x86 chose upper-zeroing for 32-bit writes so that
`int`-to-`long` promotions need no extra instruction, and so that the processor
need not carry a false dependency on a register's stale upper half. The cost is that
`%al`/`%ax` writes, kept for backward compatibility, leave a partial-register hazard
the 32-bit form does not.

## From C to instructions

A compiled program is produced in several stages, each transforming one
representation into the next. The chain from source to running process is worth
knowing as a whole, because every later lesson works at one stage of it.

$$
% caption: The compile pipeline. The compiler proper turns C into assembly text;
% caption: the assembler encodes it into object-file bytes; the linker resolves
% caption: references across objects and libraries into a single executable.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  box/.style={draw, minimum width=15mm, minimum height=9mm, align=center},
  stage/.style={font=\scriptsize, text=acc, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box] (c)   at (0,0)    {\texttt{prog.c}\\C source};
  \node[box] (s)   at (3.3,0)  {\texttt{prog.s}\\assembly};
  \node[box] (o)   at (6.6,0)  {\texttt{prog.o}\\object};
  \node[box, draw=acc, thick] (exe) at (9.9,0) {\texttt{prog}\\executable};
  \draw[->, thick] (c) -- (s)   node[stage, midway, above] {gcc -S};
  \draw[->, thick] (s) -- (o)   node[stage, midway, above] {as};
  \draw[->, thick] (o) -- (exe) node[stage, midway, above] {ld};
  \node[font=\scriptsize, align=center] at (1.65,-1.1) {compiler};
  \node[font=\scriptsize, align=center] at (4.95,-1.1) {assembler};
  \node[font=\scriptsize, align=center] at (8.25,-1.1) {linker};
\end{tikzpicture}
$$

In practice `gcc` drives all of these for you, but we will stop at the assembly
stage on purpose. We compile with `-Og`, which limits optimization
so the assembly still tracks the source, and `-S`, which stops after
generating assembly so we can read it.[^og]

```c [mstore.c]
long mult2(long, long);

void multstore(long x, long y, long *dest) {
    long t = mult2(x, y);
    *dest = t;
}
```

Compiling with `gcc -Og -S mstore.c` produces a `.s` file. Stripped to the body of
`multstore`, the assembly is short, and every line is one instruction. Later
lessons define these mnemonics formally; in brief:
`pushq` saves a register's value on the stack; `movq` copies eight bytes;
`call` invokes a function; `popq` restores a saved value; `ret` returns to the
caller. That is enough to follow the trace.

```asm [mstore.s]
multstore:
        pushq   %rbx              # save callee-saved %rbx
        movq    %rdx, %rbx        # stash dest pointer in %rbx
        call    mult2             # t = mult2(x, y), result in %rax
        movq    %rax, (%rbx)      # *dest = t
        popq    %rbx              # restore %rbx
        ret                       # return to caller
```

Reading this needs only a little vocabulary, all of which the next lessons make
precise. The first two arguments `x` and `y` arrived in `%rdi` and `%rsi`, the
pointer `dest` in `%rdx`; `call` invokes `mult2`, which leaves its result in
`%rax`; and `movq %rax, (%rbx)` stores that result through the pointer. The
`pushq`/`popq` pair preserves `%rbx` across the call, a calling-convention
obligation from [procedures](/computer-architecture/machine-level-x86-64/procedures).

### Assembly is bytes, one step down

The claim that assembly _is_ the machine code is not a figure of speech. Assemble
the file (`gcc -c mstore.c`) and disassemble the object (`objdump -d mstore.o`),
and each mnemonic reappears next to the exact bytes it encodes.

```asm [mstore.o disassembly]
0000000000000000 <multstore>:
   0:   53                      push   %rbx
   1:   48 89 d3                mov    %rdx,%rbx
   4:   e8 00 00 00 00          call   9 <multstore+0x9>
   9:   48 89 03                mov    %rax,(%rbx)
   c:   5b                      pop    %rbx
   d:   c3                      ret
```

The left column is the byte offset of each instruction inside the function; the
middle column is the raw bytes; the right column is the same mnemonics you already
read. Notice three things a byte view makes plain. First, instructions have
**different lengths**: `push %rbx` is one byte (`53`), while `mov %rdx,%rbx` needs
three (`48 89 d3`) — x86-64 is a variable-length encoding, so you cannot find the
next instruction without decoding the current one. Second, the offsets are not
uniform for the same reason: the `call` at offset `4` occupies five bytes, so the
`mov` after it lands at offset `9`, not `8`. Third, the `call`'s four target bytes
are all `00`: the linker has not yet resolved where `mult2` lives, so those bytes
are a placeholder the linker will patch. Reading assembly is reading these bytes
with the mnemonics restored; the two are the same program at two zoom levels.

## The anatomy of an instruction

Every assembly instruction is an **operation** together with zero or more
**operands**. In the AT&T syntax that GAS and this course use, the operation is a
mnemonic, an optional **size suffix** picks the operand width, and operands are
written **source first, destination last** — the opposite of Intel syntax.[^att]

$$
% caption: One instruction dissected. The mnemonic plus its size suffix names the
% caption: operation; operands follow source-then-destination, so this copies the
% caption: 8-byte value in %rax into the memory location addressed by %rbx.
\begin{tikzpicture}[font=\footnotesize, >=stealth]
  \definecolor{acc}{HTML}{2348F2}
  % the instruction, spaced out
  \node (mov) at (0,0)   {$\mathtt{mov}$};
  \node (q)   at (0.9,0) {$\mathtt{q}$};
  \node (src) at (2.4,0) {$\mathtt{\%rax}$};
  \node (dst) at (4.4,0) {$\mathtt{(\%rbx)}$};
  \node at (3.4,0) {\texttt{,}};
  % brace-style annotations, no crossing
  \node[text=acc, font=\scriptsize, align=center] at (0,1.0) {operation};
  \draw[acc, ->] (0,0.75) -- (mov.north);
  \node[text=acc, font=\scriptsize, align=center] at (0.9,1.6) {size suf\/f\/ix\\(8 bytes)};
  \draw[acc, ->] (0.9,1.1) -- (q.north);
  \node[text=acc, font=\scriptsize, align=center] at (2.4,-1.0) {source};
  \draw[acc, ->] (2.4,-0.75) -- (src.south);
  \node[text=acc, font=\scriptsize, align=center] at (4.4,-1.0) {destination};
  \draw[acc, ->] (4.4,-0.75) -- (dst.south);
\end{tikzpicture}
$$

Read aloud, `movq %rax, (%rbx)` is "move the quad word in `%rax` into the memory
at the address held in `%rbx`." The parentheses mark a memory operand — "the bytes
**at** this address" — versus a bare register, which means the register's own
contents. That distinction, and the richer addressing forms it opens up, is the
subject of the next lesson on
[data movement](/computer-architecture/machine-level-x86-64/data-movement).

## One ISA among several

CS:APP teaches x86-64 because it is the machine most readers actually run, but its
sixteen named registers and variable-length encoding are one point in a wide design
space, and the contrast sharpens what the ISA _is_. The sixteen general registers
are already generous by the standards of the 8086's eight; the register count is a
deliberate trade, since more registers cut memory traffic but cost bits in every
instruction that names one and cost saves and restores at every call.

The instructive contrast is **RISC-V**, the open ISA that grew out of Berkeley's
decades of reduced-instruction-set work and is now ratified by RISC-International.
Where x86-64 has a variable-length encoding (1 to 15 bytes) and instructions that
read one operand from memory, the RISC-V base integer ISA fixes every instruction at
32 bits, exposes 32 general registers, and touches memory only through explicit
`load` and `store` instructions — the same load-store discipline ARM's AArch64
follows with its own 31 general registers. The `multstore` above would compile on
RISC-V to a load, an explicit call, and a store, never a `mov` with a memory operand,
because no RISC-V arithmetic or move instruction has one. That the same C program can
target both, unchanged, is the whole point of the ISA-as-contract abstraction: the
compiler retargets, the source does not.[^riscv]

The other lesson from the wider literature is that the visible ISA and the hardware
underneath it diverged long ago. Since the Pentium Pro (1995), Intel and AMD
processors have decoded each x86 instruction into one or more internal **micro-ops**
and executed those out of order, so the sixteen architectural registers are renamed
onto a much larger physical register file the programmer never sees. The ISA is the
behavior the hardware guarantees; the microarchitecture underneath may be arbitrarily
elaborate, and that separation is the one Hennessy and Patterson build their
whole treatment of computer architecture around.[^hp]

> **Takeaway.** The ISA is the contract a compiler targets: a program counter
> `%rip`, sixteen 64-bit integer registers each addressable at four widths
> (`%rax`/`%eax`/`%ax`/`%al`), condition codes, and memory. Your C becomes
> instructions through compile, assemble, and link; an instruction is an operation
> with a size suffix and operands written source-then-destination.

[^isa]: **Bryant & O'Hallaron**, _CS:APP_, §3.1 — A Historical Perspective: the ISA as the model the compiler targets, distinct from the microarchitecture that implements it.
[^og]: **Bryant & O'Hallaron**, _CS:APP_, §3.2 — Program Encodings: `gcc -Og -S` generates assembly at an optimization level that keeps the output readable against the source.
[^att]: **Bryant & O'Hallaron**, _CS:APP_, §3.3 — Data Formats and §3.4: the ATT assembly convention, size suffixes `b`/`w`/`l`/`q`, and source-before-destination operand order.
[^riscv]: **Waterman & Asanović (eds.)**, _The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA_ (2019) — the base RV64I integer ISA: fixed 32-bit encoding, 32 registers, and a load-store architecture with no memory operands on arithmetic instructions.
[^hp]: **Hennessy & Patterson**, _Computer Architecture: A Quantitative Approach_, 6th ed. (2017), §3 — the separation of the architecturally visible ISA from the out-of-order microarchitecture that renames registers and executes internal micro-ops.
