From 41b8bbf5d2896b3bbf82b1ffcd77344933afbc03 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Thu, 1 Oct 2026 23:07:47 +0200 Subject: [PATCH] [cDAC][wasm] Register platform GC-info decoding Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/design/datacontracts/GCInfo.md | 9 ++++ .../GCInfo/PlatformTraits/WasmGCInfoTraits.cs | 41 +++++++++++++++++ .../CoreCLRContracts.cs | 1 + .../cdac/tests/UnitTests/StackWalkTests.cs | 46 +++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/WasmGCInfoTraits.cs diff --git a/docs/design/datacontracts/GCInfo.md b/docs/design/datacontracts/GCInfo.md index a114899650afda..7ba3cfbc90ab9d 100644 --- a/docs/design/datacontracts/GCInfo.md +++ b/docs/design/datacontracts/GCInfo.md @@ -4,6 +4,15 @@ This contract is for fetching information related to GCInfo associated with nati The GCInfo contract has platform specific implementations as GCInfo differs per architecture. With the exception of x86, all platforms have a common encoding scheme with different encoding lengths and normalization functions for data. x86 uses an entirely different scheme which is partially supported by this contract. +WebAssembly uses the common decoder with `Wasm32GcInfoEncoding` traits from +`src/coreclr/inc/gcinfotypes.h`: code lengths/offsets and stack offsets are +unscaled, the code-length encoding base is 6, and there is no fixed stack +parameter scratch area. The platform encoding has no interruptible-range +count in its fat header (`HAS_INTERRUPTIBLE_RANGES` is false). +`DecodeInterpreterGCInfo` uses the separate interpreter traits even on +WebAssembly, including its code-length encoding base of 8 and its +interruptible-range count field. + ## APIs of contract ```csharp diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/WasmGCInfoTraits.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/WasmGCInfoTraits.cs new file mode 100644 index 00000000000000..60fa8fe8f3894b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/WasmGCInfoTraits.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +// Mirrors Wasm32GcInfoEncoding in src/coreclr/inc/gcinfotypes.h. +internal sealed class WasmGCInfoTraits : IGCInfoTraits +{ + public static uint DenormalizeStackBaseRegister(uint reg) => reg; + public static uint DenormalizeCodeLength(uint len) => len; + public static uint NormalizeCodeLength(uint len) => len; + public static uint DenormalizeCodeOffset(uint offset) => offset; + public static uint NormalizeCodeOffset(uint offset) => offset; + public static int DenormalizeStackSlot(int value) => value; + public static uint DenormalizeSizeOfStackArea(uint size) => size; + + public static int GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE => 6; + public static int GS_COOKIE_STACK_SLOT_ENCBASE => 6; + public static int CODE_LENGTH_ENCBASE => 6; + public static int STACK_BASE_REGISTER_ENCBASE => 3; + public static int SIZE_OF_STACK_AREA_ENCBASE => 6; + public static int SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE => 3; + public static int SIZE_OF_EDIT_AND_CONTINUE_FIXED_STACK_FRAME_ENCBASE => 0; + public static int REVERSE_PINVOKE_FRAME_ENCBASE => 6; + public static int NUM_REGISTERS_ENCBASE => 3; + public static int NUM_STACK_SLOTS_ENCBASE => 5; + public static int NUM_UNTRACKED_SLOTS_ENCBASE => 5; + public static int NORM_PROLOG_SIZE_ENCBASE => 4; + public static int NORM_EPILOG_SIZE_ENCBASE => 3; + public static int INTERRUPTIBLE_RANGE_DELTA1_ENCBASE => 5; + public static int INTERRUPTIBLE_RANGE_DELTA2_ENCBASE => 5; + public static int REGISTER_ENCBASE => 3; + public static int REGISTER_DELTA_ENCBASE => 3; + public static int STACK_SLOT_ENCBASE => 6; + public static int STACK_SLOT_DELTA_ENCBASE => 4; + public static int NUM_SAFE_POINTS_ENCBASE => 4; + public static int NUM_INTERRUPTIBLE_RANGES_ENCBASE => 1; + public static bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA => false; + public static bool HAS_INTERRUPTIBLE_RANGES => false; + public static bool IsScratchRegister(uint regNum) => false; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index 1e36b4ea04ed33..808d6350d69001 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -68,6 +68,7 @@ public static void Register(ContractRegistry registry) RuntimeInfoArchitecture.Arm => new GCInfo_1(t), RuntimeInfoArchitecture.LoongArch64 => new GCInfo_1(t), RuntimeInfoArchitecture.RiscV64 => new GCInfo_1(t), + RuntimeInfoArchitecture.Wasm => new GCInfo_1(t), _ => default(GCInfo), }; }); diff --git a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs index 7ef05e11299899..9bbdb1273ce1b4 100644 --- a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs @@ -15,6 +15,52 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public unsafe class StackWalkTests { + [Theory] + [InlineData(false, new byte[] { 0x04, 0x05, 0x21, 0x02 }, uint.MaxValue)] + [InlineData(false, new byte[] { 0x81, 0x08, 0x0a, 0x30, 0x42, 0x04 }, 8u)] + [InlineData(true, new byte[] { 0x04, 0x4a, 0x04 }, uint.MaxValue)] + [InlineData(true, new byte[] { 0x81, 0x08, 0x84, 0x11, 0x22, 0x00 }, 8u)] + public void GCInfo_Wasm_DecodesPlatformAndInterpreterFormats( + bool interpreter, byte[] encoded, uint stackBaseRegister) + { + MockTarget.Architecture arch = new() { IsLittleEndian = true, Is64Bit = false }; + TestPlaceholderTarget.Builder builder = new(arch); + // Length 129 and safe point 17 distinguish the platform's six-bit encoding + // from the interpreter's eight-bit encoding and exercise header alignment. + byte[] gcInfo = new byte[32]; + encoded.CopyTo(gcInfo, 0); + builder.MemoryBuilder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = 0x1000, + Data = gcInfo, + Name = "Wasm GC info" + }); + Mock runtimeInfo = new(); + runtimeInfo.Setup(r => r.GetTargetArchitecture()).Returns(RuntimeInfoArchitecture.Wasm); + IGCInfo contract = builder + .AddMockContract(runtimeInfo) + .AddContract(version: "c1") + .Build().Contracts.GCInfo; + IGCInfoHandle handle = interpreter + ? contract.DecodeInterpreterGCInfo(new TargetPointer(0x1000), 4) + : contract.DecodePlatformSpecificGCInfo(new TargetPointer(0x1000), 4); + + GCInfoHeader header = contract.GetHeader(handle); + Assert.Equal(129u, contract.GetCodeLength(handle)); + Assert.Equal(129u, header.CodeSize); + Assert.Equal(stackBaseRegister, header.StackBaseRegister); + Assert.Equal(0u, header.SizeOfStackParameterArea); + Assert.Equal(new uint[] { 17 }, contract.GetSafePoints(handle)); + Assert.Empty(contract.GetInterruptibleRanges(handle)); + Assert.Equal(interpreter, contract.TryGetGenericContextStorage( + handle, GenericContextLoc.ThisPtr, 0, out GenericContextStorage storage)); + if (interpreter) + { + Assert.Equal(GenericContextStorageKind.InterpreterArgumentRelative, storage.Kind); + Assert.Equal(0, storage.Offset); + } + } + [Fact] public void LoongArch64Unwind_EpilogReturn_DoesNotRepeatStackAdjustment() {