From b266885ef35f0550de6b7ce378f1442001b69acb Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 26 Nov 2024 13:52:27 -0800 Subject: [PATCH] [cdac] Handle hot/cold map lookup in `ExecutionManager.ReadyToRunJitManager.GetMethodInfo` (#110087) Implement the hot/cold lookup logic for `ExecutionManager.ReadyToRunJitManager.GetMethodInfo`. Basic logic is now: 1. Check if the address is in a thunk for READYTORUN_HELPER_DelayLoad_MethodCall 2. Find the runtime function entry corresponding to the address - If the runtime function entry is cold code, find the runtime function entry for the corresponding hot part 3. Look up the MethodDesc for the entry point using the ReadyToRunInfo's hash map 4. Compute the relative offset based the function's start address - If the runtime function has a cold part and the address is in the cold part, compute the relative offset based on the size of the hot part and the offset from the start of the cold part Sub-bullets of 2 and 4 are the parts added by this change. Add tests for `ExecutionManager` for getting code blocks and method desc in R2R with hot/cold splitting, runtime functions lookup functionality, and hot/cold map lookup logic. - Pull out helper for RuntimeFunctions mock memory - `ExecutionManagerTests` just uses one specific runtime function / unwind info layout - The `RuntimeFunctionsTests` handle testing the matrix of different layouts The `RuntimeFunction` and `UnwindInfo` structures are different depending on the platform. This matters for calculating the function length. To facilitate testing, I pulled out the runtime functions lookup into a helper class. This change puts reading the hot/cold map in a helper class `HotColdLookup`. Having separate helpers (similarly, HashMapLookup and NibbleMap) should let us mock them out for testing if we want - there'd still be some work for how they are created (for example, tests need to be able to provide some instance instead of the contract directly creating the class), but it puts us in a reasonable position. Manually validated with `!ip2md` (temporarily commented out throwing not implemented for rejit ID requests) for an app published with R2R `--hot-cold-splitting` that we correctly map addresses in cold/hot blocks to hot/cold parts, determine their start addresses and sizes, and return the correct method desc. --- docs/design/datacontracts/ExecutionManager.md | 96 ++++----- .../debug/runtimeinfo/datadescriptor.h | 13 ++ src/coreclr/vm/readytoruninfo.h | 2 + .../Contracts/IExecutionManager.cs | 1 - .../DataType.cs | 1 + ...ecutionManagerBase.ReadyToRunJitManager.cs | 106 ++++------ .../Helpers/BinaryThenLinearSearch.cs | 46 ++++ .../ExecutionManager/Helpers/HotColdLookup.cs | 127 +++++++++++ .../Helpers/RuntimeFunctionLookup.cs | 77 +++++++ .../Data/ReadyToRunInfo.cs | 15 +- .../Data/RuntimeFunction.cs | 8 + .../Data/UnwindInfo.cs | 29 +++ .../ExecutionManager/ExecutionManagerTests.cs | 57 ++++- .../ExecutionManager/HotColdLookupTests.cs | 199 ++++++++++++++++++ .../ExecutionManager/RuntimeFunctionTests.cs | 97 +++++++++ .../MockDescriptors.ExecutionManager.cs | 63 +++--- .../MockDescriptors.RuntimeFunctions.cs | 115 ++++++++++ 17 files changed, 905 insertions(+), 147 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/BinaryThenLinearSearch.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs create mode 100644 src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs create mode 100644 src/native/managed/cdacreader/tests/ExecutionManager/RuntimeFunctionTests.cs create mode 100644 src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs 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; } ``` 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..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 @@ -13,12 +13,16 @@ internal partial class ExecutionManagerBase : IExecutionManager private class ReadyToRunJitManager : JitManager { private readonly uint _runtimeFunctionSize; - private readonly PtrHashMapLookup _lookup; + 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); + _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) @@ -43,35 +47,49 @@ 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 (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, 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 = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); + Debug.Assert(index < r2rInfo.NumRuntimeFunctions); } - 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) + { + // 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); + } - TargetPointer methodDesc = _lookup.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); Debug.Assert(methodDesc != TargetPointer.Null); + Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, 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 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) + { + // 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); + } + } + info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager); return true; } @@ -87,51 +105,19 @@ 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 TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint runtimeFunctionIndex) { - // 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; - } - } + Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, runtimeFunctionIndex); - // 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) - return (int)i; - } + // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage + TargetCodePointer startAddress = imageBase + function.BeginAddress; + TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target); - return -1; - } + TargetPointer methodDesc = _hashMap.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); + if (methodDesc == (ulong)HashMapLookup.SpecialKeys.InvalidEntry) + return TargetPointer.Null; - private Data.RuntimeFunction GetRuntimeFunction(Data.ReadyToRunInfo r2rInfo, uint index) - { - TargetPointer first = r2rInfo.RuntimeFunctions; - TargetPointer addr = first + (ulong)(index * _runtimeFunctionSize); - return Target.ProcessedData.GetOrAdd(addr); + return methodDesc; } } } 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..9fc0509bb064a --- /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 BinaryThenLinearSearch +{ + 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 new file mode 100644 index 0000000000000..d2bea5f7eb3c2 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -0,0 +1,127 @@ +// 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) + { + 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 + Debug.Assert(hotIndex % 2 != 0, "Hot part index should be an odd number"); + return _target.Read(hotColdMap + (ulong)hotIndex * sizeof(uint)); + } + + public bool TryGetColdFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex, out uint functionIndex) + { + functionIndex = ~0u; + + uint coldIndex; + 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; + } + + 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; + } + + // 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) + { + hotIndex = ~0u; + coldIndex = ~0u; + + // HotColdMappingLookupTable::LookupMappingForMethod + if (numHotColdMap == 0) + 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. + // 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); + uint indexCorrection = isColdCode ? 0u : 1u; + + // Index used for the search is the logical index of hot/cold pairs. We double it to index + // into the HotColdMap array. + if (BinaryThenLinearSearch.Search(start, end, Compare, Match, out uint index)) + { + hotIndex = index * 2 + 1; + coldIndex = index * 2; + return true; + } + + return false; + + bool Compare(uint index) + { + index = index * 2 + indexCorrection; + return runtimeFunctionIndex < _target.Read(hotColdMap + (index * sizeof(uint))); + } + + bool Match(uint index) + { + index *= 2; + + uint value = _target.Read(hotColdMap + (ulong)(index + indexCorrection) * sizeof(uint)); + if (value == runtimeFunctionIndex) + 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, + // 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))) + { + bool isFuncletIndex = index + 2 == numHotColdMap + || runtimeFunctionIndex < _target.Read(hotColdMap + (ulong)(index + 2) * sizeof(uint)); + if (isFuncletIndex) + { + return true; + } + } + + 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 new file mode 100644 index 0000000000000..e8dbbf6d22090 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -0,0 +1,77 @@ +// 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. + return BinaryThenLinearSearch.Search(start, end, Compare, Match, out index); + + bool Compare(uint index) + { + Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); + return relativeAddress < func.BeginAddress; + }; + + 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, index + 1); + if (relativeAddress >= nextFunc.BeginAddress) + return false; + + Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); + if (relativeAddress >= func.BeginAddress) + return true; + + return false; + } + } + + public Data.RuntimeFunction GetRuntimeFunction(TargetPointer runtimeFunctions, uint index) + { + TargetPointer addr = runtimeFunctions + (ulong)(index * _runtimeFunctionSize); + return _target.ProcessedData.GetOrAdd(addr); + } +} 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..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 @@ -18,7 +20,15 @@ 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); + 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; DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].Offset); @@ -31,6 +41,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..5687961764cd9 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, @@ -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; 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..c0d91f58e07f3 --- /dev/null +++ b/src/native/managed/cdacreader/tests/ExecutionManager/HotColdLookupTests.cs @@ -0,0 +1,199 @@ +// 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); + foreach (var entry in entries) + { + // Hot part as input + bool res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Hot, out uint coldFunctionIndex); + Assert.True(res); + Assert.Equal(entry.Cold, coldFunctionIndex); + + // Cold part as input + res = lookup.TryGetColdFunctionIndex(numHotColdMap, HotColdMapAddr, entry.Cold, out coldFunctionIndex); + Assert.True(res); + 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 coldFunctionIndex); + Assert.True(res); + 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 _); + 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 _); + Assert.False(res); + } +} 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.ExecutionManager.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index 98ca626103a04..e061b65972a48 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; } @@ -235,15 +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), - ] - }; - private static MockDescriptors.TypeFields ReadyToRunInfoFields(TargetTestHelpers helpers) => new() { DataType = DataType.ReadyToRunInfo, @@ -252,6 +243,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), ] @@ -264,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; @@ -278,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); @@ -285,14 +280,14 @@ 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, + ReadyToRunInfoFields(Builder.TargetTestHelpers), + MockDescriptors.ModuleFields, ]).Concat(MockDescriptors.HashMap.GetTypes(Builder.TargetTestHelpers)) + .Concat(_rfBuilder.Types) .ToDictionary(); // Tests are currently always set to use funclets @@ -436,26 +431,28 @@ 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; // 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++) + TargetPointer runtimeFunctionsAddr = _rfBuilder.AddRuntimeFunctions(runtimeFunctions); + + // Add the hot/cold map + TargetPointer hotColdMapAddr = TargetPointer.Null; + if (hotColdMap.Length > 0) { - 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]); + 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]); + } } - // 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); - // Add ReadyToRunInfo Target.TypeInfo r2rInfoType = Types[DataType.ReadyToRunInfo]; MockMemorySpace.HeapFragment r2rInfo = _allocator.Allocate(r2rInfoType.Size.Value, "ReadyToRunInfo"); @@ -467,7 +464,11 @@ public TargetPointer AddReadyToRunInfo(uint[] runtimeFunctions) // 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); + helpers.WritePointer(data.Slice(r2rInfoType.Fields[nameof(Data.ReadyToRunInfo.HotColdMap)].Offset, helpers.PointerSize), hotColdMapAddr); return r2rInfo.Address; } 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..c7d772f96d630 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeFunctions.cs @@ -0,0 +1,115 @@ +// 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; + + internal const uint DefaultFunctionLength = 0x100; + + 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 to the default function length or up to the next function start + uint functionLength = i < numRuntimeFunctions - 1 + ? 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); + + // 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; + } + } +}