diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 98f76c62771b5d..120fc3aa3cbea6 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -218,6 +218,12 @@ partial interface IRuntimeTypeSystem : IContract // Returns true if the method is eligible for tiered compilation public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc); + // Return true if the method is an async thunk method. + public virtual bool IsAsyncThunkMethod(MethodDescHandle methodDesc); + + // Return true if the method is a wrapper stub (unboxing or instantiating). + public virtual bool IsWrapperStub(MethodDescHandle methodDesc); + } ``` @@ -1098,6 +1104,13 @@ And the following enumeration definitions ILStubTypeMask = 0x000007FF, } + [Flags] + internal enum AsyncMethodFlags : uint + { + None = 0, + Thunk = 16, + } + [Flags] internal enum ILStubType : uint { @@ -1503,6 +1516,32 @@ Determining if a method supports multiple code versions: } ``` +Determining if a method is an async thunk method: + +```csharp + public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + if (!md.HasAsyncMethodData) + { + return false; + } + + Data.AsyncMethodData asyncData = // Read AsyncMethodData from the address of the async method data optional slot + return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk); + } +``` + +Determining if a method is a wrapper stub (unboxing or instantiating): + +```csharp + public bool IsWrapperStub(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + return md.IsUnboxingStub || IsInstantiatingStub(md); + } +``` + Extracting a pointer to the `MethodDescVersioningState` data for a given method ```csharp diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 3e2cfa6061d336..b5cccc1b0a335b 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -2016,8 +2016,8 @@ IDacDbiInterface : public IUnknown // // Arguments: // vmObject - The object to check for ownership - // pRetVal - [out] Inside the structure we have: - // pVmThread - the owning thread or VMPTR_Thread::NullPtr() if unowned, + // pRetVal - [out] Inside the structure we have: + // pVmThread - the owning thread or VMPTR_Thread::NullPtr() if unowned, // pAcquisitionCount - the number of times the lock would need to be released in order for it to be unowned. // // Return Value: diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 9d723e858f6dde..06f63738c3a779 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -559,6 +559,7 @@ CDAC_TYPE_END(NativeCodeSlot) CDAC_TYPE_BEGIN(AsyncMethodData) CDAC_TYPE_SIZE(sizeof(AsyncMethodData)) +CDAC_TYPE_FIELD(AsyncMethodData, T_UINT32, Flags, offsetof(AsyncMethodData, flags)) CDAC_TYPE_END(AsyncMethodData) CDAC_TYPE_BEGIN(InstantiatedMethodDesc) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 9188d081cd5ee4..e187aa39bbaf0b 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -64,6 +64,7 @@ EXTERN_C VOID STDCALL PInvokeImportThunk(); #define FEATURE_DYNAMIC_METHOD_HAS_NATIVE_STACK_ARG_SIZE #endif +// [cDAC] [RuntimeTypeSystem]: Contract depends on the values of Thunk, None. enum class AsyncMethodFlags { // Method uses CORINFO_CALLCONV_ASYNCCALL call convention. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 4db9c40c20e1f5..501443d5a80362 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -225,6 +225,10 @@ public interface IRuntimeTypeSystem : IContract OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); bool IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); + + bool IsAsyncThunkMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + bool IsWrapperStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs #region FieldDesc inspection APIs TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 496f64f2608b92..128fba5aa83017 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -151,6 +151,13 @@ internal enum DynamicMethodDescExtendedFlags : uint ILStubTypeMask = 0x000007FF, } + [Flags] + internal enum AsyncMethodFlags : uint + { + None = 0, + Thunk = 16, + } + [Flags] internal enum ILStubType : uint { @@ -303,12 +310,14 @@ private static uint ComputeSize(Target target, Data.MethodDesc desc) internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags); internal bool HasNativeCodeSlot => MethodDescOptionalSlots.HasNativeCodeSlot(_desc.Flags); + internal bool HasAsyncMethodData => MethodDescOptionalSlots.HasAsyncMethodData(_desc.Flags); internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); internal TargetPointer GetAddressOfNonVtableSlot() => MethodDescOptionalSlots.GetAddressOfNonVtableSlot(Address, Classification, _desc.Flags, _target); internal TargetPointer GetAddressOfNativeCodeSlot() => MethodDescOptionalSlots.GetAddressOfNativeCodeSlot(Address, Classification, _desc.Flags, _target); + internal TargetPointer GetAddressOfAsyncMethodData() => MethodDescOptionalSlots.GetAddressOfAsyncMethodData(Address, Classification, _desc.Flags, _target); internal bool IsLoaderModuleAttachedToChunk => HasFlags(MethodDescChunkFlags.LoaderModuleAttachedToChunk); @@ -1321,7 +1330,6 @@ public bool IsIL(MethodDescHandle methodDescHandle) public bool IsILStub(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - if (methodDesc.Classification != MethodClassification.Dynamic) { return false; @@ -1728,6 +1736,24 @@ bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDe return methodDesc.IsEligibleForTieredCompilation; } + public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + if (!md.HasAsyncMethodData) + { + return false; + } + + Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd(md.GetAddressOfAsyncMethodData()); + return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk); + } + + public bool IsWrapperStub(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + return IsWrapperStub(methodDesc); + } + private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries { private readonly RuntimeTypeSystem_1 _rts; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs new file mode 100644 index 00000000000000..c28efb170d9500 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs @@ -0,0 +1,17 @@ +// 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.Data; + +internal sealed class AsyncMethodData : IData +{ + static AsyncMethodData IData.Create(Target target, TargetPointer address) => new AsyncMethodData(target, address); + public AsyncMethodData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.AsyncMethodData); + + Flags = target.ReadField(address, type, nameof(Flags)); + } + + public uint Flags { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs index 028b58f27d1c0c..afd5075105dd50 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs @@ -4,8 +4,8 @@ namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; -// Non-vtable slot, native code slot, and MethodImpl slots are stored after the MethodDesc itself, packed tightly -// in the order: [non-vtable; method impl; native code]. +// Optional slots are stored after the MethodDesc itself, packed tightly +// in the order: [non-vtable; method impl; native code; async method data]. internal static class MethodDescOptionalSlots { internal static bool HasNonVtableSlot(ushort flags) @@ -17,6 +17,9 @@ internal static bool HasMethodImpl(ushort flags) internal static bool HasNativeCodeSlot(ushort flags) => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot) != 0; + internal static bool HasAsyncMethodData(ushort flags) + => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData) != 0; + internal static TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDesc, MethodClassification classification, ushort flags, Target target) { uint offset = StartOffset(classification, target); @@ -31,6 +34,13 @@ internal static TargetPointer GetAddressOfNativeCodeSlot(TargetPointer methodDes return methodDesc + offset; } + internal static TargetPointer GetAddressOfAsyncMethodData(TargetPointer methodDesc, MethodClassification classification, ushort flags, Target target) + { + uint offset = StartOffset(classification, target); + offset += AsyncMethodDataOffset(flags, target); + return methodDesc + offset; + } + // Offset from the MethodDesc address to the start of its optional slots private static uint StartOffset(MethodClassification classification, Target target) { @@ -89,4 +99,22 @@ private static uint NativeCodeSlotOffset(ushort flags, Target target) return offset; } + + private static uint AsyncMethodDataOffset(ushort flags, Target target) + { + if (!HasAsyncMethodData(flags)) + throw new InvalidOperationException("no async method data"); + + uint offset = 0; + if (HasNonVtableSlot(flags)) + offset += target.GetTypeInfo(DataType.NonVtableSlot).Size!.Value; + + if (HasMethodImpl(flags)) + offset += target.GetTypeInfo(DataType.MethodImpl).Size!.Value; + + if (HasNativeCodeSlot(flags)) + offset += target.GetTypeInfo(DataType.NativeCodeSlot).Size!.Value; + + return offset; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index a480fe86b576b4..dc86a868c98370 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -567,7 +567,38 @@ public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Int => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = (int)DynamicMethodType.kNone; + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc)); + if (rts.IsILStub(md) || rts.IsAsyncThunkMethod(md) || rts.IsWrapperStub(md)) + { + *pRetVal = (int)DynamicMethodType.kDiagnosticHidden; + } + else if (rts.IsDynamicMethod(md)) + { + *pRetVal = (int)DynamicMethodType.kLCGMethod; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + int resultLocal; + int hrLocal = _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == resultLocal, $"cDAC: {*pRetVal}, DAC: {resultLocal}"); + } +#endif + return hr; + } public int GetVarArgSig(ulong VASigCookieAddr, ulong* pArgBase, DacDbiTargetBuffer* pRetVal) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetVarArgSig(VASigCookieAddr, pArgBase, pRetVal) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index d26605e3b86f5a..8ea39f9d13b07a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -138,6 +138,13 @@ public struct COR_FIELD #pragma warning restore CS0649 +public enum DynamicMethodType +{ + kNone = 0, + kDiagnosticHidden = 1, + kLCGMethod = 2, +} + // Name-surface projection of IDacDbiInterface in native method order for COM binding validation. // Parameter shapes are intentionally coarse placeholders and will be refined with method implementation work. [GeneratedComInterface] diff --git a/src/native/managed/cdac/tests/MethodDescTests.cs b/src/native/managed/cdac/tests/MethodDescTests.cs index 8cf05c2c2346b8..d28673f51cef4a 100644 --- a/src/native/managed/cdac/tests/MethodDescTests.cs +++ b/src/native/managed/cdac/tests/MethodDescTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Data; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Moq; using Xunit; @@ -24,7 +25,14 @@ public class MethodDescTests [DataType.NonVtableSlot] = new Target.TypeInfo { Size = methodDescBuilder.NonVtableSlotSize }, [DataType.MethodImpl] = new Target.TypeInfo { Size = methodDescBuilder.MethodImplSize }, [DataType.NativeCodeSlot] = new Target.TypeInfo { Size = methodDescBuilder.NativeCodeSlotSize }, - [DataType.AsyncMethodData] = new Target.TypeInfo { Size = methodDescBuilder.AsyncMethodDataSize }, + [DataType.AsyncMethodData] = new Target.TypeInfo + { + Size = methodDescBuilder.AsyncMethodDataSize, + Fields = new Dictionary + { + [nameof(Data.AsyncMethodData.Flags)] = new Target.FieldInfo { Offset = 0, Type = DataType.Unknown }, + }, + }, [DataType.ArrayMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.ArrayMethodDescSize }, [DataType.FCallMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.FCallMethodDescSize }, [DataType.PInvokeMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.PInvokeMethodDescSize }, @@ -439,6 +447,197 @@ public void GetNativeCode_StableEntryPoint_NonVtableSlot(MockTarget.Architecture Assert.Equal(nativeCode, actualNativeCode); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arch) + { + TargetPointer normalMethod = TargetPointer.Null; + TargetPointer ilStubMethod = TargetPointer.Null; + TargetPointer lcgMethod = TargetPointer.Null; + TargetPointer unboxingStubMethod = TargetPointer.Null; + TargetPointer instantiatingStubMethod = TargetPointer.Null; + TargetPointer asyncThunkMethod = TargetPointer.Null; + TargetPointer asyncNonThunkMethod = TargetPointer.Null; + TargetPointer asyncThunkWithNativeCodeSlotMethod = TargetPointer.Null; + + IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(arch, methodDescBuilder => + { + TargetPointer methodTable = AddMethodTable(methodDescBuilder.RTSBuilder); + TargetTestHelpers helpers = methodDescBuilder.TargetTestHelpers; + + // Normal IL method (not diagnostics hidden, not LCG) + { + byte methodDescSize = (byte)(methodDescBuilder.MethodDescLayout.Size / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("normal", methodDescSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = methodDescSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)MethodClassification.IL; + normalMethod = new TargetPointer(md.Address); + } + + // IL stub (diagnostics hidden via IsILStub) + { + uint methodDescSize = (uint)methodDescBuilder.DynamicMethodDescLayout.Size; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)(2 * methodDescSizeByAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("dynamic", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 2; + + MockDynamicMethodDesc ilStub = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.DynamicMethodDescLayout); + ilStub.Flags = (ushort)MethodClassification.Dynamic; + ilStub.ExtendedFlags = (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsILStub; + ilStubMethod = new TargetPointer(ilStub.Address); + + // LCG method (not diagnostics hidden, is LCG) + MockDynamicMethodDesc lcg = chunk.GetMethodDescAtChunkIndex((int)methodDescSizeByAlignment, methodDescBuilder.DynamicMethodDescLayout); + lcg.ChunkIndex = (byte)methodDescSizeByAlignment; + lcg.Flags = (ushort)MethodClassification.Dynamic; + lcg.Slot = 1; + lcg.ExtendedFlags = (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsLCGMethod; + lcgMethod = new TargetPointer(lcg.Address); + } + + // Unboxing stub (diagnostics hidden via IsWrapperStub -> IsUnboxingStub) + { + byte methodDescSize = (byte)(methodDescBuilder.MethodDescLayout.Size / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("unboxing", methodDescSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = methodDescSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)MethodClassification.IL; + md.Flags3AndTokenRemainder = (ushort)MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub; + unboxingStubMethod = new TargetPointer(md.Address); + } + + // Instantiating stub (diagnostics hidden via IsWrapperStub -> IsInstantiatingStub) + { + uint methodDescSize = (uint)methodDescBuilder.InstantiatedMethodDescLayout.Size; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)methodDescSizeByAlignment; + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("instantiating", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockInstantiatedMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.InstantiatedMethodDescLayout); + md.Flags = (ushort)MethodClassification.Instantiated; + md.Flags2 = (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.WrapperStubWithInstantiations; + instantiatingStubMethod = new TargetPointer(md.Address); + } + + // Async thunk method (diagnostics hidden via IsAsyncThunkMethod, no NativeCodeSlot) + { + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncThunk", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + asyncThunkMethod = new TargetPointer(md.Address); + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.Thunk); + } + + // Async non-thunk method (not diagnostics hidden, has async data but not Thunk flag) + { + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncNonThunk", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + md.Slot = 1; + asyncNonThunkMethod = new TargetPointer(md.Address); + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.None); + } + + // Async thunk method with NativeCodeSlot (diagnostics hidden, verifies offset calculation with preceding slots) + { + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.NativeCodeSlotSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncThunkWithNCS", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + md.Slot = 2; + asyncThunkWithNativeCodeSlotMethod = new TargetPointer(md.Address); + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize + (int)methodDescBuilder.NativeCodeSlotSize; + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.Thunk); + } + }); + + // Normal IL method: not hidden by any primitive + { + MethodDescHandle handle = rts.GetMethodDescHandle(normalMethod); + Assert.False(rts.IsILStub(handle)); + Assert.False(rts.IsAsyncThunkMethod(handle)); + Assert.False(rts.IsWrapperStub(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // IL stub: hidden via IsILStub + { + MethodDescHandle handle = rts.GetMethodDescHandle(ilStubMethod); + Assert.True(rts.IsILStub(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // LCG method: not hidden, is LCG + { + MethodDescHandle handle = rts.GetMethodDescHandle(lcgMethod); + Assert.False(rts.IsILStub(handle)); + Assert.False(rts.IsAsyncThunkMethod(handle)); + Assert.False(rts.IsWrapperStub(handle)); + Assert.True(rts.IsDynamicMethod(handle)); + } + + // Unboxing stub: hidden via IsWrapperStub + { + MethodDescHandle handle = rts.GetMethodDescHandle(unboxingStubMethod); + Assert.True(rts.IsWrapperStub(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // Instantiating stub: hidden via IsWrapperStub + { + MethodDescHandle handle = rts.GetMethodDescHandle(instantiatingStubMethod); + Assert.True(rts.IsWrapperStub(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // Async thunk: hidden via IsAsyncThunkMethod + { + MethodDescHandle handle = rts.GetMethodDescHandle(asyncThunkMethod); + Assert.True(rts.IsAsyncThunkMethod(handle)); + } + + // Async non-thunk: not hidden + { + MethodDescHandle handle = rts.GetMethodDescHandle(asyncNonThunkMethod); + Assert.False(rts.IsAsyncThunkMethod(handle)); + } + + // Async thunk with NativeCodeSlot: hidden via IsAsyncThunkMethod (verifies offset calculation) + { + MethodDescHandle handle = rts.GetMethodDescHandle(asyncThunkWithNativeCodeSlotMethod); + Assert.True(rts.IsAsyncThunkMethod(handle)); + } + } + private static TargetPointer AddMethodTable(MockDescriptors.RuntimeTypeSystem rtsBuilder, ushort numVirtuals = 5) { MockEEClass eeClass = rtsBuilder.AddEEClass(string.Empty);