The Uop Cache's Immediate Operand Encoding Limit: Why Large Constants Push Instructions Out of the Decoded Cache

2026-08-30

The micro-op cache stores decoded instructions in a compressed format so the CPU can skip the expensive x86 decoders. But that compression comes with a catch: each uop cache entry has a fixed number of bits reserved for immediate operands, and instructions with large immediates can consume extra slots or get kicked out entirely.

On Intel Skylake-through-Alder Lake, each uop cache line holds up to 6 uops in 32 bytes of instruction footprint. Each uop has a small immediate/displacement field — typically 32 bits per uop slot. If an instruction needs a 64-bit immediate (like MOV RAX, 0x123456789ABCDEF0), it consumes two uop slots instead of one, even though it decodes to a single uop. Worse, if two instructions in the same fetch window both carry 32-bit displacements, they can exhaust the line's immediate storage before hitting the 6-uop limit.

The concrete rule of thumb: a uop cache line can hold at most 2 large (32-bit) immediates or displacements. A third instruction with a 32-bit immediate ends the line early, wasting cache capacity and forcing an extra fetch cycle.

Real-world example: imagine a hot loop that touches several globals through RIP-relative addressing:

Three instructions, three large displacements — but only two fit per uop cache line. The third splits into a new line, meaning the loop now consumes 2 uop cache lines instead of 1. If the loop body was already close to the 3-lines-per-32-byte-window limit, this pushes it out of the uop cache entirely, dropping frontend throughput from ~6 uops/cycle to ~4 uops/cycle through the legacy decoders.

Why this exists: storing full 64-bit immediates for every uop slot would roughly double the uop cache's SRAM footprint. Designers picked the sweet spot — most instructions have small or no immediates, so the common case stays compact. The corner cases (64-bit MOVs, multiple RIP-relative addresses in one window) pay the tax.

Practical mitigation: pool your globals into a single struct addressed through a base register. One MOV RBX, [rip+offset] to load the base, then small 8-bit displacements to reach individual fields — those don't count against the large-immediate budget.

Key Takeaway: The uop cache reserves limited bits for immediates per line, so instructions with 32-bit or 64-bit constants can push each other out and force fallback to the slow legacy decoders.

All newsletters