Skip to content

Commit d58ffd1

Browse files
authored
[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.
1 parent a538054 commit d58ffd1

File tree

17 files changed

+905
-147
lines changed

17 files changed

+905
-147
lines changed

docs/design/datacontracts/ExecutionManager.md

+45-51
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,20 @@ Data descriptors used:
5656
| `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module |
5757
| `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite |
5858
| `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` |
59-
| `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` |
59+
| `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontyperuntimefunctions)|
60+
| `ReadyToRunInfo` | `NumHotColdMap` | Number of entries in the `HotColdMap` |
61+
| `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) |
6062
| `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks |
6163
| `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers |
6264
| `ImageDataDirectory` | `VirtualAddress` | Virtual address of the image data directory |
6365
| `ImageDataDirectory` | `Size` | Size of the data |
6466
| `RuntimeFunction` | `BeginAddress` | Begin address of the function |
67+
| `RuntimeFunction` | `EndAddress` | End address of the function. Only exists on some platforms |
68+
| `RuntimeFunction` | `UnwindData` | Pointer to the unwind info for the function |
6569
| `HashMap` | `Buckets` | Pointer to the buckets of a `HashMap` |
6670
| `Bucket` | `Keys` | Array of keys of `HashMapSlotsPerBucket` length |
6771
| `Bucket` | `Values` | Array of values of `HashMapSlotsPerBucket` length |
72+
| `UnwindInfo` | `FunctionLength` | Length of the associated function in bytes. Only exists on some platforms |
6873

6974
Global variables used:
7075
| Global Name | Type | Purpose |
@@ -80,103 +85,84 @@ Contracts used:
8085
| --- |
8186
| `PlatformMetadata` |
8287

83-
The bulk of the work is done by the `GetCodeBlockHandle` API that maps a code pointer to information about the containing jitted method.
88+
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).
8489

8590
```csharp
8691
private CodeBlock? GetCodeBlock(TargetCodePointer jittedCodeAddress)
8792
{
88-
RangeSection range = RangeSection.Find(_topRangeSectionMap, jittedCodeAddress);
89-
if (range.Data == null)
90-
{
93+
TargetPointer rangeSection = // find range section corresponding to jittedCodeAddress - see RangeSectionMap below
94+
if (/* no corresponding range section */)
9195
return null;
92-
}
96+
9397
JitManager jitManager = GetJitManager(range.Data);
94-
if (jitManager.GetMethodInfo(range, jittedCodeAddress, out CodeBlock? info))
95-
{
98+
if (/* JIT manager corresponding to rangeSection */.GetMethodInfo(range, jittedCodeAddress, out CodeBlock? info))
9699
return info;
97-
}
98-
else
99-
{
100-
return null;
101-
}
100+
return null;
102101
}
103102
CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip)
104103
{
105-
TargetPointer key = ip.AsTargetPointer;
106-
if (/*cache*/.ContainsKey(key))
107-
{
108-
return new CodeBlockHandle(key);
109-
}
110104
CodeBlock? info = GetCodeBlock(ip);
111-
if (info == null || !info.Valid)
112-
{
105+
if (info == null)
113106
return null;
114-
}
115-
/*cache*/.TryAdd(key, info);
116-
return new CodeBlockHandle(key);
107+
return new CodeBlockHandle(ip.AsTargetPointer);
117108
}
118109
```
119110

120-
Here `RangeSection.Find` implements the range section lookup, summarized below.
121-
122-
There are two `JitManager`s: the "EE JitManager" for jitted code and "R2R JitManager" for ReadyToRun code.
111+
There are two JIT managers: the "EE JitManager" for jitted code and "R2R JitManager" for ReadyToRun code.
123112

124113
The EE JitManager `GetMethodInfo` implements the nibble map lookup, summarized below, followed by returning the `RealCodeHeader` data:
125114

126115
```csharp
127-
bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info)
116+
bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info)
128117
{
129-
TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); // nibble map lookup
118+
info = default;
119+
TargetPointer start = // look up jittedCodeAddress in nibble map for rangeSection - see NibbleMap below
130120
if (start == TargetPointer.Null)
131-
{
132121
return false;
133-
}
122+
134123
TargetNUInt relativeOffset = jittedCodeAddress - start;
135124
int codeHeaderOffset = Target.PointerSize;
136125
TargetPointer codeHeaderIndirect = start - codeHeaderOffset;
137-
if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect))
138-
{
126+
127+
// Check if address is in a stub code block
128+
if (codeHeaderIndirect < Target.ReadGlobal<byte>("StubCodeBlockLast"))
139129
return false;
140-
}
130+
141131
TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect);
142-
Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd<Data.RealCodeHeader>(codeHeaderAddress);
143-
info = new CodeBlock(jittedCodeAddress, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager);
132+
TargetPointer methodDesc = Target.ReadPointer(codeHeaderAddress + /* RealCodeHeader::MethodDesc offset */);
133+
info = new CodeBlock(jittedCodeAddress, realCodeHeader.MethodDesc, relativeOffset);
144134
return true;
145135
}
146136
```
147137

148138
The R2R JitManager `GetMethodInfo` finds the runtime function corresponding to an address and maps its entry point pack to a method:
149139

150140
```csharp
151-
bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info)
141+
bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info)
152142
{
153-
if (rangeSection.Data == null)
154-
throw new ArgumentException(nameof(rangeSection));
155-
156143
info = default;
157144

158145
TargetPointer r2rModule = Target.ReadPointer(/* range section address + RangeSection::R2RModule offset */);
159146
TargetPointer r2rInfo = Target.ReadPointer(r2rModule + /* Module::ReadyToRunInfo offset */);
160147

161148
// Check if address is in a thunk
162-
if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress))
149+
if (/* jittedCodeAddress is in ReadyToRunInfo::DelayLoadMethodCallThunks */)
163150
return false;
164151

165152
// Find the relative address that we are looking for
166-
TargetCodePointer code = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */
153+
TargetCodePointer addr = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */
167154
TargetPointer imageBase = Target.ReadPointer(/* range section address + RangeSection::RangeBegin offset */);
168-
TargetPointer relativeAddr = code - imageBase;
155+
TargetPointer relativeAddr = addr - imageBase;
169156

170157
TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */);
171158
int index = // Iterate through runtimeFunctions and find index of function with relativeAddress
172159
if (index < 0)
173160
return false;
174161

175-
bool featureEHFunclets = Target.ReadGlobal<byte>(Constants.Globals.FeatureEHFunclets) != 0;
162+
bool featureEHFunclets = Target.ReadGlobal<byte>("FeatureEHFunclets") != 0;
176163
if (featureEHFunclets)
177164
{
178-
// TODO: [cdac] Look up in hot/cold mapping lookup table and if the method is in the cold block,
179-
// get the index of the associated hot block.
165+
index = // look up hot part index in the hot/cold map
180166
}
181167

182168
TargetPointer function = runtimeFunctions + (ulong)(index * /* size of RuntimeFunction */);
@@ -186,11 +172,22 @@ bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddres
186172

187173
TargetPointer mapAddress = r2rInfo + /* ReadyToRunInfo::EntryPointToMethodDescMap offset */;
188174
TargetPointer methodDesc = /* look up entryPoint in HashMap at mapAddress */;
175+
while (featureEHFunclets && methodDesc == TargetPointer.Null)
176+
{
177+
index--;
178+
methodDesc = /* re-compute entryPoint based on updated index and look up in HashMap at mapAddress */
179+
}
189180

190-
// TODO: [cdac] Handle method with cold code when computing relative offset
191181
TargetNUInt relativeOffset = new TargetNUInt(code - startAddress);
182+
if (/* function has cold part and addr is in the cold part*/)
183+
{
184+
uint coldIndex = // look up cold part in hot/cold map
185+
TargetPointer coldFunction = runtimeFunctions + (ulong)(coldIndex * /* size of RuntimeFunction */);
186+
TargetPointer coldStart = imageBase + Target.Read<uint>(function + /* RuntimeFunction::BeginAddress offset */);
187+
relativeOffset = /* function length of hot part */ + addr - coldStart;
188+
}
192189

193-
info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager);
190+
info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset);
194191
return true;
195192
}
196193
```
@@ -204,19 +201,16 @@ class CodeBlock
204201

205202
public TargetCodePointer StartAddress { get; }
206203
public TargetPointer MethodDesc { get; }
207-
public TargetPointer JitManagerAddress { get; }
208204
public TargetNUInt RelativeOffset { get; }
209205

210-
public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress)
206+
public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset)
211207
{
212208
StartAddress = startAddress;
213209
MethodDesc = methodDesc;
214210
RelativeOffset = relativeOffset;
215-
JitManagerAddress = jitManagerAddress;
216211
}
217212

218213
public TargetPointer MethodDescAddress => _codeHeaderData.MethodDesc;
219-
public bool Valid => JitManagerAddress != TargetPointer.Null;
220214
}
221215
```
222216

src/coreclr/debug/runtimeinfo/datadescriptor.h

+13
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ CDAC_TYPE_INDETERMINATE(ReadyToRunInfo)
418418
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, CompositeInfo, cdac_data<ReadyToRunInfo>::CompositeInfo)
419419
CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumRuntimeFunctions, cdac_data<ReadyToRunInfo>::NumRuntimeFunctions)
420420
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data<ReadyToRunInfo>::RuntimeFunctions)
421+
CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumHotColdMap, cdac_data<ReadyToRunInfo>::NumHotColdMap)
422+
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data<ReadyToRunInfo>::HotColdMap)
421423
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data<ReadyToRunInfo>::DelayLoadMethodCallThunks)
422424
CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data<ReadyToRunInfo>::EntryPointToMethodDescMap)
423425
CDAC_TYPE_END(ReadyToRunInfo)
@@ -431,8 +433,19 @@ CDAC_TYPE_END(ImageDataDirectory)
431433
CDAC_TYPE_BEGIN(RuntimeFunction)
432434
CDAC_TYPE_SIZE(sizeof(RUNTIME_FUNCTION))
433435
CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, BeginAddress, offsetof(RUNTIME_FUNCTION, BeginAddress))
436+
#ifdef TARGET_AMD64
437+
CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, EndAddress, offsetof(RUNTIME_FUNCTION, EndAddress))
438+
#endif
439+
CDAC_TYPE_FIELD(RuntimeFunction, /*uint32*/, UnwindData, offsetof(RUNTIME_FUNCTION, UnwindData))
434440
CDAC_TYPE_END(RuntimeFunction)
435441

442+
CDAC_TYPE_BEGIN(UnwindInfo)
443+
CDAC_TYPE_INDETERMINATE(UnwindInfo)
444+
#ifdef TARGET_X86
445+
CDAC_TYPE_FIELD(UnwindInfo, /*uint32*/, FunctionLength, offsetof(UNWIND_INFO, FunctionLength))
446+
#endif
447+
CDAC_TYPE_END(UnwindInfo)
448+
436449
CDAC_TYPE_BEGIN(HashMap)
437450
CDAC_TYPE_INDETERMINATE(HashMap)
438451
CDAC_TYPE_FIELD(HashMap, /*pointer*/, Buckets, cdac_data<HashMap>::Buckets)

src/coreclr/vm/readytoruninfo.h

+2
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ struct cdac_data<ReadyToRunInfo>
347347
static constexpr size_t CompositeInfo = offsetof(ReadyToRunInfo, m_pCompositeInfo);
348348
static constexpr size_t NumRuntimeFunctions = offsetof(ReadyToRunInfo, m_nRuntimeFunctions);
349349
static constexpr size_t RuntimeFunctions = offsetof(ReadyToRunInfo, m_pRuntimeFunctions);
350+
static constexpr size_t NumHotColdMap = offsetof(ReadyToRunInfo, m_nHotColdMap);
351+
static constexpr size_t HotColdMap = offsetof(ReadyToRunInfo, m_pHotColdMap);
350352
static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks);
351353
static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap);
352354
};

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ internal interface IExecutionManager : IContract
1717
CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException();
1818
TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
1919
TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
20-
2120
}
2221

2322
internal readonly struct ExecutionManager : IExecutionManager

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs

+1
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ public enum DataType
7272
RuntimeFunction,
7373
HashMap,
7474
Bucket,
75+
UnwindInfo,
7576
}

0 commit comments

Comments
 (0)