diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 63d637129e007c..6961f63478faa7 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -51,7 +51,11 @@ public virtual TargetPointer GetIL(ILCodeVersionHandle ilCodeVersionHandle); // Determines whether an IL code version has default IL public virtual bool HasDefaultIL(ILCodeVersionHandle ilCodeVersionHandle); + +// Gets the optimization tier for a native code version +public virtual OptimizationTier GetOptimizationTier(NativeCodeVersionHandle codeVersionHandle); ``` + ### Extension Methods ```csharp // Return a handle to the active version of the native code for a given method descriptor @@ -73,6 +77,7 @@ Data descriptors used: | NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below | | NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL code version | | NativeCodeVersionNode | GCCoverageInfo | GCStress debug info, if supported | +| NativeCodeVersionNode | OptimizationTier | The optimization tier of this native code version | | ILCodeVersioningState | FirstVersionNode | pointer to the first `ILCodeVersionNode` | | ILCodeVersioningState | ActiveVersionKind | an `ILCodeVersionKind` value indicating which fields of the active version are value | | ILCodeVersioningState | ActiveVersionNode | if the active version is explicit, the NativeCodeVersionNode for the active version | @@ -390,4 +395,4 @@ bool ICodeVersions.HasDefaultIL(ILCodeVersionHandle ilCodeVersionHandle) { return ilCodeVersionHandle.IsExplicit ? AsNode(ilCodeVersionHandle).ILAddress == TargetPointer.Null : true; } -``` \ No newline at end of file +``` diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index c6943aacd0c33f..7bbb2c31f1d98e 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -69,6 +69,7 @@ IEnumerable GetInstantiatedMethods(ModuleHandle handle); bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); +bool IsReadyToRun(ModuleHandle handle); bool TryGetSimpleName(ModuleHandle handle, out string simpleName); string GetPath(ModuleHandle handle); string GetFileName(ModuleHandle handle); diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index ab521583c3edcb..5d4c5c8080c6c9 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -126,6 +126,19 @@ public enum ArrayFunctionType } ``` +```csharp +public enum OptimizationTier +{ + OptimizationTierUnknown, + OptimizationTier0, + OptimizationTier1, + OptimizationTier1OSR, + OptimizationTierOptimized, + OptimizationTier0Instrumented, + OptimizationTier1Instrumented, +} +``` + ```csharp partial interface IRuntimeTypeSystem : IContract { @@ -196,6 +209,12 @@ partial interface IRuntimeTypeSystem : IContract // Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null public virtual TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc); + // Gets the optimization tier stored on the MethodDesc's code data + public virtual OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDesc); + + // Returns true if the method is eligible for tiered compilation + public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc); + } ``` @@ -424,6 +443,20 @@ The contract additionally depends on these data descriptors | `GenericsDictInfo` | `NumDicts` | Number of instantiation dictionaries, including inherited ones, in this `GenericsDictInfo` | | `GenericsDictInfo` | `NumTypeArgs` | Number of type arguments in the type or method instantiation described by this `GenericsDictInfo` | +The value of the `NativeCodeVersionNode::OptimizationTier` field is one of: +```csharp +private enum OptimizationTier_1 : uint +{ + OptimizationTier0 = 0, + OptimizationTier1 = 1, + OptimizationTier1OSR = 2, + OptimizationTierOptimized = 3, + OptimizationTier0Instrumented = 4, + OptimizationTier1Instrumented = 5, + OptimizationTierUnknown = 0xFFFFFFFF +} +``` + Contracts used: | Contract Name | | --- | @@ -1798,4 +1831,4 @@ void GetCoreLibFieldDescAndDef(string @namespace, string typeName, string fieldN MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; fieldDef = mdReader.GetFieldDefinition(fieldHandle); } -``` \ No newline at end of file +``` diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index b644768a3e90c1..0d44575531dc4f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1279,13 +1279,9 @@ HRESULT ClrDataAccess::GetTieredVersions( break; } } - else if (pMD->IsJitOptimizationDisabled()) - { - nativeCodeAddrs[count].OptimizationTier = DacpTieredVersionData::OptimizationTier_MinOptJitted; - } else { - nativeCodeAddrs[count].OptimizationTier = DacpTieredVersionData::OptimizationTier_Optimized; + nativeCodeAddrs[count].OptimizationTier = DacpTieredVersionData::OptimizationTier_Unknown; } ++count; diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 990085695f5345..50c4b82e28fd2d 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -340,6 +340,9 @@ struct cdac_data #ifdef HAVE_GCCOVER static constexpr size_t GCCoverageInfo = offsetof(NativeCodeVersionNode, m_gcCover); #endif // HAVE_GCCOVER +#ifdef FEATURE_TIERED_COMPILATION + static constexpr size_t OptimizationTier = offsetof(NativeCodeVersionNode, m_optTier); +#endif // FEATURE_TIERED_COMPILATION }; class NativeCodeVersionCollection diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 0eea66e514270b..c9cee5c22e7808 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -617,6 +617,7 @@ CDAC_TYPE_INDETERMINATE(MethodDescCodeData) CDAC_TYPE_FIELD(MethodDescCodeData, /*CodePointer*/, TemporaryEntryPoint, offsetof(MethodDescCodeData,TemporaryEntryPoint)) #ifdef FEATURE_CODE_VERSIONING CDAC_TYPE_FIELD(MethodDescCodeData, /*pointer*/, VersioningState, offsetof(MethodDescCodeData,VersioningState)) +CDAC_TYPE_FIELD(MethodDescCodeData, /*uint32*/, OptimizationTier, offsetof(MethodDescCodeData,OptimizationTier)) #endif // FEATURE_CODE_VERSIONING CDAC_TYPE_END(MethodDescCodeData) @@ -885,6 +886,9 @@ CDAC_TYPE_FIELD(NativeCodeVersionNode, /*nuint*/, ILVersionId, cdac_data::GCCoverageInfo) #endif // HAVE_GCCOVER +#ifdef FEATURE_TIERED_COMPILATION +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*uint32*/, OptimizationTier, cdac_data::OptimizationTier) +#endif // FEATURE_TIERED_COMPILATION CDAC_TYPE_END(NativeCodeVersionNode) CDAC_TYPE_BEGIN(ILCodeVersionNode) diff --git a/src/coreclr/vm/eeconfig.h b/src/coreclr/vm/eeconfig.h index 724729bba509c7..7319b85991c1bd 100644 --- a/src/coreclr/vm/eeconfig.h +++ b/src/coreclr/vm/eeconfig.h @@ -96,7 +96,7 @@ class EEConfig bool TieredCompilation_UseCallCountingStubs() const { LIMITED_METHOD_CONTRACT; return fTieredCompilation_UseCallCountingStubs; } DWORD TieredCompilation_DeleteCallCountingStubsAfter() const { LIMITED_METHOD_CONTRACT; return tieredCompilation_DeleteCallCountingStubsAfter; } #endif // FEATURE_TIERED_COMPILATION - DWORD TieredCompilation_DefaultTier() const + DWORD TieredCompilation_DefaultTier() const { LIMITED_METHOD_CONTRACT; return tieredCompilation_DefaultTier; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs index e406b26df6bc82..8b31a943fe2a03 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs @@ -30,6 +30,7 @@ public interface ICodeVersions : IContract public virtual TargetPointer GetIL(ILCodeVersionHandle ilCodeVersionHandle) => throw new NotImplementedException(); public virtual bool HasDefaultIL(ILCodeVersionHandle ilCodeVersionHandle) => throw new NotImplementedException(); + public virtual OptimizationTier GetOptimizationTier(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); } public readonly struct ILCodeVersionHandle diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 11593d1b302e1a..1a3828bb3de62c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -75,6 +75,7 @@ public interface ILoader : IContract bool IsProbeExtensionResultValid(ModuleHandle handle) => throw new NotImplementedException(); ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); + bool IsReadyToRun(ModuleHandle handle) => throw new NotImplementedException(); bool TryGetSimpleName(ModuleHandle handle, out string simpleName) => throw new NotImplementedException(); string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); 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 dc5671b7d73dbb..202b4f37df8c0f 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 @@ -78,6 +78,18 @@ public enum ArrayFunctionType Constructor = 3 } +public enum OptimizationTier : uint +{ + OptimizationTierUnknown, + OptimizationTier0, + OptimizationTier1, + OptimizationTier1OSR, + OptimizationTierOptimized, + OptimizationTier0Instrumented, + OptimizationTier1Instrumented, +} + + public interface IRuntimeTypeSystem : IContract { static string IContract.Name => nameof(RuntimeTypeSystem); @@ -207,6 +219,9 @@ public interface IRuntimeTypeSystem : IContract TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); + bool IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) => 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/CodeVersions_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index fc50c95ffbf16f..1856cdef05606e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -413,4 +413,25 @@ bool ICodeVersions.HasDefaultIL(ILCodeVersionHandle iLCodeVersionHandle) { return iLCodeVersionHandle.IsExplicit ? AsNode(iLCodeVersionHandle).ILAddress == TargetPointer.Null : true; } + + OptimizationTier ICodeVersions.GetOptimizationTier(NativeCodeVersionHandle codeVersionHandle) + { + if (!codeVersionHandle.Valid) + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + + if (codeVersionHandle.IsExplicit) + { + NativeCodeVersionNode nativeCodeVersionNode = _target.ProcessedData.GetOrAdd(codeVersionHandle.CodeVersionNodeAddress); + return RuntimeTypeSystem_1.GetOptimizationTier(nativeCodeVersionNode.OptimizationTier); + } + else + { + IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(codeVersionHandle.MethodDescAddress); + OptimizationTier optimizationTier = rtsContract.GetMethodDescOptimizationTier(methodDescHandle); + return optimizationTier; + } + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 1a80d73c95cc90..9f23be73d90167 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -376,6 +376,12 @@ ModuleFlags ILoader.GetFlags(ModuleHandle handle) return GetFlags(module); } + bool ILoader.IsReadyToRun(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + return module.ReadyToRunInfo != TargetPointer.Null; + } + bool ILoader.TryGetSimpleName(ModuleHandle handle, out string simpleName) { simpleName = string.Empty; 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 ddeb48a6bde1e7..92d50cd31f8d5e 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 @@ -325,6 +325,17 @@ public ulong SizeOfChunk } + private enum OptimizationTier_1 : uint + { + OptimizationTier0, + OptimizationTier1, + OptimizationTier1OSR, + OptimizationTierOptimized, + OptimizationTier0Instrumented, + OptimizationTier1Instrumented, + OptimizationTierUnknown = 0xFFFFFFFF + } + private sealed class InstantiatedMethodDesc : IData { public static InstantiatedMethodDesc Create(Target target, TargetPointer address) => new InstantiatedMethodDesc(target, address); @@ -1645,6 +1656,37 @@ TargetPointer IRuntimeTypeSystem.GetGCStressCodeCopy(MethodDescHandle methodDesc return TargetPointer.Null; } + internal static OptimizationTier GetOptimizationTier(uint? optimizationTier) + { + return (OptimizationTier_1?)optimizationTier switch + { + OptimizationTier_1.OptimizationTier0 => OptimizationTier.OptimizationTier0, + OptimizationTier_1.OptimizationTier1 => OptimizationTier.OptimizationTier1, + OptimizationTier_1.OptimizationTier1OSR => OptimizationTier.OptimizationTier1OSR, + OptimizationTier_1.OptimizationTierOptimized => OptimizationTier.OptimizationTierOptimized, + OptimizationTier_1.OptimizationTier0Instrumented => OptimizationTier.OptimizationTier0Instrumented, + OptimizationTier_1.OptimizationTier1Instrumented => OptimizationTier.OptimizationTier1Instrumented, + _ => OptimizationTier.OptimizationTierUnknown, + }; + } + + OptimizationTier IRuntimeTypeSystem.GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + TargetPointer codeDataAddress = methodDesc.CodeData; + if (codeDataAddress == TargetPointer.Null) + return OptimizationTier.OptimizationTierUnknown; + + Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd(codeDataAddress); + return GetOptimizationTier(codeData.OptimizationTier); + } + + bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + return methodDesc.IsEligibleForTieredCompilation; + } + private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries { private readonly RuntimeTypeSystem_1 _rts; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs index dc76b8981b6101..69240ebb7bd86b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs @@ -13,8 +13,10 @@ public MethodDescCodeData(Target target, TargetPointer address) TemporaryEntryPoint = target.ReadCodePointer(address + (ulong)type.Fields[nameof(TemporaryEntryPoint)].Offset); VersioningState = target.ReadPointer(address + (ulong)type.Fields[nameof(VersioningState)].Offset); + OptimizationTier = target.Read(address + (ulong)type.Fields[nameof(OptimizationTier)].Offset); } public TargetCodePointer TemporaryEntryPoint { get; set; } public TargetPointer VersioningState { get; set; } + public uint OptimizationTier { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs index e68345e4499d23..37a9655c15534c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs @@ -21,6 +21,7 @@ public NativeCodeVersionNode(Target target, TargetPointer address) { GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset); } + OptimizationTier = target.Read(address + (ulong)type.Fields[nameof(OptimizationTier)].Offset); } public TargetPointer Next { get; init; } @@ -31,4 +32,5 @@ public NativeCodeVersionNode(Target target, TargetPointer address) public TargetNUInt ILVersionId { get; init; } public TargetPointer? GCCoverageInfo { get; init; } + public uint OptimizationTier { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs index 1243fb11b354ea..4b4c7d6c02f081 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -959,12 +959,37 @@ public unsafe partial interface ISOSDacInterface4 int GetClrNotification([In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[] arguments, int count, int* pNeeded); }; +public struct DacpTieredVersionData +{ + public enum OptimizationTier + { + Unknown = 0, + MinOptJitted = 1, + Optimized = 2, + QuickJitted = 3, + OptimizedTier1 = 4, + ReadyToRun = 5, + OptimizedTier1OSR = 6, + QuickJittedInstrumented = 7, + OptimizedTier1Instrumented = 8, + } + + public ClrDataAddress nativeCodeAddr; + public OptimizationTier optimizationTier; + public ClrDataAddress nativeCodeVersionNodePtr; +} + [GeneratedComInterface] [Guid("127d6abe-6c86-4e48-8e7b-220781c58101")] public unsafe partial interface ISOSDacInterface5 { [PreserveSig] - int GetTieredVersions(ClrDataAddress methodDesc, int rejitId, /*struct DacpTieredVersionData*/void* nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs); + int GetTieredVersions( + ClrDataAddress methodDesc, + int rejitId, + [In, Out, MarshalUsing(CountElementName = nameof(cNativeCodeAddrs))] DacpTieredVersionData[]? nativeCodeAddrs, + int cNativeCodeAddrs, + int* pcNativeCodeAddrs); }; public struct DacpMethodTableCollectibleData diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 213765d434874d..8ce7a3e7c5b008 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -5101,8 +5101,140 @@ int ISOSDacInterface4.GetClrNotification(ClrDataAddress[] arguments, int count, #endregion ISOSDacInterface4 #region ISOSDacInterface5 - int ISOSDacInterface5.GetTieredVersions(ClrDataAddress methodDesc, int rejitId, /*struct DacpTieredVersionData*/ void* nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs) - => _legacyImpl5 is not null ? _legacyImpl5.GetTieredVersions(methodDesc, rejitId, nativeCodeAddrs, cNativeCodeAddrs, pcNativeCodeAddrs) : HResults.E_NOTIMPL; + int ISOSDacInterface5.GetTieredVersions( + ClrDataAddress methodDesc, + int rejitId, + [In, MarshalUsing(CountElementName = nameof(cNativeCodeAddrs)), Out] DacpTieredVersionData[]? nativeCodeAddrs, + int cNativeCodeAddrs, + int* pcNativeCodeAddrs) + { + int hr = HResults.S_OK; + try + { + if (methodDesc == 0 || cNativeCodeAddrs == 0 || pcNativeCodeAddrs == null || nativeCodeAddrs is null) + { + throw new ArgumentException(); + } + + *pcNativeCodeAddrs = 0; + + ILoader loader = _target.Contracts.Loader; + ICodeVersions codeVersions = _target.Contracts.CodeVersions; + IReJIT rejitContract = _target.Contracts.ReJIT; + TargetPointer methodDescPtr = methodDesc.ToTargetPointer(_target); + ILCodeVersionHandle ilCodeVersionHandle = codeVersions.GetILCodeVersions(methodDescPtr) + .FirstOrDefault(ilcode => rejitContract.GetRejitId(ilcode).Value == (ulong)rejitId, ILCodeVersionHandle.Invalid); + + if (!ilCodeVersionHandle.IsValid) + throw new ArgumentException(); + + IRuntimeTypeSystem runtimeTypeSystemContract = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle methodDescHandle = runtimeTypeSystemContract.GetMethodDescHandle(methodDescPtr); + TargetPointer modulePtr = runtimeTypeSystemContract.GetModule(runtimeTypeSystemContract.GetTypeHandle(runtimeTypeSystemContract.GetMethodTable(methodDescHandle))); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); + + TargetPointer r2rImageBase = TargetPointer.Null; + TargetPointer r2rImageEnd = TargetPointer.Null; + if (loader.IsReadyToRun(moduleHandle) + && loader.TryGetLoadedImageContents(moduleHandle, out r2rImageBase, out uint r2rSize, out _)) + { + r2rImageEnd = r2rImageBase + r2rSize; + } + ClrDataAddress r2rImageBaseAddr = r2rImageBase.ToClrDataAddress(_target); + ClrDataAddress r2rImageEndAddr = r2rImageEnd.ToClrDataAddress(_target); + + bool isEligibleForTieredCompilation = runtimeTypeSystemContract.IsEligibleForTieredCompilation(methodDescHandle); + + int count = 0; + foreach (NativeCodeVersionHandle nativeCodeVersionHandle in codeVersions.GetNativeCodeVersions(methodDescPtr, ilCodeVersionHandle)) + { + ClrDataAddress nativeCodeAddr = codeVersions.GetNativeCode(nativeCodeVersionHandle).Value; + nativeCodeAddrs[count].nativeCodeAddr = nativeCodeAddr; + nativeCodeAddrs[count].nativeCodeVersionNodePtr = nativeCodeVersionHandle.CodeVersionNodeAddress.ToClrDataAddress(_target); + + if (r2rImageBaseAddr <= nativeCodeAddr && nativeCodeAddr < r2rImageEndAddr) + { + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.ReadyToRun; + } + else if (isEligibleForTieredCompilation) + { + switch (codeVersions.GetOptimizationTier(nativeCodeVersionHandle)) + { + default: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.Unknown; + break; + case OptimizationTier.OptimizationTier0: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.QuickJitted; + break; + case OptimizationTier.OptimizationTier1: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.OptimizedTier1; + break; + case OptimizationTier.OptimizationTier1OSR: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.OptimizedTier1OSR; + break; + case OptimizationTier.OptimizationTierOptimized: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.Optimized; + break; + case OptimizationTier.OptimizationTier0Instrumented: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.QuickJittedInstrumented; + break; + case OptimizationTier.OptimizationTier1Instrumented: + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.OptimizedTier1Instrumented; + break; + } + } + else + { + nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.Unknown; + } + + count++; + + if (count >= cNativeCodeAddrs) + { + hr = HResults.S_FALSE; + break; + } + } + + *pcNativeCodeAddrs = count; + } + catch (NotImplementedException) + { + // ReJIT contract not available — feature not active in the target runtime + return HResults.S_OK; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyImpl5 is not null) + { + var legacyBuffer = new DacpTieredVersionData[cNativeCodeAddrs]; + int legacyCount; + int hrLocal = _legacyImpl5.GetTieredVersions(methodDesc, rejitId, legacyBuffer, cNativeCodeAddrs, &legacyCount); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK || hr == HResults.S_FALSE) + { + Debug.Assert(*pcNativeCodeAddrs == legacyCount, $"cDAC count: {*pcNativeCodeAddrs}, DAC count: {legacyCount}"); + if (nativeCodeAddrs is not null) + { + for (int i = 0; i < *pcNativeCodeAddrs; i++) + { + Debug.Assert(nativeCodeAddrs[i].nativeCodeAddr == legacyBuffer[i].nativeCodeAddr, + $"[{i}] cDAC nativeCodeAddr: 0x{(ulong)nativeCodeAddrs[i].nativeCodeAddr:x}, DAC: 0x{(ulong)legacyBuffer[i].nativeCodeAddr:x}"); + Debug.Assert(nativeCodeAddrs[i].nativeCodeVersionNodePtr == legacyBuffer[i].nativeCodeVersionNodePtr, + $"[{i}] cDAC nodePtr: 0x{(ulong)nativeCodeAddrs[i].nativeCodeVersionNodePtr:x}, DAC: 0x{(ulong)legacyBuffer[i].nativeCodeVersionNodePtr:x}"); + Debug.Assert(nativeCodeAddrs[i].optimizationTier == legacyBuffer[i].optimizationTier, + $"[{i}] cDAC tier: {nativeCodeAddrs[i].optimizationTier}, DAC: {legacyBuffer[i].optimizationTier}"); + } + } + } + } +#endif // DEBUG + return hr; + } #endregion ISOSDacInterface5 #region ISOSDacInterface6 diff --git a/src/native/managed/cdac/tests/CodeVersionsTests.cs b/src/native/managed/cdac/tests/CodeVersionsTests.cs index 7ceaeea2433ef4..2d4551b777eca4 100644 --- a/src/native/managed/cdac/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdac/tests/CodeVersionsTests.cs @@ -722,4 +722,83 @@ public void GetGCStressCodeCopy_NotNull(MockTarget.Architecture arch) { GetGCStressCodeCopy_Impl(arch, returnsNull: false); } + + public static IEnumerable GetOptimizationTierValues() + { + foreach (var archData in new MockTarget.StdArch()) + { + var arch = (MockTarget.Architecture)archData[0]; + yield return [arch, 0u, OptimizationTier.OptimizationTier0]; + yield return [arch, 1u, OptimizationTier.OptimizationTier1]; + yield return [arch, 2u, OptimizationTier.OptimizationTier1OSR]; + yield return [arch, 3u, OptimizationTier.OptimizationTierOptimized]; + yield return [arch, 4u, OptimizationTier.OptimizationTier0Instrumented]; + yield return [arch, 5u, OptimizationTier.OptimizationTier1Instrumented]; + yield return [arch, 0xFFFFFFFFu, OptimizationTier.OptimizationTierUnknown]; + } + } + + [Theory] + [MemberData(nameof(GetOptimizationTierValues))] + public void GetOptimizationTier_Explicit(MockTarget.Architecture arch, uint nativeTier, OptimizationTier expectedTier) + { + MockCodeVersions builder = new(arch); + + TargetPointer nativeCodeVersionNode = builder.AddNativeCodeVersionNode(); + builder.FillNativeCodeVersionNode( + nativeCodeVersionNode, + methodDesc: new TargetPointer(0x1a0a_0000), + nativeCode: new TargetCodePointer(0x0a0a_0000), + next: TargetPointer.Null, + isActive: true, + ilVersionId: new(1), + optimizationTier: nativeTier); + + var target = CreateTarget(arch, builder); + var codeVersions = target.Contracts.CodeVersions; + + NativeCodeVersionHandle handle = NativeCodeVersionHandle.CreateExplicit(nativeCodeVersionNode); + OptimizationTier tier = codeVersions.GetOptimizationTier(handle); + Assert.Equal(expectedTier, tier); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetOptimizationTier_Synthetic_DelegatesToRuntimeTypeSystem(MockTarget.Architecture arch) + { + MockCodeVersions builder = new(arch); + TargetPointer methodDescAddress = new(0x1a0a_0000); + + Mock mockRTS = new(); + MethodDescHandle mdHandle = new(methodDescAddress); + mockRTS.Setup(r => r.GetMethodDescHandle(methodDescAddress)).Returns(mdHandle); + mockRTS.Setup(r => r.GetMethodDescOptimizationTier(mdHandle)).Returns(OptimizationTier.OptimizationTierOptimized); + + var target = CreateTarget(arch, builder, mockRuntimeTypeSystem: mockRTS); + var codeVersions = target.Contracts.CodeVersions; + + NativeCodeVersionHandle handle = NativeCodeVersionHandle.CreateSynthetic(methodDescAddress); + OptimizationTier tier = codeVersions.GetOptimizationTier(handle); + Assert.Equal(OptimizationTier.OptimizationTierOptimized, tier); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetOptimizationTier_Synthetic_NoCodeData(MockTarget.Architecture arch) + { + MockCodeVersions builder = new(arch); + TargetPointer methodDescAddress = new(0x1a0a_0000); + + Mock mockRTS = new(); + MethodDescHandle mdHandle = new(methodDescAddress); + mockRTS.Setup(r => r.GetMethodDescHandle(methodDescAddress)).Returns(mdHandle); + mockRTS.Setup(r => r.GetMethodDescOptimizationTier(mdHandle)).Returns(OptimizationTier.OptimizationTierUnknown); + + var target = CreateTarget(arch, builder, mockRuntimeTypeSystem: mockRTS); + var codeVersions = target.Contracts.CodeVersions; + + NativeCodeVersionHandle handle = NativeCodeVersionHandle.CreateSynthetic(methodDescAddress); + OptimizationTier tier = codeVersions.GetOptimizationTier(handle); + Assert.Equal(OptimizationTier.OptimizationTierUnknown, tier); + } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.CodeVersions.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.CodeVersions.cs index feee26fc9c24f8..343da91d347009 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.CodeVersions.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.CodeVersions.cs @@ -35,6 +35,7 @@ public class CodeVersions new(nameof(Data.NativeCodeVersionNode.Flags), DataType.uint32), new(nameof(Data.NativeCodeVersionNode.ILVersionId), DataType.nuint), new(nameof(Data.NativeCodeVersionNode.GCCoverageInfo), DataType.pointer), + new(nameof(Data.NativeCodeVersionNode.OptimizationTier), DataType.uint32), ] }; @@ -119,7 +120,7 @@ public TargetPointer AddNativeCodeVersionNode() return fragment.Address; } - public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId, TargetPointer? gcCoverageInfo = null) + public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId, TargetPointer? gcCoverageInfo = null, uint? optimizationTier = null) { Target.TypeInfo info = Types[DataType.NativeCodeVersionNode]; Span ncvn = Builder.BorrowAddressRange(dest, (int)info.Size!); @@ -129,6 +130,8 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe Builder.TargetTestHelpers.Write(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.Flags)].Offset, sizeof(uint)), isActive ? (uint)CodeVersions_1.NativeCodeVersionNodeFlags.IsActiveChild : 0u); Builder.TargetTestHelpers.WriteNUInt(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.ILVersionId)].Offset, Builder.TargetTestHelpers.PointerSize), ilVersionId); Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.GCCoverageInfo)].Offset, Builder.TargetTestHelpers.PointerSize), gcCoverageInfo ?? TargetPointer.Null); + uint optimizationTierValue = optimizationTier ?? 0xFFFFFFFFu; + Builder.TargetTestHelpers.Write(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.OptimizationTier)].Offset, sizeof(uint)), optimizationTierValue); } public (TargetPointer First, TargetPointer Active) AddNativeCodeVersionNodesForMethod(TargetPointer methodDesc, int count, int activeIndex, TargetCodePointer activeNativeCode, TargetNUInt ilVersion, TargetPointer? firstNode = null) diff --git a/src/native/managed/cdac/tests/SOSDacInterface5Tests.cs b/src/native/managed/cdac/tests/SOSDacInterface5Tests.cs new file mode 100644 index 00000000000000..7677152b38f48c --- /dev/null +++ b/src/native/managed/cdac/tests/SOSDacInterface5Tests.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public unsafe class SOSDacInterface5Tests +{ + private const int S_OK = 0; + private const int S_FALSE = 1; + + private static readonly TargetPointer s_methodDescAddr = new(0x1000_0000); + private static readonly TargetPointer s_moduleAddr = new(0x2000_0000); + private static readonly TargetPointer s_methodTableAddr = new(0x3000_0000); + + private record struct VersionInfo( + TargetCodePointer NativeCode, + TargetPointer CodeVersionNodeAddress, + OptimizationTier Tier); + + private static ISOSDacInterface5 CreateDac5( + MockTarget.Architecture arch, + VersionInfo[]? versions = null, + bool isEligibleForTieredCompilation = false, + bool isReadyToRun = false, + TargetPointer r2rBase = default, + uint r2rSize = 0, + int rejitId = 0) + { + var mockCodeVersions = new Mock(); + var mockRts = new Mock(); + var mockLoader = new Mock(); + var mockReJIT = new Mock(); + + ILCodeVersionHandle ilCodeVersion = ILCodeVersionHandle.CreateSynthetic(s_moduleAddr, 0x06000001); + MethodDescHandle methodDescHandle = new MethodDescHandle(s_methodDescAddr); + TypeHandle typeHandle = new TypeHandle(s_methodTableAddr); + Contracts.ModuleHandle moduleHandle = new Contracts.ModuleHandle(s_moduleAddr); + + mockCodeVersions + .Setup(c => c.GetILCodeVersions(s_methodDescAddr)) + .Returns(new[] { ilCodeVersion }); + + mockReJIT + .Setup(r => r.GetRejitId(It.IsAny())) + .Returns(new TargetNUInt((ulong)rejitId)); + + mockRts + .Setup(r => r.GetMethodDescHandle(s_methodDescAddr)) + .Returns(methodDescHandle); + mockRts + .Setup(r => r.GetMethodTable(methodDescHandle)) + .Returns(s_methodTableAddr); + mockRts + .Setup(r => r.GetTypeHandle(s_methodTableAddr)) + .Returns(typeHandle); + mockRts + .Setup(r => r.GetModule(typeHandle)) + .Returns(s_moduleAddr); + mockRts + .Setup(r => r.IsEligibleForTieredCompilation(methodDescHandle)) + .Returns(isEligibleForTieredCompilation); + + mockLoader + .Setup(l => l.GetModuleHandleFromModulePtr(s_moduleAddr)) + .Returns(moduleHandle); + mockLoader + .Setup(l => l.IsReadyToRun(moduleHandle)) + .Returns(isReadyToRun); + if (isReadyToRun) + { + uint imageFlags = 0; + TargetPointer outBase = r2rBase; + mockLoader + .Setup(l => l.TryGetLoadedImageContents(moduleHandle, out outBase, out r2rSize, out imageFlags)) + .Returns(true); + } + + versions ??= []; + + var nativeVersionHandles = new NativeCodeVersionHandle[versions.Length]; + for (int i = 0; i < versions.Length; i++) + { + NativeCodeVersionHandle handle = versions[i].CodeVersionNodeAddress != TargetPointer.Null + ? NativeCodeVersionHandle.CreateExplicit(versions[i].CodeVersionNodeAddress) + : NativeCodeVersionHandle.CreateSynthetic(s_methodDescAddr); + + nativeVersionHandles[i] = handle; + + mockCodeVersions + .Setup(c => c.GetNativeCode(handle)) + .Returns(versions[i].NativeCode); + mockCodeVersions + .Setup(c => c.GetOptimizationTier(handle)) + .Returns(versions[i].Tier); + } + + mockCodeVersions + .Setup(c => c.GetNativeCodeVersions(s_methodDescAddr, It.IsAny())) + .Returns(nativeVersionHandles); + + var target = new TestPlaceholderTarget( + arch, + (_, _) => -1, + types: []); + target.SetContracts(Mock.Of( + c => c.CodeVersions == mockCodeVersions.Object + && c.RuntimeTypeSystem == mockRts.Object + && c.Loader == mockLoader.Object + && c.ReJIT == mockReJIT.Object)); + + return new SOSDacImpl(target, legacyObj: null); + } + + private static int CallGetTieredVersions(ISOSDacInterface5 dac5, DacpTieredVersionData[] buffer, out int count, int rejitId = 0) + { + int localCount; + int hr = dac5.GetTieredVersions((ClrDataAddress)s_methodDescAddr.Value, rejitId, buffer, buffer.Length, &localCount); + count = localCount; + return hr; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_ZeroMethodDesc(MockTarget.Architecture arch) + { + ISOSDacInterface5 dac5 = CreateDac5(arch); + var buffer = new DacpTieredVersionData[1]; + int count; + int hr = dac5.GetTieredVersions(0, 0, buffer, 1, &count); + Assert.NotEqual(S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_ZeroBufferSize(MockTarget.Architecture arch) + { + ISOSDacInterface5 dac5 = CreateDac5(arch); + int count; + int hr = dac5.GetTieredVersions((ClrDataAddress)s_methodDescAddr.Value, 0, null, 0, &count); + Assert.NotEqual(S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_NullOutputPtr(MockTarget.Architecture arch) + { + ISOSDacInterface5 dac5 = CreateDac5(arch); + var buffer = new DacpTieredVersionData[1]; + int hr = dac5.GetTieredVersions((ClrDataAddress)s_methodDescAddr.Value, 0, buffer, 1, null); + Assert.NotEqual(S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_InvalidRejitId(MockTarget.Architecture arch) + { + ISOSDacInterface5 dac5 = CreateDac5(arch, rejitId: 0); + int hr = CallGetTieredVersions(dac5, new DacpTieredVersionData[1], out _, rejitId: 999); + Assert.NotEqual(S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_ReadyToRun(MockTarget.Architecture arch) + { + var r2rBase = new TargetPointer(0x5000_0000); + uint r2rSize = 0x1000; + var versions = new[] + { + new VersionInfo(new TargetCodePointer(0x5000_0100), new TargetPointer(0x7000_0001), OptimizationTier.OptimizationTierOptimized), + }; + + ISOSDacInterface5 dac5 = CreateDac5(arch, versions, isReadyToRun: true, r2rBase: r2rBase, r2rSize: r2rSize); + var buffer = new DacpTieredVersionData[2]; + int hr = CallGetTieredVersions(dac5, buffer, out int count); + + Assert.Equal(S_OK, hr); + Assert.Equal(1, count); + Assert.Equal(DacpTieredVersionData.OptimizationTier.ReadyToRun, buffer[0].optimizationTier); + } + + public static IEnumerable TierMappingData + { + get + { + (OptimizationTier, DacpTieredVersionData.OptimizationTier)[] tiers = + [ + (OptimizationTier.OptimizationTier0, DacpTieredVersionData.OptimizationTier.QuickJitted), + (OptimizationTier.OptimizationTier1, DacpTieredVersionData.OptimizationTier.OptimizedTier1), + (OptimizationTier.OptimizationTier1OSR, DacpTieredVersionData.OptimizationTier.OptimizedTier1OSR), + (OptimizationTier.OptimizationTierOptimized, DacpTieredVersionData.OptimizationTier.Optimized), + (OptimizationTier.OptimizationTier0Instrumented, DacpTieredVersionData.OptimizationTier.QuickJittedInstrumented), + (OptimizationTier.OptimizationTier1Instrumented, DacpTieredVersionData.OptimizationTier.OptimizedTier1Instrumented), + (OptimizationTier.OptimizationTierUnknown, DacpTieredVersionData.OptimizationTier.Unknown), + ]; + + foreach (var arch in new MockTarget.StdArch()) + { + foreach (var (internalTier, expectedTier) in tiers) + { + yield return [(MockTarget.Architecture)arch[0], internalTier, expectedTier]; + } + } + } + } + + [Theory] + [MemberData(nameof(TierMappingData))] + public void GetTieredVersions_TieredCompilation( + MockTarget.Architecture arch, + OptimizationTier internalTier, + DacpTieredVersionData.OptimizationTier expectedTier) + { + var versions = new[] + { + new VersionInfo(new TargetCodePointer(0x6000_0100), new TargetPointer(0x7000_0001), internalTier), + }; + + ISOSDacInterface5 dac5 = CreateDac5(arch, versions, isEligibleForTieredCompilation: true); + var buffer = new DacpTieredVersionData[2]; + int hr = CallGetTieredVersions(dac5, buffer, out int count); + + Assert.Equal(S_OK, hr); + Assert.Equal(1, count); + Assert.Equal(expectedTier, buffer[0].optimizationTier); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_NotEligibleForTieredCompilation(MockTarget.Architecture arch) + { + var versions = new[] + { + new VersionInfo(new TargetCodePointer(0x6000_0100), new TargetPointer(0x7000_0001), OptimizationTier.OptimizationTierOptimized), + }; + + ISOSDacInterface5 dac5 = CreateDac5(arch, versions); + var buffer = new DacpTieredVersionData[2]; + int hr = CallGetTieredVersions(dac5, buffer, out int count); + + Assert.Equal(S_OK, hr); + Assert.Equal(1, count); + Assert.Equal(DacpTieredVersionData.OptimizationTier.Unknown, buffer[0].optimizationTier); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_BufferTooSmall(MockTarget.Architecture arch) + { + var versions = new[] + { + new VersionInfo(new TargetCodePointer(0x6000_0100), new TargetPointer(0x7000_0001), OptimizationTier.OptimizationTier0), + new VersionInfo(new TargetCodePointer(0x6000_0200), new TargetPointer(0x7000_0002), OptimizationTier.OptimizationTier1), + new VersionInfo(new TargetCodePointer(0x6000_0300), new TargetPointer(0x7000_0003), OptimizationTier.OptimizationTierOptimized), + }; + + ISOSDacInterface5 dac5 = CreateDac5(arch, versions, isEligibleForTieredCompilation: true); + var buffer = new DacpTieredVersionData[2]; + int hr = CallGetTieredVersions(dac5, buffer, out int count); + + Assert.Equal(S_FALSE, hr); + Assert.Equal(2, count); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_PopulatesCodeAddrAndNodePtr(MockTarget.Architecture arch) + { + var codeAddr = new TargetCodePointer(0x6000_0100); + var nodeAddr = new TargetPointer(0x7000_0001); + var versions = new[] + { + new VersionInfo(codeAddr, nodeAddr, OptimizationTier.OptimizationTierOptimized), + }; + + ISOSDacInterface5 dac5 = CreateDac5(arch, versions); + var buffer = new DacpTieredVersionData[2]; + int hr = CallGetTieredVersions(dac5, buffer, out int count); + + Assert.Equal(S_OK, hr); + Assert.Equal(1, count); + Assert.Equal((ulong)codeAddr.Value, (ulong)buffer[0].nativeCodeAddr); + Assert.Equal(nodeAddr.Value, (ulong)buffer[0].nativeCodeVersionNodePtr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTieredVersions_NoVersions(MockTarget.Architecture arch) + { + ISOSDacInterface5 dac5 = CreateDac5(arch, versions: []); + int hr = CallGetTieredVersions(dac5, new DacpTieredVersionData[1], out int count); + + Assert.Equal(S_OK, hr); + Assert.Equal(0, count); + } +}