Lesson 3.12,511 words

What an ISA Is

The instruction set architecture is the contract that lets a compiler and a chip be written by people who never meet: the stable interface software targets and hardware implements. We separate architecture from microarchitecture, read RISC and CISC as opposite answers to where complexity should live, price out what each choice costs in decode hardware, code density, and pipeline friendliness, and see how x86-64 endures by translating its instructions into RISC-like operations on the fly.

╌╌╌╌

A compiler team and a chip team can ship a working product without ever speaking, and the reason is a single document neither of them is free to change on a whim: the instruction set architecture. The ISA is the agreed-upon vocabulary: the exact set of instructions, their encodings, the registers and memory they touch, and the effect each one has. The compiler emits only words from that vocabulary; the hardware promises to understand every one of them. Pin the vocabulary down and the two halves of the system can evolve independently for decades. This module builds an ISA from the ground up, and this first lesson says what kind of object an ISA is and what its designers are really arguing about.

The contract between hardware and software

The computing stack is a tower: applications on top, then a language runtime, an operating system, the compiler's output, and at the bottom the silicon. Most of those layers can be swapped freely. The one fixed seam where hardware and software meet is the ISA. It is the only interface in the system that both a piece of software and a piece of hardware are written directly against.

The ISA as a horizontal contract. Everything above it — applications, compilers, operating systems — is written to target the ISA; every microarchitecture below it is one hardware implementation of the same contract, free to differ in speed, power, and internal cleverness.

The payoff of this arrangement is binary compatibility. A program compiled to x86-64 in 2005 still runs on an x86-64 chip built today, even though the two processors share almost no internal structure: different pipelines, different cache sizes, different transistor counts by orders of magnitude. They agree only where they must, at the ISA. Conversely, a chip vendor can redesign the entire internals of a processor and the entire planet's worth of existing software keeps running, untouched, because the contract held.

Compatibility is asymmetric. Let be the instruction set a chip generation decodes. Backward compatibility — new hardware running old binaries — requires , and every vendor must keep it, because the installed software predates the chip and cannot be recompiled to suit it. Forward compatibility — old hardware running binaries built for a newer chip — is not promised: a 2005 processor faults on an instruction in (say an AVX vector op added in 2013) because its decoder was never taught the encoding. Hence the contract can grow but never shrink: encodings may be appended, old software simply never uses them, but no encoding that shipping software depends on may be withdrawn — which is how forty-year-old corners of x86 survive into every new part. The contract can only grow.

What lives in the contract is what software is allowed to depend on: the instructions and their meanings, the registers and their widths, the addressing modes, the byte ordering, how exceptions and interrupts behave. What lives outside it is everything about how the work gets done. The contract also cuts both ways in time: once software ships that depends on some corner of the ISA, even an awkward one, every future implementation must honor it. x86-64 still executes encodings whose design decisions date to the 8086 of 1978, not because anyone likes them, but because dropping them would break binaries nobody can recompile.

Architecture versus microarchitecture

It is tempting to use architecture loosely, but the field draws a hard line, and keeping it straight clears up most confusion about what a processor is.

The architecture is the contract; the microarchitecture is one way of honoring it. Two processors implement the same architecture if a program cannot tell them apart by its results, only by how long it takes. Intel's Skylake and a budget Atom core are the same architecture (x86-64) and wildly different microarchitectures. This separation is what lets the later modules in this course build a complete processor twice over: first as a simple sequential machine, then as a pipelined one, both implementing the same Y86-64 ISA we are about to define.

For example, take the register file. x86-64 the architecture names sixteen general-purpose registers, because its encodings carry 4-bit register fields. A high-end implementation actually contains a few hundred physical registers and renames the sixteen architectural names onto them on the fly, so that independent uses of %rax in nearby instructions can execute in parallel. No program can detect the extra registers; it can only run faster. The sixteen names are architecture; the hundreds of slots and the renaming table are microarchitecture. The same split recurs everywhere: the ISA says a load returns the last value stored, and says nothing about the three levels of cache that make most loads fast; the ISA says instructions execute in program order as far as results are concerned, and says nothing about the out-of-order engine reordering them internally.

The line matters for a working programmer too. The architecture tells you what your code computes; the microarchitecture tells you how fast. Cache misses, branch mispredictions, and pipeline stalls are microarchitectural facts: invisible to correctness, decisive for performance.

To see the two axes vary independently, run one program on three chips that share the architecture. The program is a tight loop summing an array of instructions. All three are x86-64, so they fetch the same bytes and produce the same sum — the architecture holding. Only the cycle count differs. Suppose the array overflows the first-level cache, so each iteration risks a memory stall.

ChipMicroarchitectureCycles
Ain-order, two-level cache
Bout-of-order, wide issue
Cout-of-order, larger LLC

Chip A stalls often; chip B keeps dozens of iterations in flight and hides most stalls behind independent work; chip C keeps more of the array warm. A better than threefold spread in time, one architecture, three microarchitectures. No line of the program changed, and no line could have changed to reveal which chip it ran on — only a stopwatch tells them apart.

One architecture, three microarchitectures. The same x86-64 program (W instructions) produces the same result on all three chips, so the architecture is fixed. Only the running time differs — in-order with a small cache is slowest, out-of-order faster, out-of-order with a big cache faster still — and the program cannot tell which one it ran on.

Two philosophies: RISC and CISC

Designing an ISA forces one central question: when should the hardware do something complicated, and when should it make the compiler do it instead? Two schools answered oppositely, and the names stuck — CISC (Complex Instruction Set Computer) and RISC (Reduced Instruction Set Computer).

CISC, the older style, grew up when memory was scarce and compilers were weak. Its approach is to make each instruction do a lot: a single instruction might load two operands from memory, multiply them, and store the result, all at once. Instructions vary in length so that common operations stay compact. x86 is the canonical CISC: it has hundreds of instructions and encodings from 1 to 15 bytes long.

RISC took the opposite bet, made once compilers improved and memory grew cheap. Keep the instruction set small and uniform: every instruction is the same length (typically 4 bytes), most run in a single cycle, and — the defining rule — only explicit load and store instructions touch memory, while all arithmetic happens register-to-register. The hardware stays simple and fast; the compiler takes on the job of stitching simple instructions into complex behavior.

RISC versus CISC as opposite answers to one question — where complexity lives. RISC pushes it up into the compiler and keeps instructions short, fixed-length, and register-only; CISC pushes it down into rich, variable-length instructions that can compute on memory directly.

What each choice costs

The table reads like a matter of taste until you price the rows out in hardware and in bytes. Three consequences carry most of the weight.

Decode complexity. With a fixed instruction length , instruction of a fetched block starts at byte — the boundaries are known before a single opcode is read, so a block of instructions feeds decoders in parallel. Variable length destroys this: the start of instruction is and on x86 depends on optional prefixes, the opcode, and the addressing-mode byte, so it is not known until instruction is itself decoded. Boundary-finding is inherently serial. To decode four instructions per cycle from a 16-byte block anyway, a wide x86 front end speculatively starts a decoder at every byte offset — up to 15 candidate starts — and discards those that land mid-instruction. That parallel-guess-and-discard is silicon and power spent recovering length information a fixed-length encoding hands over for free.

Code density. Variable length's advantage is bytes. The x86-64 encoding of pushq %rbp is one byte (55); a register-to-register add is three; only instructions hauling large constants stretch toward the maximum. Compiled x86-64 averages bytes per instruction against a flat , so the same program occupies fewer bytes and more of it fits in the instruction cache — a performance asset, not just a storage saving. The RISC camp conceded the point in its own way: ARM's Thumb and RISC-V's compressed extension add 2-byte forms of the most common instructions, trading back a little decode simplicity for density.

Pipeline friendliness. The load/store rule is the most consequential entry in the RISC column. If arithmetic never touches memory, then every instruction needs the same short list of resources in the same order — fetch, decode, read registers, execute, maybe one memory access, write back — and the pipeline can be one clean five-stage structure. A CISC instruction like addq (%rbx), %rax needs a memory read and an ALU operation, and a memory-to-memory instruction would need even more, so either the pipeline grows extra stages that most instructions waste, or the control logic becomes a thicket of special cases. When this course builds a pipelined processor, the regularity of its RISC-flavored ISA is precisely what makes the design fit in a lesson.

The design space, qualitatively. Fixed-length RISC buys cheap decode at a cost in bytes; variable-length CISC packs code tighter and pays in decode hardware. Compressed RISC variants (Thumb, RVC) split the difference. Y86-64's variable-length bytes cost it slightly more decode than classic RISC, and it spends bytes freely.

Why x86-64 survives

By the pricing above, x86-64 should have lost: it drags four decades'-worth of accumulated encodings behind it and pays the full decode tax on every fetch. It survives because modern implementations stopped executing x86 directly. The decoder at the front of every high-end x86 chip translates each incoming instruction into one or more simple, fixed-format internal operations called micro-ops, and everything past the decoder is a RISC-style machine executing those.

The translation is easy to picture on a real instruction. addq 8(%rbx), %rax is one x86-64 instruction that reads memory and adds; the decoder splits it into a load micro-op that fetches the memory operand into an internal temporary, and an add micro-op that combines the temporary with %rax. Simple instructions map one to one; the baroque ones fall back to a lookup ROM that emits longer micro-op sequences. Decoded micro-ops are even cached, so a hot loop skips the x86 decode tax entirely on repeat passes.

How x86-64 survives. The visible ISA stays CISC, but the decoder translates each instruction into fixed-format micro-ops, and everything past the front end is a RISC-style engine. addq 8(%rbx),%rax becomes a load micro-op feeding an add micro-op.

Count the work to see the split pay off. A four-instruction x86-64 loop body expands to fixed-format micro-ops:

x86-64 instructionMicro-ops
movq (%rsi,%rcx,8), %raxaddress-compute + load
addq %rax, %rdxadd
decq %rcxdec
jnebranch

Four architectural instructions become six micro-ops, each doing exactly one register-to-register operation, one memory access, or one branch — the shape a five-stage RISC pipeline was built for. The CISC encoding lives only long enough to be translated; from the micro-op queue onward the machine does RISC work, and on the loop's second pass even translation is skipped, the micro-ops having been cached the first pass.

So the contract is CISC and the machine underneath is essentially RISC, which is the neatest possible demonstration of the architecture/microarchitecture split: the entire translation layer is microarchitecture, invisible to every program ever compiled. The reason vendors pay for that layer rather than switching contracts is economic, not technical. The installed base of x86-64 binaries — operating systems, decades of commercial software nobody can recompile — is worth more than the silicon the decoder costs. Pure RISC designs, meanwhile, dominate phones and embedded systems and have moved into laptops and servers, where fixed-length decoding saves power and no comparable binary legacy holds the door shut.

The RISC resurgence and an open ISA

CS:APP presents the RISC/CISC debate as settled history — the x86-64 it targets won the desktop, and the argument moved inside the decoder. The decade since has reopened the question at both ends of the market, and in a way the pricing above predicts exactly.

Apple Silicon. In 2020 Apple replaced Intel x86-64 in its laptops with the M1, a chip built on the ARM architecture — fixed-length, load/store, the RISC column of the table.1 The pricing predicts why this was possible now and not before. Fixed 4-byte instructions let the M1's front end decode eight instructions per cycle without the speculative boundary-guessing an x86 front end pays for, a width that is brutally expensive on variable-length code and nearly free on fixed-length. The one thing that had held ARM off the laptop for years was the binary legacy — decades of x86 software — and Apple bridged it with Rosetta 2, which translates x86-64 binaries to ARM ahead of time. That is the same move x86 makes internally (translate one ISA into another), lifted up into software and run once at install time rather than continuously in hardware.

RISC-V. The other development is that the RISC idea became an open standard anyone may implement without a license. RISC-V, whose base integer ISA was ratified in 2019, is a clean modern take on the RISC column: fixed 4-byte instructions, a load/store discipline, a small base set, and — conceding exactly the density point priced above — an optional compressed extension (C) adding 2-byte forms of the commonest instructions.2 The compressed extension is the design space diagram made real: RISC-V starts at classic-RISC decode simplicity and buys back some density by adding a second instruction length, landing where compressed RISC sits on the plot. That an ISA can be a public specification with dozens of independent implementations, rather than one company's product, is the architecture/ microarchitecture split taken to its limit — the contract belongs to no one, and the implementations compete freely beneath it.

With the idea of a contract in hand, the next lesson opens it up: how an instruction actually encodes an operation and its operands, and how many operands a machine even names, in instruction formats and operands.

Footnotes

  1. The Apple M1 (2020) implements the ARMv8-A architecture, a fixed-length load/store (RISC) ISA; its wide instruction decoder and the Rosetta 2 x86-64 translation layer are documented in Apple's platform materials and analyzed in detail by Johlin and others in the trade press. The general point — that fixed-length encoding enables very wide decode cheaply — is the code-density/ decode-complexity trade of CS:APP §4.1 applied to a modern part.
  2. A. Waterman and K. Asanović, eds., The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA (RISC-V International). The base integer ISA (RV32I/ RV64I) was ratified in 2019; the C compressed extension adds 16-bit encodings of common instructions, trading a second instruction length for code density exactly as ARM's Thumb does.

╌╌ END ╌╌