Skip to content

Commit ecaa1c5

Browse files
AndyAyersMSCopilotCopilot
authored
JIT: Superpmi wasm32 neardiff (#128967)
Update coredistools version to 1.7.0 to pick up Wasm disassembly support. Implement neardiff support for Wasm. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <Copilot@users.noreply.github.com>
1 parent a8b2c92 commit ecaa1c5

6 files changed

Lines changed: 200 additions & 1 deletion

File tree

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
<Antlr4BuildTasksVersion>12.14.0</Antlr4BuildTasksVersion>
143143
<Antlr4RuntimeStandardVersion>4.13.1</Antlr4RuntimeStandardVersion>
144144
<!-- Testing -->
145-
<MicrosoftNETCoreCoreDisToolsVersion>1.6.0</MicrosoftNETCoreCoreDisToolsVersion>
145+
<MicrosoftNETCoreCoreDisToolsVersion>1.7.0</MicrosoftNETCoreCoreDisToolsVersion>
146146
<MicrosoftNETTestSdkVersion>17.4.0-preview-20220707-01</MicrosoftNETTestSdkVersion>
147147
<MicrosoftOneCollectRecordTraceVersion>0.1.33421</MicrosoftOneCollectRecordTraceVersion>
148148
<NUnitVersion>3.12.0</NUnitVersion>

src/coreclr/inc/coredistools.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ enum TargetArch {
4343
Target_Arm64,
4444
Target_LoongArch64,
4545
Target_RiscV64,
46+
Target_Wasm32,
4647
};
4748

4849
struct CorDisasm;
@@ -191,6 +192,44 @@ typedef void __cdecl DumpDiffBlocks_t(const CorAsmDiff *AsmDiff,
191192
const uint8_t *Bytes2, size_t Size2);
192193
DllIface DumpDiffBlocks_t DumpDiffBlocks;
193194

195+
// "Framed" variants -- intended for Target_Wasm32 today, where the JIT writes
196+
// the code buffer as a sequence of length-prefixed Wasm function/funclet
197+
// bodies rather than a flat instruction stream.
198+
//
199+
// Layout of each record:
200+
// [ULEB128 body_size_in_bytes][body bytes]
201+
//
202+
// Each body begins with the standard Wasm locals declaration
203+
// [ULEB128 num_local_decls][(ULEB128 count, u8 valtype)*]
204+
// followed by the opcode stream (which is expected to end with the outer
205+
// function's `end` opcode 0x0B).
206+
//
207+
// The OffsetComparator callback is invoked with BlockOffset set to the byte
208+
// offset of the current instruction's *opcode byte*, measured from the start
209+
// of the whole framed buffer (Bytes). This matches the offsets used by the
210+
// JIT-recorded relocation tables, so callers can look up reloc kind / target
211+
// directly without further bookkeeping.
212+
//
213+
// The non-framed entry points (NearDiffCodeBlocks, DumpCodeBlock,
214+
// DumpDiffBlocks) automatically delegate to these framed implementations
215+
// when the configured target architecture is Target_Wasm32.
216+
typedef bool __cdecl NearDiffCodeBlocksFramed_t(const CorAsmDiff *AsmDiff,
217+
const void *UserData,
218+
const uint8_t *Address1,
219+
const uint8_t *Bytes1, size_t Size1,
220+
const uint8_t *Address2,
221+
const uint8_t *Bytes2, size_t Size2);
222+
DllIface NearDiffCodeBlocksFramed_t NearDiffCodeBlocksFramed;
223+
224+
typedef void __cdecl DumpCodeBlockFramed_t(const CorDisasm *Disasm,
225+
const uint8_t *Address, const uint8_t *Bytes, size_t Size);
226+
DllIface DumpCodeBlockFramed_t DumpCodeBlockFramed;
227+
228+
typedef void __cdecl DumpDiffBlocksFramed_t(const CorAsmDiff *AsmDiff,
229+
const uint8_t *Address1, const uint8_t *Bytes1, size_t Size1,
230+
const uint8_t *Address2, const uint8_t *Bytes2, size_t Size2);
231+
DllIface DumpDiffBlocksFramed_t DumpDiffBlocksFramed;
232+
194233
// Get a pointer to the buffered output buffer.
195234
typedef const char* __cdecl GetOutputBuffer_t();
196235
DllIface GetOutputBuffer_t GetOutputBuffer;

src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,15 @@ void CompileResult::applyRelocs(RelocContext* rc, unsigned char* block1, ULONG b
749749
if (blocksize1 == 0)
750750
return;
751751

752+
// For wasm32, the JIT writes padded ULEB128/SLEB128 placeholders (PADDED_RELOC_SIZE
753+
// bytes of `80 80 ... 80 00` that decode to zero) in place of the real handle at
754+
// every reloc site. Both baseline and diff JITs write the same placeholder bytes,
755+
// so byte-by-byte comparison naturally succeeds at reloc sites without any
756+
// patching here. The near-differ validates reloc-target equivalence via
757+
// `compareOffsetsWasm` when the surrounding immediate bytes happen to differ.
758+
if (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_WASM32)
759+
return;
760+
752761
size_t section_begin = (size_t)block1;
753762
size_t section_end = (size_t)block1 + (size_t)blocksize1; // address is exclusive
754763

@@ -1166,6 +1175,37 @@ void CompileResult::applyRelocs(RelocContext* rc, unsigned char* block1, ULONG b
11661175
}
11671176
}
11681177

1178+
const Agnostic_RecordRelocation* CompileResult::findRelocationInRange(size_t originalBufferStart,
1179+
size_t originalBufferOffset,
1180+
size_t windowSize)
1181+
{
1182+
if (RecordRelocation == nullptr)
1183+
return nullptr;
1184+
1185+
if (windowSize == 0)
1186+
return nullptr;
1187+
1188+
const size_t rangeLo = originalBufferStart + originalBufferOffset;
1189+
const size_t rangeHi = rangeLo + windowSize;
1190+
1191+
// Guard against size_t wraparound (rangeLo near SIZE_MAX). Bail rather than
1192+
// scan with an inverted half-open range that could match unrelated relocs.
1193+
if (rangeHi < rangeLo)
1194+
return nullptr;
1195+
1196+
const unsigned int count = RecordRelocation->GetCount();
1197+
const Agnostic_RecordRelocation* items = RecordRelocation->GetRawItems();
1198+
for (unsigned int i = 0; i < count; i++)
1199+
{
1200+
const size_t loc = (size_t)items[i].location;
1201+
if ((rangeLo <= loc) && (loc < rangeHi))
1202+
{
1203+
return &items[i];
1204+
}
1205+
}
1206+
return nullptr;
1207+
}
1208+
11691209
void CompileResult::recProcessName(const char* name)
11701210
{
11711211
if (ProcessName == nullptr)

src/coreclr/tools/superpmi/superpmi-shared/compileresult.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ class CompileResult
185185
void repRecordRelocation(void* location, void* target, CorInfoReloc fRelocType, int32_t addlDelta);
186186
void applyRelocs(RelocContext* rc, unsigned char* block1, ULONG blocksize1, void* originalAddr);
187187

188+
// Find the recorded relocation (if any) whose location falls in the half-open
189+
// buffer range [originalBufferOffset, originalBufferOffset + windowSize) relative
190+
// to `originalBufferStart`. Used by the wasm32 near-differ to map a coredistools
191+
// opcode-byte block offset to a JIT-recorded reloc on the immediate-payload byte.
192+
// Returns nullptr if no reloc is recorded in the range.
193+
const Agnostic_RecordRelocation* findRelocationInRange(size_t originalBufferStart,
194+
size_t originalBufferOffset,
195+
size_t windowSize);
196+
188197
void recProcessName(const char* name);
189198
void dmpProcessName(DWORD key, DWORD value);
190199
const char* repProcessName();

src/coreclr/tools/superpmi/superpmi/neardiffer.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ bool NearDiffer::InitAsmDiff()
157157
{
158158
coreDisTargetArchitecture = Target_RiscV64;
159159
}
160+
else if ((0 == _stricmp(TargetArchitecture, "wasm")) || (0 == _stricmp(TargetArchitecture, "wasm32")))
161+
{
162+
// Requires coredistools >= 1.7.0 (Target_Wasm32 + framed entry points).
163+
// Existing NearDiffCodeBlocks / DumpCodeBlock / DumpDiffBlocks auto-route
164+
// to the Wasm framing-aware implementations inside coredistools.
165+
coreDisTargetArchitecture = Target_Wasm32;
166+
}
160167
else
161168
{
162169
LogError("Illegal target architecture '%s'", TargetArchitecture);
@@ -545,6 +552,16 @@ bool NearDiffer::compareOffsets(
545552
return true;
546553
}
547554

555+
// For wasm32, recorded relocations are the source of truth: handles materialize
556+
// as i32.const immediates with padded ULEB128/SLEB128 placeholders that the JIT
557+
// separately records in a reloc table. Defer the "are these two distinct
558+
// immediates semantically equivalent" decision to a wasm-specific path that
559+
// consults the reloc tables instead of replaying generic IP-relative heuristics.
560+
if (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_WASM32)
561+
{
562+
return compareOffsetsWasm(payload, blockOffset, instrLen, offset1, offset2);
563+
}
564+
548565
const DiffData* data = (const DiffData*)payload;
549566
size_t ip1 = data->originalBlock1 + blockOffset;
550567
size_t ip2 = data->originalBlock2 + blockOffset;
@@ -623,6 +640,97 @@ bool NearDiffer::compareOffsets(
623640
return false;
624641
}
625642

643+
//
644+
// Wasm32-specific offset comparator.
645+
//
646+
// For wasm32, integer immediates that materialize JIT handles (function indices,
647+
// type indices, memory addresses, globals) are emitted as 5-byte ULEB128/SLEB128
648+
// placeholders (`80 80 80 80 00`) that the JIT separately records in a reloc
649+
// table. For un-prefixed forms like `i32.const`/`call`, the placeholder sits at
650+
// `opcode-byte + 1`. For un-prefixed memory ops (`i32.load`, `i32.store`, ...),
651+
// the layout is `<opcode> <align>:u32 <reloc-offset>:u32`, so the relocatable
652+
// payload sits at `opcode-byte + 2`. Either way, both baseline and diff JITs
653+
// write the same placeholder bytes, so byte-by-byte comparison naturally
654+
// succeeds at reloc sites without any patching. Any immediate mismatch surfaced
655+
// to this comparator therefore falls into one of three cases:
656+
//
657+
// 1. Hard-coded non-reloc literal that genuinely differs across baseline/diff.
658+
// This is a real codegen change; return false.
659+
//
660+
// 2. Reloc sites where both sides recorded a reloc but the immediates somehow
661+
// differ (e.g. a future change patches the placeholder into real bytes).
662+
// Treat as equivalent when both reloc kinds match and the targets are equal
663+
// after addlDelta correction.
664+
//
665+
// 3. A reloc on one side but not the other - genuinely asymmetric codegen.
666+
// Return false.
667+
//
668+
// `blockOffset` is the opcode-byte offset within the framed buffer, per the
669+
// coredistools Wasm32 contract. The recorded reloc location is the payload byte,
670+
// which may be `opcode-byte + 1` (un-prefixed i32.const/call/etc.),
671+
// `opcode-byte + 2` (un-prefixed loads/stores after the align u32), or further
672+
// for prefixed opcodes. We search the entire immediate window
673+
// `[blockOffset+1, blockOffset+instrLen)` to handle all of these uniformly.
674+
//
675+
bool NearDiffer::compareOffsetsWasm(
676+
const void* payload, size_t blockOffset, size_t instrLen, uint64_t offset1, uint64_t offset2)
677+
{
678+
if (offset1 == offset2)
679+
{
680+
return true;
681+
}
682+
683+
const DiffData* data = (const DiffData*)payload;
684+
685+
// The relocated payload byte may live at `opcode-byte + 1` (un-prefixed
686+
// i32.const/call/etc.), `opcode-byte + 2` (un-prefixed memory ops, after
687+
// the align u32), or even deeper for prefixed forms (0xFC/0xFD/0xFE).
688+
// Rather than threading per-opcode payload offsets through coredistools,
689+
// walk the entire immediate window [blockOffset+1, blockOffset+instrLen)
690+
// and accept the first reloc found. Multiple relocs in a single instruction
691+
// are not emitted today.
692+
const size_t windowStart = blockOffset + 1;
693+
const size_t windowSize = (instrLen > 1) ? (instrLen - 1) : 0;
694+
695+
if (windowSize == 0)
696+
{
697+
// Single-byte opcode (no immediate) -- this comparator should not have
698+
// been called at all. Treat as a real mismatch.
699+
return false;
700+
}
701+
702+
const Agnostic_RecordRelocation* reloc1 =
703+
data->cr1->findRelocationInRange(data->originalBlock1, windowStart, windowSize);
704+
const Agnostic_RecordRelocation* reloc2 =
705+
data->cr2->findRelocationInRange(data->originalBlock2, windowStart, windowSize);
706+
707+
if ((reloc1 == nullptr) || (reloc2 == nullptr))
708+
{
709+
// At most one side has a reloc here. Either a genuine non-reloc immediate
710+
// mismatch (case 1) or an asymmetric reloc (case 3). Real diff.
711+
return false;
712+
}
713+
714+
if (reloc1->fRelocType != reloc2->fRelocType)
715+
{
716+
// Different reloc kinds at the same site is a real codegen change.
717+
return false;
718+
}
719+
720+
const uint64_t target1 = (uint64_t)reloc1->target + (int32_t)reloc1->addlDelta;
721+
const uint64_t target2 = (uint64_t)reloc2->target + (int32_t)reloc2->addlDelta;
722+
if (target1 == target2)
723+
{
724+
return true;
725+
}
726+
727+
// Targets differ. This is the "different handle picked for the same source"
728+
// case. For now, treat as a real mismatch; a future revision can plug in
729+
// handle-equivalence logic mirroring the cr1->cr2 handle remapping used by
730+
// the non-wasm `compareOffsets` heuristics.
731+
return false;
732+
}
733+
626734
//
627735
// Compares two code sections for syntactic equality. This is the core of the asm diffing logic.
628736
//

src/coreclr/tools/superpmi/superpmi/neardiffer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class NearDiffer
7171
static bool compareOffsets(
7272
const void* payload, size_t blockOffset, size_t instrLen, uint64_t offset1, uint64_t offset2);
7373

74+
static bool compareOffsetsWasm(
75+
const void* payload, size_t blockOffset, size_t instrLen, uint64_t offset1, uint64_t offset2);
76+
7477
static bool mungeOffsets(
7578
const void* payload, size_t blockOffset, size_t instrLen, uint64_t* offset1, uint64_t* offset2, uint32_t* skip1, uint32_t* skip2);
7679

0 commit comments

Comments
 (0)