The Zero-Cycle Move (Move Elimination): How CPUs Execute Register MOVs Without an Execution Unit

2026-08-30

When your compiler emits mov rax, rbx, you'd expect the CPU to route rbx's value through an ALU port and land it in rax. On modern Intel (Ivy Bridge+) and AMD (Zen+), it does no such thing. The move is eliminated during register rename — before any execution unit ever sees it.

Recall the rename stage maps logical registers (RAX, RBX...) to physical registers (P42, P97...) in the PRF. A mov rax, rbx just needs the physical register that currently backs RBX to also back RAX. So the renamer updates the rename map: RAX now points to the same physical register as RBX. Zero uops dispatched to execution ports. Zero latency in the dependency chain. The instruction still allocates a ROB slot (for retirement bookkeeping) but consumes no scheduler bandwidth and no execution port.

What qualifies: Register-to-register integer MOVs (same width, no partial writes), XMM/YMM register moves, and some vector moves. What doesn't: MOVs with memory operands, sign/zero-extending moves like movsx/movzx, MOVs that write partial registers (AL, AX), and MOVs between register domains (integer ↔ vector — those cost bypass latency).

Concrete example: A tight software renderer swaps two accumulators every iteration:

mov rcx, rax
mov rax, rbx
mov rbx, rcx

Naively that's 3 cycles on a single ALU. With move elimination: 0 execution cycles. All three MOVs vanish at rename. Intel's optimization manual lists this pattern as a canonical win — hand-tuned register shuffles that used to cost cycles are now free scaffolding for the actual work.

The catch — the free list. When you fuse RAX and RBX to the same physical register, you can't free that register until both logical mappings retire it. This means move elimination increases pressure on the physical register free list. Intel Skylake tracks a limited number of eliminated moves per cycle (the "move elimination bandwidth"); exceed it and the move falls back to a normal uop on port 0/1/5/6.

Rule of thumb: Register-to-register integer MOVs are effectively free. Sign/zero extension is not — prefer mov eax, ebx (which zeros the upper 32 bits and eliminates) over movzx rax, ebx (which allocates an actual uop on an ALU port). This is why compilers aggressively emit 32-bit MOVs on x86-64 even when you asked for a 64-bit copy.

How to verify: On Linux, perf stat -e uops_executed.thread,uops_issued.any ./prog — if issued ≫ executed, move elimination is doing work.

See it in action: Check out How does Computer Hardware Work? 💻🛠🔬 [3D Animated Teardown] by Branch Education to see this theory applied.
Key Takeaway: Modern CPUs eliminate register-to-register MOVs at the rename stage by pointing two logical registers at one physical register, so the MOV consumes zero execution cycles — but only for full-width, same-domain integer or vector moves.

All newsletters