Out-of-Order Execution

A single stalled instruction should not freeze an entire processor pipeline. Consider a load that misses in cache and takes 200 cycles to return. Ten independent arithmetic instructions are ready to execute immediately afterward. A strictly in-order pipeline stalls all ten behind the load. This wastes hundreds of cycles of available Instruction-Level Parallelism. Out-of-order execution lets the hardware discover and exploit that parallelism at run time. Instructions execute as soon as their true data dependences are satisfied, regardless of program order. This idea underlies every high-performance general-purpose processor built since the mid-1990s. Examples include Intel's Pentium Pro and its descendants, AMD's Zen line, Arm's Cortex-A, and Apple's M-series cores. The challenge is subtle. Executing instructions out of order must never change the meaning of the program. The processor must produce results identical to some in-order execution. It must handle exceptions and mispredicted branches precisely. It must not let false register-reuse dependences throttle throughput. Solving this requires three cooperating mechanisms. Register renaming eliminates artificial dependences. Reservation stations buffer and dynamically schedule instructions. A reorder buffer makes results retire in program order even though they were computed out of order. The design was first realized commercially in the IBM System/360 Model 91. It was refined into its modern form by Robert Tomasulo's algorithm and Yale Patt's HPS work. Its elegance lies in achieving this illusion of sequential execution using entirely distributed, asynchronous hardware. No instruction ever waits for a stage simply because an earlier instruction is slow. Only true dependences cause stalls. This article builds the three mechanisms up from first principles. It gives a cycle-by-cycle worked trace. It explains precisely why the resulting execution is guaranteed to be correct. By the end, you will be able to trace an instruction from fetch through dispatch, wakeup, execution, and in-order commit. You will be able to explain why renaming removes WAR and WAW hazards. You will also be able to reason about the throughput and complexity costs of the associative hardware that makes it all possible.

Key takeaways
  • Out-of-order execution issues instructions based on data readiness, not program order, to hide latency and exploit ILP.
  • Register renaming maps architectural registers to physical registers, eliminating WAR and WAW false dependences.
  • Reservation stations buffer instructions near functional units and dispatch them via a broadcast/wakeup mechanism once operands are ready.
  • A reorder buffer (ROB) tracks in-flight instructions and forces results to commit to architectural state in program order.
  • Precise exceptions and correct speculative-recovery (e.g., branch mispredicts) are guaranteed entirely by in-order commit from the ROB.
  • The cost is associative, power-hungry hardware — CAMs, wide broadcast buses — that scales poorly beyond tens of in-flight instructions.
The core insight

Out-of-order execution schedules instructions by data dependence rather than program order, while an in-order commit stage makes the result look exactly as if nothing had been reordered at all.

The idea

An in-order pipeline issues instructions strictly in the order the compiler produced them; if instruction i stalls, every instruction after it stalls too, even if instruction i+1 has all its operands ready. Out-of-order execution (OoOE) breaks this rigidity by separating instruction fetch and decode order from execution order from commit order. Instructions are fetched and decoded in program order, dispatched into a pool of waiting instructions, executed as soon as their operands become available (possibly in a completely different order than they were fetched), and finally committed — writing results into architectural state — strictly in program order again. Three structures make this possible. Register renaming replaces architectural register names with entries in a larger pool of physical registers, so that two instructions that happen to write the same architectural register (e.g., both writing `R1`) are given distinct physical registers and no longer create a false dependence. This eliminates WAR (write-after-read) and WAW (write-after-write) hazards — anti-dependences and output dependences — leaving only true RAW (read-after-write) data dependences, which are the only ones a correct execution must respect. Reservation stations (or, in modern designs, a unified scheduler/issue queue) hold decoded instructions with their renamed source operands, waiting until those operands are produced by other functional units; a common data bus (CDB) or broadcast network delivers each result to every waiting station simultaneously, "waking up" instructions whose last needed operand just arrived. The reorder buffer (ROB) is a circular queue, in program order, of all instructions currently in flight between dispatch and commit; each entry tracks whether the instruction has finished execution and holds its (renamed) destination and result, and the processor retires entries from the head of the ROB only when they are complete, in strict order. The result is a machine that behaves, from the outside, exactly like an in-order processor — same final register and memory state, same exceptions raised at the same points — while internally exploiting far more parallelism. This design pattern is often summarized as "dispatch and commit in order, execute out of order," and it is the defining feature of every modern superscalar core.

How it works

The classic realization of this idea is Tomasulo's algorithm, originally designed for the IBM 360/91's floating-point unit and now used, in spirit, in nearly all out-of-order cores. It has four logical phases per instruction, and a single global broadcast mechanism ties them together.

1. Dispatch (issue into the machine). The instruction is decoded, allocated an entry at the tail of the ROB, and allocated a reservation station at a compatible functional unit. Its architectural source registers are renamed: for each source, the register alias table (RAT) is consulted — if the architectural register has an outstanding rename (an in-flight producer), the reservation station records the producer's tag (typically its ROB or physical-register-file index) instead of a value; if there is no outstanding producer, the current committed value is read directly from the register file. The instruction's destination register is renamed to a fresh physical register / ROB entry, and the RAT is updated to point future readers of that architectural register to this new producer.

2. Wait / wakeup. The reservation station holds the instruction until all of its source operand tags have been resolved to actual values. Every cycle, the CDB broadcasts the tag and value of instructions that just finished executing; every reservation station compares (associatively, in parallel) its own pending source tags against the broadcast tag, and if a match occurs, it captures the value. This is the wakeup step. An instruction becomes ready to execute — is selected for dispatch to a functional unit — once all of its operands have arrived.

3. Execute. Once selected, the instruction is sent to its functional unit (ALU, multiplier, load/store unit, etc.) for however many cycles that operation requires. Structural hazards are resolved here: if more instructions are ready than there are functional-unit ports, an arbitration/select policy (e.g., oldest-first) picks a subset each cycle.

4. Write result / broadcast. When execution finishes, the result and its producer tag are broadcast on the CDB, simultaneously updating the ROB entry (marking it complete, storing the value) and waking up any reservation stations waiting on that tag. The reservation station is then freed for reuse.

5. Commit (retire). Independently of execution order, the processor inspects the head of the ROB every cycle. If the instruction at the head has completed (and did not fault, and — if it was a branch — was correctly predicted), its result is written from the ROB into the architectural register file (or, for a store, the memory system), the head pointer advances, and the corresponding rename in the RAT is retired if no younger instruction still refers to it. If the head instruction has not completed, commit stalls for that cycle — no younger instruction, however finished, can commit ahead of it. This in-order-commit rule is what makes the whole machine precise. ``` // One cycle of an out-of-order core, Tomasulo-style, informal pseudocode structure ROBEntry { valid, busy, done, dest_areg, value, exception, is_branch, mispredicted } structure RSEntry { busy, op, tag_src1, val_src1, ready1, tag_src2, val_src2, ready2, dest_tag } ROB : circular queue of ROBEntry, size N, head/tail pointers RAT : map from architectural register -> ROB tag (or "none" = use register file) RegFile : architectural register values (committed state only) RS : array of RSEntry per functional-unit class function DISPATCH(instr): if ROB is full or no free RS for instr's unit: stall tag = ROB.tail (v1, t1, ready1) = resolve(instr.src1) // read RegFile or RAT (v2, t2, ready2) = resolve(instr.src2) rs = allocate RSEntry(instr.op, t1, v1, ready1, t2, v2, ready2, dest_tag=tag) RAT[instr.dest] = tag ROB.push(ROBEntry(valid=true, busy=true, done=false, dest_areg=instr.dest)) RS.insert(rs) function resolve(areg): if RAT[areg] == none: return (RegFile[areg], -, ready=true) else: t = RAT[areg] if ROB[t].done: return (ROB[t].value, -, ready=true) else: return (-, t, ready=false) // wait for broadcast tag t function WAKEUP_AND_SELECT(): for rs in RS where rs.busy and not rs.ready1 or not rs.ready2: pass // handled by BROADCAST below ready_set = { rs in RS : rs.busy and rs.ready1 and rs.ready2 } chosen = arbitrate(ready_set) // e.g., oldest-first, per functional unit for rs in chosen: send rs to its functional unit; rs.busy = false (in flight) function EXECUTE_AND_BROADCAST(): for each functional unit finishing this cycle: (tag, value, exception) = result ROB[tag].done = true ROB[tag].value = value ROB[tag].exception = exception for rs in RS: // associative broadcast compare if rs.tag_src1 == tag: rs.val_src1 = value; rs.ready1 = true if rs.tag_src2 == tag: rs.val_src2 = value; rs.ready2 = true function COMMIT(): entry = ROB[ROB.head] if entry.valid and entry.done: if entry.exception: handle_exception(); flush_pipeline(); return if entry.is_branch and entry.mispredicted: flush_pipeline(); redirect_fetch(); return RegFile[entry.dest_areg] = entry.value if RAT[entry.dest_areg] == ROB.head: RAT[entry.dest_areg] = none ROB.pop() // else: stall commit this cycle ``` The crucial global invariant enforced by this pseudocode is: a physical result never overwrites architectural state until its ROB entry reaches the head and is proven exception-free and correctly speculated. Everything upstream of commit — dispatch, wakeup, execute, broadcast — can happen in any order the data dependences allow.

Figure
Out-of-order pipeline: renaming, reservation stations, and in-order commit via the ROB
Fetch/Decode Register Alias Table Reg File (arch.) Reservation Stations RS1: MUL tagA,tagB RS2: ADD val,tagC RS3: LOAD val,val (free) Functional Units ALU MUL LD/ST Common Data Bus broadcast wakes waiting RS operands Reorder Buffer (ROB) head → I1: done, R2=17 I2: done, R3=9 I3: waiting (load) I4: waiting (mul) tail → (free) commit (in program order) → writes Reg File Dispatch and commit are in program order; execution between them is by data readiness.

Complexity & correctness

Hardware complexity per cycle. The wakeup broadcast compares the tag of every completing instruction against the pending source tags of every reservation station entry. If there are `W` reservation stations and the CDB delivers `B` results per cycle, wakeup costs `O(W·B)` comparators per cycle — an associative, fully parallel structure, not a search. Select/arbitration among ready instructions competing for a functional unit is a priority encoder over `O(W)` entries, typically `O(log W)` gate delay if implemented as a tree, though many designs use age-ordered matrices costing `O(W²)` bits of state to track relative age cheaply. The ROB is a simple circular buffer, `O(1)` per operation (push at tail, inspect/pop at head), but detecting when some entry has all its consumers retired, for reclaiming physical registers, again costs bookkeeping proportional to ROB size, `O(1)` amortized with a free-list of physical registers.

Why complexity is bounded, not unbounded. The number of reservation stations and ROB entries is fixed at design time (typically tens to a few hundred entries in real cores), so the instruction window — the set of in-flight instructions the hardware can reorder among — is bounded. This is a deliberate engineering tradeoff: a larger window exposes more ILP but the `O(W·B)` broadcast comparators and `O(W)`-wide select logic grow directly with window size, and associative CAM structures do not scale linearly in area or power. This is precisely why real designs use bounded structures (32–512 ROB entries typical in 2020s cores) rather than unbounded ones, and why increasing window size yields diminishing throughput returns per unit power past some point.

Correctness argument. The claim to prove is: the sequence of committed architectural states produced by the out-of-order machine is identical to the sequence produced by some in-order execution of the same instruction stream (respecting the actual control-flow path taken). The proof rests on three invariants maintained by the mechanism above. 1. Only true dependences constrain execution order. Register renaming assigns a distinct physical destination to every dynamic instruction that writes an architectural register. Hence two instructions can never collide over a physical register the way they might over an architectural one; WAR and WAW hazards, which are naming artifacts rather than real data flow, are eliminated by construction. What remains — an instruction cannot execute before the producer of a value it reads — is a RAW dependence, and the resolve/wakeup logic enforces exactly this: an operand is marked ready only once its producing tag has broadcast a value. So execution order always respects the true dependence DAG, and any topological order of that DAG (which out-of-order execution is free to choose) yields the same final values for each physical register, since each physical register is written exactly once (single static assignment property of renaming). 2. Commit order equals program order. The ROB is a FIFO indexed by dispatch (= program) order; the commit rule only ever advances the head, and only when the head entry is complete. Thus the sequence of writes into architectural registers and memory, as observed by anything outside the core (interrupts, other cores, I/O), is exactly the program order sequence — indistinguishable from in-order execution, satisfying the classical requirement of precise exceptions: at the moment any exception is recognized (at commit of the faulting instruction), every architecturally-prior instruction has already committed and every architecturally-later one has not touched architectural state. This is what allows a page-fault handler, debugger, or `signal` handler to see a clean, sequential machine state. 3. Speculative work is safely discardable. Instructions dispatched past a not-yet-resolved branch are speculative; they occupy ROB and RS entries and may execute and broadcast results, but none of that becomes architectural until commit. If the branch resolves as mispredicted, the ROB entries younger than the branch are simply invalidated/flushed (`ROB.pop` from the tail down to the branch, RAT restored to a checkpoint taken at dispatch of the branch) and fetch is redirected — no architectural state was ever touched by the wrong-path instructions, so no rollback of committed state is needed. This is exactly why speculation composes safely with renaming and the ROB: speculation touches only the "not yet real" side of the commit boundary.

Honest tradeoffs. Out-of-order hardware buys ILP at real cost: renaming tables, CAM-based reservation stations, and wide broadcast buses consume significant die area and dynamic power (CAM compares are notoriously power-hungry per bit compared to RAM reads). Deeper windows help hide longer-latency misses (e.g., last-level cache or DRAM) but with diminishing returns, since the rate of independent ready instructions available in typical code saturates; beyond a certain window size, extra hardware mostly buffers instructions that are still waiting on the same long-latency miss rather than genuinely independent work. Verification complexity also rises sharply — precise-exception and memory-ordering corner cases (load/store disambiguation, speculative loads bypassing stores) are a leading source of processor design bugs.

Worked example

Consider this small sequence (register names are architectural; assume ADD/SUB take 1 cycle to execute, MUL takes 3, and all instructions are already fetched/decoded and ready to dispatch one per cycle): ``` I1: R1 = R2 + R3 I2: R4 = R1 * R5 // RAW on R1 from I1 I3: R1 = R6 - R7 // WAW on R1 with I1; also enables later readers I4: R8 = R1 + R4 // RAW on R1 from I3 (post-rename), RAW on R4 from I2 ``` Without renaming, `I3` would appear to have a WAW hazard on `R1` with `I1`, and a naive in-order-issue machine might even force `I3` to wait for `I1`'s value to be consumed before overwriting it. With renaming, `I1`'s destination becomes physical register `P10`, and `I3`'s destination becomes physical register `P11` — two entirely distinct storage locations, so `I3` can compute immediately, independent of `I1` and `I2`.

CycleDispatchRAT after dispatchExecutes this cycleBroadcasts (tag→val)ROB head commits
1I1 (dest→P10; src R2,R3 from RegFile)R1→P10———
2I2 (dest→P11; src1 = tag P10 [waiting], src2 R5 ready)R4→P11I1 (ADD, 1 cyc)——
3I3 (dest→P12; src R6,R7 from RegFile — independent!)R1→P12I3 (SUB, 1 cyc, has all operands)P10 → value(R1)—
4I4 (dest→P13; src1 = tag P12 [ready now], src2 = tag P11 [waiting])R8→P13I2 begins MUL (3 cyc, operand P10 arrived)P12 → value(new R1)I1 commits (head of ROB, done)
5——(I2 still executing, cyc 2/3)—I2 not yet done → stall commit at I2? No — I3 is younger than I2 but I2 is older; ROB head is I2
6——I2 finishes MULP11 → value(R4)commit stalls: head=I2, not done until this cycle
7——I4 executes (ADD, both operands ready: P12 from cyc 3, P11 from cyc 6)P13 → value(R8)I2 commits
8————I3 commits
9————I4 commits

Two points this trace makes concrete. First, I3 executed in cycle 3, before I2 finished its 3-cycle multiply in cycle 6 — a clear case of out-of-order completion, made safe only because renaming gave `I3` its own physical destination `P12`, so it never had to wait for anything related to `I1`'s `R1`. Second, commit still proceeded strictly in program order (I1, then I2, then I3, then I4), even though I3 finished computing long before I2 did; the ROB simply stalled retirement at the I2 entry until I2's multiply completed, guaranteeing that any exception or observer sees state exactly as if the program executed sequentially I1→I2→I3→I4.

Common pitfalls & traps

PitfallWhy it happensConsequence
Forgetting to eliminate WAR/WAW hazardsTreating architectural register names as if they were the actual storageFalse stalls; without renaming, `I3` above would incorrectly wait on `I1`/`I2`
Committing out of order "for speed"Mistakenly assuming execution order and commit order can be the same relaxationBreaks precise exceptions; debuggers and OS see inconsistent state
Losing speculative state on flush incorrectlyForgetting to restore the RAT checkpoint, only clearing the ROBRenaming table still points to squashed physical registers; wrong values read after misprediction
Reusing a physical register too earlyFreeing a physical register as soon as its ROB entry commits, without checking younger readersA still-in-flight consumer reads garbage or a reused register's new value (WAR-like bug reintroduced)
Treating store instructions like normal ALU opsStores must not modify memory until commit, unlike ALU results which can broadcast earlySpeculative or mispredicted stores corrupt memory irreversibly if allowed to write early
Assuming bigger window always means proportionally more IPCIgnoring that CAM/broadcast cost grows with window while ready independent work saturatesDiminishing returns; wasted area/power for little throughput gain

Pioneers & history

Origins · 1 minRobert Tomasulo and how this was discovered
RT
Robert Tomasulo
1934–2008 · IBM, developer of dynamic scheduling for the System/360 Model 91

Tomasulo designed the algorithm that bears his name to let the IBM 360/91's floating-point unit tolerate its long, variable-latency multiply and divide operations without stalling independent instructions. The chip lacked full register renaming in the modern software sense, but Tomasulo's reservation stations and common-data-bus tag-broadcast mechanism solved exactly the WAR/WAW/RAW problem that renaming solves today, and the "Tomasulo algorithm" remains the standard name for this style of dynamic scheduling in every architecture course and processor design.

History
1967
Robert Tomasulo publishes the dynamic-scheduling algorithm used in the IBM System/360 Model 91 floating-point unit.
1985
James E. Smith and others formalize reorder-buffer-style precise-interrupt handling for pipelined machines.
1988
Yale Patt's HPS (High Performance Substrate) work extends dynamic scheduling with restricted dataflow and speculative execution ideas.
1995
Intel ships the Pentium Pro, the first mainstream x86 processor with a full out-of-order core (register renaming, reservation stations, ROB) decoding CISC instructions into internal micro-ops.
2000
s–present: Out-of-order cores with deep windows and aggressive speculation (Intel Core, AMD Zen, Arm Cortex-A, Apple Silicon) become the default design point for all high-performance general-purpose CPUs.

Where this matters

Every mainstream high-performance CPU core — Intel Core and Xeon, AMD Zen, Arm's Cortex-A and Neoverse lines, Apple's M-series and A-series — implements some variant of register renaming, reservation stations or unified issue queues, and a reorder buffer (sometimes called a "future file" or physical-register-file-based renaming with an ROB of tags rather than values). The technique is what lets these processors tolerate cache misses, long-latency floating-point and vector operations, and unpredictable memory-system delays without stalling the entire pipeline behind them. It is a prerequisite for effective branch speculation (mispredictions are cheap to recover from precisely because the ROB discards uncommitted work cleanly) and works hand-in-hand with superscalar issue width and multi-level cache hierarchies to determine overall single-thread performance. The same in-order-commit discipline that guarantees precise exceptions here is also what a processor's virtual memory subsystem and operating system rely on for correct page-fault handling and context switching. Looking forward, once a core can already track many independent in-flight instructions and physical registers per architectural thread, it is a comparatively small step to let two independent instruction streams share that same renaming and scheduling hardware — the idea behind Simultaneous Multithreading.

Ask the tutor: How does simultaneous multithreading share this out-of-order machinery — renaming tables, reservation stations, and the ROB — across multiple hardware threads at once?

Why is out-of-order execution needed if the compiler can already reorder instructions statically?

Static compiler reordering only helps with dependences and latencies known at compile time — fixed pipeline depths, predictable functional-unit latencies. It cannot react to run-time events: a cache miss whose latency depends on which level of the memory hierarchy actually satisfies it, a mispredicted branch, or contention for a shared functional unit. Out-of-order hardware observes actual operand-ready times as they happen and reorders execution dynamically, so it captures parallelism static scheduling cannot see. In practice, real compilers and out-of-order hardware are complementary: compilers do what they can (instruction scheduling, loop unrolling) and the hardware recovers the remaining dynamic ILP, especially across unpredictable memory latencies.

Does out-of-order execution change the final results of a program?

No — that is the entire point of the design. The reorder buffer enforces that architectural state (registers and memory, as observed by anything outside the core) is updated in exactly program order, and register renaming ensures the data dependences respected during out-of-order execution are the true ones. The final observable behavior — including exceptions, interrupts, and memory side effects — is provably identical to some valid in-order execution of the same dynamic instruction stream. Only the internal timing changes; program semantics do not.

What is the difference between reservation stations and a unified issue queue used in modern designs?

Classic Tomasulo reservation stations are dedicated per functional-unit type and hold the instruction plus its operand values or tags directly at the station. Many modern designs instead use a centralized (or banked) issue queue that holds instructions with renamed tags, separate from a physical register file that holds actual values; wakeup broadcasts tags, and selected instructions read their operands from the physical register file rather than from a local buffer. Functionally these are equivalent — both perform tag-based wakeup and readiness tracking — but the physical-register-file organization scales better to large windows since it avoids duplicating operand values across many reservation stations.

How does out-of-order execution interact with branch misprediction recovery?

Every branch instruction gets a checkpoint of the register alias table (or an equivalent recovery mechanism) at dispatch time. Instructions dispatched after the branch are speculative and populate reservation stations and ROB entries as usual, potentially executing and broadcasting results. If the branch resolves as mispredicted, the processor flushes all ROB entries younger than the branch, restores the RAT checkpoint, and redirects fetch to the correct path. Because none of the speculative instructions' results were ever committed to architectural state, this squash is completely safe — nothing needs to be "undone," only discarded before it would have committed.

Why can't reservation stations and the ROB simply be made arbitrarily large to expose more parallelism?

The wakeup logic that lets an instruction detect that its operand just arrived is inherently associative: every cycle, every waiting reservation station's pending tags must be compared against every broadcast result tag. This comparator network's cost scales with the product of window size and broadcast width, and content-addressable memory structures of this kind are notoriously area- and power-expensive per bit compared to ordinary RAM. Beyond a certain size, the marginal amount of additional independent, ready-to-run work found in typical programs also shrinks, so bigger windows cost more per unit of extra throughput — a classic diminishing-returns tradeoff that keeps real ROB and issue-queue sizes in the tens-to-low-hundreds range rather than thousands.

What happens to a physical register once its value is no longer needed?

A physical register can be freed and returned to a free list only after two conditions hold: its ROB entry has committed (so the corresponding architectural register mapping is now stale and can be replaced), and no younger, still-in-flight instruction still holds that physical register as a source-operand tag. Different designs handle this bookkeeping differently — some retire the previous mapping of an architectural register only when the new mapping commits, guaranteeing no live reader is stranded. Getting this reclamation wrong reintroduces exactly the WAR-style hazard renaming was meant to eliminate, so it is treated as a correctness-critical part of the renaming logic, not just a performance optimization.

Before this