@@ -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//
0 commit comments