Skip to content

[x64\[APX]Add support in JIT for jmpabs and use jmpabs where possible. #131817

Description

@DeepakRajendrakumaran

Add support for the APX JMPABS instruction in the .NET JIT compiler and use it to optimize virtual call stubs on x64.

What is JMPABS?
APX (Advanced Performance Extensions) introduces the JMPABS instruction - a direct absolute 64-bit jump encoded in 11 bytes:

D5 00 A1 [8-byte address] ; jmpabs <absolute 64-bit address>

Advantages over existing jumps
More compact than register-indirect jumps:

Legacy: mov rax, imm64; jmp rax = 12 bytes
JMPABS: 11 bytes (saves 1 byte, no register clobbering)
More flexible than relative jumps:

Relative jump (jmp rel32): limited to ±2GB range, 5 bytes
JMPABS: can reach any 64-bit address, 11 bytes
Cleaner than register-indirect:

No register allocation/spilling required
No data dependencies on register state
Direct encoding of target address

Activity

  1. added
    apxRelated to the Intel Advanced Performance Extensions (APX)
    on Aug 4, 2026
  2. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Aug 4, 2026
  3. dotnet-policy-service commented on Aug 4, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  4. DeepakRajendrakumaran commented on Aug 4, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    This items is mentioned in #120362

  5. DeepakRajendrakumaran commented on Aug 4, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    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

  6. jkotas commented on Aug 4, 2026

    @jkotas
    Member

    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.

  7. added this to the 12.0.0 milestone on Aug 4, 2026
  8. removed
    untriagedNew issue has not been triaged by the area owner
    on Aug 4, 2026
  9. MichalPetryka commented on Aug 4, 2026

    @MichalPetryka
    Contributor

    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.

  10. DeepakRajendrakumaran commented on Aug 4, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    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.

  11. jkotas commented on Aug 4, 2026

    @jkotas
    Member

    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.

    What's the point of adding dead code to the JIT?

  12. DeepakRajendrakumaran commented on Aug 4, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    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.

    What's the point of adding dead code to the JIT?

    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

  13. MichalPetryka commented on Aug 4, 2026

    @MichalPetryka
    Contributor

    The plan is to enable the encoding in JIT for now but i don't have any ready use cases.

    I think this could possibly be used for calls to helpers.

  14. jkotas commented on Aug 5, 2026

    @jkotas
    Member

    I think this could possibly be used for calls to helpers.

    How? jmpabs is a jump, it is not a call.

  15. MichalPetryka commented on Aug 5, 2026

    @MichalPetryka
    Contributor

    I think this could possibly be used for calls to helpers.

    How? jmpabs is a jump, it is not a call.

    tailcalls I mean.

  16. jkotas commented on Aug 5, 2026

    @jkotas
    Member

    Tailcalls outside 2GB range are rare.

    Also, enabling jmpabs for tailcalls would require updating to Windows unwind info v3.

    } else if ((NextByte[0] == JMP_IMM8_OP) ||
    (NextByte[0] == JMP_IMM32_OP)) {
    does not handle jmpabs .

    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.

  17. DeepakRajendrakumaran commented on Aug 5, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    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.

    I'll remove jmpabs use in LookupHolder in the PR

  18. DeepakRajendrakumaran commented on Aug 5, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    @jkotas Do you think this will help with #62302 (comment) ?

    Edit : I mean switching to jmpabs in stubs

  19. jkotas commented on Aug 5, 2026

    @jkotas
    Member

    Do you think this will help with #62302 (comment) ?

    Yes. I am not sure how much. I assume you have some data about the generally expected improvement from jmpabs.

  20. MichalPetryka commented on Aug 5, 2026

    @MichalPetryka
  21. jkotas commented on Aug 5, 2026

    @jkotas
  22. DeepakRajendrakumaran commented on Aug 6, 2026

    @DeepakRajendrakumaran
    ContributorAuthor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

apxRelated to the Intel Advanced Performance Extensions (APX)area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions