Skip to content

[APX] Add Jmpabs support. - #131826

Open
DeepakRajendrakumaran wants to merge 14 commits into
dotnet:mainfrom
DeepakRajendrakumaran:jmpabs
Open

[APX] Add Jmpabs support.#131826
DeepakRajendrakumaran wants to merge 14 commits into
dotnet:mainfrom
DeepakRajendrakumaran:jmpabs

Conversation

@DeepakRajendrakumaran

@DeepakRajendrakumaranDeepakRajendrakumaran commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Add JMPABS (APX) Support for Virtual Call Stubs on AMD64

Summary

This PR adds support for the JMPABS instruction (part of Intel's APX - Advanced Performance Extensions) to virtual call stubs on AMD64, reducing stub size and improving instruction fetch efficiency.

Background

APX introduces the JMPABS instruction (D5 00 A1 + 8-byte address), which performs a 64-bit absolute jump. JMPABS provides performance benefits over the traditional mov rax, imm64; jmp rax sequence:

Virtual call stubs are small code sequences generated at runtime for dispatching interface and virtual method calls. Currently, these stubs use a two-instruction sequence (mov rax, target; jmp rax) to perform 64-bit absolute jumps.

Changes

Virtual Call Stub Infrastructure

Stub Generation Helpers (src/coreclr/vm/amd64/cgenamd64.cpp, cgencpu.h)

  • Added emitJmpAbsJump() helper function to emit raw JMPABS instructions (11 bytes)
  • Modified emitBackToBackJump() to use JMPABS encoding when available, with fallback to legacy encoding
  • Extend AMD64 virtual call short dispatch stubs to support an APX encoding path (jne rel32; nop; jmpabs imm64) and use it when APX is available.
  • Added IsJmpAbsAvailable() CPU capability detection function
  • Removed dead code: EncodeLoadAndJumpThunk() (obsolete thunk generation helper)
  • Fixed DispatchStubShort_offsetof_failDisplBase macro referencing wrong struct type (DispatchStubLongDispatchStubShort)

Testing

  1. JIT Emitter Unit Tests: Ran unit tests to verify JMPABS instruction encoding in the JIT emitter
  2. Library Tests: Ran library tests with APX enabled/disabled to verify stub changes cause no failures on non-APX hardware
  3. Runtime Tests: Ran src/tests with and without APX to validate stub changes on non-APX hardware

Issue

Fixes#131817

JIT Emitter

src/coreclr/jit/emitxarch.cpp

  • Added JMPABS instruction encoding support in the emitter
  • Added unit test for JMPABS encoding
  • Note: JMPABS is not currently used anywhere in the JIT itself

Edit - the emitter changes have been reveretd based on feedback from @jkotas

Opens

1. Static Stub Size with NOP Padding

Current approach: To maintain consistent stub size and simplify stub management, this PR uses NOP padding in the APX encoding path:

emitBackToBackJump (used in jump stubs): Both encodings are 12 bytes

Legacy: mov rax, imm64; jmp rax (12 bytes)
APX: jmpabs imm64 (11 bytes) + nop (1 byte) = 12 bytes
DispatchStubShort (used in dispatch stubs): Both encodings are 18 bytes

Legacy: jne rel32 (6 bytes) + mov rax, imm64; jmp rax (12 bytes) = 18 bytes
APX: jne rel32 (6 bytes) + nop (1 byte) + jmpabs imm64 (11 bytes) = 18 bytes

2. APX Availability Detection

JMPABS availability is determined at runtime via IsJmpAbsAvailable(), which checks HasInstructionSet(InstructionSet_APX) from the JIT's CPU capability flags. Is runtime-only detection sufficient, or do we need additional compile-time or toolchain checks for crossgen/ReadyToRun scenarios?

3. DispatchStubLong JMPABS Support

DispatchStubLong could benefit from JMPABS encoding, but the instruction's 3-byte prefix shifts the 64-bit immediate from offset +2 (legacy mov rax, imm64) to offset +3 (JMPABS). This offset change may break the 8-byte alignment requirement.Should we defer this optimization?

CopilotAI lite review requested due to automatic review settings August 4, 2026 18:27
@DeepakRajendrakumaranDeepakRajendrakumaran changed the title [APX] Add Jmpabs support.[WIP] [APX] Add Jmpabs support.Aug 4, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 4, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 4, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial AMD64 APX JMPABS support in CoreCLR, wiring it into VM virtual-call stub emission and adding emitter support/tests in RyuJIT.

Changes:

  • Extend AMD64 virtual call short dispatch stubs to support an APX encoding path (jne rel32; nop; jmpabs imm64) and use it when APX is available.
  • Add VM-side capability detection (IsJmpAbsAvailable) plus a raw emitJmpAbsJump helper, and route emitBackToBackJump through JMPABS when available.
  • Introduce INS_jmpabs in the JIT instruction set and add emitter unit-test coverage for encoding.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/coreclr/vm/amd64/virtualcallstubcpu.hppAdds APX/legacy union encoding for DispatchStubShort, updates fail/impl target accessors, and adjusts short-stub reachability logic.
src/coreclr/vm/amd64/cgencpu.hReplaces removed thunk helper with IsJmpAbsAvailable declaration and adds emitJmpAbsJump API.
src/coreclr/vm/amd64/cgenamd64.cppImplements APX availability detection and emits JMPABS for back-to-back jump stubs (with legacy fallback).
src/coreclr/jit/instrsxarch.hAdds INS_jmpabs as an APX instruction.
src/coreclr/jit/emitxarch.cppTeaches the emitter to size/output INS_jmpabs (REX2 + opcode + imm64).
src/coreclr/jit/codegenxarch.cppAdds an emitter unit-test emission site for INS_jmpabs.

Comment threadsrc/coreclr/vm/amd64/cgencpu.h
Comment threadsrc/coreclr/vm/amd64/cgenamd64.cpp
Comment threadsrc/coreclr/vm/amd64/virtualcallstubcpu.hpp
Comment threadsrc/coreclr/jit/emitxarch.cpp Outdated
Comment threadsrc/coreclr/vm/amd64/virtualcallstubcpu.hpp
@jkotasjkotas added the apx Related to the Intel Advanced Performance Extensions (APX) label Aug 4, 2026
CopilotAI review requested due to automatic review settings August 4, 2026 22:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/coreclr/jit/emitxarch.cpp:17260

  • The new INS_jmpabs support in this function conflicts with the existing immed/reloc assumptions immediately below: noway_assert(size < EA_8BYTE || ((int)val == val && !id->idIsCnsReloc())) and the idIsCnsReloc block currently only allow 32-bit immediates and relocations for push. As a result, valid jmpabs uses (64-bit targets and/or relocations) will hit debug asserts. Please special-case INS_jmpabs in those checks.
 emitAttr size = id->idOpSize();
ssize_t val = emitGetInsSC(id);
bool valInByte = ((signed char)val == (target_ssize_t)val);
// We would to update GC info correctly
assert(!IsSSEInstruction(ins));

src/coreclr/vm/amd64/virtualcallstubcpu.hpp:129

  • DispatchHolder/LookupHolder now call IsJmpAbsAvailable() and emitJmpAbsJump(), but this header doesn't include cgencpu.h or otherwise declare these functions. That makes the header fragile and can break compilation depending on include order (and virtualcallstub.h includes this directly). Add forward declarations here (or include cgencpu.h) so the header is self-contained.
/*DispatchStubShort*********************************************************************************
This is the logical continuation of DispatchStub for the case when the failure target is within
a rel32 jump (DISPL). Uses a union to handle both legacy and APX encodings. */
struct DispatchStubShort
{

CopilotAI review requested due to automatic review settings August 6, 2026 18:15
Comment threadsrc/coreclr/vm/amd64/virtualcallstubcpu.hpp
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@DeepakRajendrakumaran
DeepakRajendrakumaran marked this pull request as ready for review August 6, 2026 18:59
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@DeepakRajendrakumaranDeepakRajendrakumaran changed the title [WIP] [APX] Add Jmpabs support. [APX] Add Jmpabs support.Aug 6, 2026
CopilotAI review requested due to automatic review settings August 28, 2026 16:26
LIMITED_METHOD_CONTRACT;
PTR_BYTE pbCode = PTR_BYTE(pCode);

// Check for JMPABS encoding (APX): D5 00 A1 [8 bytes] 90

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is also duplicated in

privateboolIsJumpRel64(TargetPointerpThunk)
=>0x48==_target.Read<byte>(pThunk)&&
0xB8==_target.Read<byte>(pThunk+1)&&
0xFF==_target.Read<byte>(pThunk+10)&&
0xE0==_target.Read<byte>(pThunk+11);
privateTargetPointerDecodeJump64(TargetPointerpThunk)
{
Debug.Assert(IsJumpRel64(pThunk),"Expected a jump thunk");
return_target.ReadPointer(pThunk+2);
}
.

cc @max-charlamb@noahfalk data contract versioning

@DeepakRajendrakumaranDeepakRajendrakumaranSep 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed the cdac implementation.
The following is what i imagine this would look like if we were to add the logic in SOSDacImpl.cs directly

 // A 64-bit jump thunk has two possible encodings, both 12 bytes:
// legacy: 48 B8 <imm64> FF E0 mov rax, imm64; jmp rax
// APX: D5 00 A1 <imm64> 90 jmpabs imm64; nop
private bool IsJumpAbs64(TargetPointer pThunk)
=> 0xD5 == _target.Read<byte>(pThunk) &&
0x00 == _target.Read<byte>(pThunk + 1) &&
0xA1 == _target.Read<byte>(pThunk + 2) &&
0x90 == _target.Read<byte>(pThunk + 11);
private bool IsJumpMovRax64(TargetPointer pThunk)
=> 0x48 == _target.Read<byte>(pThunk) &&
0xB8 == _target.Read<byte>(pThunk + 1) &&
0xFF == _target.Read<byte>(pThunk + 10) &&
0xE0 == _target.Read<byte>(pThunk + 11);
private bool IsJumpRel64(TargetPointer pThunk)
=> IsJumpAbs64(pThunk) || IsJumpMovRax64(pThunk);
private TargetPointer DecodeJump64(TargetPointer pThunk)
{
Debug.Assert(IsJumpRel64(pThunk), "Expected a jump thunk");
// imm64 follows the 3-byte JMPABS opcode, or the 2-byte mov rax opcode.
return IsJumpAbs64(pThunk)
? _target.ReadPointer(pThunk + 3)
: _target.ReadPointer(pThunk + 2);
}

As you pointed out, this is obviously not how the newer contract/descriptor methodology works . PrecodeStubs has the runtime publish so cdac doesn't hardcode opcodes, and this does the opposite. So if the preference is to keep new decode logic off the hardcoded path, I'm happy to go the descriptor route instead.

That said, I think(from what I understand of how this works) the hardcoded version is benign here specifically, for a reason that doesn't generalize to most cdac hardcoding. 0xD5 is unambiguous across versions. It's the REX2 prefix, which only exists under APX; in 64-bit mode 0xD5 was previously an invalid opcode. No pre-APX runtime can emit a jump thunk starting with D5, so there's no aliasing risk against older targets. A new cdac reads old runtimes correctly, and an old cdac simply never sees the pattern.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple thoughts on this:

  • officially IsJump64/DecodeJump64 doesn't appear to be part of any versioning contract so the change here isn't breaking the contract.
  • if hypothetically the exact set of jump thunk encodings had been part of a contract then this would have been a breaking change. Old cDAC interpreting new runtime is a valid scenario.
  • The implementation of GetJumpThunkTarget is making undocumented assumptions about the runtime's code generation patterns not backed by any contract guarantee so it is inherently unreliable right now.

As for what to do about it I could see a few paths forward:

  1. Decide that changes to jump thunk code generation are rare and reliable decoding functionality is useful so we create an explicit contract on it going forward attempting to guarantee it works.
  2. Treat the disassembly that occurs in SOS !u and !DumpStack commands as best-effort. Deprecate ISOSDacInterface.GetJumpThunkTarget, delete its implementation in cDAC, and move the disassembly logic to SOS or use general purpose disassembly mechanisms when available. We could update SOS with new encodings periodically but there would be no explicit versioning determining whether SOS understands the instructions used by a particular runtime.

Given disassembly is usually a best effort in other debugger scenarios I lean towards option (2) but open to feedback.

@DeepakRajendrakumaran - regardless where the long term thing ends up, could you add the code you suggested to SOSDacImpl.cs? We could deal with the rest as a separate PR. Thanks!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@noahfalk I have added the code to SOSDacImpl.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +137 to +141
LIMITED_METHOD_CONTRACT;
// JMPABS encoding starts with 0x0F 0x85 (jne near)
// Legacy encoding starts with 0x48 0xB8 (mov rax, imm64)
return _bytes[0] == 0x0F && _bytes[1] == 0x85;
}
CopilotAI review requested due to automatic review settings September 4, 2026 19:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are a couple of small but concrete correctness/maintainability issues in the new/updated contracts and comments that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

src/coreclr/vm/amd64/cgenamd64.cpp:469

  • emitJmpAbsJump writes through pBufferRW but only checks pBufferRX in PRECONDITIONs. Add a PRECONDITION for pBufferRW as well.
 PRECONDITION(CheckPointer(pBufferRX));
PRECONDITION(IsJmpAbsAvailable()); // Caller must check APX availability
}

src/coreclr/vm/amd64/virtualcallstubcpu.hpp:203

  • This check is identifying the APX short-stub layout (which begins with JNE), not the JMPABS opcode. Updating the comment will make the intent clearer.
 // JMPABS encoding: starts with 0x0F 0x85 (jne near) at byte 0
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines 428 to 435
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;

PRECONDITION(CheckPointer(pBuffer));
PRECONDITION(CheckPointer(pBufferRX));
}
Comment on lines +138 to +140
// JMPABS encoding starts with 0x0F 0x85 (jne near)
// Legacy encoding starts with 0x48 0xB8 (mov rax, imm64)
return _bytes[0] == 0x0F && _bytes[1] == 0x85;

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes low-level AMD64 stub encodings and CPU-feature gating in CoreCLR, which has a high regression blast radius and warrants careful human validation (including on APX-capable hardware).

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

{
BYTE part1[2]; // 0F 85 jne near
DISPL _failDispl; // xx xx xx xx 4-byte displacement
BYTE nop; // 90 nop

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the nop here and not after jmpabs?

LIMITED_METHOD_CONTRACT;
const BYTE *bytes = reinterpret_cast<const BYTE *>(this);
// APX encoding starts with 0x0F 0x85 (jne near). Legacy starts with 0x48 0xB8 (mov rax, imm64).
return bytes[0] == 0x0F && bytes[1] == 0x85;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@noahfalk Do we need to worry about debugger placing a breakpoint at the first instruction of these stubs? The instruction decoding is going to be confused in case that happens that will result into hard to diagnose crash.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that does look like an issue. A couple potential fixes:

  • Using bytes[1] as the discriminator appears unique in both encodings and wouldn't be a valid place for a breakpoint to be written.
  • If we could consistently use the jmpabs encoding everywhere when the hardware supported it then we could avoid infering it from the specific assembly bytes and instead use some runtime-wide capability flag to know which encoding to expect.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we could consistently use the jmpabs encoding everywhere when the hardware supported it then we could avoid infering it from the specific assembly bytes and instead use some runtime-wide capability flag to know which encoding to expect.

I think this is preferred where possible.

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

Labels

apxRelated to the Intel Advanced Performance Extensions (APX)area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants

@DeepakRajendrakumaran@noahfalk@jkotas