How Are Precise Exceptions Maintained in Out-of-Order CPUs?

Modern processors execute instructions out of program order to maximize performance, yet they must still present the illusion of sequential execution when an error or interrupt occurs. Maintaining precise exceptions means ensuring that when an instruction faults, all prior instructions in program order have completed and updated the architectural state, while no subsequent instructions have modified it. Hardware achieves this by separating out-of-order execution from in-order retirement using structures like the reorder buffer, register renaming, and store buffers.

What Is a Precise Exception?

An exception is precise if the processor can stop at a well-defined boundary in the program's instruction stream. Suppose a program consists of instructions \(I_1, I_2, \dots, I_n\) in sequential program order. If instruction \(I_k\) raises an exception—such as a page fault, division by zero, or an invalid opcode—the architectural state visible to the operating system must satisfy two conditions:

  1. Every instruction \(I_j\) with \(j < k\) has completed and committed its results to the architectural registers and memory.
  2. Every instruction \(I_j\) with \(j \ge k\) has not modified the architectural state, and can be restarted cleanly after the exception is handled.

Without out-of-order execution, a simple in-order pipeline satisfies this naturally if exceptions are checked at a single pipeline stage. When instructions complete out of order, a younger instruction \(I_{k+1}\) might finish and write its result before an older instruction \(I_k\) even detects that it has faulted.

The Reorder Buffer (ROB)

The primary mechanism for preserving precise exceptions is the reorder buffer (ROB). The ROB is a circular FIFO queue that tracks every in-flight instruction in its original program order.

When instructions are decoded and dispatched, they are allocated an entry at the tail of the ROB. Execution units may complete instructions in any order, writing their results and any exception flags into their assigned ROB entry rather than directly into the architectural register file.

An instruction is only allowed to commit (or retire) when it reaches the head of the ROB and has finished execution without raising an exception. During commit:

  • The result stored in the ROB entry is written to the permanent architectural state.
  • The ROB head pointer advances to the next instruction in program order.

If the instruction at the head of the ROB has its exception flag set, the processor does not commit it. Instead, it flushes all younger instructions from the ROB and the pipeline, records the faulting program counter, and transfers control to the operating system's exception handler. Because no younger instruction has reached the head of the ROB, none of them have altered the architectural state.

Speculative State and Register Renaming

To execute instructions out of order without corrupting architectural registers, processors use register renaming. Architectural registers (such as R1 or RAX) are mapped to a larger pool of physical registers via a Register Alias Table (RAT).

When an instruction writes to an architectural register, it actually writes to a newly allocated physical register. The old physical register holding the previous value is not freed until the new instruction commits. If an exception occurs at the head of the ROB, the processor simply discards the speculative mappings and restores the RAT to the state corresponding to the last committed instruction.

Handling Memory Writes with Store Buffers

Memory writes cannot be undone easily once they reach the data cache or main memory. To prevent speculative stores from corrupting memory before an older instruction's exception status is known, processors use a store buffer (or store queue).

Store instructions compute their target address and data out of order, placing both into the store buffer. The write is held there speculatively and is only drained to the cache hierarchy after the store instruction commits at the head of the ROB. If an older instruction faults, the uncommitted entries in the store buffer are simply discarded.

Summary of the Commit Boundary

By buffering register results in the ROB, isolating speculative register mappings through renaming, and holding memory writes in the store buffer, an out-of-order processor decouples execution speed from architectural correctness. Instructions can execute as soon as their operands are ready, but their effects become permanent strictly in program order, guaranteeing precise exceptions.