diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 19cee2b20730c..27f0814c37dd3 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -4,32 +4,36 @@ This contract encapsulates support for [code versioning](../features/code-versio ## APIs of contract +```csharp +internal readonly struct ILCodeVersionHandle +{ + public static ILCodeVersionHandle Invalid; + + public bool IsValid; +} +``` + ```csharp internal struct NativeCodeVersionHandle { - // no public constructors - internal readonly TargetPointer MethodDescAddress; - internal readonly TargetPointer CodeVersionNodeAddress; - internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) - { - if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null) - { - throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null"); - } - MethodDescAddress = methodDescAddress; - CodeVersionNodeAddress = codeVersionNodeAddress; - } + internal static NativeCodeVersionHandle Invalid; - internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null); - public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null; + public bool Valid; } ``` ```csharp +// Return a handle to the active version of the IL code for a given method descriptor +public virtual ILCodeVersionHandle GetActiveILCodeVersion(TargetPointer methodDesc); +// Return a handle to the IL code version representing the given native code version +public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle codeVersionHandle); +// Return all of the IL code versions for a given method descriptor +public virtual IEnumerable GetILCodeVersions(TargetPointer methodDesc); + // Return a handle to the version of the native code that includes the given instruction pointer public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip); -// Return a handle to the active version of the native code for a given method descriptor -public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc); +// Return a handle to the active version of the native code for a given method descriptor and IL code version. The IL code version and method descriptor must represent the same method +public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle); // returns true if the given method descriptor supports multiple code versions public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); @@ -37,6 +41,11 @@ public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); // Return the instruction pointer corresponding to the start of the given native code version public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle); ``` +### Extension Methods +```csharp +// Return a handle to the active version of the native code for a given method descriptor +public static NativeCodeVersionHandle GetActiveNativeCodeVersion(this ICodeVersions, TargetPointer methodDesc); +``` ## Version 1 @@ -52,11 +61,13 @@ Data descriptors used: | NativeCodeVersionNode | NativeCode | indicates an explicit native code version node | | NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below | | NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL 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 | | ILCodeVersioningState | ActiveVersionModule | if the active version is synthetic or unknown, the pointer to the Module that defines the method | | ILCodeVersioningState | ActiveVersionMethodDef | if the active version is synthetic or unknown, the MethodDef token for the method | | ILCodeVersionNode | VersionId | Version ID of the node | +| ILCodeVersionNode | Next | Pointer to the next `ILCodeVersionNode`| The flag indicates that the default version of the code for a method desc is active: ```csharp @@ -93,6 +104,51 @@ Contracts used: | Loader | | RuntimeTypeSystem | +### Finding active ILCodeVersion for a method +```csharp +public virtual ILCodeVersionHandle GetActiveILCodeVersion(TargetPointer methodDesc); +``` +1. Check if the method has an `ILCodeVersioningState`. +2. If the method does not have an `ILCodeVersioningState`, the synthetic ILCodeVersion must be active. Return the synthetic ILCodeVersion for the method. +3. Otherwise, read the active ILCodeVersion off of the `ILCodeVersioningState`. + +### Finding ILCodeVersion from a NativeCodeVersion +```csharp +public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle nativeCodeVersionHandle); +``` +1. If `nativeCodeVersionHandle` is invalid, return an invalid `ILCodeVersionHandle`. +2. If `nativeCodeVersionHandle` is synthetic, the corresponding ILCodeVersion must also be synthetic; return the synthetic ILCodeVersion for the method. +3. Search the linked list of ILCodeVersions for one with the matching ILVersionId. Return the ILCodeVersion if found. Otherwise return invalid. + +### Finding all of the ILCodeVersions for a method +```csharp +IEnumerable ICodeVersions.GetILCodeVersions(TargetPointer methodDesc) +{ + // CodeVersionManager::GetILCodeVersions + GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); + TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; + TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + + // always add the synthetic version + yield return new ILCodeVersionHandle(module, methodDefToken, TargetPointer.Null); + + // if explicit versions exist, iterate linked list and return them + if (ilVersionStateAddress != TargetPointer.Null) + { + Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); + TargetPointer nodePointer = ilState.FirstVersionNode; + while (nodePointer != TargetPointer.Null) + { + Data.ILCodeVersionNode current = _target.ProcessedData.GetOrAdd(nodePointer); + yield return new ILCodeVersionHandle(TargetPointer.Null, 0, nodePointer); + nodePointer = current.Next; + } + } +} +``` + ### Finding the start of a specific native code version ```csharp @@ -113,7 +169,7 @@ NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointe MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); if (!rts.IsVersionable(md)) { - return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + return NativeCodeVersion.OfSynthetic(methodDescAddress); } else { @@ -128,7 +184,7 @@ NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, Target TargetCodePointer firstNativeCode = rts.GetNativeCode(md); if (firstNativeCode == startAddress) { - NativeCodeVersionHandle first = new NativeCodeVersionHandle(md.Address, TargetPointer.Null); + NativeCodeVersionHandle first = NativeCodeVersionHandle.OfSynthetic(md.Address); return first; } @@ -154,7 +210,7 @@ NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescH Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); if (predicate(current)) { - return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); + return NativeCodeVersionHandle.OfExplicit(currentAddress); } currentAddress = current.Next; } @@ -162,106 +218,15 @@ NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescH } ``` -### Finding the active native code version of a method descriptor - +### Finding the active native code version of an ILCodeVersion for a method descriptor ```csharp -NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) -{ - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); - TargetPointer mtAddr = rts.GetMethodTable(md); - TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); - TargetPointer module = rts.GetModule(typeHandle); - uint methodDefToken = rts.GetMethodToken(md); - ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); - if (!methodDefActiveVersion.IsValid) - { - return NativeCodeVersionHandle.Invalid; - } - return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); -} - -ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState) -{ - switch ((ILCodeVersionKind)ilState.ActiveVersionKind) - { - case ILCodeVersionKind.Explicit: - return new ILCodeVersionHandle(module: TargetPointer.Null, methodDef: 0, ilState.ActiveVersionNode); - case ILCodeVersionKind.Synthetic: - case ILCodeVersionKind.Unknown: - return new ILCodeVersionHandle(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef, TargetPointer.Null); - default: - throw new InvalidOperationException($"Unknown ILCodeVersionKind {ilState.ActiveVersionKind}"); - } -} - -ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition) -{ - ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); - TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; - TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefinition, out var _); - if (ilVersionStateAddress == TargetPointer.Null) - { - return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null); - } - Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); - return ILCodeVersionHandleFromState(ilState); -} - -bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) -{ - if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null) - { - MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress); - TargetPointer versioningStateAddress = _target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); - if (versioningStateAddress == TargetPointer.Null) - { - return true; - } - Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); - MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; - return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); - } - else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) - { - uint flags = _target.Read(nativeCodeVersion.CodeVersionNodeAddress + /* NativeCodVersionNode::Flags offset*/) - return ((NativeCodeVersionNodeFlags)flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); - } - else - { - throw new ArgumentException("Invalid NativeCodeVersionHandle"); - } -} - -NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) -{ - TargetNUInt? ilVersionId = default; - if (methodDefActiveVersion.Module != TargetPointer.Null) - { - NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); - if (IsActiveNativeCodeVersion(provisionalHandle)) - { - return provisionalHandle; - } - } - else - { - // Get the explicit IL code version - Debug.Assert(methodDefActiveVersion.ILCodeVersionNode != TargetPointer.Null); - ilVersionId = _target.ReadNUint(methodDefActiveVersion.ILCodeVersionNode + /* ILCodeVersionNode::VersionId offset */); - } - - // Iterate through versioning state nodes and return the active one, matching any IL code version - Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); - return FindFirstCodeVersion(rts, md, (codeVersion) => - { - return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value) - && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); - }); -} +public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle); ``` +1. If `ilCodeVersionHandle` is invalid, return invalid. +2. If `ilCodeVersionHandle` is synthetic, the active native code version could be synthetic. Check if the method's synthetic NativeCodeVersion is active. If it is, return that NativeCodeVersion. +3. Search the linked list of NativeCodeVersions for one with the active flag and the relevent ILVersionId. If found return that node. Otherwise return invalid. + ### Determining whether a method descriptor supports code versioning ```csharp diff --git a/docs/design/datacontracts/ReJIT.md b/docs/design/datacontracts/ReJIT.md index 53ed0767a6a9d..106360c6d9768 100644 --- a/docs/design/datacontracts/ReJIT.md +++ b/docs/design/datacontracts/ReJIT.md @@ -4,8 +4,22 @@ This contract encapsulates support for [ReJIT](../features/code-versioning.md) i ## APIs of contract +```csharp +public enum RejitState +{ + Requested, + Active +} +``` + ```csharp bool IsEnabled(); + +RejitState GetRejitState(ILCodeVersionHandle codeVersionHandle); + +TargetNUInt GetRejitId(ILCodeVersionHandle codeVersionHandle); + +IEnumerable GetRejitIds(TargetPointer methodDesc) ``` ## Version 1 @@ -14,6 +28,8 @@ Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | ProfControlBlock | GlobalEventMask | an `ICorProfiler` `COR_PRF_MONITOR` value | +| ILCodeVersionNode | VersionId | `ILCodeVersion` ReJIT ID +| ILCodeVersionNode | RejitState | a `RejitFlags` value | Global variables used: | Global Name | Type | Purpose | @@ -23,6 +39,7 @@ Global variables used: Contracts used: | Contract Name | | --- | +| CodeVersions | ```csharp // see src/coreclr/inc/corprof.idl @@ -32,6 +49,21 @@ private enum COR_PRF_MONITOR COR_PRF_ENABLE_REJIT = 0x00040000, } +// see src/coreclr/vm/codeversion.h +[Flags] +public enum RejitFlags : uint +{ + kStateRequested = 0x00000000, + + kStateGettingReJITParameters = 0x00000001, + + kStateActive = 0x00000002, + + kStateMask = 0x0000000F, + + kSuppressParams = 0x80000000 +} + bool IsEnabled() { TargetPointer address = target.ReadGlobalPointer("ProfilerControlBlock"); @@ -40,4 +72,56 @@ bool IsEnabled() bool clrConfigEnabledReJit = /* host process does not have environment variable DOTNET_ProfAPI_ReJitOnAttach set to 0 */; return profEnabledReJIT || clrConfigEnabledReJIT; } + +RejitState GetRejitState(ILCodeVersionHandle codeVersion) +{ + // ILCodeVersion::GetRejitState + if (codeVersion is not explicit) + { + // for non explicit ILCodeVersions, ReJITState is always kStateActive + return RejitState.Active; + } + else + { + // ILCodeVersionNode::GetRejitState + ILCodeVersionNode codeVersionNode = AsNode(codeVersion); + return ((RejitFlags)ilCodeVersionNode.RejitState & RejitFlags.kStateMask) switch + { + RejitFlags.kStateRequested => RejitState.Requested, + RejitFlags.kStateActive => RejitState.Active, + _ => throw new NotImplementedException($"Unknown ReJIT state: {ilCodeVersionNode.RejitState}"), + }; + } +} + +TargetNUInt GetRejitId(ILCodeVersionHandle codeVersion) +{ + // ILCodeVersion::GetVersionId + if (codeVersion is not explicit) + { + // for non explicit ILCodeVersions, ReJITId is always 0 + return new TargetNUInt(0); + } + else + { + // ILCodeVersionNode::GetVersionId + ILCodeVersionNode codeVersionNode = AsNode(codeVersion); + return codeVersionNode.VersionId; + } +} + +IEnumerable GetRejitIds(TargetPointer methodDesc) +{ + // ReJitManager::GetReJITIDs + ICodeVersions cv = _target.Contracts.CodeVersions; + IEnumerable ilCodeVersions = cv.GetILCodeVersions(methodDesc); + + foreach (ILCodeVersionHandle ilCodeVersionHandle in ilCodeVersions) + { + if (GetRejitState(ilCodeVersionHandle) == RejitState.Active) + { + yield return GetRejitId(ilCodeVersionHandle); + } + } +} ``` diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 4568395df3cac..3aef2fc875aa6 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -483,6 +483,7 @@ CDAC_TYPE_END(CodeHeapListNode) CDAC_TYPE_BEGIN(ILCodeVersioningState) CDAC_TYPE_INDETERMINATE(ILCodeVersioningState) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, FirstVersionNode, cdac_data::FirstVersionNode) CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionKind, cdac_data::ActiveVersionKind) CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionNode, cdac_data::ActiveVersionNode) CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionModule, cdac_data::ActiveVersionModule) @@ -501,6 +502,8 @@ CDAC_TYPE_END(NativeCodeVersionNode) CDAC_TYPE_BEGIN(ILCodeVersionNode) CDAC_TYPE_INDETERMINATE(ILCodeVersionNode) CDAC_TYPE_FIELD(ILCodeVersionNode, /*nuint*/, VersionId, cdac_data::VersionId) +CDAC_TYPE_FIELD(ILCodeVersionNode, /*pointer*/, Next, cdac_data::Next) +CDAC_TYPE_FIELD(ILCodeVersionNode, /*uint32*/, RejitState, cdac_data::RejitState) CDAC_TYPE_END(ILCodeVersionNode) CDAC_TYPE_BEGIN(ProfControlBlock) diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index c7687d0746c53..442a284654375 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -419,6 +419,8 @@ template<> struct cdac_data { static constexpr size_t VersionId = offsetof(ILCodeVersionNode, m_rejitId); + static constexpr size_t Next = offsetof(ILCodeVersionNode, m_pNextILVersionNode); + static constexpr size_t RejitState = offsetof(ILCodeVersionNode, m_rejitState); }; class ILCodeVersionCollection @@ -543,6 +545,7 @@ class ILCodeVersioningState template<> struct cdac_data { + static constexpr size_t FirstVersionNode = offsetof(ILCodeVersioningState, m_pFirstVersionNode); static constexpr size_t ActiveVersionKind = offsetof(ILCodeVersioningState, m_activeVersion.m_storageKind); static constexpr size_t ActiveVersionNode = offsetof(ILCodeVersioningState, m_activeVersion.m_pVersionNode); static constexpr size_t ActiveVersionModule = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_pModule); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs new file mode 100644 index 0000000000000..37cd53791f576 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs @@ -0,0 +1,13 @@ +// 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.Extensions; + +internal static class ICodeVersionsExtensions +{ + internal static NativeCodeVersionHandle GetActiveNativeCodeVersion(this ICodeVersions cv, TargetPointer methodDesc) + { + ILCodeVersionHandle ilCodeVersionHandle = cv.GetActiveILCodeVersion(methodDesc); + return cv.GetActiveNativeCodeVersionForILCodeVersion(methodDesc, ilCodeVersionHandle); + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/IReJITExtensions.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/IReJITExtensions.cs new file mode 100644 index 0000000000000..be505aa3d662f --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/IReJITExtensions.cs @@ -0,0 +1,24 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; + +internal static class IReJITExtensions +{ + public static IEnumerable GetRejitIds(this IReJIT rejit, Target target, TargetPointer methodDesc) + { + ICodeVersions cv = target.Contracts.CodeVersions; + + IEnumerable ilCodeVersions = cv.GetILCodeVersions(methodDesc); + + foreach (ILCodeVersionHandle ilCodeVersionHandle in ilCodeVersions) + { + if (rejit.GetRejitState(ilCodeVersionHandle) == RejitState.Active) + { + yield return rejit.GetRejitId(ilCodeVersionHandle); + } + } + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs index bddba2cdc2787..a6db692f7dede 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -9,21 +10,61 @@ internal interface ICodeVersions : IContract { static string IContract.Name { get; } = nameof(CodeVersions); + public virtual ILCodeVersionHandle GetActiveILCodeVersion(TargetPointer methodDesc) => throw new NotImplementedException(); + + public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + + public virtual IEnumerable GetILCodeVersions(TargetPointer methodDesc) => throw new NotImplementedException(); + public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) => throw new NotImplementedException(); - public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc) => throw new NotImplementedException(); - public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException(); + public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle) => throw new NotImplementedException(); public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException(); } -internal struct NativeCodeVersionHandle +internal readonly struct ILCodeVersionHandle +{ + internal readonly TargetPointer Module; + internal readonly uint MethodDefinition; + internal readonly TargetPointer ILCodeVersionNode; + private ILCodeVersionHandle(TargetPointer module, uint methodDef, TargetPointer ilCodeVersionNodeAddress) + { + if (module != TargetPointer.Null && ilCodeVersionNodeAddress != TargetPointer.Null) + throw new ArgumentException("Both MethodDesc and ILCodeVersionNode cannot be non-null"); + + if (module != TargetPointer.Null && methodDef == 0) + throw new ArgumentException("MethodDefinition must be non-zero if Module is non-null"); + + if (module == TargetPointer.Null && methodDef != 0) + throw new ArgumentException("MethodDefinition must be zero if Module is null"); + + Module = module; + MethodDefinition = methodDef; + ILCodeVersionNode = ilCodeVersionNodeAddress; + } + + // for more information on Explicit/Synthetic code versions see docs/design/features/code-versioning.md + internal static ILCodeVersionHandle CreateExplicit(TargetPointer ilCodeVersionNodeAddress) => + new ILCodeVersionHandle(TargetPointer.Null, 0, ilCodeVersionNodeAddress); + internal static ILCodeVersionHandle CreateSynthetic(TargetPointer module, uint methodDef) => + new ILCodeVersionHandle(module, methodDef, TargetPointer.Null); + + public static ILCodeVersionHandle Invalid { get; } = new(TargetPointer.Null, 0, TargetPointer.Null); + + public bool IsValid => Module != TargetPointer.Null || ILCodeVersionNode != TargetPointer.Null; + + internal bool IsExplicit => ILCodeVersionNode != TargetPointer.Null; +} + +internal readonly struct NativeCodeVersionHandle { // no public constructors internal readonly TargetPointer MethodDescAddress; internal readonly TargetPointer CodeVersionNodeAddress; - internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) + private NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) { if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null) { @@ -33,9 +74,17 @@ internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer CodeVersionNodeAddress = codeVersionNodeAddress; } - internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null); + // for more information on Explicit/Synthetic code versions see docs/design/features/code-versioning.md + internal static NativeCodeVersionHandle CreateExplicit(TargetPointer codeVersionNodeAddress) => + new NativeCodeVersionHandle(TargetPointer.Null, codeVersionNodeAddress); + internal static NativeCodeVersionHandle CreateSynthetic(TargetPointer methodDescAddress) => + new NativeCodeVersionHandle(methodDescAddress, TargetPointer.Null); + + public static NativeCodeVersionHandle Invalid { get; } = new(TargetPointer.Null, TargetPointer.Null); + public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null; + internal bool IsExplicit => CodeVersionNodeAddress != TargetPointer.Null; } internal readonly struct CodeVersions : ICodeVersions diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs index 6bb9f4e0f0f17..0a99efd76d5a3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs @@ -2,13 +2,25 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; +public enum RejitState +{ + Requested, + Active +} + internal interface IReJIT : IContract { static string IContract.Name { get; } = nameof(ReJIT); + bool IsEnabled() => throw new NotImplementedException(); + + RejitState GetRejitState(ILCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + + TargetNUInt GetRejitId(ILCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); } internal readonly struct ReJIT : IReJIT diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs index 8a565225ba831..3072c484edb2e 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs @@ -7,10 +7,20 @@ namespace Microsoft.Diagnostics.DataContractReader; [DebuggerDisplay("{Hex}")] -public readonly struct TargetNUInt +public readonly struct TargetNUInt : IEquatable { public readonly ulong Value; public TargetNUInt(ulong value) => Value = value; internal string Hex => $"0x{Value:x}"; + + public override bool Equals(object? obj) => obj is TargetNUInt other && Equals(other); + + public bool Equals(TargetNUInt t) => Value == t.Value; + + public override int GetHashCode() => Value.GetHashCode(); + + public static bool operator ==(TargetNUInt lhs, TargetNUInt rhs) => lhs.Equals(rhs); + + public static bool operator !=(TargetNUInt lhs, TargetNUInt rhs) => !(lhs == rhs); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index 37086698d95e0..a0d06e79349c3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -16,6 +18,82 @@ public CodeVersions_1(Target target) _target = target; } + ILCodeVersionHandle ICodeVersions.GetActiveILCodeVersion(TargetPointer methodDesc) + { + // CodeVersionManager::GetActiveILCodeVersion + GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); + TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; + TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + if (ilVersionStateAddress == TargetPointer.Null) + { + return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); + } + Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); + return ActiveILCodeVersionHandleFromState(ilState); + } + + ILCodeVersionHandle ICodeVersions.GetILCodeVersion(NativeCodeVersionHandle nativeCodeVersionHandle) + { + // NativeCodeVersion::GetILCodeVersion + if (!nativeCodeVersionHandle.Valid) + { + return ILCodeVersionHandle.Invalid; + } + + if (!nativeCodeVersionHandle.IsExplicit) + { + // There is only a single synthetic NativeCodeVersion per + // method and it must be on the synthetic ILCodeVersion + GetModuleAndMethodDesc( + nativeCodeVersionHandle.MethodDescAddress, + out TargetPointer module, + out uint methodDefToken); + return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); + } + else + { + // Otherwise filter all the ILCodeVersions for the one that matches the version id + NativeCodeVersionNode nativeCodeVersionNode = AsNode(nativeCodeVersionHandle); + foreach (ILCodeVersionHandle ilCodeVersionHandle in ((ICodeVersions)this).GetILCodeVersions(nativeCodeVersionNode.MethodDesc)) + { + if (GetId(ilCodeVersionHandle) == nativeCodeVersionNode.ILVersionId) + { + return ilCodeVersionHandle; + } + } + } + + return ILCodeVersionHandle.Invalid; + } + + IEnumerable ICodeVersions.GetILCodeVersions(TargetPointer methodDesc) + { + // CodeVersionManager::GetILCodeVersions + GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); + TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; + TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + + // always add the synthetic version + yield return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); + + // if explicit versions exist, iterate linked list and return them + if (ilVersionStateAddress != TargetPointer.Null) + { + Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); + TargetPointer nodePointer = ilState.FirstVersionNode; + while (nodePointer != TargetPointer.Null) + { + Data.ILCodeVersionNode current = _target.ProcessedData.GetOrAdd(nodePointer); + yield return ILCodeVersionHandle.CreateExplicit(nodePointer); + nodePointer = current.Next; + } + } + } + NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointer ip) { // ExecutionManager::GetNativeCodeVersion(PCODE ip)) @@ -35,7 +113,7 @@ NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointe MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); if (!rts.IsVersionable(md)) { - return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + return NativeCodeVersionHandle.CreateSynthetic(methodDescAddress); } else { @@ -44,23 +122,6 @@ NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointe } } - NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) - { - // CodeVersionManager::GetActiveILCodeVersion - // then ILCodeVersion::GetActiveNativeCodeVersion - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); - TargetPointer mtAddr = rts.GetMethodTable(md); - TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); - TargetPointer module = rts.GetModule(typeHandle); - uint methodDefToken = rts.GetMethodToken(md); - ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); - if (!methodDefActiveVersion.IsValid) - { - return NativeCodeVersionHandle.Invalid; - } - return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); - } bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddress) { IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; @@ -82,43 +143,51 @@ bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddr TargetCodePointer ICodeVersions.GetNativeCode(NativeCodeVersionHandle codeVersionHandle) { - if (codeVersionHandle.MethodDescAddress != TargetPointer.Null) + if (!codeVersionHandle.Valid) + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + + if (!codeVersionHandle.IsExplicit) { MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(codeVersionHandle.MethodDescAddress); return _target.Contracts.RuntimeTypeSystem.GetNativeCode(md); } - else if (codeVersionHandle.CodeVersionNodeAddress != TargetPointer.Null) + else { Data.NativeCodeVersionNode nativeCodeVersionNode = _target.ProcessedData.GetOrAdd(codeVersionHandle.CodeVersionNodeAddress); return nativeCodeVersionNode.NativeCode; } - else - { - throw new ArgumentException("Invalid NativeCodeVersionHandle"); - } } - internal struct ILCodeVersionHandle + NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle) { - internal readonly TargetPointer Module; - internal uint MethodDefinition; - internal readonly TargetPointer ILCodeVersionNode; - internal readonly uint RejitId; + // ILCodeVersion::GetActiveNativeCodeVersion + if (!ilCodeVersionHandle.IsValid) + { + return NativeCodeVersionHandle.Invalid; + } - internal ILCodeVersionHandle(TargetPointer module, uint methodDef, TargetPointer ilCodeVersionNodeAddress) + if (!ilCodeVersionHandle.IsExplicit) { - if (module != TargetPointer.Null && ilCodeVersionNodeAddress != TargetPointer.Null) - throw new ArgumentException("Both MethodDesc and ILCodeVersionNode cannot be non-null"); + // if the ILCodeVersion is synthetic, then check if the active NativeCodeVersion is the synthetic one + NativeCodeVersionHandle provisionalHandle = NativeCodeVersionHandle.CreateSynthetic(methodDescAddress: methodDesc); + if (IsActiveNativeCodeVersion(provisionalHandle)) + { + return provisionalHandle; + } + } - if (module != TargetPointer.Null && methodDef == 0) - throw new ArgumentException("MethodDefinition must be non-zero if Module is non-null"); + // Iterate through versioning state nodes and return the active one, matching any IL code version + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetNUInt ilVersionId = GetId(ilCodeVersionHandle); + return FindFirstCodeVersion(rts, md, (codeVersion) => + { + return (ilVersionId == codeVersion.ILVersionId) + && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); + }); - Module = module; - MethodDefinition = methodDef; - ILCodeVersionNode = ilCodeVersionNodeAddress; - } - public static ILCodeVersionHandle Invalid => new ILCodeVersionHandle(TargetPointer.Null, 0, TargetPointer.Null); - public bool IsValid => Module != TargetPointer.Null || ILCodeVersionNode != TargetPointer.Null; } [Flags] @@ -133,7 +202,7 @@ private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem TargetCodePointer firstNativeCode = rts.GetNativeCode(md); if (firstNativeCode == startAddress) { - NativeCodeVersionHandle first = new NativeCodeVersionHandle(md.Address, TargetPointer.Null); + NativeCodeVersionHandle first = NativeCodeVersionHandle.CreateSynthetic(md.Address); return first; } @@ -160,7 +229,7 @@ private NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, Met Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); if (predicate(current)) { - return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); + return NativeCodeVersionHandle.CreateExplicit(currentAddress); } currentAddress = current.Next; } @@ -173,34 +242,20 @@ private enum ILCodeVersionKind Explicit = 1, // means Node is set Synthetic = 2, // means Module and Token are set } - private static ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState) + private static ILCodeVersionHandle ActiveILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState) { switch ((ILCodeVersionKind)ilState.ActiveVersionKind) { case ILCodeVersionKind.Explicit: - return new ILCodeVersionHandle(module: TargetPointer.Null, methodDef: 0, ilState.ActiveVersionNode); + return ILCodeVersionHandle.CreateExplicit(ilState.ActiveVersionNode); case ILCodeVersionKind.Synthetic: case ILCodeVersionKind.Unknown: - return new ILCodeVersionHandle(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef, TargetPointer.Null); + return ILCodeVersionHandle.CreateSynthetic(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef); default: throw new InvalidOperationException($"Unknown ILCodeVersionKind {ilState.ActiveVersionKind}"); } } - private ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition) - { - // CodeVersionManager::GetActiveILCodeVersion - ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); - TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; - TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefinition, out var _); - if (ilVersionStateAddress == TargetPointer.Null) - { - return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null); - } - Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); - return ILCodeVersionHandleFromState(ilState); - } - [Flags] internal enum NativeCodeVersionNodeFlags : uint { @@ -210,7 +265,12 @@ internal enum NativeCodeVersionNodeFlags : uint private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) { // NativeCodeVersion::IsActiveChildVersion - if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null) + if (!nativeCodeVersion.Valid) + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + + if (!nativeCodeVersion.IsExplicit) { MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress); TargetPointer versioningStateAddress = _target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); @@ -223,45 +283,52 @@ private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); } - else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) + else { // NativeCodeVersionNode::IsActiveChildVersion Data.NativeCodeVersionNode codeVersion = _target.ProcessedData.GetOrAdd(nativeCodeVersion.CodeVersionNodeAddress); return ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); } - else - { - throw new ArgumentException("Invalid NativeCodeVersionHandle"); - } } - private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) + private void GetModuleAndMethodDesc(TargetPointer methodDesc, out TargetPointer module, out uint methodDefToken) { - TargetNUInt? ilVersionId = default; - if (methodDefActiveVersion.Module != TargetPointer.Null) + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + module = rts.GetModule(typeHandle); + methodDefToken = rts.GetMethodToken(md); + } + + private ILCodeVersionNode AsNode(ILCodeVersionHandle handle) + { + if (handle.ILCodeVersionNode == TargetPointer.Null) { - NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); - if (IsActiveNativeCodeVersion(provisionalHandle)) - { - return provisionalHandle; - } + throw new InvalidOperationException("Synthetic ILCodeVersion does not have a backing node."); } - else + + return _target.ProcessedData.GetOrAdd(handle.ILCodeVersionNode); + } + + private NativeCodeVersionNode AsNode(NativeCodeVersionHandle handle) + { + if (handle.CodeVersionNodeAddress == TargetPointer.Null) { - // Get the explicit IL code version - Debug.Assert(methodDefActiveVersion.ILCodeVersionNode != TargetPointer.Null); - Data.ILCodeVersionNode ilCodeVersion = _target.ProcessedData.GetOrAdd(methodDefActiveVersion.ILCodeVersionNode); - ilVersionId = ilCodeVersion.VersionId; + throw new InvalidOperationException("Synthetic NativeCodeVersion does not have a backing node."); } - // Iterate through versioning state nodes and return the active one, matching any IL code version - Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); - return FindFirstCodeVersion(rts, md, (codeVersion) => - { - return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value) - && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); - }); + return _target.ProcessedData.GetOrAdd(handle.CodeVersionNodeAddress); } + private TargetNUInt GetId(ILCodeVersionHandle ilCodeVersionHandle) + { + if (!ilCodeVersionHandle.IsExplicit) + { + // for non explicit ILCodeVersions, id is always 0 + return new TargetNUInt(0); + } + ILCodeVersionNode ilCodeVersionNode = AsNode(ilCodeVersionHandle); + return ilCodeVersionNode.VersionId; + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs index d5bcd98bd4b43..6e9e80022a573 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -17,6 +19,21 @@ private enum COR_PRF_MONITOR COR_PRF_ENABLE_REJIT = 0x00040000, } + // see src/coreclr/vm/codeversion.h + [Flags] + public enum RejitFlags : uint + { + kStateRequested = 0x00000000, + + kStateGettingReJITParameters = 0x00000001, + + kStateActive = 0x00000002, + + kStateMask = 0x0000000F, + + kSuppressParams = 0x80000000 + } + public ReJIT_1(Target target, Data.ProfControlBlock profControlBlock) { _target = target; @@ -32,4 +49,41 @@ bool IReJIT.IsEnabled() bool clrConfigEnabledReJIT = true; return profEnabledReJIT || clrConfigEnabledReJIT; } + + RejitState IReJIT.GetRejitState(ILCodeVersionHandle ilCodeVersionHandle) + { + if (!ilCodeVersionHandle.IsExplicit) + { + // for non explicit ILCodeVersions, ReJITState is always kStateActive + return RejitState.Active; + } + ILCodeVersionNode ilCodeVersionNode = AsNode(ilCodeVersionHandle); + return ((RejitFlags)ilCodeVersionNode.RejitState & RejitFlags.kStateMask) switch + { + RejitFlags.kStateRequested => RejitState.Requested, + RejitFlags.kStateActive => RejitState.Active, + _ => throw new InvalidOperationException($"Unknown ReJIT state: {ilCodeVersionNode.RejitState}"), + }; + } + + TargetNUInt IReJIT.GetRejitId(ILCodeVersionHandle ilCodeVersionHandle) + { + if (ilCodeVersionHandle.ILCodeVersionNode == TargetPointer.Null) + { + // for non explicit ILCodeVersions, ReJITId is always 0 + return new TargetNUInt(0); + } + ILCodeVersionNode ilCodeVersionNode = AsNode(ilCodeVersionHandle); + return ilCodeVersionNode.VersionId; + } + + private ILCodeVersionNode AsNode(ILCodeVersionHandle ilCodeVersionHandle) + { + if (ilCodeVersionHandle.ILCodeVersionNode == TargetPointer.Null) + { + throw new InvalidOperationException("Synthetic ILCodeVersion does not have a backing node."); + } + + return _target.ProcessedData.GetOrAdd(ilCodeVersionHandle.ILCodeVersionNode); + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs index fbc7183c6688e..fb6fdc32164b4 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs @@ -13,7 +13,13 @@ public ILCodeVersionNode(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.ILCodeVersionNode); VersionId = target.ReadNUInt(address + (ulong)type.Fields[nameof(VersionId)].Offset); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + RejitState = target.Read(address + (ulong)type.Fields[nameof(RejitState)].Offset); } public TargetNUInt VersionId { get; init; } + + public TargetPointer Next { get; init; } + + public uint RejitState { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs index 92a214d910f6d..a37e93ed9637e 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs @@ -12,12 +12,14 @@ public ILCodeVersioningState(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.ILCodeVersioningState); + FirstVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(FirstVersionNode)].Offset); ActiveVersionKind = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionKind)].Offset); ActiveVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionNode)].Offset); ActiveVersionModule = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionModule)].Offset); ActiveVersionMethodDef = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionMethodDef)].Offset); } + public TargetPointer FirstVersionNode { get; set; } public uint ActiveVersionKind { get; set; } public TargetPointer ActiveVersionNode { get; set; } public TargetPointer ActiveVersionModule { get; set; } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index dce6b1bf9d0b2..e9c9644121eb1 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -2,12 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -194,13 +197,10 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes int hr = HResults.E_NOTIMPL; try { - if (cRevertedRejitVersions != 0) - { - throw new NotImplementedException(); // TODO[cdac]: rejit - } Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc); Contracts.ICodeVersions nativeCodeContract = _target.Contracts.CodeVersions; + Contracts.IReJIT rejitContract = _target.Contracts.ReJIT; if (rgRevertedRejitData != null) { @@ -256,11 +256,97 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes TypeHandle typeHandle = rtsContract.GetTypeHandle(methodTableAddr); data->ModulePtr = rtsContract.GetModule(typeHandle); - // TODO[cdac]: everything in the ReJIT TRY/CATCH in GetMethodDescDataImpl in request.cpp - if (pcNeededRevertedRejitData != null) + // If rejit info is appropriate, get the following: + // * ReJitInfo for the current, active version of the method + // * ReJitInfo for the requested IP (for !ip2md and !u) + // * ReJitInfos for all reverted versions of the method (up to + // cRevertedRejitVersions) + // + // Minidumps will not have all this rejit info, and failure to get rejit info + // should not be fatal. So enclose all rejit stuff in a try. + try { + if (activeNativeCodeVersion is null || !activeNativeCodeVersion.Value.Valid) + { + activeNativeCodeVersion = nativeCodeContract.GetActiveNativeCodeVersion(new TargetPointer(methodDesc)); + } + + if (activeNativeCodeVersion is null || !activeNativeCodeVersion.Value.Valid) + { + throw new InvalidOperationException("No active native code version found"); + } + + // Active ReJitInfo + CopyNativeCodeVersionToReJitData( + activeNativeCodeVersion.Value, + activeNativeCodeVersion.Value, + &data->rejitDataCurrent); + + // Requested ReJitInfo + Debug.Assert(data->rejitDataRequested.rejitID == 0); + if (ip != 0 && requestedNativeCodeVersion.Valid) + { + CopyNativeCodeVersionToReJitData( + requestedNativeCodeVersion, + activeNativeCodeVersion.Value, + &data->rejitDataRequested); + } + + // Total number of jitted rejit versions + int cJittedRejitVersions = rejitContract.GetRejitIds(_target, methodDescHandle.Address).Count(); + data->cJittedRejitVersions = (uint)cJittedRejitVersions; + + // Reverted ReJitInfos + if (rgRevertedRejitData == null) + { + // No reverted rejit versions will be returned, but maybe caller wants a + // count of all versions + if (pcNeededRevertedRejitData != null) + { + *pcNeededRevertedRejitData = data->cJittedRejitVersions; + } + } + else + { + // Caller wants some reverted rejit versions. Gather reverted rejit version data to return + + // Returns all available rejitids, including the rejitid for the one non-reverted + // current version. + List reJitIds = rejitContract.GetRejitIds(_target, methodDescHandle.Address).ToList(); + + // Go through rejitids. For each reverted one, populate a entry in rgRevertedRejitData + uint iRejitDataReverted = 0; + ILCodeVersionHandle activeVersion = nativeCodeContract.GetActiveILCodeVersion(methodDesc); + TargetNUInt activeVersionId = rejitContract.GetRejitId(activeVersion); + for (int i = 0; (i < reJitIds.Count) && (iRejitDataReverted < cRevertedRejitVersions); i++) + { + ILCodeVersionHandle ilCodeVersion = nativeCodeContract.GetILCodeVersions(methodDesc) + .FirstOrDefault(ilcode => rejitContract.GetRejitId(ilcode) == reJitIds[i], + ILCodeVersionHandle.Invalid); + + if (!ilCodeVersion.IsValid || rejitContract.GetRejitId(ilCodeVersion) == activeVersionId) + { + continue; + } - throw new NotImplementedException(); // TODO[cdac]: rejit stuff + NativeCodeVersionHandle activeRejitChild = nativeCodeContract.GetActiveNativeCodeVersionForILCodeVersion(methodDesc, ilCodeVersion); + CopyNativeCodeVersionToReJitData( + activeRejitChild, + activeNativeCodeVersion.Value, + &rgRevertedRejitData[iRejitDataReverted]); + + iRejitDataReverted++; + } + // We already checked that pcNeededRevertedRejitData != NULL because rgRevertedRejitData != NULL + *pcNeededRevertedRejitData = iRejitDataReverted; + } + } + catch (global::System.Exception) + { + if (pcNeededRevertedRejitData != null) + { + *pcNeededRevertedRejitData = 0; + } } #if false // TODO[cdac]: HAVE_GCCOVER @@ -297,7 +383,7 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes rgRevertedRejitDataLocal = new DacpReJitData[cRevertedRejitVersions]; } uint cNeededRevertedRejitDataLocal = 0; - uint *pcNeededRevertedRejitDataLocal = null; + uint* pcNeededRevertedRejitDataLocal = null; if (pcNeededRevertedRejitData != null) { pcNeededRevertedRejitDataLocal = &cNeededRevertedRejitDataLocal; @@ -321,12 +407,22 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes Debug.Assert(data->GCStressCodeCopy == dataLocal.GCStressCodeCopy); Debug.Assert(data->managedDynamicMethodObject == dataLocal.managedDynamicMethodObject); Debug.Assert(data->requestedIP == dataLocal.requestedIP); - // TODO[cdac]: cdacreader always returns 0 currently - Debug.Assert(data->cJittedRejitVersions == 0 || data->cJittedRejitVersions == dataLocal.cJittedRejitVersions); - // TODO[cdac]: compare rejitDataCurrent and rejitDataRequested, too + Debug.Assert(data->cJittedRejitVersions == dataLocal.cJittedRejitVersions); + + // rejitDataCurrent + Debug.Assert(data->rejitDataCurrent.rejitID == dataLocal.rejitDataCurrent.rejitID); + Debug.Assert(data->rejitDataCurrent.NativeCodeAddr == dataLocal.rejitDataCurrent.NativeCodeAddr); + Debug.Assert(data->rejitDataCurrent.flags == dataLocal.rejitDataCurrent.flags); + + // rejitDataRequested + Debug.Assert(data->rejitDataRequested.rejitID == dataLocal.rejitDataRequested.rejitID); + Debug.Assert(data->rejitDataRequested.NativeCodeAddr == dataLocal.rejitDataRequested.NativeCodeAddr); + Debug.Assert(data->rejitDataRequested.flags == dataLocal.rejitDataRequested.flags); + + // rgRevertedRejitData if (rgRevertedRejitData != null && rgRevertedRejitDataLocal != null) { - Debug.Assert (cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData); + Debug.Assert(cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData); for (ulong i = 0; i < cNeededRevertedRejitDataLocal; i++) { Debug.Assert(rgRevertedRejitData[i].rejitID == rgRevertedRejitDataLocal[i].rejitID); @@ -343,6 +439,45 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes return hr; } + private void CopyNativeCodeVersionToReJitData( + NativeCodeVersionHandle nativeCodeVersion, + NativeCodeVersionHandle activeNativeCodeVersion, + DacpReJitData* pReJitData) + { + ICodeVersions cv = _target.Contracts.CodeVersions; + IReJIT rejit = _target.Contracts.ReJIT; + + ILCodeVersionHandle ilCodeVersion = cv.GetILCodeVersion(nativeCodeVersion); + + pReJitData->rejitID = rejit.GetRejitId(ilCodeVersion).Value; + pReJitData->NativeCodeAddr = cv.GetNativeCode(nativeCodeVersion); + + if (nativeCodeVersion.CodeVersionNodeAddress != activeNativeCodeVersion.CodeVersionNodeAddress || + nativeCodeVersion.MethodDescAddress != activeNativeCodeVersion.MethodDescAddress) + { + pReJitData->flags = DacpReJitData.Flags.kReverted; + } + else + { + DacpReJitData.Flags flags = DacpReJitData.Flags.kUnknown; + switch (rejit.GetRejitState(ilCodeVersion)) + { + // kStateRequested + case RejitState.Requested: + flags = DacpReJitData.Flags.kRequested; + break; + // kStateActive + case RejitState.Active: + flags = DacpReJitData.Flags.kActive; + break; + default: + Debug.Fail("Unknown RejitState. cDAC should be updated to understand this new state."); + break; + } + pReJitData->flags = flags; + } + } + int ISOSDacInterface.GetMethodDescFromToken(ulong moduleAddr, uint token, ulong* methodDesc) => _legacyImpl is not null ? _legacyImpl.GetMethodDescFromToken(moduleAddr, token, methodDesc) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodDescName(ulong methodDesc, uint count, char* name, uint* pNeeded) diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index f60f75df5947a..26e8859352787 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; using Moq; using Xunit; @@ -345,7 +347,12 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch) var methodDescAddress = new TargetPointer(0x00aa_aa00); var moduleAddress = new TargetPointer(0x00ca_ca00); - TargetPointer versioningState = builder.AddILCodeVersioningState(activeVersionKind: 0/*==unknown*/, activeVersionNode: TargetPointer.Null, activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken); + TargetPointer versioningState = builder.AddILCodeVersioningState( + activeVersionKind: 0/*==unknown*/, + activeVersionNode: TargetPointer.Null, + activeVersionModule: moduleAddress, + activeVersionMethodDef: methodDefToken, + firstVersionNode: TargetPointer.Null); var oneModule = new MockModule() { Address = moduleAddress, MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), @@ -399,7 +406,12 @@ private void GetActiveNativeCodeVersion_IterateVersionNodes_Impl(MockTarget.Arch var methodDescAddress = new TargetPointer(0x00aa_aa00); var moduleAddress = new TargetPointer(0x00ca_ca00); - TargetPointer versioningState = builder.AddILCodeVersioningState(activeVersionKind: 0/*==unknown*/, activeVersionNode: TargetPointer.Null, activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken); + TargetPointer versioningState = builder.AddILCodeVersioningState( + activeVersionKind: 0/*==unknown*/, + activeVersionNode: TargetPointer.Null, + activeVersionModule: moduleAddress, + activeVersionMethodDef: methodDefToken, + firstVersionNode: TargetPointer.Null); var module = new MockModule() { Address = moduleAddress, @@ -468,8 +480,13 @@ private void GetActiveNativeCodeVersion_ExplicitILCodeVersion_Impl(MockTarget.Ar var moduleAddress = new TargetPointer(0x00ca_ca00); TargetNUInt ilVersionId = new TargetNUInt(5); - TargetPointer ilVersionNode = builder.AddILCodeVersionNode(ilVersionId); - TargetPointer versioningState = builder.AddILCodeVersioningState(activeVersionKind: 1 /* Explicit */, activeVersionNode: ilVersionNode, activeVersionModule: TargetPointer.Null, activeVersionMethodDef: 0); + TargetPointer ilVersionNode = builder.AddILCodeVersionNode(TargetPointer.Null, ilVersionId, /* kStateActive */ 0x00000002); + TargetPointer versioningState = builder.AddILCodeVersioningState( + activeVersionKind: 1 /* Explicit */, + activeVersionNode: ilVersionNode, + activeVersionModule: TargetPointer.Null, + activeVersionMethodDef: 0, + firstVersionNode: ilVersionNode); var module = new MockModule() { Address = moduleAddress, @@ -515,4 +532,138 @@ private void GetActiveNativeCodeVersion_ExplicitILCodeVersion_Impl(MockTarget.Ar Assert.False(handle.Valid); } } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetILCodeVersions_SyntheticAndExplicit(MockTarget.Architecture arch) + { + uint methodRowId = 0x25; // arbitrary + TargetCodePointer expectedSyntheticCodePointer = new TargetCodePointer(0x0700_abc0); + TargetCodePointer expectedExplicitCodePointer = new TargetCodePointer(0x0780_abc0); + var builder = new MockCodeVersions(arch); + var methodDescAddress = new TargetPointer(0x00aa_aa00); + var moduleAddress = new TargetPointer(0x00ca_ca00); + + TargetNUInt ilVersionId = new TargetNUInt(2); + TargetPointer ilVersionNode = builder.AddILCodeVersionNode(TargetPointer.Null, ilVersionId, /* kStateActive */ 0x00000002); + TargetPointer versioningState = builder.AddILCodeVersioningState( + activeVersionKind: 1 /* Explicit */, + activeVersionNode: ilVersionNode, + activeVersionModule: TargetPointer.Null, + activeVersionMethodDef: 0, + firstVersionNode: ilVersionNode); + var module = new MockModule() + { + Address = moduleAddress, + MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), + MethodDefToILCodeVersioningStateTable = new Dictionary() { + { methodRowId, versioningState} + }, + }; + var methodTable = new MockMethodTable() + { + Address = new TargetPointer(0x00ba_ba00), + Module = module, + }; + + (TargetPointer firstNode, _) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedSyntheticCodePointer, new TargetNUInt(0)); + (firstNode, _) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedExplicitCodePointer, ilVersionId, firstNode); + + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode: firstNode, isDefaultVersionActive: false); + + var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: methodDescVersioningStateAddress); + oneMethod.MethodTable = methodTable; + oneMethod.RowId = methodRowId; + + var target = CreateTarget(arch, [oneMethod], [methodTable], [], [module], builder); + + // TEST + + var codeVersions = target.Contracts.CodeVersions; + Assert.NotNull(codeVersions); + + // Get all ILCodeVersions + List ilCodeVersions = codeVersions.GetILCodeVersions(methodDescAddress).ToList(); + Assert.Equal(2, ilCodeVersions.Count); + + // Get the explicit ILCodeVersion and assert that it is in the list of ILCodeVersions + ILCodeVersionHandle explicitILCodeVersion = codeVersions.GetActiveILCodeVersion(methodDescAddress); + Assert.Contains(ilCodeVersions, ilcodeVersion => ilcodeVersion.Equals(explicitILCodeVersion)); + Assert.Equal(expectedExplicitCodePointer, codeVersions.GetNativeCode(codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, explicitILCodeVersion))); + + // Find the other ILCodeVersion (synthetic) and assert that it is valid. + ILCodeVersionHandle syntheticILcodeVersion = ilCodeVersions.Find(ilCodeVersion => !ilCodeVersion.Equals(explicitILCodeVersion)); + Assert.True(syntheticILcodeVersion.IsValid); + Assert.Equal(expectedSyntheticCodePointer, codeVersions.GetNativeCode(codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, syntheticILcodeVersion))); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IlToNativeToIlCodeVersion_SyntheticAndExplicit(MockTarget.Architecture arch) + { + uint methodRowId = 0x25; // arbitrary + TargetCodePointer expectedSyntheticCodePointer = new TargetCodePointer(0x0700_abc0); + TargetCodePointer expectedExplicitCodePointer = new TargetCodePointer(0x0780_abc0); + var builder = new MockCodeVersions(arch); + var methodDescAddress = new TargetPointer(0x00aa_aa00); + var moduleAddress = new TargetPointer(0x00ca_ca00); + + TargetNUInt ilVersionId = new TargetNUInt(2); + TargetPointer ilVersionNode = builder.AddILCodeVersionNode(TargetPointer.Null, ilVersionId, /* kStateActive */ 0x00000002); + TargetPointer versioningState = builder.AddILCodeVersioningState( + activeVersionKind: 1 /* Explicit */, + activeVersionNode: ilVersionNode, + activeVersionModule: TargetPointer.Null, + activeVersionMethodDef: 0, + firstVersionNode: ilVersionNode); + var module = new MockModule() + { + Address = moduleAddress, + MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), + MethodDefToILCodeVersioningStateTable = new Dictionary() { + { methodRowId, versioningState} + }, + }; + var methodTable = new MockMethodTable() + { + Address = new TargetPointer(0x00ba_ba00), + Module = module, + }; + + (TargetPointer firstNode, _) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedSyntheticCodePointer, new TargetNUInt(0)); + (firstNode, _) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedExplicitCodePointer, ilVersionId, firstNode); + + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode: firstNode, isDefaultVersionActive: false); + + var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: methodDescVersioningStateAddress); + oneMethod.MethodTable = methodTable; + oneMethod.RowId = methodRowId; + + var target = CreateTarget(arch, [oneMethod], [methodTable], [], [module], builder); + + // TEST + + var codeVersions = target.Contracts.CodeVersions; + Assert.NotNull(codeVersions); + + // Get all ILCodeVersions + List ilCodeVersions = codeVersions.GetILCodeVersions(methodDescAddress).ToList(); + Assert.Equal(2, ilCodeVersions.Count); + + // Get the explicit ILCodeVersion and assert that it is in the list of ILCodeVersions + ILCodeVersionHandle explicitILCodeVersion = codeVersions.GetActiveILCodeVersion(methodDescAddress); + Assert.Contains(ilCodeVersions, ilcodeVersion => ilcodeVersion.Equals(explicitILCodeVersion)); + Assert.True(explicitILCodeVersion.IsValid); + + // Find the other ILCodeVersion (synthetic) and assert that it is valid. + ILCodeVersionHandle syntheticILcodeVersion = ilCodeVersions.Find(ilCodeVersion => !ilCodeVersion.Equals(explicitILCodeVersion)); + Assert.True(syntheticILcodeVersion.IsValid); + + // Verify getting ILCode is equal to ILCode from NativeCode from ILCode. + NativeCodeVersionHandle explicitNativeCodeVersion = codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, explicitILCodeVersion); + Assert.True(explicitILCodeVersion.Equals(codeVersions.GetILCodeVersion(explicitNativeCodeVersion))); + + NativeCodeVersionHandle syntheticNativeCodeVersion = codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, syntheticILcodeVersion); + Assert.True(syntheticILcodeVersion.Equals(codeVersions.GetILCodeVersion(syntheticNativeCodeVersion))); + } } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs index f97f099433e9e..a4b2575eb2ac4 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs @@ -42,6 +42,7 @@ public class CodeVersions DataType = DataType.ILCodeVersioningState, Fields = [ + new(nameof(Data.ILCodeVersioningState.FirstVersionNode), DataType.pointer), new(nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef), DataType.uint32), new(nameof(Data.ILCodeVersioningState.ActiveVersionModule), DataType.pointer), new(nameof(Data.ILCodeVersioningState.ActiveVersionKind), DataType.uint32), @@ -55,6 +56,8 @@ public class CodeVersions Fields = [ new(nameof(Data.ILCodeVersionNode.VersionId), DataType.nuint), + new(nameof(Data.ILCodeVersionNode.Next), DataType.pointer), + new(nameof(Data.ILCodeVersionNode.RejitState), DataType.uint32), ] }; @@ -67,6 +70,10 @@ public CodeVersions(MockTarget.Architecture arch) : this(new MockMemorySpace.Builder(new TargetTestHelpers(arch)), (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) { } + public CodeVersions(MockMemorySpace.Builder builder) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { } + public CodeVersions(MockTarget.Architecture arch, (ulong Start, ulong End) allocationRange) : this(new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange) { } @@ -120,16 +127,16 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe Builder.TargetTestHelpers.WriteNUInt(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.ILVersionId)].Offset, Builder.TargetTestHelpers.PointerSize), ilVersionId); } - public (TargetPointer First, TargetPointer Active) AddNativeCodeVersionNodesForMethod(TargetPointer methodDesc, int count, int activeIndex, TargetCodePointer activeNativeCode, TargetNUInt explicitILVersion) + public (TargetPointer First, TargetPointer Active) AddNativeCodeVersionNodesForMethod(TargetPointer methodDesc, int count, int activeIndex, TargetCodePointer activeNativeCode, TargetNUInt ilVersion, TargetPointer? firstNode = null) { TargetPointer activeVersionNode = TargetPointer.Null; - TargetPointer next = TargetPointer.Null; + TargetPointer next = firstNode != null ? firstNode.Value : TargetPointer.Null; for (int i = count - 1; i >= 0; i--) { TargetPointer node = AddNativeCodeVersionNode(); bool isActive = i == activeIndex; TargetCodePointer nativeCode = isActive ? activeNativeCode : 0; - TargetNUInt ilVersionId = isActive ? explicitILVersion : default; + TargetNUInt ilVersionId = ilVersion; FillNativeCodeVersionNode(node, methodDesc, nativeCode, next, isActive, ilVersionId); next = node; if (isActive) @@ -139,7 +146,7 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe return (next, activeVersionNode); } - public TargetPointer AddILCodeVersioningState(uint activeVersionKind, TargetPointer activeVersionNode, TargetPointer activeVersionModule, uint activeVersionMethodDef) + public TargetPointer AddILCodeVersioningState(uint activeVersionKind, TargetPointer activeVersionNode, TargetPointer activeVersionModule, uint activeVersionMethodDef, TargetPointer firstVersionNode) { Target.TypeInfo info = Types[DataType.ILCodeVersioningState]; MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)Types[DataType.ILCodeVersioningState].Size, "ILCodeVersioningState"); @@ -149,15 +156,28 @@ public TargetPointer AddILCodeVersioningState(uint activeVersionKind, TargetPoin Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionNode)].Offset, Builder.TargetTestHelpers.PointerSize), activeVersionNode); Builder.TargetTestHelpers.Write(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef)].Offset, sizeof(uint)), activeVersionMethodDef); Builder.TargetTestHelpers.Write(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionKind)].Offset, sizeof(uint)), activeVersionKind); + Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.FirstVersionNode)].Offset), firstVersionNode); return fragment.Address; } - public TargetPointer AddILCodeVersionNode(TargetNUInt versionId) + public TargetPointer AddILCodeVersionNode(TargetPointer prevNodeAddress, TargetNUInt versionId, uint rejitFlags) { Target.TypeInfo info = Types[DataType.ILCodeVersionNode]; MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)Types[DataType.ILCodeVersionNode].Size, "NativeCodeVersionNode"); Builder.AddHeapFragment(fragment); Builder.TargetTestHelpers.WriteNUInt(fragment.Data.AsSpan().Slice(info.Fields[nameof(Data.ILCodeVersionNode.VersionId)].Offset), versionId); + Builder.TargetTestHelpers.Write(fragment.Data.AsSpan().Slice(info.Fields[nameof(Data.ILCodeVersionNode.RejitState)].Offset), (uint)rejitFlags); + + // set new node next pointer to null + Builder.TargetTestHelpers.WritePointer(fragment.Data.AsSpan().Slice(info.Fields[nameof(Data.ILCodeVersionNode.Next)].Offset), TargetPointer.Null); + + // set the previous node next pointer to the new node + if(prevNodeAddress != TargetPointer.Null) + { + Span prevNode = Builder.BorrowAddressRange(prevNodeAddress, fragment.Data.Length); + Builder.TargetTestHelpers.WritePointer(prevNode.Slice(info.Fields[nameof(Data.ILCodeVersionNode.Next)].Offset), fragment.Address); + } + return fragment.Address; } } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ReJIT.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ReJIT.cs new file mode 100644 index 0000000000000..335fbc5905753 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ReJIT.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +internal partial class MockDescriptors +{ + public class ReJIT + { + private const ulong DefaultAllocationRangeStart = 0x0010_1000; + private const ulong DefaultAllocationRangeEnd = 0x00011_0000; + + // see src/coreclr/vm/codeversion.h + [Flags] + public enum RejitFlags : uint + { + kStateRequested = 0x00000000, + + kStateGettingReJITParameters = 0x00000001, + + kStateActive = 0x00000002, + + kStateMask = 0x0000000F, + + kSuppressParams = 0x80000000 + } + + private static readonly TypeFields ProfControlBlockFields = new TypeFields() + { + DataType = DataType.ProfControlBlock, + Fields = + [ + new(nameof(Data.ProfControlBlock.GlobalEventMask), DataType.uint64) + ] + }; + + internal readonly MockMemorySpace.Builder Builder; + + internal Dictionary Types { get; } + internal (string Name, ulong Value)[] Globals { get; } + + private CodeVersions _codeVersions { get; } + + private readonly MockMemorySpace.BumpAllocator _rejitAllocator; + + public ReJIT(MockTarget.Architecture arch) + : this(new MockMemorySpace.Builder(new TargetTestHelpers(arch)), (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { } + + public ReJIT(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) + { + Builder = builder; + _rejitAllocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); + + _codeVersions = new CodeVersions(Builder); + + Types = GetTypes(builder.TargetTestHelpers); + + Globals = + [ + (nameof(Constants.Globals.ProfilerControlBlock), AddProfControlBlock()), + ]; + } + + public ILCodeVersionHandle AddExplicitILCodeVersion(TargetNUInt rejitId, RejitFlags rejitFlags) + { + TargetPointer codeVersionNode = _codeVersions.AddILCodeVersionNode(TargetPointer.Null, rejitId, (uint)rejitFlags); + + return ILCodeVersionHandle.CreateExplicit(codeVersionNode); + } + + internal static Dictionary GetTypes(TargetTestHelpers helpers) + { + Dictionary cvTypes = CodeVersions.GetTypes(helpers); + Dictionary types = GetTypesForTypeFields( + helpers, + [ + ProfControlBlockFields + ]); + foreach(var (dataType, typeInfo) in cvTypes) + { + types.Add(dataType, typeInfo); + } + return types; + } + + private ulong AddProfControlBlock() + { + Target.TypeInfo info = Types[DataType.ProfControlBlock]; + MockMemorySpace.HeapFragment fragment = _rejitAllocator.Allocate((ulong)Types[DataType.ProfControlBlock].Size, "ProfControlBlock"); + Builder.AddHeapFragment(fragment); + Span pcb = Builder.BorrowAddressRange(fragment.Address, fragment.Data.Length); + Builder.TargetTestHelpers.Write(pcb.Slice(info.Fields[nameof(Data.ProfControlBlock.GlobalEventMask)].Offset, sizeof(ulong)), 0ul); + return fragment.Address; + } + } +} diff --git a/src/native/managed/cdacreader/tests/ReJITTests.cs b/src/native/managed/cdacreader/tests/ReJITTests.cs new file mode 100644 index 0000000000000..34b47914fd977 --- /dev/null +++ b/src/native/managed/cdacreader/tests/ReJITTests.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +using MockReJIT = MockDescriptors.ReJIT; + +public class ReJITTests +{ + internal static Target CreateTarget( + MockTarget.Architecture arch, + MockReJIT builder, + Mock mockCodeVersions = null) + { + TestPlaceholderTarget target = new TestPlaceholderTarget(arch, builder.Builder.GetReadContext().ReadFromTarget, builder.Types, builder.Globals); + + mockCodeVersions ??= new Mock(); + + IContractFactory rejitFactory = new ReJITFactory(); + + ContractRegistry reg = Mock.Of( + c => c.ReJIT == rejitFactory.CreateContract(target, 1) + && c.CodeVersions == mockCodeVersions.Object); + target.SetContracts(reg); + return target; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetRejitId_SyntheticAndExplicit_Success(MockTarget.Architecture arch) + { + MockReJIT mockRejit = new MockReJIT(arch); + + Dictionary expectedRejitIds = new() + { + // synthetic ILCodeVersionHandle + { ILCodeVersionHandle.CreateSynthetic(new TargetPointer(/* arbitrary */ 0x100), /* arbitrary */ 100), new TargetNUInt(0) }, + { mockRejit.AddExplicitILCodeVersion(new TargetNUInt(1), MockReJIT.RejitFlags.kStateActive), new TargetNUInt(1) }, + { mockRejit.AddExplicitILCodeVersion(new TargetNUInt(2), MockReJIT.RejitFlags.kStateRequested), new TargetNUInt(2) }, + { mockRejit.AddExplicitILCodeVersion(new TargetNUInt(3), MockReJIT.RejitFlags.kStateRequested), new TargetNUInt(3) } + }; + + var target = CreateTarget(arch, mockRejit); + + // TEST + + var rejit = target.Contracts.ReJIT; + Assert.NotNull(rejit); + + foreach (var (ilCodeVersionHandle, expectedRejitId) in expectedRejitIds) + { + TargetNUInt rejitState = rejit.GetRejitId(ilCodeVersionHandle); + Assert.Equal(expectedRejitId, rejitState); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetRejitState_SyntheticAndExplicit_Success(MockTarget.Architecture arch) + { + MockReJIT mockRejit = new MockReJIT(arch); + + Dictionary expectedRejitStates = new() + { + // synthetic ILCodeVersionHandle + { ILCodeVersionHandle.CreateSynthetic(new TargetPointer(/* arbitrary */ 0x100), /* arbitrary */ 100), RejitState.Active }, + { mockRejit.AddExplicitILCodeVersion(new TargetNUInt(1), MockReJIT.RejitFlags.kStateActive), RejitState.Active }, + { mockRejit.AddExplicitILCodeVersion(new TargetNUInt(2), MockReJIT.RejitFlags.kStateRequested), RejitState.Requested }, + { mockRejit.AddExplicitILCodeVersion(new TargetNUInt(3), MockReJIT.RejitFlags.kSuppressParams | MockReJIT.RejitFlags.kStateRequested), RejitState.Requested } + }; + + var target = CreateTarget(arch, mockRejit); + + // TEST + + var rejit = target.Contracts.ReJIT; + Assert.NotNull(rejit); + + foreach (var (ilCodeVersionHandle, expectedRejitState) in expectedRejitStates) + { + RejitState rejitState = rejit.GetRejitState(ilCodeVersionHandle); + Assert.Equal(expectedRejitState, rejitState); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetRejitIds_SyntheticAndExplicit_Success(MockTarget.Architecture arch) + { + MockReJIT mockRejit = new MockReJIT(arch); + Mock mockCodeVersions = new Mock(); + + List expectedRejitIds = [0, 1]; + expectedRejitIds.Sort(); + + List ilCodeVersionHandles = + [ + // synthetic ILCodeVersionHandle + ILCodeVersionHandle.CreateSynthetic(new TargetPointer(/* arbitrary */ 0x100), /* arbitrary */ 100), + mockRejit.AddExplicitILCodeVersion(new TargetNUInt(1), MockReJIT.RejitFlags.kStateActive), + mockRejit.AddExplicitILCodeVersion(new TargetNUInt(2), MockReJIT.RejitFlags.kStateRequested) + ]; + + TargetPointer methodDesc = new TargetPointer(/* arbitrary */ 0x200); + mockCodeVersions.Setup(cv => cv.GetILCodeVersions(methodDesc)) + .Returns(ilCodeVersionHandles); + var target = CreateTarget(arch, mockRejit, mockCodeVersions); + + // TEST + + var rejit = target.Contracts.ReJIT; + Assert.NotNull(rejit); + + List rejitIds = rejit.GetRejitIds(target, methodDesc) + .Select(e => e.Value) + .ToList(); + rejitIds.Sort(); + + Assert.Equal(expectedRejitIds, rejitIds); + } +}