From a2cbb4f25700423f50e9f2321e6c640746576d57 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 16 Jul 2026 12:26:30 -0700 Subject: [PATCH 1/2] SuperPMI: fix spurious arm asmdiffs from out-of-range BL relocs The near-differ applies recorded relocations before comparing. For an arm32 direct BL (ARM32_THUMB_BRANCH24), applyRelocs substitutes a placeholder target when the code block lands more than +-16MB from the call target, so the two sides' immediates differ even though both call the same recorded target. Compare the recorded reloc kind and target instead. Also error out when base and diff JITs are the same module. --- .../tools/superpmi/superpmi/jitinstance.h | 4 ++++ .../tools/superpmi/superpmi/neardiffer.cpp | 22 +++++++++++++++++++ .../tools/superpmi/superpmi/superpmi.cpp | 12 ++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/coreclr/tools/superpmi/superpmi/jitinstance.h b/src/coreclr/tools/superpmi/superpmi/jitinstance.h index b264894d368499..cbc6e5f5043ad5 100644 --- a/src/coreclr/tools/superpmi/superpmi/jitinstance.h +++ b/src/coreclr/tools/superpmi/superpmi/jitinstance.h @@ -52,6 +52,10 @@ class JitInstance ULONGLONG times[2]; ICorJitCompiler* pJitInstance; + // The loaded JIT module handle. Used to detect when the baseline and diff + // JITs resolve to the same loaded module (which shares global state). + HMODULE getModule() const { return hLib; } + // Allocate and initialize the jit provided static JitInstance* InitJit(char* nameOfJit, bool breakOnAssert, diff --git a/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp b/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp index 9bb7f913393056..e60b9d64454a81 100644 --- a/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp +++ b/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp @@ -562,6 +562,28 @@ bool NearDiffer::compareOffsets( return compareOffsetsWasm(payload, blockOffset, instrLen, offset1, offset2); } + // On arm32 a direct BL is fixed up with an ARM32_THUMB_BRANCH24 reloc. When the + // two code blocks are allocated more than +-16MB apart, applyRelocs substitutes a + // placeholder target for the out-of-range side, so the immediates differ even + // though both BLs target the same recorded address. Treat the sites as equal when + // both recorded the same reloc kind and target. + if (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_ARM) + { + const DiffData* data = (const DiffData*)payload; + + const Agnostic_RecordRelocation* reloc1 = + data->cr1->findRelocationInRange(data->originalBlock1, blockOffset, instrLen); + const Agnostic_RecordRelocation* reloc2 = + data->cr2->findRelocationInRange(data->originalBlock2, blockOffset, instrLen); + + if ((reloc1 != nullptr) && (reloc2 != nullptr) && (reloc1->fRelocType == reloc2->fRelocType) && + ((uint64_t)reloc1->target + (int32_t)reloc1->addlDelta == + (uint64_t)reloc2->target + (int32_t)reloc2->addlDelta)) + { + return true; + } + } + const DiffData* data = (const DiffData*)payload; size_t ip1 = data->originalBlock1 + blockOffset; size_t ip2 = data->originalBlock2 + blockOffset; diff --git a/src/coreclr/tools/superpmi/superpmi/superpmi.cpp b/src/coreclr/tools/superpmi/superpmi/superpmi.cpp index c509589330c9c9..b906f243703612 100644 --- a/src/coreclr/tools/superpmi/superpmi/superpmi.cpp +++ b/src/coreclr/tools/superpmi/superpmi/superpmi.cpp @@ -447,6 +447,18 @@ int __cdecl main(int argc, char* argv[]) // InitJit already printed a failure message return (int)SpmiResult::JitFailedToInit; } + + if (jit2->getModule() == jit->getModule()) + { + // The baseline and diff JITs resolved to the same loaded module. Because the JIT keeps + // global state (e.g. g_jitHost, JitConfig), sharing a single module between the two + // JitInstances corrupts that state and produces spurious diffs and intermittent crashes. + // Require the two JITs to be distinct files (copy one to a different path if needed). + LogError("The baseline JIT ('%s') and diff JIT ('%s') resolve to the same loaded module. " + "They must be distinct files; copy one JIT to a different path.", + o.nameOfJit, o.nameOfJit2); + return (int)SpmiResult::JitFailedToInit; + } } } From 77545413d206e56f91d6d58f0708b1800b816a43 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 17 Jul 2026 09:45:49 -0700 Subject: [PATCH 2/2] fix arm32 BL reloc in applyRelocs --- .../superpmi-shared/compileresult.cpp | 14 ++++++------ .../tools/superpmi/superpmi/neardiffer.cpp | 22 ------------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp b/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp index 1dcbbf1bca20e4..b27003b6930fb1 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp @@ -821,15 +821,15 @@ void CompileResult::applyRelocs(RelocContext* rc, unsigned char* block1, ULONG b case CorInfoReloc::ARM32_THUMB_BRANCH24: { - INT32 delta = (INT32)(tmp.target - fixupLocation); if ((section_begin <= address) && (address < section_end)) // A reloc for our section? { - if (!FitsInThumb2BlRel24(delta)) - { - DWORDLONG target = (DWORDLONG)originalAddr + (DWORDLONG)blocksize1; - delta = (INT32)(target - fixupLocation); - } - PutThumb2BlRel24((UINT16*)address, delta); + // Like the arm64 ARM64_BRANCH26 and x64 RELATIVE32 handling, hardcode the + // bottom bits of the target into the instruction so the encoding does not + // depend on where SuperPMI allocated the code buffer. Otherwise a BL whose + // target is out of the +-16MB range for one of the two compared blocks would + // get a buffer-dependent placeholder, producing spurious asm diffs. + DWORDLONG target = tmp.target + (int32_t)tmp.addlDelta; + PutThumb2BlRel24((UINT16*)address, (INT32)(target & 0x00FFFFFE)); } wasRelocHandled = true; } diff --git a/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp b/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp index e60b9d64454a81..9bb7f913393056 100644 --- a/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp +++ b/src/coreclr/tools/superpmi/superpmi/neardiffer.cpp @@ -562,28 +562,6 @@ bool NearDiffer::compareOffsets( return compareOffsetsWasm(payload, blockOffset, instrLen, offset1, offset2); } - // On arm32 a direct BL is fixed up with an ARM32_THUMB_BRANCH24 reloc. When the - // two code blocks are allocated more than +-16MB apart, applyRelocs substitutes a - // placeholder target for the out-of-range side, so the immediates differ even - // though both BLs target the same recorded address. Treat the sites as equal when - // both recorded the same reloc kind and target. - if (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_ARM) - { - const DiffData* data = (const DiffData*)payload; - - const Agnostic_RecordRelocation* reloc1 = - data->cr1->findRelocationInRange(data->originalBlock1, blockOffset, instrLen); - const Agnostic_RecordRelocation* reloc2 = - data->cr2->findRelocationInRange(data->originalBlock2, blockOffset, instrLen); - - if ((reloc1 != nullptr) && (reloc2 != nullptr) && (reloc1->fRelocType == reloc2->fRelocType) && - ((uint64_t)reloc1->target + (int32_t)reloc1->addlDelta == - (uint64_t)reloc2->target + (int32_t)reloc2->addlDelta)) - { - return true; - } - } - const DiffData* data = (const DiffData*)payload; size_t ip1 = data->originalBlock1 + blockOffset; size_t ip2 = data->originalBlock2 + blockOffset;