Dispatcher: Optimize syscall return with L1 lookup

Ever since #5978 this has been a performance regression. Add back the L1
lookup since tight syscall loops are likely to be in L1.

AmpereOne Before:
```
Test, Total Cycles, Iterations, Cycles Average, Iter Time Average, iterations/Second
syscall - none, 638309680, 6170000, 103.45, 103.45, 9666154.52
```

AmpereOne After:
```
Test, Total Cycles, Iterations, Cycles Average, Iter Time Average, iterations/Second
syscall - none, 629629840, 13880000, 45.36, 45.36, 22044698.52
```

From ~103ns down to ~45ns.
Or from 966k syscalls per second to 2,204k syscalls per second.

TR7995WX for fun:
```
Test, Total Cycles, Iterations, Cycles Average, Iter Time Average, iterations/Second
syscall - none, 1822837150, 20820000, 87.55, 35.07, 28511271.29
```
This commit is contained in:
Ryan Houdek committed 2026-10-23 12:57:46 -07:00
1 parent 3da673e695
commit 502a1900ee
1 file changed
+17 -2
@@ -566,8 +566,23 @@ void Dispatcher::EmitDispatcher() {
// We can safely claim we are no longer in a syscall
str(ARMEmitter::XReg::zr, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo));
// Now go back to the regular dispatcher loop
(void)b(&LoopTop);
{
// L1 lookup after syscall. Mirrors the implementation in `ExitFunction`.
// Most syscall instructions just return to the following RIP, so it is highly likely to be in the L1 in tight loops.
ldr(TMP3, STATE_PTR(CpuStateFrame, State.rip));
ldp<ARMEmitter::IndexType::OFFSET>(TMP1, TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, State.L1Pointer));
// Calculate (tmp1 + ((ripreg & L1_ENTRIES_MASK) << 4)) for the address
// L1Mask is pre-shifted.
and_(ARMEmitter::Size::i64Bit, TMP2, TMP2, TMP3, ARMEmitter::ShiftType::LSL, FEXCore::ilog2(sizeof(LookupCache::LookupCacheEntry)));
add(TMP1, TMP1, TMP2);
ldp<ARMEmitter::IndexType::OFFSET>(TMP2, TMP1, TMP1, 0);
sub(TMP1, TMP1, TMP3);
(void)cbnz(ARMEmitter::Size::i64Bit, TMP1, &LoopTop);
br(TMP2);
}
}
{