Peephole Optimization: How Compilers Improve Code with Local Pattern Rewrites
Peephole optimization is a compiler technique that repeatedly examines a small, contiguous “window” of instructions—called a Peephole (or window—and replaces it with an equivalent sequence that runs faster or is smaller in code size, while preserving program semantics. Peephole optimization is commonly applied in the backend, after instruction selection, because the compiler can exploit machine-specific instruction patterns efficiently.
Why “local” helps
Because the transformation scope is small, peephole rules are:
- Easy to specify as pattern→replacement rules,
- Fast to apply (linear-ish scanning with a small lookahead),
- Useful for machine-dependent cleanups that higher-level passes may miss.
Canonical example transformations
Common peephole replacements include:
- removing redundant stack operations,
- simplifying arithmetic by algebraic identities,
- turning slow arithmetic sequences into faster machine idioms (e.g., shifts for multiply-by-powers-of-two),
- deleting “null sequences” (instructions that do nothing).
Mermaid overview:
Key takeaway: peephole optimization is about localized equivalence rewriting.
Footnotes
-
Peephole optimization - Wikipedia - Defines peephole optimization and provides examples like removing redundant stack ops and replacing multiplies with shifts. ↩ ↩2 ↩3
Peephole Optimization (definition + examples in compiler design)
Core idea: pattern matching on a sliding window
A peephole optimizer operates by scanning instructions and applying rules when it sees a specific short pattern. Wikipedia describes the technique as replacing a peephole (small instruction set) with a logically equivalent set that has better performance, e.g., removing redundant operations or simplifying multiplies.
In modern compilers, peephole behavior is often implemented as instruction-sequence pattern matching, possibly across multiple internal forms (IR, assembly, or machine code). LLVM has a dedicated peephole optimizer implementation for machine code (e.g., PeepholeOptimizer.cpp).
Footnotes
-
Peephole optimization - Wikipedia - Defines peephole optimization and provides examples like removing redundant stack ops and replacing multiplies with shifts. ↩
-
LLVM: lib/CodeGen/PeepholeOptimizer.cpp Source File - Documents LLVM’s peephole optimizer pass for machine code. ↩
How a peephole optimizer applies transformations
- 1Step 1
Pick a small contiguous sequence (often a few instructions) in the current code representation.
- 2Step 2
Check whether the current window matches a known inefficient pattern (e.g., redundant stack ops, useless moves, algebraic identities).
- 3Step 3
Ensure the replacement is semantically equivalent for the compiler’s model of the program (including assumptions about undefined/poison behavior where applicable).
- 4Step 4
Replace the matched window with the optimized instruction sequence.
- 5Step 5
Advance the scan (often re-checking overlapping windows) until a fixed point is reached or the pass completes.
Examples of peephole optimizations (with before/after)
Example 1: Remove redundant stack instructions around calls
A classic example is removing patterns like POP immediately followed by PUSH for the same registers. Wikipedia explains that such sequences are often redundant; removing one may expose another redundant pair, which can be removed in turn.
Before (two consecutive calls in Z80 example):
PUSH AF PUSH BC PUSH DE PUSH HL CALL _ADDR1 POP HL POP DE POP BC POP AF PUSH AF PUSH BC PUSH DE PUSH HL CALL _ADDR2 POP HL POP DE POP BC POP AF
After (redundant pairs removed, leaving fewer stack operations):
PUSH AF PUSH BC PUSH DE PUSH HL CALL _ADDR1 CALL _ADDR2 POP HL POP DE POP BC POP AF
(Conceptually: consecutive save/restore sequences can collapse if the intermediate state doesn’t matter.)
Keywords:
- Redundant stack operations
- Call sequence
- POP/PUSH redundancy
Example 2: Replace multiply by 2 with shift-left
Wikipedia gives examples such as replacing multiplication by with a left shift (e.g., x << 1).
Before:
x = x * 2
After:
x = x << 1
Benefits:
- Shifts are often cheaper than general multiplication on many ISAs.
- Encoding may be shorter depending on target instructions.
Keywords:
- Strength reduction
- Shift-left idiom
- Algebraic simplification
Example 3: Multiply by 8 via exponent adjustment (floating-point idea)
Wikipedia also mentions that floating-point multiplies by powers of two can be optimized by manipulating the exponent (e.g., adding 3 to exponent for multiply by ).
This illustrates a peephole principle: recognize specific local arithmetic patterns and use ISA/representation-specific equivalents.
Keywords:
- Floating-point exponent manipulation
- Machine-specific idioms
Example 4: Remove a null sequence / useless operations
Peephole optimization can delete useless operations that do not affect results (e.g., arithmetic with identities or instructions that are overwritten immediately). Wikipedia lists “null sequences” as part of the replacement repertoire.
Typical shapes:
x + 0→ no-op,x * 1→ no-op,- redundant move/copy where a value is immediately overwritten.
Keywords:
- Null sequence
- Dead instruction
- Redundant move elimination
Footnotes
Common Peephole Optimization Payoffs
Typical local improvements peephole passes target (qualitative).
Benefits (why peephole optimization matters)
-
Improves performance with cheap local fixes
Peephole optimization aims to produce “shorter and faster” equivalent sequences, which directly reduces runtime cost for tight loops and hot instruction paths. -
Reduces code size
By replacing longer patterns with shorter instruction sequences, peephole optimization lowers instruction count and can improve instruction cache behavior. High-level descriptions of peephole objectives include reducing code size and improving speed. -
Eliminates redundant instructions and memory traffic
Examples include removing redundant stack ops and simplifying arithmetic sequences into faster idioms (e.g., shift-based multiply-by-powers-of-two). -
Creates opportunities for subsequent optimizations
Because peephole rewrites may remove instructions and expose new neighboring patterns, a transformation can enable further reductions (e.g., removing one redundant save/restore can reveal another). -
Integrates well with machine-dependent code generation
LLVM and other production compilers implement peephole-style passes in their backend to perform machine-code cleanups such as extension/comparison elimination.
Practical note about correctness
Peephole rewrites must preserve semantics under the compiler’s IR/program model. Research on LLVM peephole optimization emphasizes that correctness depends on careful handling of undefined behavior concepts (e.g., refinement assumptions enabled by the IR model).
Footnotes
-
Peephole optimization - Wikipedia - Defines peephole optimization and provides examples like removing redundant stack ops and replacing multiplies with shifts. ↩ ↩2 ↩3
-
Compiler Design - Peephole Optimization (Tutorialspoint) - Lists objectives such as increasing code speed and reducing code size; describes techniques like constant folding/strength reduction. ↩
-
LLVM: lib/CodeGen/PeepholeOptimizer.cpp Source File - Documents LLVM’s peephole optimizer pass for machine code. ↩
-
Practical formal techniques and tools for developing LLVM’s peephole optimizations (Rutgers) - Discusses the importance of semantics preservation and careful treatment of undefined behavior in LLVM peephole optimizations. ↩
Pro Tip: Think in local equivalence
When designing peephole rules, focus on patterns that are provably equivalent within a small window—e.g., redundant saves/restores, algebraic identities, or strength-reduced arithmetic (shift for powers of two).
Warning: Correctness can be subtle with undefined/poison behavior
Even tiny rewrites can become invalid if your rule ignores how the IR treats undefined or poison-like values. Production peephole optimizers must encode and respect these semantics.
Footnotes
-
Practical formal techniques and tools for developing LLVM’s peephole optimizations (Rutgers) - Discusses the importance of semantics preservation and careful treatment of undefined behavior in LLVM peephole optimizations. ↩
Where peephole optimization fits in the compiler pipeline
Peephole optimization is typically a backend (or late-stage) transformation because it acts on small instruction sequences produced by earlier steps (instruction selection / lowering). Descriptions in compiler literature emphasize that peephole works on small instruction windows and replaces them with better alternatives while preserving behavior.
In LLVM specifically, an implementation exists to perform peephole optimizations on machine code (e.g., optimizing sign/zero extension patterns).
Keywords:
- Backend optimization
- Instruction selection
- Canonicalization
Footnotes
-
Peephole optimization - Wikipedia - Defines peephole optimization and provides examples like removing redundant stack ops and replacing multiplies with shifts. ↩
-
LLVM: lib/CodeGen/PeepholeOptimizer.cpp Source File - Documents LLVM’s peephole optimizer pass for machine code. ↩
Common questions about peephole optimization
Knowledge Check
Peephole optimization primarily works by: