While chasing an unrelated SPURS hang I noticed the JIT publishes
freshly written code on ARM64 with no instruction-cache maintenance at
all. A grep for clear_cache or flushInstructionCache over the JIT layer
comes back empty. The branch-rewrite sites only issue ISB; DSB ISH,
which performs no D-cache clean or I-cache invalidation and is ordered
backwards for self-modifying code besides. On ARMv8 a correct
publication needs the DC CVAU / IC IVAU broadcast sequence; x86 has a
coherent instruction cache, so none of this was ever visible there.
All sites use the bundled asmjit::VirtMem::flushInstructionCache(),
which emits that sequence portably across toolchains.
This covers every publication path I could find:
- MemoryManager1::finalizeMemory() and MemoryManager2::finalizeMemory()
were both no-ops. RuntimeDyld calls finalizeMemory() after writing
code and relies on it for cache maintenance, so LLVM emitted PPU and
SPU code was never flushed. MemoryManager1 serves the primary PPU
JIT, MemoryManager2 the SPU JIT and auxiliary engines. Both managers
now record code section allocations and flush them on finalize. I
confirmed at runtime that the MemoryManager2 path executes (about
12800 calls per cold boot).
- jit_runtime_base::_add() copies asmjit output into executable memory
with no flush.
- jit_runtime::finalize() restores an executable code snapshot in place
during emulator restart with only the ISB/DSB pair.
- spu_runtime::rebuild_ubertrampoline() publishes a hand-written
trampoline via CAS with no flush; the flush now happens before the
publication.
- spu_runtime::make_branch_patchpoint() writes a patchpoint byte by
byte and returns it with only the ISB/DSB pair.
- Both 16-byte branch-site rewrites (dispatch and branch) atomically
overwrite live code and only issued the ISB/DSB pair.
The ISB/DSB pairs adjacent to the new flushes are removed along with
their misleading "flush all cache lines" comments: the flush helper
already issues the trailing barriers, and the pairs never performed
any cache maintenance in the first place.
I want to be upfront that this was not the cause of the hang I was
debugging (a same-item compilation race, fixed separately), and I have
not observed a failure that this change alone fixes. It is a latent
correctness issue on any ARM64 host: nothing prevents another core
from fetching stale instruction bytes for freshly published code.