You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[x64\[APX]Add support in JIT for jmpabs and use jmpabs where possible. #131817
Quick Background JMPABS is a 64-bit-only ISA extension that acts as an unconditional direct jump with an absolute 64-bit target address.
JIT Use Cases In the JIT, when INS_l_jmp needs to be emitted but the target is beyond ±2GB, it currently falls back to an indirect jump through RAX (mov rax, addr; jmp rax). This is a reasonable candidate for JMPABS as it would avoid the register clobber and keep it as a direct jump. That said, this seems fairly rare in practice — the most relevant case I found is when genEmitCallWithCurrentGC resolves to INS_l_jmp via emitter::emitIns_Call.
VM Use Cases I found a few scenarios in the VM where JMPABS would be emitted:
Jump Stubs (emitBackToBackJump)
In all cases, JMPABS replaces the legacy mov rax, addr; jmp rax with jmpabs addr. - source code
Generic Resolvers (LookupHolder)
Legacy: 48 B8 [resolveWorkerAddr] FF E0 — load resolver into RAX, then jmp rax - source code
JMPABS: D5 00 A1 [resolveWorkerAddr] — direct absolute jump, no RAX clobber
VSD Dispatch Stubs (DispatchHolder)
Both of theses also move address into rax before jmp and that makes it a possible target for jmpabs - current code
JIT Use Cases In the JIT, when INS_l_jmp needs to be emitted but the target is beyond ±2GB, it currently falls back to an indirect jump through RAX (mov rax, addr; jmp rax). This is a reasonable candidate for JMPABS as it would avoid the register clobber and keep it as a direct jump. That said, this seems fairly rare in practice — the most relevant case I found is when genEmitCallWithCurrentGC resolves to INS_l_jmp via emitter::emitIns_Call.
For JIT jumps to constant addresses should be rare due to tiering and loading from tiering cells.
JIT Use Cases In the JIT, when INS_l_jmp needs to be emitted but the target is beyond ±2GB, it currently falls back to an indirect jump through RAX (mov rax, addr; jmp rax). This is a reasonable candidate for JMPABS as it would avoid the register clobber and keep it as a direct jump. That said, this seems fairly rare in practice — the most relevant case I found is when genEmitCallWithCurrentGC resolves to INS_l_jmp via emitter::emitIns_Call.
For JIT jumps to constant addresses should be rare due to tiering and loading from tiering cells.
This was my take away too. The plan is to enable the encoding in JIT for now but i don't have any ready use cases.
The VM stubs are the only legitimate current use cases.
I assume you have some data about the generally expected improvement from jmpabs.
Yes, got some data from the Intel architects on this.
They ran a synthetic stress test on Novalake P-cores using large trampoline tables
Results comparing MOV RAX + JMP RAX vs JMPABS:
Metric
Indirect
JMPABS
Cycles
741B
340B
IPC
0.07
0.16
Branch misses
96.76%
0.01%
~2x IPC improvement, ~54% cycle reduction under heavy branch pressure.
The reason is straightforward - indirect jumps require the BPU to predict the target at decode time, and under pressure with many unique targets the BTB gets thrashed, causing a cascade of JEClears. JMPABS encodes the target directly in the instruction so it's resolvable at decode and essentially behaves like a direct jump from the BPU's perspective.
This is a stress test and real gains in our stub paths will be smaller but hopefully directionally positive
Activity
dotnet-policy-service commentedon Aug 4, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
DeepakRajendrakumaran commentedon Aug 4, 2026
This items is mentioned in #120362
DeepakRajendrakumaran commentedon Aug 4, 2026
Quick Background JMPABS is a 64-bit-only ISA extension that acts as an unconditional direct jump with an absolute 64-bit target address.
JIT Use Cases In the JIT, when INS_l_jmp needs to be emitted but the target is beyond ±2GB, it currently falls back to an indirect jump through RAX (mov rax, addr; jmp rax). This is a reasonable candidate for JMPABS as it would avoid the register clobber and keep it as a direct jump. That said, this seems fairly rare in practice — the most relevant case I found is when genEmitCallWithCurrentGC resolves to INS_l_jmp via emitter::emitIns_Call.
VM Use Cases I found a few scenarios in the VM where JMPABS would be emitted:
Jump Stubs (emitBackToBackJump)
In all cases, JMPABS replaces the legacy mov rax, addr; jmp rax with jmpabs addr. - source code
Generic Resolvers (LookupHolder)
Legacy: 48 B8 [resolveWorkerAddr] FF E0 — load resolver into RAX, then jmp rax - source code
JMPABS: D5 00 A1 [resolveWorkerAddr] — direct absolute jump, no RAX clobber
VSD Dispatch Stubs (DispatchHolder)
Both of theses also move address into rax before jmp and that makes it a possible target for jmpabs - current code
jkotas commentedon Aug 4, 2026
Jump Stubs and VSD Dispatch (DispatchHolder) are the hot paths that can benefit from JMPABS.
LookupHolder is slow path that is typically only executed once. JMPABS won't make a difference there.
MichalPetryka commentedon Aug 4, 2026
For JIT jumps to constant addresses should be rare due to tiering and loading from tiering cells.
DeepakRajendrakumaran commentedon Aug 4, 2026
This was my take away too. The plan is to enable the encoding in JIT for now but i don't have any ready use cases.
The VM stubs are the only legitimate current use cases.
jkotas commentedon Aug 4, 2026
What's the point of adding dead code to the JIT?
DeepakRajendrakumaran commentedon Aug 4, 2026
I'm okay with either removing the encoding and just using in VM stubs or keeping the logic to encode in JIT for future uses if any present itself
MichalPetryka commentedon Aug 4, 2026
I think this could possibly be used for calls to helpers.
jkotas commentedon Aug 5, 2026
How?
jmpabsis a jump, it is not a call.MichalPetryka commentedon Aug 5, 2026
tailcalls I mean.
jkotas commentedon Aug 5, 2026
Tailcalls outside 2GB range are rare.
Also, enabling jmpabs for tailcalls would require updating to Windows unwind info v3.
runtime/src/coreclr/unwinder/amd64/unwinder.cpp
Lines 1332 to 1333 in 4945fe8
Unwind info v3 is prerequisite for other apx enablement too, so it is something that will need to happen. I am just highlighting the dependency.
DeepakRajendrakumaran commentedon Aug 5, 2026
I'll remove jmpabs use in
LookupHolderin the PRDeepakRajendrakumaran commentedon Aug 5, 2026
@jkotas Do you think this will help with #62302 (comment) ?
Edit : I mean switching to
jmpabsin stubsjkotas commentedon Aug 5, 2026
Yes. I am not sure how much. I assume you have some data about the generally expected improvement from
jmpabs.MichalPetryka commentedon Aug 5, 2026
jkotas commentedon Aug 5, 2026
DeepakRajendrakumaran commentedon Aug 6, 2026
Yes, got some data from the Intel architects on this.
They ran a synthetic stress test on Novalake P-cores using large trampoline tables
Results comparing
MOV RAX + JMP RAXvsJMPABS:~2x IPC improvement, ~54% cycle reduction under heavy branch pressure.
The reason is straightforward - indirect jumps require the BPU to predict the target at decode time, and under pressure with many unique targets the BTB gets thrashed, causing a cascade of JEClears. JMPABS encodes the target directly in the instruction so it's resolvable at decode and essentially behaves like a direct jump from the BPU's perspective.
This is a stress test and real gains in our stub paths will be smaller but hopefully directionally positive