From edcce831b14944ba26466bc01c1a029b323aadfc Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 19 Nov 2024 09:18:04 -0800 Subject: [PATCH 01/13] Handle hot/cold map lookup in ExecutionManager.ReadyToRunJitManager.GetMethodInfo --- .../debug/runtimeinfo/datadescriptor.h | 13 ++ src/coreclr/vm/readytoruninfo.h | 2 + .../Contracts/IExecutionManager.cs | 1 - .../DataType.cs | 1 + ...ecutionManagerBase.ReadyToRunJitManager.cs | 204 ++++++++++++++++-- .../Data/ReadyToRunInfo.cs | 12 +- .../Data/RuntimeFunction.cs | 8 + .../Data/UnwindInfo.cs | 29 +++ .../ExecutionManager/ExecutionManagerTests.cs | 6 +- .../MockDescriptors.ExecutionManager.cs | 41 +++- 10 files changed, 282 insertions(+), 35 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 4568395df3cac..77e9221f05b34 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -418,6 +418,8 @@ CDAC_TYPE_INDETERMINATE(ReadyToRunInfo) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, CompositeInfo, cdac_data::CompositeInfo) CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumRuntimeFunctions, cdac_data::NumRuntimeFunctions) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::RuntimeFunctions) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumHotColdMap, cdac_data::NumHotColdMap) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data::HotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) @@ -431,8 +433,19 @@ CDAC_TYPE_END(ImageDataDirectory) CDAC_TYPE_BEGIN(RuntimeFunction) CDAC_TYPE_SIZE(sizeof(RUNTIME_FUNCTION)) CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, BeginAddress, offsetof(RUNTIME_FUNCTION, BeginAddress)) +#ifdef TARGET_AMD64 +CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, EndAddress, offsetof(RUNTIME_FUNCTION, EndAddress)) +#endif +CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, UnwindData, offsetof(RUNTIME_FUNCTION, UnwindData)) CDAC_TYPE_END(RuntimeFunction) +CDAC_TYPE_BEGIN(UnwindInfo) +CDAC_TYPE_INDETERMINATE(UnwindInfo) +#ifdef TARGET_X86 +CDAC_TYPE_FIELD(UnwindInfo, /*uint32*/, FunctionLength, offsetof(UNWIND_INFO, FunctionLength)) +#endif +CDAC_TYPE_END(UnwindInfo) + CDAC_TYPE_BEGIN(HashMap) CDAC_TYPE_INDETERMINATE(HashMap) CDAC_TYPE_FIELD(HashMap, /*pointer*/, Buckets, cdac_data::Buckets) diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 2777d5fbc98e3..74456a6e82aa1 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -347,6 +347,8 @@ struct cdac_data static constexpr size_t CompositeInfo = offsetof(ReadyToRunInfo, m_pCompositeInfo); static constexpr size_t NumRuntimeFunctions = offsetof(ReadyToRunInfo, m_nRuntimeFunctions); static constexpr size_t RuntimeFunctions = offsetof(ReadyToRunInfo, m_pRuntimeFunctions); + static constexpr size_t NumHotColdMap = offsetof(ReadyToRunInfo, m_nHotColdMap); + static constexpr size_t HotColdMap = offsetof(ReadyToRunInfo, m_pHotColdMap); static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap); }; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index 6e50d85af40fd..994dec03a67ab 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -17,7 +17,6 @@ internal interface IExecutionManager : IContract CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException(); TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); - } internal readonly struct ExecutionManager : IExecutionManager diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 71076275b7fee..98190f5308a8f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -72,4 +72,5 @@ public enum DataType RuntimeFunction, HashMap, Bucket, + UnwindInfo, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index 68bc4c7493314..309f816555ea0 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -43,39 +43,72 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer TargetPointer imageBase = rangeSection.Data.RangeBegin; TargetPointer relativeAddr = addr - imageBase; - int index = GetRuntimeFunctionIndexForAddress(r2rInfo, relativeAddr); - if (index < 0) + uint index; + if (!TryGetRuntimeFunctionIndexForAddress(r2rInfo, relativeAddr, out index)) return false; bool featureEHFunclets = Target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0; if (featureEHFunclets) { - // TODO: [cdac] Look up in hot/cold mapping lookup table and if the method is in the cold block, - // get the index of the associated hot block. - // HotColdMappingLookupTable::LookupMappingForMethod - // - // while GetMethodDescForEntryPoint for the begin address of function at index is null - // index-- + // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. + index = GetHotFunctionIndex(r2rInfo, index); } - TargetPointer functionEntry = r2rInfo.RuntimeFunctions + (ulong)(index * _runtimeFunctionSize); - Data.RuntimeFunction function = Target.ProcessedData.GetOrAdd(functionEntry); - - // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage - TargetCodePointer startAddress = imageBase + function.BeginAddress; - TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target); + TargetPointer methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); + while (featureEHFunclets && methodDesc == TargetPointer.Null) + { + index--; + methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); + } - TargetPointer methodDesc = _lookup.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); Debug.Assert(methodDesc != TargetPointer.Null); + Data.RuntimeFunction function = GetRuntimeFunction(r2rInfo, index); - // TODO: [cdac] Handle method with cold code when computing relative offset - // ReadyToRunJitManager::JitTokenToMethodRegionInfo + TargetCodePointer startAddress = imageBase + function.BeginAddress; TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); + // Take any cold code into account for the relative offset + if (TryGetColdFunctionIndex(r2rInfo, index, out uint hotColdMapIndex, out uint coldFunctionIndex)) + { + // ReadyToRunJitManager::JitTokenToMethodRegionInfo + Data.RuntimeFunction coldFunction = GetRuntimeFunction(r2rInfo, coldFunctionIndex); + TargetPointer coldStart = imageBase + coldFunction.BeginAddress; + if (addr >= coldStart) + { + uint nextColdFunctionIndex = hotColdMapIndex == r2rInfo.NumHotColdMap - 2 + ? r2rInfo.NumRuntimeFunctions - 1 + : Target.Read(r2rInfo.HotColdMap + (hotColdMapIndex + 2) * sizeof(uint)) - 1; + Data.RuntimeFunction nextColdFunction = GetRuntimeFunction(r2rInfo, nextColdFunctionIndex); + uint coldSize = nextColdFunction.BeginAddress + GetFunctionLength(nextColdFunction) - coldFunction.BeginAddress; + if (coldSize > 0) + { + uint hotSize = GetFunctionLength(function); + relativeOffset = new TargetNUInt(hotSize + addr - coldStart); + } + } + } + info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager); return true; } + private uint GetFunctionLength(Data.RuntimeFunction function) + { + if (function.EndAddress.HasValue) + return function.EndAddress.Value - function.BeginAddress; + + Data.UnwindInfo unwindInfo = Target.ProcessedData.GetOrAdd(function.UnwindData); + if (unwindInfo.FunctionLength.HasValue) + return unwindInfo.FunctionLength.Value; + + Debug.Assert(unwindInfo.Header.HasValue); + + // First 18 bits are function length / (pointer size / 2). + // See UnwindFragmentInfo::Finalize + uint funcLengthInHeader = unwindInfo.Header.Value & ((1 << 18) - 1); + return (uint)(funcLengthInHeader * (Target.PointerSize / 2)); + } + private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) { if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null) @@ -87,7 +120,7 @@ private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRu return thunksData.VirtualAddress <= rva && rva < thunksData.VirtualAddress + thunksData.Size; } - private int GetRuntimeFunctionIndexForAddress(Data.ReadyToRunInfo r2rInfo, TargetPointer relativeAddress) + private bool TryGetRuntimeFunctionIndexForAddress(Data.ReadyToRunInfo r2rInfo, TargetPointer relativeAddress, out uint index) { // NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod uint start = 0; @@ -121,10 +154,14 @@ private int GetRuntimeFunctionIndexForAddress(Data.ReadyToRunInfo r2rInfo, Targe Data.RuntimeFunction func = GetRuntimeFunction(r2rInfo, i); if (relativeAddress >= func.BeginAddress) - return (int)i; + { + index = i; + return true; + } } - return -1; + index = ~0u; + return false; } private Data.RuntimeFunction GetRuntimeFunction(Data.ReadyToRunInfo r2rInfo, uint index) @@ -133,5 +170,132 @@ private Data.RuntimeFunction GetRuntimeFunction(Data.ReadyToRunInfo r2rInfo, uin TargetPointer addr = first + (ulong)(index * _runtimeFunctionSize); return Target.ProcessedData.GetOrAdd(addr); } + + private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint runtimeFunctionIndex) + { + Data.RuntimeFunction function = GetRuntimeFunction(r2rInfo, runtimeFunctionIndex); + + // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage + TargetCodePointer startAddress = imageBase + function.BeginAddress; + TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target); + + TargetPointer methodDesc = _lookup.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); + if (methodDesc == (ulong)HashMapLookup.SpecialKeys.InvalidEntry) + return TargetPointer.Null; + + return methodDesc; + } + + private uint GetHotFunctionIndex(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex) + { + int lookupIndex = LookupHotColdMappingForMethod(r2rInfo, runtimeFunctionIndex); + + // If runtime function is in the cold part, get the associated hot part + if (lookupIndex != -1 && (lookupIndex & 1) == 1) + runtimeFunctionIndex = Target.Read(r2rInfo.HotColdMap + (ulong)lookupIndex * sizeof(uint)); + + return runtimeFunctionIndex; + } + + private bool IsColdCode(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex) + { + if (r2rInfo.NumHotColdMap == 0) + return false; + + // Determine if the method index represents a hot or cold part by comparing against the first + // cold part index (hot < cold). + uint firstColdRuntimeFunctionIndex = Target.Read(r2rInfo.HotColdMap); + return runtimeFunctionIndex >= firstColdRuntimeFunctionIndex; + } + + private bool TryGetColdFunctionIndex(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex, out uint lookupIndex, out uint coldFunctionIndex) + { + Debug.Assert(!IsColdCode(r2rInfo, runtimeFunctionIndex)); + + lookupIndex = ~0u; + coldFunctionIndex = ~0u; + int lookupIndexLocal = LookupHotColdMappingForMethod(r2rInfo, runtimeFunctionIndex); + + // Runtime function has no cold part + if (lookupIndexLocal == -1) + return false; + + Debug.Assert((lookupIndexLocal & 1) == 0); + lookupIndex = (uint)lookupIndexLocal; + coldFunctionIndex = Target.Read(r2rInfo.HotColdMap + (ulong)lookupIndexLocal * sizeof(uint)); + return true; + } + + // Look up a runtime function index in the hot/cold map + // If the runtime function index is: + // - cold and in the map, returns the index of the hot part in the hot/cold map + // - hot and in the map, returns the index of the cold part in the hot/cold map + // - not in the map, returns -1 + private int LookupHotColdMappingForMethod(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex) + { + // HotColdMappingLookupTable::LookupMappingForMethod + if (r2rInfo.NumHotColdMap == 0) + return -1; + + // Hot/cold lookup table should contain a subset of indices in the runtime functions + Debug.Assert(r2rInfo.NumHotColdMap <= r2rInfo.NumRuntimeFunctions); + + // Each method is represented by a pair of unsigned 32-bit integers. First is the runtime + // function index of the cold part, second is the runtime function index of the hot part. + // HotColdMap is these pairs as an array, so the logical size is half the array size. + uint start = 0; + uint end = (r2rInfo.NumHotColdMap - 1) / 2; + + bool isColdCode = IsColdCode(r2rInfo, runtimeFunctionIndex); + int indexCorrection = isColdCode ? 0 : 1; + + // Entries are sorted by the hot part runtime function indices. This also means they are sorted + // by the cold part indices, as the cold part is emitted in the same order as hot parts. + // Binary search until we get to 10 or fewer items. + while (end - start > 10) + { + uint middle = start + (end - start) / 2; + long index = middle * 2 + indexCorrection; + + if (runtimeFunctionIndex < Target.Read(r2rInfo.HotColdMap + (ulong)(index * sizeof(uint)))) + { + end = middle - 1; + } + else + { + start = middle; + } + } + + // Find the hot/cold map index corresponding to the cold/hot runtime function index + for (uint i = start; i <= end; ++i) + { + uint index = i * 2; + + uint value = Target.Read(r2rInfo.HotColdMap + (ulong)(index + indexCorrection) * sizeof(uint)); + if (value == runtimeFunctionIndex) + { + return isColdCode + ? (int)index + 1 + : (int)index; + } + else if (isColdCode && runtimeFunctionIndex > Target.Read(r2rInfo.HotColdMap + (ulong)index * sizeof(uint))) + { + // If function index is a cold funclet from a cold block, the above check for equality will fail. + // To get its corresponding hot block, find the cold block containing the funclet, + // then use the lookup table. + // The cold funclet's function index will be greater than its cold block's function index, + // but less than the next cold block's function index in the lookup table. + bool isFuncletIndex = index + 2 == r2rInfo.NumHotColdMap + || runtimeFunctionIndex < Target.Read(r2rInfo.HotColdMap + (ulong)(index + 2) * sizeof(uint)); + if (isFuncletIndex) + { + return (int)index + 1; + } + } + } + + return -1; + } } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index e64bf88f26711..ded82a1255db3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -18,7 +18,14 @@ public ReadyToRunInfo(Target target, TargetPointer address) CompositeInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(CompositeInfo)].Offset); NumRuntimeFunctions = target.Read(address + (ulong)type.Fields[nameof(NumRuntimeFunctions)].Offset); - RuntimeFunctions = target.ReadPointer(address + (ulong)type.Fields[nameof(RuntimeFunctions)].Offset); + RuntimeFunctions = NumRuntimeFunctions > 0 + ? target.ReadPointer(address + (ulong)type.Fields[nameof(RuntimeFunctions)].Offset) + : TargetPointer.Null; + + NumHotColdMap = target.Read(address + (ulong)type.Fields[nameof(NumHotColdMap)].Offset); + HotColdMap = NumHotColdMap > 0 + ? target.ReadPointer(address + (ulong)type.Fields[nameof(HotColdMap)].Offset) + : TargetPointer.Null; DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].Offset); @@ -31,6 +38,9 @@ public ReadyToRunInfo(Target target, TargetPointer address) public uint NumRuntimeFunctions { get; } public TargetPointer RuntimeFunctions { get; } + public uint NumHotColdMap { get; } + public TargetPointer HotColdMap { get; } + public TargetPointer DelayLoadMethodCallThunks { get; } public TargetPointer EntryPointToMethodDescMap { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs index a95cc6e7647fe..f87da0de3f07f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs @@ -13,7 +13,15 @@ public RuntimeFunction(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.RuntimeFunction); BeginAddress = target.Read(address + (ulong)type.Fields[nameof(BeginAddress)].Offset); + + // Not all platforms define EndAddress + if (type.Fields.ContainsKey(nameof(EndAddress))) + EndAddress = target.Read(address + (ulong)type.Fields[nameof(EndAddress)].Offset); + + UnwindData = target.Read(address + (ulong)type.Fields[nameof(UnwindData)].Offset); } public uint BeginAddress { get; } + public uint? EndAddress { get; } + public uint UnwindData { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs new file mode 100644 index 0000000000000..add5603fdfd51 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class UnwindInfo : IData +{ + static UnwindInfo IData.Create(Target target, TargetPointer address) + => new UnwindInfo(target, address); + + public UnwindInfo(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.UnwindInfo); + + if (type.Fields.ContainsKey(nameof(FunctionLength))) + { + // The unwind info contains the function length on some platforms (x86) + FunctionLength = target.Read(address + (ulong)type.Fields[nameof(FunctionLength)].Offset); + } + else + { + // Otherwise, it starts with a bitfield header + Header = target.Read(address); + } + } + + public uint? FunctionLength { get; } + public uint? Header { get; } +} diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs index d4ba7eafdc483..4c23b99b022b3 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs @@ -147,7 +147,7 @@ public void GetCodeBlockHandle_R2R_NoRuntimeFunctionMatch(int version, MockTarge uint runtimeFunction = 0x100; - TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([runtimeFunction]); + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([runtimeFunction], []); MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); hashMapBuilder.PopulatePtrMap( r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, @@ -182,7 +182,7 @@ public void GetMethodDesc_R2R_OneRuntimeFunction(int version, MockTarget.Archite uint expectedRuntimeFunction = 0x100; - TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([expectedRuntimeFunction]); + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([expectedRuntimeFunction], []); MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); hashMapBuilder.PopulatePtrMap( r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, @@ -228,7 +228,7 @@ public void GetMethodDesc_R2R_MultipleRuntimeFunctions(int version, MockTarget.A uint[] runtimeFunctions = [ 0x100, 0xc00 ]; - TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo(runtimeFunctions); + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo(runtimeFunctions, []); MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); hashMapBuilder.PopulatePtrMap( r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index 98ca626103a04..c005fb578aa52 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -27,8 +27,8 @@ public struct AllocationRange // nibble maps for various range section fragments are allocated in this range public ulong NibbleMapStart; public ulong NibbleMapEnd; - // "RealCodeHeader" objects for jitted methods and the module, info, and runtime functions for R2R - // are allocated in this range + // "RealCodeHeader" objects for jitted methods and the module, info, runtime functions + // and hot/cold map for R2R are allocated in this range public ulong ExecutionManagerStart; public ulong ExecutionManagerEnd; } @@ -241,6 +241,7 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect Fields = [ new(nameof(Data.RuntimeFunction.BeginAddress), DataType.uint32), + new(nameof(Data.RuntimeFunction.UnwindData), DataType.uint32), ] }; @@ -252,6 +253,8 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect new(nameof(Data.ReadyToRunInfo.CompositeInfo), DataType.pointer), new(nameof(Data.ReadyToRunInfo.NumRuntimeFunctions), DataType.uint32), new(nameof(Data.ReadyToRunInfo.RuntimeFunctions), DataType.pointer), + new(nameof(Data.ReadyToRunInfo.NumHotColdMap), DataType.uint32), + new(nameof(Data.ReadyToRunInfo.HotColdMap), DataType.pointer), new(nameof(Data.ReadyToRunInfo.DelayLoadMethodCallThunks), DataType.pointer), new(nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap), DataType.Unknown, helpers.LayoutFields(MockDescriptors.HashMap.HashMapFields.Fields).Stride), ] @@ -285,13 +288,13 @@ internal ExecutionManager(int version, MockMemorySpace.Builder builder, Allocati Builder.TargetTestHelpers, [ RangeSectionMapFields, - RangeSectionFragmentFields, - RangeSectionFields, - CodeHeapListNodeFields, - RealCodeHeaderFields, - RuntimeFunctionFields, - ReadyToRunInfoFields(Builder.TargetTestHelpers), - MockDescriptors.ModuleFields, + RangeSectionFragmentFields, + RangeSectionFields, + CodeHeapListNodeFields, + RealCodeHeaderFields, + RuntimeFunctionFields, + ReadyToRunInfoFields(Builder.TargetTestHelpers), + MockDescriptors.ModuleFields, ]).Concat(MockDescriptors.HashMap.GetTypes(Builder.TargetTestHelpers)) .ToDictionary(); @@ -436,7 +439,7 @@ public TargetCodePointer AddJittedMethod(JittedCodeRange jittedCodeRange, uint c return codeStart; } - public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions) + public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions, uint[] hotColdMap) { TargetTestHelpers helpers = Builder.TargetTestHelpers; @@ -456,6 +459,20 @@ public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions) Span sentinel = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + numRuntimeFunctions * runtimeFunctionSize, (int)runtimeFunctionSize); helpers.Write(sentinel.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), ~0u); + // Add the hot/cold map + TargetPointer hotColdMapAddr = TargetPointer.Null; + if (hotColdMap.Length > 0) + { + MockMemorySpace.HeapFragment hotColdMapFragment = _allocator.Allocate((ulong)hotColdMap.Length * sizeof(uint), $"HotColdMap[{hotColdMap.Length}]"); + Builder.AddHeapFragment(hotColdMapFragment); + hotColdMapAddr = hotColdMapFragment.Address; + for (uint i = 0; i < hotColdMap.Length; i++) + { + Span span = Builder.BorrowAddressRange(hotColdMapFragment.Address + i * sizeof(uint), sizeof(uint)); + helpers.Write(span, hotColdMap[i]); + } + } + // Add ReadyToRunInfo Target.TypeInfo r2rInfoType = Types[DataType.ReadyToRunInfo]; MockMemorySpace.HeapFragment r2rInfo = _allocator.Allocate(r2rInfoType.Size.Value, "ReadyToRunInfo"); @@ -469,6 +486,10 @@ public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions) helpers.Write(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.NumRuntimeFunctions)].Offset, sizeof(uint)), numRuntimeFunctions); helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.RuntimeFunctions)].Offset, helpers.PointerSize), runtimeFunctionsFragment.Address); + // Point at the hot/cold map + helpers.Write(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.NumHotColdMap)].Offset, sizeof(uint)), hotColdMap.Length); + helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.HotColdMap)].Offset, helpers.PointerSize), hotColdMapAddr); + return r2rInfo.Address; } From 3574da49fc138be86c7b5cd359bf3b9d09299a98 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 19 Nov 2024 10:05:13 -0800 Subject: [PATCH 02/13] Move hot/cold lookup into a separate helper class --- ...ecutionManagerBase.ReadyToRunJitManager.cs | 118 +--------------- .../ExecutionManager/Helpers/HotColdLookup.cs | 128 ++++++++++++++++++ 2 files changed, 132 insertions(+), 114 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index 309f816555ea0..56b797abbc947 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -14,11 +14,13 @@ private class ReadyToRunJitManager : JitManager { private readonly uint _runtimeFunctionSize; private readonly PtrHashMapLookup _lookup; + private readonly HotColdLookup _hotColdLookup; public ReadyToRunJitManager(Target target) : base(target) { _runtimeFunctionSize = Target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value; _lookup = PtrHashMapLookup.Create(target); + _hotColdLookup = HotColdLookup.Create(target); } public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) @@ -51,7 +53,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer if (featureEHFunclets) { // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. - index = GetHotFunctionIndex(r2rInfo, index); + index = _hotColdLookup.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); } TargetPointer methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); @@ -68,7 +70,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); // Take any cold code into account for the relative offset - if (TryGetColdFunctionIndex(r2rInfo, index, out uint hotColdMapIndex, out uint coldFunctionIndex)) + if (_hotColdLookup.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint hotColdMapIndex, out uint coldFunctionIndex)) { // ReadyToRunJitManager::JitTokenToMethodRegionInfo Data.RuntimeFunction coldFunction = GetRuntimeFunction(r2rInfo, coldFunctionIndex); @@ -185,117 +187,5 @@ private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInf return methodDesc; } - - private uint GetHotFunctionIndex(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex) - { - int lookupIndex = LookupHotColdMappingForMethod(r2rInfo, runtimeFunctionIndex); - - // If runtime function is in the cold part, get the associated hot part - if (lookupIndex != -1 && (lookupIndex & 1) == 1) - runtimeFunctionIndex = Target.Read(r2rInfo.HotColdMap + (ulong)lookupIndex * sizeof(uint)); - - return runtimeFunctionIndex; - } - - private bool IsColdCode(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex) - { - if (r2rInfo.NumHotColdMap == 0) - return false; - - // Determine if the method index represents a hot or cold part by comparing against the first - // cold part index (hot < cold). - uint firstColdRuntimeFunctionIndex = Target.Read(r2rInfo.HotColdMap); - return runtimeFunctionIndex >= firstColdRuntimeFunctionIndex; - } - - private bool TryGetColdFunctionIndex(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex, out uint lookupIndex, out uint coldFunctionIndex) - { - Debug.Assert(!IsColdCode(r2rInfo, runtimeFunctionIndex)); - - lookupIndex = ~0u; - coldFunctionIndex = ~0u; - int lookupIndexLocal = LookupHotColdMappingForMethod(r2rInfo, runtimeFunctionIndex); - - // Runtime function has no cold part - if (lookupIndexLocal == -1) - return false; - - Debug.Assert((lookupIndexLocal & 1) == 0); - lookupIndex = (uint)lookupIndexLocal; - coldFunctionIndex = Target.Read(r2rInfo.HotColdMap + (ulong)lookupIndexLocal * sizeof(uint)); - return true; - } - - // Look up a runtime function index in the hot/cold map - // If the runtime function index is: - // - cold and in the map, returns the index of the hot part in the hot/cold map - // - hot and in the map, returns the index of the cold part in the hot/cold map - // - not in the map, returns -1 - private int LookupHotColdMappingForMethod(Data.ReadyToRunInfo r2rInfo, uint runtimeFunctionIndex) - { - // HotColdMappingLookupTable::LookupMappingForMethod - if (r2rInfo.NumHotColdMap == 0) - return -1; - - // Hot/cold lookup table should contain a subset of indices in the runtime functions - Debug.Assert(r2rInfo.NumHotColdMap <= r2rInfo.NumRuntimeFunctions); - - // Each method is represented by a pair of unsigned 32-bit integers. First is the runtime - // function index of the cold part, second is the runtime function index of the hot part. - // HotColdMap is these pairs as an array, so the logical size is half the array size. - uint start = 0; - uint end = (r2rInfo.NumHotColdMap - 1) / 2; - - bool isColdCode = IsColdCode(r2rInfo, runtimeFunctionIndex); - int indexCorrection = isColdCode ? 0 : 1; - - // Entries are sorted by the hot part runtime function indices. This also means they are sorted - // by the cold part indices, as the cold part is emitted in the same order as hot parts. - // Binary search until we get to 10 or fewer items. - while (end - start > 10) - { - uint middle = start + (end - start) / 2; - long index = middle * 2 + indexCorrection; - - if (runtimeFunctionIndex < Target.Read(r2rInfo.HotColdMap + (ulong)(index * sizeof(uint)))) - { - end = middle - 1; - } - else - { - start = middle; - } - } - - // Find the hot/cold map index corresponding to the cold/hot runtime function index - for (uint i = start; i <= end; ++i) - { - uint index = i * 2; - - uint value = Target.Read(r2rInfo.HotColdMap + (ulong)(index + indexCorrection) * sizeof(uint)); - if (value == runtimeFunctionIndex) - { - return isColdCode - ? (int)index + 1 - : (int)index; - } - else if (isColdCode && runtimeFunctionIndex > Target.Read(r2rInfo.HotColdMap + (ulong)index * sizeof(uint))) - { - // If function index is a cold funclet from a cold block, the above check for equality will fail. - // To get its corresponding hot block, find the cold block containing the funclet, - // then use the lookup table. - // The cold funclet's function index will be greater than its cold block's function index, - // but less than the next cold block's function index in the lookup table. - bool isFuncletIndex = index + 2 == r2rInfo.NumHotColdMap - || runtimeFunctionIndex < Target.Read(r2rInfo.HotColdMap + (ulong)(index + 2) * sizeof(uint)); - if (isFuncletIndex) - { - return (int)index + 1; - } - } - } - - return -1; - } } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs new file mode 100644 index 0000000000000..0ac3db4718b46 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +internal sealed class HotColdLookup +{ + public static HotColdLookup Create(Target target) + => new HotColdLookup(target); + + private readonly Target _target; + + private HotColdLookup(Target target) + { + _target = target; + } + + public uint GetHotFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) + { + int lookupIndex = LookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex); + + // If runtime function is in the cold part, get the associated hot part + if (lookupIndex != -1 && (lookupIndex & 1) == 1) + runtimeFunctionIndex = _target.Read(hotColdMap + (ulong)lookupIndex * sizeof(uint)); + + return runtimeFunctionIndex; + } + + private bool IsColdCode(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) + { + if (numHotColdMap == 0) + return false; + + // Determine if the method index represents a hot or cold part by comparing against the first + // cold part index (hot < cold). + uint firstColdRuntimeFunctionIndex = _target.Read(hotColdMap); + return runtimeFunctionIndex >= firstColdRuntimeFunctionIndex; + } + + public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex, out uint lookupIndex, out uint coldFunctionIndex) + { + Debug.Assert(!IsColdCode(numHotColdMap, hotColdMap, runtimeFunctionIndex)); + + lookupIndex = ~0u; + coldFunctionIndex = ~0u; + int lookupIndexLocal = LookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex); + + // Runtime function has no cold part + if (lookupIndexLocal == -1) + return false; + + Debug.Assert((lookupIndexLocal & 1) == 0); + lookupIndex = (uint)lookupIndexLocal; + coldFunctionIndex = _target.Read(hotColdMap + (ulong)lookupIndexLocal * sizeof(uint)); + return true; + } + + // Look up a runtime function index in the hot/cold map + // If the runtime function index is: + // - cold and in the map, returns the index of the hot part in the hot/cold map + // - hot and in the map, returns the index of the cold part in the hot/cold map + // - not in the map, returns -1 + private int LookupHotColdMappingForMethod(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) + { + // HotColdMappingLookupTable::LookupMappingForMethod + if (numHotColdMap == 0) + return -1; + + // Each method is represented by a pair of unsigned 32-bit integers. First is the runtime + // function index of the cold part, second is the runtime function index of the hot part. + // HotColdMap is these pairs as an array, so the logical size is half the array size. + uint start = 0; + uint end = (numHotColdMap - 1) / 2; + + bool isColdCode = IsColdCode(numHotColdMap, hotColdMap, runtimeFunctionIndex); + int indexCorrection = isColdCode ? 0 : 1; + + // Entries are sorted by the hot part runtime function indices. This also means they are sorted + // by the cold part indices, as the cold part is emitted in the same order as hot parts. + // Binary search until we get to 10 or fewer items. + while (end - start > 10) + { + uint middle = start + (end - start) / 2; + long index = middle * 2 + indexCorrection; + + if (runtimeFunctionIndex < _target.Read(hotColdMap + (ulong)(index * sizeof(uint)))) + { + end = middle - 1; + } + else + { + start = middle; + } + } + + // Find the hot/cold map index corresponding to the cold/hot runtime function index + for (uint i = start; i <= end; ++i) + { + uint index = i * 2; + + uint value = _target.Read(hotColdMap + (ulong)(index + indexCorrection) * sizeof(uint)); + if (value == runtimeFunctionIndex) + { + return isColdCode + ? (int)index + 1 + : (int)index; + } + else if (isColdCode && runtimeFunctionIndex > _target.Read(hotColdMap + (ulong)index * sizeof(uint))) + { + // If function index is a cold funclet from a cold block, the above check for equality will fail. + // To get its corresponding hot block, find the cold block containing the funclet, + // then use the lookup table. + // The cold funclet's function index will be greater than its cold block's function index, + // but less than the next cold block's function index in the lookup table. + bool isFuncletIndex = index + 2 == numHotColdMap + || runtimeFunctionIndex < _target.Read(hotColdMap + (ulong)(index + 2) * sizeof(uint)); + if (isFuncletIndex) + { + return (int)index + 1; + } + } + } + + return -1; + } +} From 0aeebc3b2028a133f3e8385e0655940bb717463c Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 19 Nov 2024 15:20:01 -0800 Subject: [PATCH 03/13] Make HotColdLookup helper return both hot and cold indexes --- .../ExecutionManager/Helpers/HotColdLookup.cs | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs index 0ac3db4718b46..06db23d0eee62 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -19,13 +19,30 @@ private HotColdLookup(Target target) public uint GetHotFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) { - int lookupIndex = LookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex); + if (!IsColdCode(numHotColdMap, hotColdMap, runtimeFunctionIndex)) + return runtimeFunctionIndex; + + uint hotIndex; + if (!TryLookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex, out hotIndex, out _)) + return runtimeFunctionIndex; // If runtime function is in the cold part, get the associated hot part - if (lookupIndex != -1 && (lookupIndex & 1) == 1) - runtimeFunctionIndex = _target.Read(hotColdMap + (ulong)lookupIndex * sizeof(uint)); + Debug.Assert((hotIndex & 1) == 1); + return _target.Read(hotColdMap + (ulong)hotIndex * sizeof(uint)); + } + + public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex, out uint lookupIndex, out uint functionIndex) + { + lookupIndex = ~0u; + functionIndex = ~0u; - return runtimeFunctionIndex; + uint coldIndex; + if (!TryLookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex, out uint _, out coldIndex)) + return false; + + lookupIndex = coldIndex; + functionIndex = _target.Read(hotColdMap + (ulong)lookupIndex * sizeof(uint)); + return true; } private bool IsColdCode(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) @@ -39,34 +56,22 @@ private bool IsColdCode(uint numHotColdMap, TargetPointer hotColdMap, uint runti return runtimeFunctionIndex >= firstColdRuntimeFunctionIndex; } - public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex, out uint lookupIndex, out uint coldFunctionIndex) + // Look up a runtime function index in the hot/cold map. If the function is in the + // hot/cold map, return whether the function corresponds to cold code and the hot + // and cold lookup indexes for the function. + private bool TryLookupHotColdMappingForMethod( + uint numHotColdMap, + TargetPointer hotColdMap, + uint runtimeFunctionIndex, + out uint hotIndex, + out uint coldIndex) { - Debug.Assert(!IsColdCode(numHotColdMap, hotColdMap, runtimeFunctionIndex)); - - lookupIndex = ~0u; - coldFunctionIndex = ~0u; - int lookupIndexLocal = LookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex); - - // Runtime function has no cold part - if (lookupIndexLocal == -1) - return false; + hotIndex = ~0u; + coldIndex = ~0u; - Debug.Assert((lookupIndexLocal & 1) == 0); - lookupIndex = (uint)lookupIndexLocal; - coldFunctionIndex = _target.Read(hotColdMap + (ulong)lookupIndexLocal * sizeof(uint)); - return true; - } - - // Look up a runtime function index in the hot/cold map - // If the runtime function index is: - // - cold and in the map, returns the index of the hot part in the hot/cold map - // - hot and in the map, returns the index of the cold part in the hot/cold map - // - not in the map, returns -1 - private int LookupHotColdMappingForMethod(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) - { // HotColdMappingLookupTable::LookupMappingForMethod if (numHotColdMap == 0) - return -1; + return false; // Each method is represented by a pair of unsigned 32-bit integers. First is the runtime // function index of the cold part, second is the runtime function index of the hot part. @@ -103,26 +108,29 @@ private int LookupHotColdMappingForMethod(uint numHotColdMap, TargetPointer hotC uint value = _target.Read(hotColdMap + (ulong)(index + indexCorrection) * sizeof(uint)); if (value == runtimeFunctionIndex) { - return isColdCode - ? (int)index + 1 - : (int)index; + hotIndex = index + 1; + coldIndex = index; + return true; } - else if (isColdCode && runtimeFunctionIndex > _target.Read(hotColdMap + (ulong)index * sizeof(uint))) + + // If function index is a cold funclet from a cold block, the above check for equality will fail. + // To get its corresponding hot block, find the cold block containing the funclet, + // then use the lookup table. + // The cold funclet's function index will be greater than its cold block's function index, + // but less than the next cold block's function index in the lookup table. + if (isColdCode && runtimeFunctionIndex > _target.Read(hotColdMap + (ulong)index * sizeof(uint))) { - // If function index is a cold funclet from a cold block, the above check for equality will fail. - // To get its corresponding hot block, find the cold block containing the funclet, - // then use the lookup table. - // The cold funclet's function index will be greater than its cold block's function index, - // but less than the next cold block's function index in the lookup table. bool isFuncletIndex = index + 2 == numHotColdMap || runtimeFunctionIndex < _target.Read(hotColdMap + (ulong)(index + 2) * sizeof(uint)); if (isFuncletIndex) { - return (int)index + 1; + hotIndex = index + 1; + coldIndex = index; + return true; } } } - return -1; + return false; } } From ce91651a56ef3797dfe4aa6f6179403040f6397b Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 19 Nov 2024 15:23:42 -0800 Subject: [PATCH 04/13] Add tests for HotColdLookup --- .../ExecutionManager/HotColdLookupTests.cs | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs new file mode 100644 index 0000000000000..1c920c017b5d3 --- /dev/null +++ b/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Moq; +using Xunit; + +using System; +using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; + +public class HotColdLookupTests +{ + private static readonly TargetPointer HotColdMapAddr = 0x1000; // arbitrary + + private static Target CreateMockTarget((uint Cold, uint Hot)[] entries) + { + Mock target = new(); + target.Setup(t => t.Read(It.IsAny())) + .Returns((ulong addr) => + { + for (uint i = 0; i < entries.Length; i++) + { + if (addr == HotColdMapAddr + (i * 2) * sizeof(uint)) + return entries[i].Cold; + + if (addr == HotColdMapAddr + (i * 2 + 1) * sizeof(uint)) + return entries[i].Hot; + } + + throw new NotImplementedException(); + }); + + return target.Object; + } + + [Fact] + public void GetHotFunctionIndex() + { + (uint Cold, uint Hot)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + (0x300, 0x30), + (0x400, 0x40), + ]; + + uint numHotColdMap = (uint)entries.Length * 2; + Target target = CreateMockTarget(entries); + + var lookup = HotColdLookup.Create(target); + foreach (var entry in entries) + { + // Hot part as input + uint hotIndex = lookup.GetHotFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Hot); + Assert.Equal(entry.Hot, hotIndex); + + // Cold part as input + hotIndex = lookup.GetHotFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Cold); + Assert.Equal(entry.Hot, hotIndex); + } + } + + [Fact] + public void GetHotFunctionIndex_ColdFunclet() + { + (uint Cold, uint Hot)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + ]; + + uint numHotColdMap = (uint)entries.Length * 2; + Target target = CreateMockTarget(entries); + + var lookup = HotColdLookup.Create(target); + + // Cold funclet - between two cold blocks' indexes + uint functionIndex = 0x110; + uint hotIndex = lookup.GetHotFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex); + Assert.Equal(entries[0].Hot, hotIndex); + } + + [Fact] + public void GetHotFunctionIndex_EmptyMap() + { + Target target = CreateMockTarget([]); + + var lookup = HotColdLookup.Create(target); + + // Function must be hot if there is no map + uint functionIndex = 0x110; + uint hotIndex = lookup.GetHotFunctionIndex(0, HotColdMapAddr, functionIndex); + Assert.Equal(functionIndex, hotIndex); + } + + [Fact] + public void GetHotFunctionIndex_NoEntryInMap() + { + (uint Cold, uint Hot)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + ]; + + uint numHotColdMap = (uint)entries.Length * 2; + Target target = CreateMockTarget(entries); + + var lookup = HotColdLookup.Create(target); + + // Function must be hot if it is not in the map + uint functionIndex = 0x30; + uint hotIndex = lookup.GetHotFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex); + Assert.Equal(functionIndex, hotIndex); + } + + [Fact] + public void TryGetColdFunctionIndex() + { + (uint Cold, uint Hot)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + (0x300, 0x30), + (0x400, 0x40), + ]; + + uint numHotColdMap = (uint)entries.Length * 2; + Target target = CreateMockTarget(entries); + + var lookup = HotColdLookup.Create(target); + for (uint i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + + // Hot part as input + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Hot, out uint lookupIndex, out uint coldFunctionIndex); + Assert.True(res); + Assert.Equal(i * 2, lookupIndex); + Assert.Equal(entry.Cold, coldFunctionIndex); + + // Cold part as input + res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Cold, out lookupIndex, out coldFunctionIndex); + Assert.True(res); + Assert.Equal(i * 2, lookupIndex); + Assert.Equal(entry.Cold, coldFunctionIndex); + } + } + + [Fact] + public void TryGetColdFunctionIndex_ColdFunclet() + { + (uint Cold, uint Hot)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + ]; + + uint numHotColdMap = (uint)entries.Length * 2; + Target target = CreateMockTarget(entries); + + var lookup = HotColdLookup.Create(target); + + // Cold funclet - between two cold blocks' indexes + uint functionIndex = 0x110; + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex, out uint lookupIndex, out uint coldFunctionIndex); + Assert.True(res); + Assert.Equal(0u, lookupIndex); + Assert.Equal(entries[0].Cold, coldFunctionIndex); + } + + [Fact] + public void TryGetColdFunctionIndex_EmptyMap() + { + Target target = CreateMockTarget([]); + + var lookup = HotColdLookup.Create(target); + + // Function has no cold part if map is empty + uint functionIndex = 0x110; + bool res = lookup.TryGetColdFunctionIndex(0, HotColdMapAddr, functionIndex, out _, out _); + Assert.False(res); + } + + [Fact] + public void TryGetColdFunctionIndex_NoEntryInMap() + { + (uint Cold, uint Hot)[] entries = + [ + (0x100, 0x10), + (0x200, 0x20), + ]; + + uint numHotColdMap = (uint)entries.Length * 2; + Target target = CreateMockTarget(entries); + + var lookup = HotColdLookup.Create(target); + + // Function has no cold part if it is not in the map + uint functionIndex = 0x30; + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex, out _, out _); + Assert.False(res); + } +} From 8aa70bf785942f6e5d609dd3309feacd54dc9ead Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 20 Nov 2024 15:09:00 -0800 Subject: [PATCH 05/13] Split out helper for RuntimeFunctions mock memory --- .../MockDescriptors.ExecutionManager.cs | 30 +---- .../MockDescriptors.RuntimeFunctions.cs | 113 ++++++++++++++++++ 2 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index c005fb578aa52..e061b65972a48 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -235,16 +235,6 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect ] }; - private static readonly MockDescriptors.TypeFields RuntimeFunctionFields = new() - { - DataType = DataType.RuntimeFunction, - Fields = - [ - new(nameof(Data.RuntimeFunction.BeginAddress), DataType.uint32), - new(nameof(Data.RuntimeFunction.UnwindData), DataType.uint32), - ] - }; - private static MockDescriptors.TypeFields ReadyToRunInfoFields(TargetTestHelpers helpers) => new() { DataType = DataType.ReadyToRunInfo, @@ -267,6 +257,7 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect internal (string Name, ulong Value)[] Globals { get; } private readonly RangeSectionMapTestBuilder _rsmBuilder; + private readonly RuntimeFunctions _rfBuilder; private readonly MockMemorySpace.BumpAllocator _rangeSectionMapAllocator; private readonly MockMemorySpace.BumpAllocator _nibbleMapAllocator; @@ -281,6 +272,7 @@ internal ExecutionManager(int version, MockMemorySpace.Builder builder, Allocati Version = version; Builder = builder; _rsmBuilder = new RangeSectionMapTestBuilder(ExecutionManagerCodeRangeMapAddress, builder); + _rfBuilder = new RuntimeFunctions(builder); _rangeSectionMapAllocator = Builder.CreateAllocator(allocationRange.RangeSectionMapStart, allocationRange.RangeSectionMapEnd); _nibbleMapAllocator = Builder.CreateAllocator(allocationRange.NibbleMapStart, allocationRange.NibbleMapEnd); _allocator = Builder.CreateAllocator(allocationRange.ExecutionManagerStart, allocationRange.ExecutionManagerEnd); @@ -292,10 +284,10 @@ internal ExecutionManager(int version, MockMemorySpace.Builder builder, Allocati RangeSectionFields, CodeHeapListNodeFields, RealCodeHeaderFields, - RuntimeFunctionFields, ReadyToRunInfoFields(Builder.TargetTestHelpers), MockDescriptors.ModuleFields, ]).Concat(MockDescriptors.HashMap.GetTypes(Builder.TargetTestHelpers)) + .Concat(_rfBuilder.Types) .ToDictionary(); // Tests are currently always set to use funclets @@ -445,19 +437,7 @@ public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions, uint[] hotColdMa // Add the array of runtime functions uint numRuntimeFunctions = (uint)runtimeFunctions.Length; - Target.TypeInfo runtimeFunctionType = Types[DataType.RuntimeFunction]; - uint runtimeFunctionSize = runtimeFunctionType.Size.Value; - MockMemorySpace.HeapFragment runtimeFunctionsFragment = _allocator.Allocate((numRuntimeFunctions + 1) * runtimeFunctionSize, $"RuntimeFunctions[{numRuntimeFunctions}]"); - Builder.AddHeapFragment(runtimeFunctionsFragment); - for (uint i = 0; i < numRuntimeFunctions; i++) - { - Span func = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + i * runtimeFunctionSize, (int)runtimeFunctionSize); - helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), runtimeFunctions[i]); - } - - // Runtime function entries are terminated by a sentinel value of -1 - Span sentinel = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + numRuntimeFunctions * runtimeFunctionSize, (int)runtimeFunctionSize); - helpers.Write(sentinel.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), ~0u); + TargetPointer runtimeFunctionsAddr = _rfBuilder.AddRuntimeFunctions(runtimeFunctions); // Add the hot/cold map TargetPointer hotColdMapAddr = TargetPointer.Null; @@ -484,7 +464,7 @@ public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions, uint[] hotColdMa // Point at the runtime functions helpers.Write(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.NumRuntimeFunctions)].Offset, sizeof(uint)), numRuntimeFunctions); - helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.RuntimeFunctions)].Offset, helpers.PointerSize), runtimeFunctionsFragment.Address); + helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.RuntimeFunctions)].Offset, helpers.PointerSize), runtimeFunctionsAddr); // Point at the hot/cold map helpers.Write(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.NumHotColdMap)].Offset, sizeof(uint)), hotColdMap.Length); diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs new file mode 100644 index 0000000000000..5073fb6696818 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs @@ -0,0 +1,113 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +internal partial class MockDescriptors +{ + internal class RuntimeFunctions + { + private static TypeFields RuntimeFunctionFields(bool includeEndAddress) + { + TargetTestHelpers.Field[] fields = [ + new(nameof(Data.RuntimeFunction.BeginAddress), DataType.uint32), + new(nameof(Data.RuntimeFunction.UnwindData), DataType.uint32), + ]; + if (includeEndAddress) + fields = fields.Append(new(nameof(Data.RuntimeFunction.EndAddress), DataType.uint32)).ToArray(); + + return new() + { + DataType = DataType.RuntimeFunction, + Fields = fields + }; + } + + private static TypeFields UnwindInfoFields(bool isFunctionLength) => new() + { + DataType = DataType.UnwindInfo, + Fields = isFunctionLength + ? [new(nameof(Data.UnwindInfo.FunctionLength), DataType.uint32)] + : [new(nameof(Data.UnwindInfo.Header), DataType.uint32)] + }; + + internal MockMemorySpace.Builder Builder { get; } + internal Dictionary Types { get; } + + private const ulong DefaultAllocationRangeStart = 0x0004_0000; + private const ulong DefaultAllocationRangeEnd = 0x0005_0000; + + private readonly MockMemorySpace.BumpAllocator _allocator; + + public RuntimeFunctions(MockMemorySpace.Builder builder, bool includeEndAddress = true, bool unwindInfoIsFunctionLength = false) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd), includeEndAddress, unwindInfoIsFunctionLength) + { } + + public RuntimeFunctions(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange, bool includeEndAddress, bool unwindInfoIsFunctionLength) + { + Builder = builder; + _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); + Types = GetTypesForTypeFields( + Builder.TargetTestHelpers, + [ + RuntimeFunctionFields(includeEndAddress), + UnwindInfoFields(unwindInfoIsFunctionLength) + ]); + } + + public TargetPointer AddRuntimeFunctions(uint[] runtimeFunctions) + { + TargetTestHelpers helpers = Builder.TargetTestHelpers; + + // Add the array of runtime functions + uint numRuntimeFunctions = (uint)runtimeFunctions.Length; + Target.TypeInfo runtimeFunctionType = Types[DataType.RuntimeFunction]; + uint runtimeFunctionSize = runtimeFunctionType.Size.Value; + Target.TypeInfo unwindInfoType = Types[DataType.UnwindInfo]; + MockMemorySpace.HeapFragment runtimeFunctionsFragment = _allocator.Allocate((numRuntimeFunctions + 1) * runtimeFunctionSize, $"RuntimeFunctions[{numRuntimeFunctions}]"); + Builder.AddHeapFragment(runtimeFunctionsFragment); + for (uint i = 0; i < numRuntimeFunctions; i++) + { + Span func = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + i * runtimeFunctionSize, (int)runtimeFunctionSize); + helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), runtimeFunctions[i]); + + // Set the function length at halfway to the start of the next function (or 0x100 for the last function) + uint functionLength = i < numRuntimeFunctions - 1 + ? (runtimeFunctions[i + 1] - runtimeFunctions[i]) / 2 + : 0x100; + if (runtimeFunctionType.Fields.ContainsKey(nameof(Data.RuntimeFunction.EndAddress))) + helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.EndAddress)].Offset, sizeof(uint)), runtimeFunctions[i] + functionLength); + + // Add the unwindInfo + MockMemorySpace.HeapFragment unwindInfoFragment = _allocator.Allocate(unwindInfoType.Size.Value, $"UnwindInfo for RuntimeFunction {runtimeFunctions[i]}"); + Builder.AddHeapFragment(unwindInfoFragment); + Span unwindInfo = unwindInfoFragment.Data.AsSpan(); + if (Types[DataType.UnwindInfo].Fields.ContainsKey(nameof(Data.UnwindInfo.FunctionLength))) + { + helpers.Write(unwindInfo.Slice(unwindInfoType.Fields[nameof(Data.UnwindInfo.FunctionLength)].Offset, sizeof(uint)), functionLength); + } + else + { + // First 18 bits of the header are function length / (pointer size / 2) + uint headerBits = (uint)(functionLength / (helpers.PointerSize / 2)); + if (headerBits > 1 << 18 - 1) + throw new InvalidOperationException("Function length is too long "); + + helpers.Write(unwindInfo.Slice(unwindInfoType.Fields[nameof(Data.UnwindInfo.Header)].Offset, sizeof(uint)), headerBits); + } + + helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.UnwindData)].Offset, sizeof(uint)), (uint)unwindInfoFragment.Address); + } + + // Runtime function entries are terminated by a sentinel value of -1 + Span sentinel = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + numRuntimeFunctions * runtimeFunctionSize, (int)runtimeFunctionSize); + helpers.Write(sentinel.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), ~0u); + + return runtimeFunctionsFragment.Address; + } + } +} From eedee376186649a55fd70bbf34a15936fdda5087 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 20 Nov 2024 15:09:40 -0800 Subject: [PATCH 06/13] Add ExecutionManager test for GetMethodDesc with hot/cold map --- .../ExecutionManager/ExecutionManagerTests.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs index 4c23b99b022b3..5687961764cd9 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs @@ -277,6 +277,57 @@ public void GetMethodDesc_R2R_MultipleRuntimeFunctions(int version, MockTarget.A } } + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetMethodDesc_R2R_HotColdBlock(int version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary + const uint codeRangeSize = 0xc000u; // arbitrary + TargetPointer jitManagerAddress = new(0x000b_ff00); // arbitrary + + TargetPointer[] methodDescAddresses = [0x0101_aaa0, 0x0201_aaa0]; + + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + + uint[] runtimeFunctions = [0x100, 0x200, 0x300, 0x400, 0x500]; + uint[] hotColdMap = [3, 0, 4, 1]; + + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo(runtimeFunctions, hotColdMap); + MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, + [ + (jittedCode.RangeStart + runtimeFunctions[hotColdMap[1]], methodDescAddresses[0]), + (jittedCode.RangeStart + runtimeFunctions[hotColdMap[3]], methodDescAddresses[1]), + ]); + + TargetPointer r2rModule = emBuilder.AddReadyToRunModule(r2rInfo); + TargetPointer rangeSectionAddress = emBuilder.AddReadyToRunRangeSection(jittedCode, jitManagerAddress, r2rModule); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSectionAddress); + + Target target = CreateTarget(emBuilder); + + IExecutionManager em = target.Contracts.ExecutionManager; + Assert.NotNull(em); + + // Hot and cold parts should map to the same method desc + for (int i = 0; i < hotColdMap.Length; i++) + { + // Function start + var handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunctions[hotColdMap[i]]); + Assert.NotNull(handle); + TargetPointer actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(methodDescAddresses[i / 2], actualMethodDesc); + + // Past function start + handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunctions[hotColdMap[i]] + 8); + Assert.NotNull(handle); + actualMethodDesc = em.GetMethodDesc(handle.Value); + Assert.Equal(methodDescAddresses[i / 2], actualMethodDesc); + } + } + public static IEnumerable StdArchAllVersions() { const int highestVersion = 2; From b65045372f128a24aad727c119dfca4f80e93006 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 21 Nov 2024 10:53:16 -0800 Subject: [PATCH 07/13] Separate runtime functions lookup into a helper class --- ...ecutionManagerBase.ReadyToRunJitManager.cs | 98 +++---------------- .../Helpers/RuntimeFunctionLookup.cs | 88 +++++++++++++++++ 2 files changed, 104 insertions(+), 82 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index 56b797abbc947..5feec03dc62fd 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -13,14 +13,16 @@ internal partial class ExecutionManagerBase : IExecutionManager private class ReadyToRunJitManager : JitManager { private readonly uint _runtimeFunctionSize; - private readonly PtrHashMapLookup _lookup; - private readonly HotColdLookup _hotColdLookup; + private readonly PtrHashMapLookup _hashMap; + private readonly HotColdLookup _hotCold; + private readonly RuntimeFunctionLookup _runtimeFunctions; public ReadyToRunJitManager(Target target) : base(target) { _runtimeFunctionSize = Target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value; - _lookup = PtrHashMapLookup.Create(target); - _hotColdLookup = HotColdLookup.Create(target); + _hashMap = PtrHashMapLookup.Create(target); + _hotCold = HotColdLookup.Create(target); + _runtimeFunctions = RuntimeFunctionLookup.Create(target); } public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) @@ -46,14 +48,14 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer TargetPointer relativeAddr = addr - imageBase; uint index; - if (!TryGetRuntimeFunctionIndexForAddress(r2rInfo, relativeAddr, out index)) + if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out index)) return false; bool featureEHFunclets = Target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0; if (featureEHFunclets) { // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. - index = _hotColdLookup.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); + index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); } TargetPointer methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); @@ -64,27 +66,27 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer } Debug.Assert(methodDesc != TargetPointer.Null); - Data.RuntimeFunction function = GetRuntimeFunction(r2rInfo, index); + Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index); TargetCodePointer startAddress = imageBase + function.BeginAddress; TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); // Take any cold code into account for the relative offset - if (_hotColdLookup.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint hotColdMapIndex, out uint coldFunctionIndex)) + if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint hotColdMapIndex, out uint coldFunctionIndex)) { // ReadyToRunJitManager::JitTokenToMethodRegionInfo - Data.RuntimeFunction coldFunction = GetRuntimeFunction(r2rInfo, coldFunctionIndex); + Data.RuntimeFunction coldFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldFunctionIndex); TargetPointer coldStart = imageBase + coldFunction.BeginAddress; if (addr >= coldStart) { uint nextColdFunctionIndex = hotColdMapIndex == r2rInfo.NumHotColdMap - 2 ? r2rInfo.NumRuntimeFunctions - 1 : Target.Read(r2rInfo.HotColdMap + (hotColdMapIndex + 2) * sizeof(uint)) - 1; - Data.RuntimeFunction nextColdFunction = GetRuntimeFunction(r2rInfo, nextColdFunctionIndex); - uint coldSize = nextColdFunction.BeginAddress + GetFunctionLength(nextColdFunction) - coldFunction.BeginAddress; + Data.RuntimeFunction nextColdFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, nextColdFunctionIndex); + uint coldSize = nextColdFunction.BeginAddress + _runtimeFunctions.GetFunctionLength(nextColdFunction) - coldFunction.BeginAddress; if (coldSize > 0) { - uint hotSize = GetFunctionLength(function); + uint hotSize = _runtimeFunctions.GetFunctionLength(function); relativeOffset = new TargetNUInt(hotSize + addr - coldStart); } } @@ -94,23 +96,6 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } - private uint GetFunctionLength(Data.RuntimeFunction function) - { - if (function.EndAddress.HasValue) - return function.EndAddress.Value - function.BeginAddress; - - Data.UnwindInfo unwindInfo = Target.ProcessedData.GetOrAdd(function.UnwindData); - if (unwindInfo.FunctionLength.HasValue) - return unwindInfo.FunctionLength.Value; - - Debug.Assert(unwindInfo.Header.HasValue); - - // First 18 bits are function length / (pointer size / 2). - // See UnwindFragmentInfo::Finalize - uint funcLengthInHeader = unwindInfo.Header.Value & ((1 << 18) - 1); - return (uint)(funcLengthInHeader * (Target.PointerSize / 2)); - } - private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) { if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null) @@ -122,66 +107,15 @@ private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRu return thunksData.VirtualAddress <= rva && rva < thunksData.VirtualAddress + thunksData.Size; } - private bool TryGetRuntimeFunctionIndexForAddress(Data.ReadyToRunInfo r2rInfo, TargetPointer relativeAddress, out uint index) - { - // NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod - uint start = 0; - uint end = r2rInfo.NumRuntimeFunctions - 1; - relativeAddress = CodePointerUtils.CodePointerFromAddress(relativeAddress, Target).AsTargetPointer; - - // Entries are sorted. Binary search until we get to 10 or fewer items. - while (end - start > 10) - { - uint middle = start + (end - start) / 2; - Data.RuntimeFunction func = GetRuntimeFunction(r2rInfo, middle); - if (relativeAddress < func.BeginAddress) - { - end = middle - 1; - } - else - { - start = middle; - } - } - - // Find the runtime function that contains the address of interest - for (uint i = start; i <= end; ++i) - { - // Entries are terminated by a sentinel value of -1, so we can index one past the end safely. - // Read as a runtime function, its begin address is 0xffffffff (always > relative address). - // See RuntimeFunctionsTableNode.GetData in RuntimeFunctionsTableNode.cs - Data.RuntimeFunction nextFunc = GetRuntimeFunction(r2rInfo, i + 1); - if (relativeAddress >= nextFunc.BeginAddress) - continue; - - Data.RuntimeFunction func = GetRuntimeFunction(r2rInfo, i); - if (relativeAddress >= func.BeginAddress) - { - index = i; - return true; - } - } - - index = ~0u; - return false; - } - - private Data.RuntimeFunction GetRuntimeFunction(Data.ReadyToRunInfo r2rInfo, uint index) - { - TargetPointer first = r2rInfo.RuntimeFunctions; - TargetPointer addr = first + (ulong)(index * _runtimeFunctionSize); - return Target.ProcessedData.GetOrAdd(addr); - } - private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint runtimeFunctionIndex) { - Data.RuntimeFunction function = GetRuntimeFunction(r2rInfo, runtimeFunctionIndex); + Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, runtimeFunctionIndex); // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage TargetCodePointer startAddress = imageBase + function.BeginAddress; TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target); - TargetPointer methodDesc = _lookup.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); + TargetPointer methodDesc = _hashMap.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); if (methodDesc == (ulong)HashMapLookup.SpecialKeys.InvalidEntry) return TargetPointer.Null; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs new file mode 100644 index 0000000000000..58f9911959da7 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +internal sealed class RuntimeFunctionLookup +{ + public static RuntimeFunctionLookup Create(Target target) + => new RuntimeFunctionLookup(target); + + private readonly uint _runtimeFunctionSize; + private readonly Target _target; + + private RuntimeFunctionLookup(Target target) + { + _target = target; + _runtimeFunctionSize = target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value; + } + + public uint GetFunctionLength(Data.RuntimeFunction function) + { + if (function.EndAddress.HasValue) + return function.EndAddress.Value - function.BeginAddress; + + Data.UnwindInfo unwindInfo = _target.ProcessedData.GetOrAdd(function.UnwindData); + if (unwindInfo.FunctionLength.HasValue) + return unwindInfo.FunctionLength.Value; + + Debug.Assert(unwindInfo.Header.HasValue); + + // First 18 bits are function length / (pointer size / 2). + // See UnwindFragmentInfo::Finalize + uint funcLengthInHeader = unwindInfo.Header.Value & ((1 << 18) - 1); + return (uint)(funcLengthInHeader * (_target.PointerSize / 2)); + } + + public bool TryGetRuntimeFunctionIndexForAddress(TargetPointer runtimeFunctions, uint numRuntimeFunctions, TargetPointer relativeAddress, out uint index) + { + // NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod + uint start = 0; + uint end = numRuntimeFunctions - 1; + relativeAddress = CodePointerUtils.CodePointerFromAddress(relativeAddress, _target).AsTargetPointer; + + // Entries are sorted. Binary search until we get to 10 or fewer items. + while (end - start > 10) + { + uint middle = start + (end - start) / 2; + Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, middle); + if (relativeAddress < func.BeginAddress) + { + end = middle - 1; + } + else + { + start = middle; + } + } + + // Find the runtime function that contains the address of interest + for (uint i = start; i <= end; ++i) + { + // Entries are terminated by a sentinel value of -1, so we can index one past the end safely. + // Read as a runtime function, its begin address is 0xffffffff (always > relative address). + // See RuntimeFunctionsTableNode.GetData in RuntimeFunctionsTableNode.cs + Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, i + 1); + if (relativeAddress >= nextFunc.BeginAddress) + continue; + + Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, i); + if (relativeAddress >= func.BeginAddress) + { + index = i; + return true; + } + } + + index = ~0u; + return false; + } + + public Data.RuntimeFunction GetRuntimeFunction(TargetPointer runtimeFunctions, uint index) + { + TargetPointer addr = runtimeFunctions + (ulong)(index * _runtimeFunctionSize); + return _target.ProcessedData.GetOrAdd(addr); + } +} From f35ae7c1a7a3c45caffc1698100388bcd2887dac Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 21 Nov 2024 14:15:57 -0800 Subject: [PATCH 08/13] Add tests for runtime functions lookup --- .../ExecutionManager/RuntimeFunctionTests.cs | 97 +++++++++++++++++++ .../MockDescriptors.RuntimeFunctions.cs | 8 +- 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/ExecutionManager/RuntimeFunctionTests.cs diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/RuntimeFunctionTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/RuntimeFunctionTests.cs new file mode 100644 index 0000000000000..fe256024de09a --- /dev/null +++ b/src/native/managed/cdacreader/tests/ExecutionManager/RuntimeFunctionTests.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; +using System.Collections.Generic; +using System; +using Moq; + +namespace Microsoft.Diagnostics.DataContractReader.Tests.ExecutionManager; + +public class RuntimeFunctionTests +{ + public static IEnumerable StdArchFunctionLengthData() + { + foreach (object[] arr in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + yield return new object[] { arch, true /*includeEndAddress*/, false /*unwindInfoIsFunctionLength*/}; + yield return new object[] { arch, true /*includeEndAddress*/, true /*unwindInfoIsFunctionLength*/}; + yield return new object[] { arch, false /*includeEndAddress*/, false /*unwindInfoIsFunctionLength*/}; + yield return new object[] { arch, false /*includeEndAddress*/, true /*unwindInfoIsFunctionLength*/}; + } + } + + [Theory] + [MemberData(nameof(StdArchFunctionLengthData))] + public void GetFunctionLength(MockTarget.Architecture arch, bool includeEndAddress, bool unwindInfoIsFunctionLength) + { + MockMemorySpace.Builder builder = new(new TargetTestHelpers(arch)); + MockDescriptors.RuntimeFunctions runtimeFunctions = new(builder, includeEndAddress, unwindInfoIsFunctionLength); + + uint[] entries = [0x100, 0x1f0, 0x1000, 0x2000, 0xa000]; + TargetPointer addr = runtimeFunctions.AddRuntimeFunctions(entries); + + Target target = new TestPlaceholderTarget(builder.TargetTestHelpers.Arch, builder.GetReadContext().ReadFromTarget, runtimeFunctions.Types); + RuntimeFunctionLookup lookup = RuntimeFunctionLookup.Create(target); + + for (uint i = 0; i < entries.Length; i++) + { + uint expectedFunctionLength = i < entries.Length - 1 + ? Math.Min(entries[i + 1] - entries[i], MockDescriptors.RuntimeFunctions.DefaultFunctionLength) + : MockDescriptors.RuntimeFunctions.DefaultFunctionLength; + + Data.RuntimeFunction function = lookup.GetRuntimeFunction(addr, i); + uint functionLength = lookup.GetFunctionLength(function); + Assert.Equal(expectedFunctionLength, functionLength); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetRuntimeFunctionIndexForAddress(MockTarget.Architecture arch) + { + MockMemorySpace.Builder builder = new(new TargetTestHelpers(arch)); + MockDescriptors.RuntimeFunctions runtimeFunctions = new(builder); + + uint[] entries = [0x100, 0x1f0, 0x1000, 0x2000, 0xa000]; + TargetPointer addr = runtimeFunctions.AddRuntimeFunctions(entries); + + TestPlaceholderTarget target = new TestPlaceholderTarget(builder.TargetTestHelpers.Arch, builder.GetReadContext().ReadFromTarget, runtimeFunctions.Types); + ContractRegistry reg = Mock.Of( + c => c.PlatformMetadata == new Mock().Object); + target.SetContracts(reg); + RuntimeFunctionLookup lookup = RuntimeFunctionLookup.Create(target); + + for (uint i = 0; i < entries.Length; i++) + { + TargetPointer relativeAddress = (TargetPointer)entries[i]; + bool res = lookup.TryGetRuntimeFunctionIndexForAddress(addr, (uint)entries.Length, relativeAddress, out uint index); + Assert.True(res); + Assert.Equal(i, index); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetRuntimeFunctionIndexForAddress_NoMatch(MockTarget.Architecture arch) + { + MockMemorySpace.Builder builder = new(new TargetTestHelpers(arch)); + MockDescriptors.RuntimeFunctions runtimeFunctions = new(builder); + + uint[] entries = [0x100, 0x1f0]; + TargetPointer addr = runtimeFunctions.AddRuntimeFunctions(entries); + + TestPlaceholderTarget target = new TestPlaceholderTarget(builder.TargetTestHelpers.Arch, builder.GetReadContext().ReadFromTarget, runtimeFunctions.Types); + ContractRegistry reg = Mock.Of( + c => c.PlatformMetadata == new Mock().Object); + target.SetContracts(reg); + RuntimeFunctionLookup lookup = RuntimeFunctionLookup.Create(target); + + TargetPointer relativeAddress = 0x0ff; + bool res = lookup.TryGetRuntimeFunctionIndexForAddress(addr, (uint)entries.Length, relativeAddress, out _); + Assert.False(res); + } +} diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs index 5073fb6696818..c7d772f96d630 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs @@ -41,6 +41,8 @@ private static TypeFields RuntimeFunctionFields(bool includeEndAddress) private const ulong DefaultAllocationRangeStart = 0x0004_0000; private const ulong DefaultAllocationRangeEnd = 0x0005_0000; + internal const uint DefaultFunctionLength = 0x100; + private readonly MockMemorySpace.BumpAllocator _allocator; public RuntimeFunctions(MockMemorySpace.Builder builder, bool includeEndAddress = true, bool unwindInfoIsFunctionLength = false) @@ -75,10 +77,10 @@ public TargetPointer AddRuntimeFunctions(uint[] runtimeFunctions) Span func = Builder.BorrowAddressRange(runtimeFunctionsFragment.Address + i * runtimeFunctionSize, (int)runtimeFunctionSize); helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.BeginAddress)].Offset, sizeof(uint)), runtimeFunctions[i]); - // Set the function length at halfway to the start of the next function (or 0x100 for the last function) + // Set the function length to the default function length or up to the next function start uint functionLength = i < numRuntimeFunctions - 1 - ? (runtimeFunctions[i + 1] - runtimeFunctions[i]) / 2 - : 0x100; + ? Math.Min(runtimeFunctions[i + 1] - runtimeFunctions[i], DefaultFunctionLength) + : DefaultFunctionLength; if (runtimeFunctionType.Fields.ContainsKey(nameof(Data.RuntimeFunction.EndAddress))) helpers.Write(func.Slice(runtimeFunctionType.Fields[nameof(Data.RuntimeFunction.EndAddress)].Offset, sizeof(uint)), runtimeFunctions[i] + functionLength); From c7f4153e54a00d464c10b770ae9f7726d34f82e1 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 22 Nov 2024 08:36:14 -0800 Subject: [PATCH 09/13] Simplify relative offset calculation for address in cold code --- ...ecutionManagerBase.ReadyToRunJitManager.cs | 19 ++++++------------- .../ExecutionManager/Helpers/HotColdLookup.cs | 6 ++---- .../ExecutionManager/HotColdLookupTests.cs | 17 ++++++----------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index 5feec03dc62fd..fc8f1283d00a1 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -71,24 +71,17 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer TargetCodePointer startAddress = imageBase + function.BeginAddress; TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); - // Take any cold code into account for the relative offset - if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint hotColdMapIndex, out uint coldFunctionIndex)) + // Take hot/cold splitting into account for the relative offset + if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint coldFunctionIndex)) { - // ReadyToRunJitManager::JitTokenToMethodRegionInfo Data.RuntimeFunction coldFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldFunctionIndex); TargetPointer coldStart = imageBase + coldFunction.BeginAddress; if (addr >= coldStart) { - uint nextColdFunctionIndex = hotColdMapIndex == r2rInfo.NumHotColdMap - 2 - ? r2rInfo.NumRuntimeFunctions - 1 - : Target.Read(r2rInfo.HotColdMap + (hotColdMapIndex + 2) * sizeof(uint)) - 1; - Data.RuntimeFunction nextColdFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, nextColdFunctionIndex); - uint coldSize = nextColdFunction.BeginAddress + _runtimeFunctions.GetFunctionLength(nextColdFunction) - coldFunction.BeginAddress; - if (coldSize > 0) - { - uint hotSize = _runtimeFunctions.GetFunctionLength(function); - relativeOffset = new TargetNUInt(hotSize + addr - coldStart); - } + // If the address is in the cold part, the relative offset is the size of the + // hot part plus the offset from the address to the start of the cold part + uint hotSize = _runtimeFunctions.GetFunctionLength(function); + relativeOffset = new TargetNUInt(hotSize + addr - coldStart); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs index 06db23d0eee62..40d5a24954110 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -31,17 +31,15 @@ public uint GetHotFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, ui return _target.Read(hotColdMap + (ulong)hotIndex * sizeof(uint)); } - public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex, out uint lookupIndex, out uint functionIndex) + public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex, out uint functionIndex) { - lookupIndex = ~0u; functionIndex = ~0u; uint coldIndex; if (!TryLookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex, out uint _, out coldIndex)) return false; - lookupIndex = coldIndex; - functionIndex = _target.Read(hotColdMap + (ulong)lookupIndex * sizeof(uint)); + functionIndex = _target.Read(hotColdMap + (ulong)coldIndex * sizeof(uint)); return true; } diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs index 1c920c017b5d3..c0d91f58e07f3 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs @@ -129,20 +129,16 @@ public void TryGetColdFunctionIndex() Target target = CreateMockTarget(entries); var lookup = HotColdLookup.Create(target); - for (uint i = 0; i < entries.Length; i++) + foreach (var entry in entries) { - var entry = entries[i]; - // Hot part as input - bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Hot, out uint lookupIndex, out uint coldFunctionIndex); + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Hot, out uint coldFunctionIndex); Assert.True(res); - Assert.Equal(i * 2, lookupIndex); Assert.Equal(entry.Cold, coldFunctionIndex); // Cold part as input - res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Cold, out lookupIndex, out coldFunctionIndex); + res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Cold, out coldFunctionIndex); Assert.True(res); - Assert.Equal(i * 2, lookupIndex); Assert.Equal(entry.Cold, coldFunctionIndex); } } @@ -163,9 +159,8 @@ public void TryGetColdFunctionIndex_ColdFunclet() // Cold funclet - between two cold blocks' indexes uint functionIndex = 0x110; - bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex, out uint lookupIndex, out uint coldFunctionIndex); + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex, out uint coldFunctionIndex); Assert.True(res); - Assert.Equal(0u, lookupIndex); Assert.Equal(entries[0].Cold, coldFunctionIndex); } @@ -178,7 +173,7 @@ public void TryGetColdFunctionIndex_EmptyMap() // Function has no cold part if map is empty uint functionIndex = 0x110; - bool res = lookup.TryGetColdFunctionIndex(0, HotColdMapAddr, functionIndex, out _, out _); + bool res = lookup.TryGetColdFunctionIndex(0, HotColdMapAddr, functionIndex, out _); Assert.False(res); } @@ -198,7 +193,7 @@ public void TryGetColdFunctionIndex_NoEntryInMap() // Function has no cold part if it is not in the map uint functionIndex = 0x30; - bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex, out _, out _); + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, functionIndex, out _); Assert.False(res); } } From 2932af2e2fe1559c0042777d8ab3ed8bbb0930c7 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 22 Nov 2024 14:05:40 -0800 Subject: [PATCH 10/13] Update doc, remove unnecessary implementation details --- docs/design/datacontracts/ExecutionManager.md | 96 +++++++++---------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 655feda2e2a96..8504456e1d749 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -56,15 +56,20 @@ Data descriptors used: | `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | | `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite | | `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` | -| `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` | +| `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontyperuntimefunctions)| +| `ReadyToRunInfo` | `NumHotColdMap` | Number of entries in the `HotColdMap` | +| `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) | | `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks | | `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers | | `ImageDataDirectory` | `VirtualAddress` | Virtual address of the image data directory | | `ImageDataDirectory` | `Size` | Size of the data | | `RuntimeFunction` | `BeginAddress` | Begin address of the function | +| `RuntimeFunction` | `EndAddress` | End address of the function. Only exists on some platforms | +| `RuntimeFunction` | `UnwindData` | Pointer to the unwind info for the function | | `HashMap` | `Buckets` | Pointer to the buckets of a `HashMap` | | `Bucket` | `Keys` | Array of keys of `HashMapSlotsPerBucket` length | | `Bucket` | `Values` | Array of values of `HashMapSlotsPerBucket` length | +| `UnwindInfo` | `FunctionLength` | Length of the associated function in bytes. Only exists on some platforms | Global variables used: | Global Name | Type | Purpose | @@ -80,67 +85,52 @@ Contracts used: | --- | | `PlatformMetadata` | -The bulk of the work is done by the `GetCodeBlockHandle` API that maps a code pointer to information about the containing jitted method. +The bulk of the work is done by the `GetCodeBlockHandle` API that maps a code pointer to information about the containing jitted method. This relies the [range section lookup](#rangesectionmap). ```csharp private CodeBlock? GetCodeBlock(TargetCodePointer jittedCodeAddress) { - RangeSection range = RangeSection.Find(_topRangeSectionMap, jittedCodeAddress); - if (range.Data == null) - { + TargetPointer rangeSection = // find range section corresponding to jittedCodeAddress - see RangeSectionMap below + if (/* no corresponding range section */) return null; - } + JitManager jitManager = GetJitManager(range.Data); - if (jitManager.GetMethodInfo(range, jittedCodeAddress, out CodeBlock? info)) - { + if (/* JIT manager corresponding to rangeSection */.GetMethodInfo(range, jittedCodeAddress, out CodeBlock? info)) return info; - } - else - { - return null; - } + return null; } CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip) { - TargetPointer key = ip.AsTargetPointer; - if (/*cache*/.ContainsKey(key)) - { - return new CodeBlockHandle(key); - } CodeBlock? info = GetCodeBlock(ip); - if (info == null || !info.Valid) - { + if (info == null) return null; - } - /*cache*/.TryAdd(key, info); - return new CodeBlockHandle(key); + return new CodeBlockHandle(ip.AsTargetPointer); } ``` -Here `RangeSection.Find` implements the range section lookup, summarized below. - -There are two `JitManager`s: the "EE JitManager" for jitted code and "R2R JitManager" for ReadyToRun code. +There are two JIT managers: the "EE JitManager" for jitted code and "R2R JitManager" for ReadyToRun code. The EE JitManager `GetMethodInfo` implements the nibble map lookup, summarized below, followed by returning the `RealCodeHeader` data: ```csharp -bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) +bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) { - TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); // nibble map lookup + info = default; + TargetPointer start = // look up jittedCodeAddress in nibble map for rangeSection - see NibbleMap below if (start == TargetPointer.Null) - { return false; - } + TargetNUInt relativeOffset = jittedCodeAddress - start; int codeHeaderOffset = Target.PointerSize; TargetPointer codeHeaderIndirect = start - codeHeaderOffset; - if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) - { + + // Check if address is in a stub code block + if (codeHeaderIndirect < Target.ReadGlobal("StubCodeBlockLast")) return false; - } + TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); - Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); - info = new CodeBlock(jittedCodeAddress, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); + TargetPointer methodDesc = Target.ReadPointer(codeHeaderAddress + /* RealCodeHeader::MethodDesc offset */); + info = new CodeBlock(jittedCodeAddress, realCodeHeader.MethodDesc, relativeOffset); return true; } ``` @@ -148,35 +138,31 @@ bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddres The R2R JitManager `GetMethodInfo` finds the runtime function corresponding to an address and maps its entry point pack to a method: ```csharp -bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) +bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) { - if (rangeSection.Data == null) - throw new ArgumentException(nameof(rangeSection)); - info = default; TargetPointer r2rModule = Target.ReadPointer(/* range section address + RangeSection::R2RModule offset */); TargetPointer r2rInfo = Target.ReadPointer(r2rModule + /* Module::ReadyToRunInfo offset */); // Check if address is in a thunk - if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + if (/* jittedCodeAddress is in ReadyToRunInfo::DelayLoadMethodCallThunks */) return false; // Find the relative address that we are looking for - TargetCodePointer code = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */ + TargetCodePointer addr = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */ TargetPointer imageBase = Target.ReadPointer(/* range section address + RangeSection::RangeBegin offset */); - TargetPointer relativeAddr = code - imageBase; + TargetPointer relativeAddr = addr - imageBase; TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */); int index = // Iterate through runtimeFunctions and find index of function with relativeAddress if (index < 0) return false; - bool featureEHFunclets = Target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0; + bool featureEHFunclets = Target.ReadGlobal("FeatureEHFunclets") != 0; if (featureEHFunclets) { - // TODO: [cdac] Look up in hot/cold mapping lookup table and if the method is in the cold block, - // get the index of the associated hot block. + index = // look up hot part index in the hot/cold map } TargetPointer function = runtimeFunctions + (ulong)(index * /* size of RuntimeFunction */); @@ -186,11 +172,22 @@ bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddres TargetPointer mapAddress = r2rInfo + /* ReadyToRunInfo::EntryPointToMethodDescMap offset */; TargetPointer methodDesc = /* look up entryPoint in HashMap at mapAddress */; + while (featureEHFunclets && methodDesc == TargetPointer.Null) + { + index--; + methodDesc = /* re-compute entryPoint based on updated index and look up in HashMap at mapAddress */ + } - // TODO: [cdac] Handle method with cold code when computing relative offset TargetNUInt relativeOffset = new TargetNUInt(code - startAddress); + if (/* function has cold part and addr is in the cold part*/) + { + uint coldIndex = // look up cold part in hot/cold map + TargetPointer coldFunction = runtimeFunctions + (ulong)(coldIndex * /* size of RuntimeFunction */); + TargetPointer coldStart = imageBase + Target.Read(function + /* RuntimeFunction::BeginAddress offset */); + relativeOffset = /* function length of hot part */ + addr - coldStart; + } - info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager); + info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset); return true; } ``` @@ -204,19 +201,16 @@ class CodeBlock public TargetCodePointer StartAddress { get; } public TargetPointer MethodDesc { get; } - public TargetPointer JitManagerAddress { get; } public TargetNUInt RelativeOffset { get; } - public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress) + public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset) { StartAddress = startAddress; MethodDesc = methodDesc; RelativeOffset = relativeOffset; - JitManagerAddress = jitManagerAddress; } public TargetPointer MethodDescAddress => _codeHeaderData.MethodDesc; - public bool Valid => JitManagerAddress != TargetPointer.Null; } ``` From 3d339ba7ef52480499070b641cf0f0a7a7167d78 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 22 Nov 2024 14:11:15 -0800 Subject: [PATCH 11/13] Add asserts --- .../ExecutionManagerBase.ReadyToRunJitManager.cs | 5 +++++ .../Contracts/ExecutionManager/Helpers/HotColdLookup.cs | 3 ++- .../Data/ReadyToRunInfo.cs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index fc8f1283d00a1..8390469e1d76c 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -56,11 +56,15 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer { // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); + Debug.Assert(index < r2rInfo.NumRuntimeFunctions); } TargetPointer methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); while (featureEHFunclets && methodDesc == TargetPointer.Null) { + // Funclets won't have a direct entry in the map of runtime function entry point to method desc. + // The funclet's address (and index) will be greater than that of the corresponding function, so + // we decrement the index to find the actual function / method desc for the funclet. index--; methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); } @@ -74,6 +78,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer // Take hot/cold splitting into account for the relative offset if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint coldFunctionIndex)) { + Debug.Assert(coldFunctionIndex < r2rInfo.NumRuntimeFunctions); Data.RuntimeFunction coldFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldFunctionIndex); TargetPointer coldStart = imageBase + coldFunction.BeginAddress; if (addr >= coldStart) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs index 40d5a24954110..9e5f052dc1651 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -27,7 +27,7 @@ public uint GetHotFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, ui return runtimeFunctionIndex; // If runtime function is in the cold part, get the associated hot part - Debug.Assert((hotIndex & 1) == 1); + Debug.Assert(hotIndex % 2 != 0, "Hot part index should be an odd number"); return _target.Read(hotColdMap + (ulong)hotIndex * sizeof(uint)); } @@ -39,6 +39,7 @@ public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap if (!TryLookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex, out uint _, out coldIndex)) return false; + Debug.Assert(coldIndex % 2 == 0, "Cold part index should be an even number"); functionIndex = _target.Read(hotColdMap + (ulong)coldIndex * sizeof(uint)); return true; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index ded82a1255db3..01db9d76bd978 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; + namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class ReadyToRunInfo : IData @@ -23,6 +25,7 @@ public ReadyToRunInfo(Target target, TargetPointer address) : TargetPointer.Null; NumHotColdMap = target.Read(address + (ulong)type.Fields[nameof(NumHotColdMap)].Offset); + Debug.Assert(NumHotColdMap % 2 == 0, "Hot/cold map should have an even number of entries (pairs of hot/cold runtime function indexes)"); HotColdMap = NumHotColdMap > 0 ? target.ReadPointer(address + (ulong)type.Fields[nameof(HotColdMap)].Offset) : TargetPointer.Null; From 0e002e8cb39861049918d4afccadd1935bd60677 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 22 Nov 2024 14:58:25 -0800 Subject: [PATCH 12/13] Add helper for binary then linear search --- .../Helpers/BinaryThenLinearSearch.cs | 46 +++++++++++++++++++ .../ExecutionManager/Helpers/HotColdLookup.cs | 44 ++++++++---------- .../Helpers/RuntimeFunctionLookup.cs | 37 ++++++--------- 3 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs new file mode 100644 index 0000000000000..399db2f82dba7 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +internal static class BinaryThenLinearSeach +{ + private const uint BinarySearchCountThreshold = 10; + + public static bool Search( + uint start, + uint end, + Func compare, + Func match, + out uint index) + { + // Binary search until we get to a fewer than the threshold number of items. + while (end - start > BinarySearchCountThreshold) + { + uint middle = start + (end - start) / 2; + if (compare(middle)) + { + end = middle - 1; + } + else + { + start = middle; + } + } + + // Linear search over the remaining items + for (uint i = start; i <= end; ++i) + { + if (!match(i)) + continue; + + index = i; + return true; + } + + index = ~0u; + return false; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs index 9e5f052dc1651..48ba10f3fe07b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -79,38 +79,32 @@ private bool TryLookupHotColdMappingForMethod( uint end = (numHotColdMap - 1) / 2; bool isColdCode = IsColdCode(numHotColdMap, hotColdMap, runtimeFunctionIndex); - int indexCorrection = isColdCode ? 0 : 1; + uint indexCorrection = isColdCode ? 0u : 1u; - // Entries are sorted by the hot part runtime function indices. This also means they are sorted - // by the cold part indices, as the cold part is emitted in the same order as hot parts. - // Binary search until we get to 10 or fewer items. - while (end - start > 10) + // Index used for the search is the logical index of hot/cold pairs. We double it to index + // into the HotColdMap array. + if (BinaryThenLinearSeach.Search(start, end, Compare, Match, out uint index)) { - uint middle = start + (end - start) / 2; - long index = middle * 2 + indexCorrection; + hotIndex = index * 2 + 1; + coldIndex = index * 2; + return true; + } - if (runtimeFunctionIndex < _target.Read(hotColdMap + (ulong)(index * sizeof(uint)))) - { - end = middle - 1; - } - else - { - start = middle; - } + return false; + + bool Compare(uint index) + { + index = index * 2 + indexCorrection; + return runtimeFunctionIndex < _target.Read(hotColdMap + (index * sizeof(uint))); } - // Find the hot/cold map index corresponding to the cold/hot runtime function index - for (uint i = start; i <= end; ++i) + bool Match(uint index) { - uint index = i * 2; + index *= 2; uint value = _target.Read(hotColdMap + (ulong)(index + indexCorrection) * sizeof(uint)); if (value == runtimeFunctionIndex) - { - hotIndex = index + 1; - coldIndex = index; return true; - } // If function index is a cold funclet from a cold block, the above check for equality will fail. // To get its corresponding hot block, find the cold block containing the funclet, @@ -123,13 +117,11 @@ private bool TryLookupHotColdMappingForMethod( || runtimeFunctionIndex < _target.Read(hotColdMap + (ulong)(index + 2) * sizeof(uint)); if (isFuncletIndex) { - hotIndex = index + 1; - coldIndex = index; return true; } } - } - return false; + return false; + } } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs index 58f9911959da7..2af92ba5c7064 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -43,41 +43,30 @@ public bool TryGetRuntimeFunctionIndexForAddress(TargetPointer runtimeFunctions, uint end = numRuntimeFunctions - 1; relativeAddress = CodePointerUtils.CodePointerFromAddress(relativeAddress, _target).AsTargetPointer; - // Entries are sorted. Binary search until we get to 10 or fewer items. - while (end - start > 10) + // Entries are sorted. + return BinaryThenLinearSeach.Search(start, end, Compare, Match, out index); + + bool Compare(uint index) { - uint middle = start + (end - start) / 2; - Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, middle); - if (relativeAddress < func.BeginAddress) - { - end = middle - 1; - } - else - { - start = middle; - } - } + Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); + return relativeAddress < func.BeginAddress; + }; - // Find the runtime function that contains the address of interest - for (uint i = start; i <= end; ++i) + bool Match(uint index) { // Entries are terminated by a sentinel value of -1, so we can index one past the end safely. // Read as a runtime function, its begin address is 0xffffffff (always > relative address). // See RuntimeFunctionsTableNode.GetData in RuntimeFunctionsTableNode.cs - Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, i + 1); + Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, index + 1); if (relativeAddress >= nextFunc.BeginAddress) - continue; + return false; - Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, i); + Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); if (relativeAddress >= func.BeginAddress) - { - index = i; return true; - } - } - index = ~0u; - return false; + return false; + } } public Data.RuntimeFunction GetRuntimeFunction(TargetPointer runtimeFunctions, uint index) From ff2c0a52fccfb1a491ea53cc2be9e48d77b5f354 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 22 Nov 2024 15:01:04 -0800 Subject: [PATCH 13/13] Fix typo --- .../ExecutionManager/Helpers/BinaryThenLinearSearch.cs | 2 +- .../Contracts/ExecutionManager/Helpers/HotColdLookup.cs | 2 +- .../Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs index 399db2f82dba7..9fc0509bb064a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs @@ -5,7 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; -internal static class BinaryThenLinearSeach +internal static class BinaryThenLinearSearch { private const uint BinarySearchCountThreshold = 10; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs index 48ba10f3fe07b..d2bea5f7eb3c2 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -83,7 +83,7 @@ private bool TryLookupHotColdMappingForMethod( // Index used for the search is the logical index of hot/cold pairs. We double it to index // into the HotColdMap array. - if (BinaryThenLinearSeach.Search(start, end, Compare, Match, out uint index)) + if (BinaryThenLinearSearch.Search(start, end, Compare, Match, out uint index)) { hotIndex = index * 2 + 1; coldIndex = index * 2; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs index 2af92ba5c7064..e8dbbf6d22090 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -44,7 +44,7 @@ public bool TryGetRuntimeFunctionIndexForAddress(TargetPointer runtimeFunctions, relativeAddress = CodePointerUtils.CodePointerFromAddress(relativeAddress, _target).AsTargetPointer; // Entries are sorted. - return BinaryThenLinearSeach.Search(start, end, Compare, Match, out index); + return BinaryThenLinearSearch.Search(start, end, Compare, Match, out index); bool Compare(uint index) {