Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
<Antlr4BuildTasksVersion>12.14.0</Antlr4BuildTasksVersion>
<Antlr4RuntimeStandardVersion>4.13.1</Antlr4RuntimeStandardVersion>
<!-- Testing -->
<MicrosoftNETCoreCoreDisToolsVersion>1.6.0</MicrosoftNETCoreCoreDisToolsVersion>
<MicrosoftNETCoreCoreDisToolsVersion>1.7.0</MicrosoftNETCoreCoreDisToolsVersion>
<MicrosoftNETTestSdkVersion>17.4.0-preview-20220707-01</MicrosoftNETTestSdkVersion>
<MicrosoftOneCollectRecordTraceVersion>0.1.33421</MicrosoftOneCollectRecordTraceVersion>
<NUnitVersion>3.12.0</NUnitVersion>
Expand Down
39 changes: 39 additions & 0 deletions src/coreclr/inc/coredistools.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ enum TargetArch {
Target_Arm64,
Target_LoongArch64,
Target_RiscV64,
Target_Wasm32,
};

struct CorDisasm;
Expand Down Expand Up @@ -191,6 +192,44 @@ typedef void __cdecl DumpDiffBlocks_t(const CorAsmDiff *AsmDiff,
const uint8_t *Bytes2, size_t Size2);
DllIface DumpDiffBlocks_t DumpDiffBlocks;

// "Framed" variants -- intended for Target_Wasm32 today, where the JIT writes
// the code buffer as a sequence of length-prefixed Wasm function/funclet
// bodies rather than a flat instruction stream.
//
// Layout of each record:
// [ULEB128 body_size_in_bytes][body bytes]
//
// Each body begins with the standard Wasm locals declaration
// [ULEB128 num_local_decls][(ULEB128 count, u8 valtype)*]
// followed by the opcode stream (which is expected to end with the outer
// function's `end` opcode 0x0B).
//
// The OffsetComparator callback is invoked with BlockOffset set to the byte
// offset of the current instruction's *opcode byte*, measured from the start
// of the whole framed buffer (Bytes). This matches the offsets used by the
// JIT-recorded relocation tables, so callers can look up reloc kind / target
// directly without further bookkeeping.
//
// The non-framed entry points (NearDiffCodeBlocks, DumpCodeBlock,
// DumpDiffBlocks) automatically delegate to these framed implementations
// when the configured target architecture is Target_Wasm32.
typedef bool __cdecl NearDiffCodeBlocksFramed_t(const CorAsmDiff *AsmDiff,
const void *UserData,
const uint8_t *Address1,
const uint8_t *Bytes1, size_t Size1,
const uint8_t *Address2,
const uint8_t *Bytes2, size_t Size2);
DllIface NearDiffCodeBlocksFramed_t NearDiffCodeBlocksFramed;

typedef void __cdecl DumpCodeBlockFramed_t(const CorDisasm *Disasm,
const uint8_t *Address, const uint8_t *Bytes, size_t Size);
DllIface DumpCodeBlockFramed_t DumpCodeBlockFramed;

typedef void __cdecl DumpDiffBlocksFramed_t(const CorAsmDiff *AsmDiff,
const uint8_t *Address1, const uint8_t *Bytes1, size_t Size1,
const uint8_t *Address2, const uint8_t *Bytes2, size_t Size2);
DllIface DumpDiffBlocksFramed_t DumpDiffBlocksFramed;

// Get a pointer to the buffered output buffer.
typedef const char* __cdecl GetOutputBuffer_t();
DllIface GetOutputBuffer_t GetOutputBuffer;
Expand Down
40 changes: 40 additions & 0 deletions src/coreclr/tools/superpmi/superpmi-shared/compileresult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,15 @@ void CompileResult::applyRelocs(RelocContext* rc, unsigned char* block1, ULONG b
if (blocksize1 == 0)
return;

// For wasm32, the JIT writes padded ULEB128/SLEB128 placeholders (PADDED_RELOC_SIZE
// bytes of `80 80 ... 80 00` that decode to zero) in place of the real handle at
// every reloc site. Both baseline and diff JITs write the same placeholder bytes,
// so byte-by-byte comparison naturally succeeds at reloc sites without any
// patching here. The near-differ validates reloc-target equivalence via
// `compareOffsetsWasm` when the surrounding immediate bytes happen to differ.
if (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_WASM32)
return;

size_t section_begin = (size_t)block1;
size_t section_end = (size_t)block1 + (size_t)blocksize1; // address is exclusive

Expand Down Expand Up @@ -1166,6 +1175,37 @@ void CompileResult::applyRelocs(RelocContext* rc, unsigned char* block1, ULONG b
}
}

const Agnostic_RecordRelocation* CompileResult::findRelocationInRange(size_t originalBufferStart,
size_t originalBufferOffset,
size_t windowSize)
{
if (RecordRelocation == nullptr)
return nullptr;

if (windowSize == 0)
return nullptr;

const size_t rangeLo = originalBufferStart + originalBufferOffset;
const size_t rangeHi = rangeLo + windowSize;

Comment thread
AndyAyersMS marked this conversation as resolved.
// Guard against size_t wraparound (rangeLo near SIZE_MAX). Bail rather than
// scan with an inverted half-open range that could match unrelated relocs.
if (rangeHi < rangeLo)
return nullptr;

const unsigned int count = RecordRelocation->GetCount();
const Agnostic_RecordRelocation* items = RecordRelocation->GetRawItems();
for (unsigned int i = 0; i < count; i++)
{
const size_t loc = (size_t)items[i].location;
if ((rangeLo <= loc) && (loc < rangeHi))
{
return &items[i];
}
}
return nullptr;
}

void CompileResult::recProcessName(const char* name)
{
if (ProcessName == nullptr)
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/tools/superpmi/superpmi-shared/compileresult.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ class CompileResult
void repRecordRelocation(void* location, void* target, CorInfoReloc fRelocType, int32_t addlDelta);
void applyRelocs(RelocContext* rc, unsigned char* block1, ULONG blocksize1, void* originalAddr);

// Find the recorded relocation (if any) whose location falls in the half-open
// buffer range [originalBufferOffset, originalBufferOffset + windowSize) relative
// to `originalBufferStart`. Used by the wasm32 near-differ to map a coredistools
// opcode-byte block offset to a JIT-recorded reloc on the immediate-payload byte.
// Returns nullptr if no reloc is recorded in the range.
const Agnostic_RecordRelocation* findRelocationInRange(size_t originalBufferStart,
size_t originalBufferOffset,
size_t windowSize);

void recProcessName(const char* name);
void dmpProcessName(DWORD key, DWORD value);
const char* repProcessName();
Expand Down
108 changes: 108 additions & 0 deletions src/coreclr/tools/superpmi/superpmi/neardiffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ bool NearDiffer::InitAsmDiff()
{
coreDisTargetArchitecture = Target_RiscV64;
}
else if ((0 == _stricmp(TargetArchitecture, "wasm")) || (0 == _stricmp(TargetArchitecture, "wasm32")))
{
// Requires coredistools >= 1.7.0 (Target_Wasm32 + framed entry points).
// Existing NearDiffCodeBlocks / DumpCodeBlock / DumpDiffBlocks auto-route
// to the Wasm framing-aware implementations inside coredistools.
coreDisTargetArchitecture = Target_Wasm32;
}
else
{
LogError("Illegal target architecture '%s'", TargetArchitecture);
Expand Down Expand Up @@ -545,6 +552,16 @@ bool NearDiffer::compareOffsets(
return true;
}

// For wasm32, recorded relocations are the source of truth: handles materialize
// as i32.const immediates with padded ULEB128/SLEB128 placeholders that the JIT
// separately records in a reloc table. Defer the "are these two distinct
// immediates semantically equivalent" decision to a wasm-specific path that
// consults the reloc tables instead of replaying generic IP-relative heuristics.
if (GetSpmiTargetArchitecture() == SPMI_TARGET_ARCHITECTURE_WASM32)
{
return compareOffsetsWasm(payload, blockOffset, instrLen, offset1, offset2);
}

const DiffData* data = (const DiffData*)payload;
size_t ip1 = data->originalBlock1 + blockOffset;
size_t ip2 = data->originalBlock2 + blockOffset;
Expand Down Expand Up @@ -623,6 +640,97 @@ bool NearDiffer::compareOffsets(
return false;
}

//
// Wasm32-specific offset comparator.
//
// For wasm32, integer immediates that materialize JIT handles (function indices,
// type indices, memory addresses, globals) are emitted as 5-byte ULEB128/SLEB128
// placeholders (`80 80 80 80 00`) that the JIT separately records in a reloc
// table. For un-prefixed forms like `i32.const`/`call`, the placeholder sits at
// `opcode-byte + 1`. For un-prefixed memory ops (`i32.load`, `i32.store`, ...),
// the layout is `<opcode> <align>:u32 <reloc-offset>:u32`, so the relocatable
// payload sits at `opcode-byte + 2`. Either way, both baseline and diff JITs
// write the same placeholder bytes, so byte-by-byte comparison naturally
// succeeds at reloc sites without any patching. Any immediate mismatch surfaced
// to this comparator therefore falls into one of three cases:
//
// 1. Hard-coded non-reloc literal that genuinely differs across baseline/diff.
// This is a real codegen change; return false.
//
// 2. Reloc sites where both sides recorded a reloc but the immediates somehow
// differ (e.g. a future change patches the placeholder into real bytes).
// Treat as equivalent when both reloc kinds match and the targets are equal
// after addlDelta correction.
//
// 3. A reloc on one side but not the other - genuinely asymmetric codegen.
// Return false.
//
// `blockOffset` is the opcode-byte offset within the framed buffer, per the
// coredistools Wasm32 contract. The recorded reloc location is the payload byte,
// which may be `opcode-byte + 1` (un-prefixed i32.const/call/etc.),
// `opcode-byte + 2` (un-prefixed loads/stores after the align u32), or further
// for prefixed opcodes. We search the entire immediate window
// `[blockOffset+1, blockOffset+instrLen)` to handle all of these uniformly.
//
bool NearDiffer::compareOffsetsWasm(
const void* payload, size_t blockOffset, size_t instrLen, uint64_t offset1, uint64_t offset2)
{
if (offset1 == offset2)
{
return true;
}

const DiffData* data = (const DiffData*)payload;

// The relocated payload byte may live at `opcode-byte + 1` (un-prefixed
// i32.const/call/etc.), `opcode-byte + 2` (un-prefixed memory ops, after
// the align u32), or even deeper for prefixed forms (0xFC/0xFD/0xFE).
// Rather than threading per-opcode payload offsets through coredistools,
// walk the entire immediate window [blockOffset+1, blockOffset+instrLen)
// and accept the first reloc found. Multiple relocs in a single instruction
// are not emitted today.
const size_t windowStart = blockOffset + 1;
const size_t windowSize = (instrLen > 1) ? (instrLen - 1) : 0;

if (windowSize == 0)
{
// Single-byte opcode (no immediate) -- this comparator should not have
// been called at all. Treat as a real mismatch.
return false;
}

const Agnostic_RecordRelocation* reloc1 =
data->cr1->findRelocationInRange(data->originalBlock1, windowStart, windowSize);
const Agnostic_RecordRelocation* reloc2 =
data->cr2->findRelocationInRange(data->originalBlock2, windowStart, windowSize);

if ((reloc1 == nullptr) || (reloc2 == nullptr))
{
// At most one side has a reloc here. Either a genuine non-reloc immediate
// mismatch (case 1) or an asymmetric reloc (case 3). Real diff.
return false;
}

if (reloc1->fRelocType != reloc2->fRelocType)
{
// Different reloc kinds at the same site is a real codegen change.
return false;
}

const uint64_t target1 = (uint64_t)reloc1->target + (int32_t)reloc1->addlDelta;
const uint64_t target2 = (uint64_t)reloc2->target + (int32_t)reloc2->addlDelta;
if (target1 == target2)
{
return true;
}

// Targets differ. This is the "different handle picked for the same source"
// case. For now, treat as a real mismatch; a future revision can plug in
// handle-equivalence logic mirroring the cr1->cr2 handle remapping used by
// the non-wasm `compareOffsets` heuristics.
return false;
}

//
// Compares two code sections for syntactic equality. This is the core of the asm diffing logic.
//
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/tools/superpmi/superpmi/neardiffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class NearDiffer
static bool compareOffsets(
const void* payload, size_t blockOffset, size_t instrLen, uint64_t offset1, uint64_t offset2);

static bool compareOffsetsWasm(
const void* payload, size_t blockOffset, size_t instrLen, uint64_t offset1, uint64_t offset2);

static bool mungeOffsets(
const void* payload, size_t blockOffset, size_t instrLen, uint64_t* offset1, uint64_t* offset2, uint32_t* skip1, uint32_t* skip2);

Expand Down
Loading