Skip to content

Commit

Permalink
[cdac] Handle hot/cold map lookup in `ExecutionManager.ReadyToRunJitM…
Browse files Browse the repository at this point in the history
…anager.GetMethodInfo` (dotnet#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.
  • Loading branch information
elinor-fung authored and mikelle-rogers committed Dec 4, 2024
1 parent 8b03f92 commit b266885
Show file tree
Hide file tree
Showing 17 changed files with 905 additions and 147 deletions.
96 changes: 45 additions & 51 deletions docs/design/datacontracts/ExecutionManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -80,103 +85,84 @@ 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<byte>("StubCodeBlockLast"))
return false;
}

TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect);
Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd<Data.RealCodeHeader>(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;
}
```

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<byte>(Constants.Globals.FeatureEHFunclets) != 0;
bool featureEHFunclets = Target.ReadGlobal<byte>("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 */);
Expand All @@ -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<uint>(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;
}
```
Expand All @@ -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;
}
```

Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ CDAC_TYPE_INDETERMINATE(ReadyToRunInfo)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, CompositeInfo, cdac_data<ReadyToRunInfo>::CompositeInfo)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumRuntimeFunctions, cdac_data<ReadyToRunInfo>::NumRuntimeFunctions)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data<ReadyToRunInfo>::RuntimeFunctions)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumHotColdMap, cdac_data<ReadyToRunInfo>::NumHotColdMap)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data<ReadyToRunInfo>::HotColdMap)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data<ReadyToRunInfo>::DelayLoadMethodCallThunks)
CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data<ReadyToRunInfo>::EntryPointToMethodDescMap)
CDAC_TYPE_END(ReadyToRunInfo)
Expand All @@ -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<HashMap>::Buckets)
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/readytoruninfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ struct cdac_data<ReadyToRunInfo>
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);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ public enum DataType
RuntimeFunction,
HashMap,
Bucket,
UnwindInfo,
}
Loading

0 comments on commit b266885

Please sign in to comment.