FEX 2611 Tagged

You know the drill, it’s been around a month so that means we have a new release landing. A smattering of changes all over the codebase this time. Let’s see what’s interesting!

CPU core index through TPIDRRO_EL0

This is a fun feature that has come about primarily because Windows on ARM mandates this feature. The TPIDRRO_EL0 register is an ARM system register that is readable by userspace that the kernel can set to whatever 64-bit value it wants. In particular under Windows it is part of their ABI that this register must contain the current CPU core index once read. This gives ARM64/ARM64ec applications a really quick way to read what core it is executing on. This is kind of a funny thing that needs to exist because technically the MPIDR_EL1 register is supposed to accomplish the same task. Although this register is kernel-mode only, through their CPU feature register emulation you can read this register on Linux with a bit of overhead. The problem with this register is that many vendors messed it up, and also it’s not just an index from zero to the number of cores in the system. So in the end the kernel actually only ever returns zero. On Linux, FEX will use the getcpu syscall instead to get the CPU core index.

So what are we to do? ARM64 WINE applications need this register to be ABI compliant and FEX would like to use it for faster emulation of RDPID/RDTSCP/CPUID. What does Linux do for this register? Turns out in the 15 years since ARM64 was introduced, the Linux kernel has hardcoded this register to always be zero and be reserved for future work. Simple enough then would be to send a patch upstream to set the register to the core index just like Windows.

While that’s working through the system, we have a downstream patch (That we failed to push before this release, sorry!) that adds a PRCTL so FEX can query if the kernel supports the register to contain the CPU index or not. Once your kernel lands this patch then ARM64 Windows games will get correct results and FEX will automatically pick it up.

In particular this register is required for when FEX is running under WINE because we can’t use Linux syscalls properly there so we couldn’t safely or easily emulate RDPID/RDTSC, so we just disabled those instructions. Problem is that games are starting to mandate these instructions so we were stuck in a bit of a hard spot. This ends up being a combination of correctness and performance improvements! Win-win!

All the JIT things

This month we have a bunch of JIT fixes, features, and optimizations. Let’s try and walk through some of these.

Enable preserve_all ABI on mingw

This will give a decent performance improvement for anything using x87 under full 80-bit precision

SHA256 peephole optimization

ARM64 and x86 SHA256 instructions don’t quite behave the same. x86 operates on two rounds where ARM operates on four rounds. Which is an easy way to think about how typically we throw away half the data on the instruction. We now do a peephole optimization for common SHA256 code patterns so we can stop throwing away half the computation. This gives around a 1.9x perf improvement for heavy SHA code which some games use heavily.

Inline 128-bit division

Previously FEX would jump out to a C handler for 128-bit division when it was detected as mandatory. This is required because ARM doesn’t have an equivalent divide and can only go up to 64-bit. We now inline the code generation when a 128-bit divide is necessary, resulting in around a 5x performance uplift. Luckily these are fairly uncommon so most games aren’t affected by this slow path.

Wire up SERIALIZE and AVX-NE-Convert

Following last month’s VNNI implementation, we are spending a bit more time implementing some new extension. The serialize instruction is fairly trivial since it’s x86’s new instruction and memory barrier instruction. Before this games actually needed to abuse the cpuid instruction of all things because it was the only guaranteed instruction serialization instruction.

AVX-NE-Convert is a funny little extension that basically maps directly for ARM64 instructions for converting FP16 and BF16 floats to FP32 and back. It was simple to implement and complements the remaining new AVX-VNNI instructions.

While not landing this month, we’re also planning on the AVX-VNNI-Int8/16 and SHA512 extensions in the coming months. So look forward to that.

Wire up support for X87 FIP

X87 is kind of a funny little thing, there’s always new bits of information to learn about it. Most people know that x87 is a monster 80-bit float stack machine. But did you know that for most every x87 ALU operation, the co-processor actually records every RIP where that code executed and also which RIP has accessed memory? Turns out some versions of SecuROM actually check to ensure that the Floating point RIP is being set correctly for some form of anti-tamper. Once we wired up support for this, it fixed a little known game Split/Second. Requires the setting the environment variable FEX_JITFEATURES=enablex87fip

Partial debug register support

Under Windows, applications are allowed to touch the x86 debug registers fairly easily. On Linux this is protected by everything going through the ptrace interface. WINE implements these debug registers by having the wineserver ptrace the process to be able to pass these through on a real x86 environment. Under FEX we don’t have that luxury because we need to translate what these registers represent.

So today FEX emulates the debug registers on wow64 under WINE only. Also we only support instruction breakpoints today, no data breakpoints. This requires some JIT interaction to punch this through wine which is why it is tricky. For ARM64EC this is also quirky because we need some form of WINE implementation coordination because the boundary between Arm64 and x86-64 code is a lot closer. We’ll get there eventually because games need this to work.

As for the Linux side…We need to implement ptrace first which is its own nightmare.

Smattering of changes

Let’s just slam through a bunch of the other random JIT changes

  • Fix 0x82 instruction aliases.
    • Apparenty Crysis 2 abuses this.
  • Fix performance regression on syscall instruction
  • Support partial instruction faulting information
    • Used by anti-tampers
  • Fix 0xBX instructions with prefixes
  • Fix XSETBV, SWAGGS, SLDT
  • Fix DCE accidentally removing memory accesses
    • Affected Java Hotspot JIT.
  • Have arm64ec skip double-faulting when not required
    • Makes Arxan protected games run slightly faster

See the 2611 Release Notes or the detailed change log in Github.

Written on November 5, 2026