diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 19cee2b20730ce..7bab465b0a761b 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -4,6 +4,29 @@ This contract encapsulates support for [code versioning](../features/code-versio ## APIs of contract +```csharp +internal readonly struct ILCodeVersionHandle +{ + internal readonly TargetPointer Module; + internal readonly uint MethodDefinition; + internal readonly TargetPointer ILCodeVersionNode; + internal 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"); + + 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; +} +``` + ```csharp internal struct NativeCodeVersionHandle { @@ -26,10 +49,19 @@ internal struct NativeCodeVersionHandle ``` ```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); @@ -52,11 +84,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 +127,40 @@ Contracts used: | Loader | | RuntimeTypeSystem | +### Finding all of the ILCodeVersions for a method +```csharp +IEnumerable ICodeVersions.GetILCodeVersions(TargetPointer methodDesc) +{ + // CodeVersionManager::GetILCodeVersions + 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); + + 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 @@ -235,7 +303,7 @@ bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) { - TargetNUInt? ilVersionId = default; + TargetNUInt ilVersionId = GetId(ilcodeVersion); if (methodDefActiveVersion.Module != TargetPointer.Null) { NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); @@ -256,7 +324,7 @@ NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDe MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); return FindFirstCodeVersion(rts, md, (codeVersion) => { - return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value) + return (ilVersionId == codeVersion.ILVersionId) && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); }); } diff --git a/docs/design/datacontracts/ReJIT.md b/docs/design/datacontracts/ReJIT.md index 53ed0767a6a9d3..b31bfcbf9e354e 100644 --- a/docs/design/datacontracts/ReJIT.md +++ b/docs/design/datacontracts/ReJIT.md @@ -4,8 +4,20 @@ This contract encapsulates support for [ReJIT](../features/code-versioning.md) i ## APIs of contract +```chsarp +public enum RejitState +{ + Requested, + Active +} +``` + ```csharp bool IsEnabled(); + +RejitState GetRejitState(ILCodeVersionHandle codeVersionHandle); +TargetNUInt GetRejitId(ILCodeVersionHandle codeVersionHandle); +IEnumerable GetRejitIds(TargetPointer methodDesc) ``` ## Version 1 @@ -14,6 +26,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 +37,7 @@ Global variables used: Contracts used: | Contract Name | | --- | +| CodeVersions | ```csharp // see src/coreclr/inc/corprof.idl @@ -32,6 +47,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 +70,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 4568395df3cac1..3aef2fc875aa61 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 55f3407ca0a555..bddad32caddf42 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/ICodeVersions.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs index bddba2cdc27875..8214d12ae3b4aa 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,16 +10,45 @@ 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 readonly struct ILCodeVersionHandle +{ + internal readonly TargetPointer Module; + internal readonly uint MethodDefinition; + internal readonly TargetPointer ILCodeVersionNode; + internal 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"); + + 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; } -internal struct NativeCodeVersionHandle +internal readonly struct NativeCodeVersionHandle { // no public constructors internal readonly TargetPointer MethodDescAddress; 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 6bb9f4e0f0f176..f48e775aaf77a6 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,27 @@ // 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(); + + IEnumerable GetRejitIds(TargetPointer methodDesc) => 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 8a565225ba8311..3072c484edb2ee 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 37086698d95e08..a1c30a59ca2ad0 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,84 @@ public CodeVersions_1(Target target) _target = target; } + ILCodeVersionHandle ICodeVersions.GetActiveILCodeVersion(TargetPointer methodDesc) + { + // CodeVersionManager::GetActiveILCodeVersion + 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); + return FindActiveILCodeVersion(module, methodDefToken); + } + + ILCodeVersionHandle ICodeVersions.GetILCodeVersion(NativeCodeVersionHandle nativeCodeVersionHandle) + { + // NativeCodeVersion::GetILCodeVersion + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + if (!nativeCodeVersionHandle.Valid) + { + return ILCodeVersionHandle.Invalid; + } + + if (!IsExplicit(nativeCodeVersionHandle)) + { + // synthetic case + MethodDescHandle md = rts.GetMethodDescHandle(nativeCodeVersionHandle.MethodDescAddress); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(typeHandle); + uint methodDefToken = rts.GetMethodToken(md); + return new ILCodeVersionHandle(module, methodDefToken, TargetPointer.Null); + } + else + { + // explicit case + 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 + 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); + + 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; + } + } + } + NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointer ip) { // ExecutionManager::GetNativeCodeVersion(PCODE ip)) @@ -98,27 +178,14 @@ TargetCodePointer ICodeVersions.GetNativeCode(NativeCodeVersionHandle codeVersio } } - 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; - - internal ILCodeVersionHandle(TargetPointer module, uint methodDef, TargetPointer ilCodeVersionNodeAddress) + // ILCodeVersion::GetActiveNativeCodeVersion + if (!ilCodeVersionHandle.IsValid) { - 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"); - - Module = module; - MethodDefinition = methodDef; - ILCodeVersionNode = ilCodeVersionNodeAddress; + return NativeCodeVersionHandle.Invalid; } - public static ILCodeVersionHandle Invalid => new ILCodeVersionHandle(TargetPointer.Null, 0, TargetPointer.Null); - public bool IsValid => Module != TargetPointer.Null || ILCodeVersionNode != TargetPointer.Null; + return FindActiveNativeCodeVersion(ilCodeVersionHandle, methodDesc); } [Flags] @@ -173,7 +240,7 @@ 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) { @@ -198,7 +265,7 @@ private ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint m return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null); } Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); - return ILCodeVersionHandleFromState(ilState); + return ActiveILCodeVersionHandleFromState(ilState); } [Flags] @@ -235,10 +302,10 @@ private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion } } - private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) + private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle ilcodeVersion, TargetPointer methodDescAddress) { - TargetNUInt? ilVersionId = default; - if (methodDefActiveVersion.Module != TargetPointer.Null) + TargetNUInt ilVersionId = GetId(ilcodeVersion); + if (!IsExplicit(ilcodeVersion)) { NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); if (IsActiveNativeCodeVersion(provisionalHandle)) @@ -249,8 +316,8 @@ private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle else { // Get the explicit IL code version - Debug.Assert(methodDefActiveVersion.ILCodeVersionNode != TargetPointer.Null); - Data.ILCodeVersionNode ilCodeVersion = _target.ProcessedData.GetOrAdd(methodDefActiveVersion.ILCodeVersionNode); + Debug.Assert(ilcodeVersion.ILCodeVersionNode != TargetPointer.Null); + Data.ILCodeVersionNode ilCodeVersion = _target.ProcessedData.GetOrAdd(ilcodeVersion.ILCodeVersionNode); ilVersionId = ilCodeVersion.VersionId; } @@ -259,9 +326,49 @@ private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); return FindFirstCodeVersion(rts, md, (codeVersion) => { - return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value) + return (ilVersionId == codeVersion.ILVersionId) && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); }); } + private static bool IsExplicit(ILCodeVersionHandle handle) + { + return handle.ILCodeVersionNode != TargetPointer.Null; + } + + private static bool IsExplicit(NativeCodeVersionHandle handle) + { + return handle.CodeVersionNodeAddress != TargetPointer.Null; + } + + private ILCodeVersionNode AsNode(ILCodeVersionHandle handle) + { + if (handle.ILCodeVersionNode == TargetPointer.Null) + { + throw new NotImplementedException("Synthetic ILCodeVersion does not have a backing node."); + } + + return _target.ProcessedData.GetOrAdd(handle.ILCodeVersionNode); + } + + private NativeCodeVersionNode AsNode(NativeCodeVersionHandle handle) + { + if (handle.CodeVersionNodeAddress == TargetPointer.Null) + { + throw new NotImplementedException("Synthetic NativeCodeVersion does not have a backing node."); + } + + return _target.ProcessedData.GetOrAdd(handle.CodeVersionNodeAddress); + } + + private TargetNUInt GetId(ILCodeVersionHandle ilCodeVersionHandle) + { + if (!IsExplicit(ilCodeVersionHandle)) + { + // 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 d5bcd98bd4b432..833a3cf38db324 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,56 @@ bool IReJIT.IsEnabled() bool clrConfigEnabledReJIT = true; return profEnabledReJIT || clrConfigEnabledReJIT; } + + RejitState IReJIT.GetRejitState(ILCodeVersionHandle ilCodeVersionHandle) + { + if (ilCodeVersionHandle.ILCodeVersionNode == TargetPointer.Null) + { + // 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 NotImplementedException($"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; + } + + IEnumerable IReJIT.GetRejitIds(TargetPointer methodDesc) + { + ICodeVersions cv = _target.Contracts.CodeVersions; + + IEnumerable ilCodeVersions = cv.GetILCodeVersions(methodDesc); + + foreach (ILCodeVersionHandle ilCodeVersionHandle in ilCodeVersions) + { + if (((IReJIT)this).GetRejitState(ilCodeVersionHandle) == RejitState.Active) + { + yield return ((IReJIT)this).GetRejitId(ilCodeVersionHandle); + } + } + } + + private ILCodeVersionNode AsNode(ILCodeVersionHandle ilCodeVersionHandle) + { + if (ilCodeVersionHandle.ILCodeVersionNode == TargetPointer.Null) + { + throw new NotImplementedException("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 fbc7183c6688e2..fb6fdc32164b4d 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 92a214d910f6d2..a37e93ed9637e8 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 dce6b1bf9d0b2f..b644d045668bc2 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.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 System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Text; @@ -194,13 +196,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) { @@ -257,10 +256,92 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes data->ModulePtr = rtsContract.GetModule(typeHandle); // TODO[cdac]: everything in the ReJIT TRY/CATCH in GetMethodDescDataImpl in request.cpp - if (pcNeededRevertedRejitData != null) + 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); + } - throw new NotImplementedException(); // TODO[cdac]: rejit stuff + // Total number of jitted rejit versions + int cJittedRejitVersions = rejitContract.GetRejitIds(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 + + // Prepare array to populate with rejitids. "+ 1" because GetReJITIDs + // returns all available rejitids, including the rejitid for the one non-reverted + // current version. + List reJitIds = rejitContract.GetRejitIds(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 (uint i = 0; (i < reJitIds.Count) && (iRejitDataReverted < cRevertedRejitVersions); i++) + { + ILCodeVersionHandle ilCodeVersion = nativeCodeContract.GetILCodeVersions(methodDesc) + .FirstOrDefault(ilcode => rejitContract.GetRejitId(ilcode) == reJitIds[(int)i], + ILCodeVersionHandle.Invalid); + + + if (!ilCodeVersion.IsValid || rejitContract.GetRejitId(ilCodeVersion) == activeVersionId) + { + continue; + } + + NativeCodeVersionHandle activeRejitChild = nativeCodeContract.GetActiveNativeCodeVersionForILCodeVersion(methodDesc, ilCodeVersion); + CopyNativeCodeVersionToReJitData( + activeRejitChild, + activeNativeCodeVersion.Value, + &rgRevertedRejitData[iRejitDataReverted]); + + iRejitDataReverted++; + } + // pcNeededRevertedRejitData != NULL as per condition at top of function (cuz rgRevertedRejitData != + // NULL). + *pcNeededRevertedRejitData = iRejitDataReverted; + } + } + catch (global::System.Exception) + { + if (pcNeededRevertedRejitData != null) + { + *pcNeededRevertedRejitData = 0; + } } #if false // TODO[cdac]: HAVE_GCCOVER @@ -297,7 +378,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 +402,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 +434,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 SharedRejitInfo state. 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 a7628695731433..a12f816484baaf 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts; using Xunit; @@ -351,7 +352,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), @@ -405,7 +411,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, @@ -474,8 +485,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, Data.ILCodeVersionNode.RejitFlags.kStateActive); + TargetPointer versioningState = builder.AddILCodeVersioningState( + activeVersionKind: 1 /* Explicit */, + activeVersionNode: ilVersionNode, + activeVersionModule: TargetPointer.Null, + activeVersionMethodDef: 0, + firstVersionNode: ilVersionNode); var module = new MockModule() { Address = moduleAddress, @@ -521,4 +537,137 @@ 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, Data.ILCodeVersionNode.RejitFlags.kStateActive); + 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, TargetPointer activeSyntheticNativeNode) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedSyntheticCodePointer, new TargetNUInt(0)); + (firstNode, TargetPointer activeExplicitNativeNode) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedExplicitCodePointer, ilVersionId, firstNode); + + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode: firstNode, isDefaultVersionActive: false); + + var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: methodDescVersioningStateAddress, nativeCode: expectedExplicitCodePointer); + oneMethod.MethodTable = methodTable; + oneMethod.RowId = methodRowId; + + var target = CVTestTarget.FromBuilder(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 GetILCodeVersionFromNativeCodeVersion_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, Data.ILCodeVersionNode.RejitFlags.kStateActive); + 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, TargetPointer activeSyntheticNativeNode) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedSyntheticCodePointer, new TargetNUInt(0)); + (firstNode, TargetPointer activeExplicitNativeNode) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, 2, 1, expectedExplicitCodePointer, ilVersionId, firstNode); + + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode: firstNode, isDefaultVersionActive: false); + + var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: methodDescVersioningStateAddress, nativeCode: expectedExplicitCodePointer); + oneMethod.MethodTable = methodTable; + oneMethod.RowId = methodRowId; + + var target = CVTestTarget.FromBuilder(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)); + + // 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 a6b833044abd7f..ad224a1c61e389 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), ] }; @@ -122,16 +125,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 explicitILVersion, 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 = explicitILVersion; FillNativeCodeVersionNode(node, methodDesc, nativeCode, next, isActive, ilVersionId); next = node; if (isActive) @@ -141,7 +144,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"); @@ -151,15 +154,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, Data.ILCodeVersionNode.RejitFlags 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; } }