2026-07-16
Store-to-load forwarding sounds simple: a load checks the store buffer, finds a matching older store, and reads its data. But what if multiple older stores match? A load at address X might have three older stores to X still sitting in the buffer. The load needs the youngest one older than itself — and the hardware that picks it is a priority encoder wired across every store buffer entry.
Here's the mechanism. Every cycle a load probes the store buffer, each entry does a parallel CAM (content-addressable memory) match on the load's address. That produces a bit vector — one bit per entry, set if that store overlaps the load. Then a age matrix masks off any store younger than the load (those don't exist yet in program order). Finally, a priority encoder finds the youngest surviving match and routes that entry's data onto the forwarding bus. All in one cycle, typically stage EX1 of the load pipeline.
The priority encoder scales badly. A 48-entry store buffer needs a 48-input priority encoder resolving in ~300 picoseconds at 4GHz. This is the reason store buffers stopped growing around 50-60 entries even as ROBs pushed past 500 — you'd need pipelined forwarding, which adds a cycle to every load.
Concrete example: a tight loop doing arr[i] += 1 generates a load and a store to the same address every iteration. The load in iteration N+1 hits the store buffer, matches the store from iteration N, and the priority encoder picks it over any older stores to the same address that haven't drained yet. Zebra-stripe pattern in the forwarding-hit performance counter tells you it's working. Now unroll the loop 4x with arr[i]+=1; arr[i]+=2; arr[i]+=3 — every load has 3+ matching older stores, and the priority encoder earns its silicon on every access.
Rule of thumb: forwarding latency is 4-5 cycles (Intel) or 3-4 cycles (Apple/AMD Zen). If the priority encoder can't fully resolve — because the youngest matching store is only partially overlapping the load — you eat a store-forwarding-block penalty of 10-20 cycles as the load waits for the store to drain to L1. This is why writing a 4-byte int then reading it as an 8-byte long is catastrophic.
