2026-06-10
The x86 architecture guarantees that if you write a byte to memory and then execute that byte as an instruction, the CPU sees the new value. This sounds trivial. It is not. The CPU has already fetched, decoded, and possibly speculatively executed instructions hundreds of bytes ahead of the program counter — and any of those bytes might be the ones you just wrote.
To preserve the architectural illusion, every store address is checked against the addresses of in-flight instructions. The mechanism is called self-modifying code (SMC) detection, and it lives in the front-end. When a store retires (or sometimes when it dispatches) the physical address is compared against the addresses of instructions currently in the pipeline, the µop cache, and the L1 instruction cache lines being tracked for execution.
If a collision is detected, the CPU performs a machine clear: flush the pipeline, invalidate the µop cache (or at least the affected entries), and refetch from the new program counter. On Intel cores this costs roughly 1000–3000 cycles per event. It happens regardless of whether the modified instruction was going to execute.
Concrete example: A JIT compiler emits machine code into a buffer, then jumps to it. The naive pattern — emit, jump, emit more, jump — triggers SMC clears on every emit. V8 and HotSpot mitigate this by emitting code into a fresh page that is not in the I-cache, then issuing the necessary cross-modifying sequence (store, serializing instruction like CPUID or a far jump on older CPUs, execute). On modern x86 the serializing barrier isn't required for the writing thread, but another thread executing the modified code needs a serializing instruction after the writer's store becomes visible — see Intel SDM Vol. 3, section 8.1.3.
A subtler trap: code prefetch. The hardware prefetcher reads the next cache line of instructions speculatively. If your data write hits a line the I-prefetcher already pulled, you eat the clear even though no instruction from that line ever executed. This is why hot data structures placed near hot code — common in template-heavy C++ binaries — sometimes show mysterious SMC events in perf stat -e machine_clears.smc.
Rule of thumb: Keep writable data on pages at least 4 KB away from any code that runs in the same hyperthread, and prefer to write JIT output into a separate mmap region with PROT_READ|PROT_WRITE, then mprotect to PROT_READ|PROT_EXEC. The mprotect implicitly serializes via TLB shootdown, side-stepping the SMC machinery entirely.
Check your binaries: perf stat -e machine_clears.smc ./yourprogram. Anything above a few hundred per second on a non-JIT workload means code and data share cache lines somewhere.
