Skip to content

Commit ac1bb9f

Browse files
committed
[cdac] Add a CodeVersions contract
The CodeVersions contract implements the IL and native code versioning as described in [code-versioning.md](docs/design/features/code-versioning.md) Contributes to dotnet#108553 Contributes to dotnet#99302 * rename contract NativeCodePointers => CodeVersions * FindActiveILCodeVersion * implement GetModuleLookupMapElement * FindActiveILCodeVersion/FindActiveNativeCodeVersion * il code version lookup table * remove AppDomain.CodeVersionManager from cdac * CodeVersionManager is basically a static class in the C++ side * NativeCodeVersionContract.GetSpecificNativeCodeVersion * checkpoint: start adding NativeCodeVersion operations * WIP: native code version
1 parent 1b8f744 commit ac1bb9f

File tree

21 files changed

+686
-3
lines changed

21 files changed

+686
-3
lines changed
+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Contract CodeVersions
2+
3+
This contract encapsulates support for [code versioning](../features/code-versioning.md) in the runtime.
4+
5+
## APIs of contract
6+
7+
```csharp
8+
internal struct NativeCodeVersionHandle
9+
{
10+
// no public constructors
11+
internal readonly TargetPointer MethodDescAddress;
12+
internal readonly TargetPointer CodeVersionNodeAddress;
13+
internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress)
14+
{
15+
if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null)
16+
{
17+
throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null");
18+
}
19+
MethodDescAddress = methodDescAddress;
20+
CodeVersionNodeAddress = codeVersionNodeAddress;
21+
}
22+
23+
internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null);
24+
public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null;
25+
}
26+
```
27+
28+
```csharp
29+
// Return a handle to the version of the native code that includes the given instruction pointer
30+
public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip);
31+
// Return a handle to the active version of the native code for a given method descriptor
32+
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc);
33+
34+
// returns true if the given method descriptor supports multiple code versions
35+
public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc);
36+
37+
// Return the instruction pointer corresponding to the start of the given native code version
38+
public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle);
39+
```
40+
41+
## Version 1
42+
43+
Data descriptors used:
44+
| Data Descriptor Name | Field | Meaning |
45+
| --- | --- | --- |
46+
| MethodDescVersioningState | ? | ? |
47+
| NativeCodeVersionNode | ? | ? |
48+
| ILCodeVersioningState | ? | ? |
49+
50+
51+
Global variables used:
52+
| Global Name | Type | Purpose |
53+
| --- | --- | --- |
54+
55+
Contracts used:
56+
| Contract Name |
57+
| --- |
58+
| ExecutionManager |
59+
| Loader |
60+
| RuntimeTypeSystem |
61+
62+
### Finding the start of a specific native code version
63+
64+
```csharp
65+
NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip)
66+
{
67+
Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager;
68+
EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip);
69+
if (!info.HasValue)
70+
{
71+
return NativeCodeVersionHandle.Invalid;
72+
}
73+
TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value);
74+
if (methodDescAddress == TargetPointer.Null)
75+
{
76+
return NativeCodeVersionHandle.Invalid;
77+
}
78+
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
79+
MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress);
80+
if (!rts.IsVersionable(md))
81+
{
82+
return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null);
83+
}
84+
else
85+
{
86+
TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value);
87+
return GetSpecificNativeCodeVersion(md, startAddress);
88+
}
89+
}
90+
91+
private NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress)
92+
{
93+
TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md);
94+
if (methodDescVersioningStateAddress == TargetPointer.Null)
95+
{
96+
return NativeCodeVersionHandle.Invalid;
97+
}
98+
Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd<Data.MethodDescVersioningState>(methodDescVersioningStateAddress);
99+
// CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc, PCODE startAddress)
100+
return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) =>
101+
{
102+
return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress;
103+
});
104+
}
105+
106+
private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func<Data.NativeCodeVersionNode, bool> predicate)
107+
{
108+
// NativeCodeVersion::Next, heavily inlined
109+
TargetPointer currentAddress = versioningState.NativeCodeVersionNode;
110+
while (currentAddress != TargetPointer.Null)
111+
{
112+
Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd<Data.NativeCodeVersionNode>(currentAddress);
113+
if (predicate(current))
114+
{
115+
return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress);
116+
}
117+
currentAddress = current.Next;
118+
}
119+
return NativeCodeVersionHandle.Invalid;
120+
}
121+
```
122+
123+
### Finding the active native code version of a method descriptor
124+
125+
```csharp
126+
NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc)
127+
{
128+
// CodeVersionManager::GetActiveILCodeVersion
129+
// then ILCodeVersion::GetActiveNativeCodeVersion
130+
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
131+
MethodDescHandle md = rts.GetMethodDescHandle(methodDesc);
132+
TargetPointer mtAddr = rts.GetMethodTable(md);
133+
TypeHandle typeHandle = rts.GetTypeHandle(mtAddr);
134+
TargetPointer module = rts.GetModule(typeHandle);
135+
uint methodDefToken = rts.GetMethodToken(md);
136+
ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken);
137+
if (!methodDefActiveVersion.IsValid)
138+
{
139+
return NativeCodeVersionHandle.Invalid;
140+
}
141+
return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc);
142+
}
143+
```
144+
145+
**FIXME**
146+
147+
### Determining whether a method descriptor supports code versioning
148+
149+
**TODO**

docs/design/datacontracts/Loader.md

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ TargetPointer GetLoaderAllocator(ModuleHandle handle);
3838
TargetPointer GetThunkHeap(ModuleHandle handle);
3939
TargetPointer GetILBase(ModuleHandle handle);
4040
ModuleLookupTables GetLookupTables(ModuleHandle handle);
41+
TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags);
4142
```
4243

4344
## Version 1
@@ -58,6 +59,9 @@ Data descriptors used:
5859
| `Module` | `TypeDefToMethodTableMap` | Mapping table |
5960
| `Module` | `TypeRefToMethodTableMap` | Mapping table |
6061
| `ModuleLookupMap` | `TableData` | Start of the mapping table's data |
62+
| `ModuleLookupMap` | `SupportedFlagsMask` | Mask for flag bits on lookup map entries |
63+
| `ModuleLookupMap` | `Count` | Number of TargetPointer sized entries in this section of the map |
64+
| `ModuleLookupMap` | `Next` | Pointer to next ModuleLookupMap segment for this map
6165

6266
``` csharp
6367
ModuleHandle GetModuleHandle(TargetPointer modulePointer)
@@ -110,3 +114,5 @@ ModuleLookupTables GetLookupTables(ModuleHandle handle)
110114
Module::MethodDefToILCodeVersioningState */));
111115
}
112116
```
117+
118+
**TODO* pseudocode for IsCollectibleLoaderAllocator and LookupTableMap element lookup

docs/design/datacontracts/RuntimeTypeSystem.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ partial interface IRuntimeTypeSystem : IContract
138138
// Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime
139139
// A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod
140140
public virtual bool IsILStub(MethodDescHandle methodDesc);
141+
142+
// Return true if a MethodDesc is in a collectible module
143+
public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc);
144+
145+
// Return true if a MethodDesc supports mulitiple code versions
146+
public virtual bool IsVersionable(MethodDescHandle methodDesc);
147+
148+
// Return a pointer to the IL versioning state of the MethodDesc
149+
public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc);
150+
151+
// Get an instruction pointer that can be called to cause the MethodDesc to be executed
152+
public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc);
153+
141154
}
142155
```
143156

@@ -607,6 +620,7 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t
607620
| `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. |
608621
| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` |
609622

623+
**TODO** MethodDesc code pointers additions
610624

611625
In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table
612626
will typically have multiple chunks. There are subkinds of MethodDescs at runtime of varying sizes (but the sizes must be mutliples of `MethodDescAlignment`) and each chunk contains method descriptors of the same size.
@@ -631,6 +645,15 @@ We depend on the following data descriptors:
631645
| `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` |
632646
| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc |
633647

648+
**TODO** MethodDesc code pointers additions
649+
650+
The contract depends on the following other contracts
651+
652+
| Contract |
653+
| --- |
654+
| Loader |
655+
| ReJIT |
656+
| CodeVersions |
634657

635658
And the following enumeration definitions
636659

@@ -821,4 +844,4 @@ And the various apis are implemented with the following algorithms
821844
return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub);
822845
}
823846
```
824-
**TODO(cdac)**
847+
**TODO(cdac)** additional code pointers methods on MethodDesc

src/coreclr/debug/runtimeinfo/contracts.jsonc

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// cdac-build-tool can take multiple "-c contract_file" arguments
1010
// so to conditionally include contracts, put additional contracts in a separate file
1111
{
12+
"CodeVersions": 1,
1213
"DacStreams": 1,
1314
"EcmaMetadata" : 1,
1415
"Exception": 1,

src/coreclr/debug/runtimeinfo/datadescriptor.h

+24
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ CDAC_TYPE_END(Module)
238238

239239
CDAC_TYPE_BEGIN(ModuleLookupMap)
240240
CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, TableData, offsetof(LookupMapBase, pTable))
241+
CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, Next, offsetof(LookupMapBase, pNext))
242+
CDAC_TYPE_FIELD(ModuleLookupMap, /*uint32*/, Count, offsetof(LookupMapBase, dwCount))
243+
CDAC_TYPE_FIELD(ModuleLookupMap, /*nuint*/, SupportedFlagsMask, offsetof(LookupMapBase, supportedFlags))
241244
CDAC_TYPE_END(ModuleLookupMap)
242245

243246
// RuntimeTypeSystem
@@ -344,6 +347,12 @@ CDAC_TYPE_BEGIN(CodePointer)
344347
CDAC_TYPE_SIZE(sizeof(PCODE))
345348
CDAC_TYPE_END(CodePointer)
346349

350+
CDAC_TYPE_BEGIN(MethodDescVersioningState)
351+
CDAC_TYPE_INDETERMINATE(MethodDescVersioningState)
352+
CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, cdac_data<MethodDescVersioningState>::NativeCodeVersionNode)
353+
CDAC_TYPE_FIELD(MethodDescVersioningState, /*uint8*/, Flags, cdac_data<MethodDescVersioningState>::Flags)
354+
CDAC_TYPE_END(MethodDescVersioningState)
355+
347356
CDAC_TYPE_BEGIN(RangeSectionMap)
348357
CDAC_TYPE_INDETERMINATE(RangeSectionMap)
349358
CDAC_TYPE_FIELD(RangeSectionMap, /*pointer*/, TopLevelData, cdac_data<RangeSectionMap>::TopLevelData)
@@ -381,6 +390,21 @@ CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, MapBase, offsetof(HeapList, mapBa
381390
CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, HeaderMap, offsetof(HeapList, pHdrMap))
382391
CDAC_TYPE_END(CodeHeapListNode)
383392

393+
CDAC_TYPE_BEGIN(ILCodeVersioningState)
394+
CDAC_TYPE_INDETERMINATE(ILCodeVersioningState)
395+
CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, Node, cdac_data<ILCodeVersioningState>::Node)
396+
CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionKind, cdac_data<ILCodeVersioningState>::ActiveVersionKind)
397+
CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionNode, cdac_data<ILCodeVersioningState>::ActiveVersionNode)
398+
CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionModule, cdac_data<ILCodeVersioningState>::ActiveVersionModule)
399+
CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionMethodDef, cdac_data<ILCodeVersioningState>::ActiveVersionMethodDef)
400+
CDAC_TYPE_END(ILCodeVersioningState)
401+
402+
CDAC_TYPE_BEGIN(NativeCodeVersionNode)
403+
CDAC_TYPE_INDETERMINATE(NativeCodeVersionNode)
404+
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, Next, cdac_data<NativeCodeVersionNode>::Next)
405+
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data<NativeCodeVersionNode>::MethodDesc)
406+
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, NativeCode, cdac_data<NativeCodeVersionNode>::NativeCode)
407+
CDAC_TYPE_END(NativeCodeVersionNode)
384408
CDAC_TYPES_END()
385409

386410
CDAC_GLOBALS_BEGIN()

src/coreclr/vm/codeversion.h

+34-1
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,10 @@ class ILCodeVersion
248248
mdMethodDef m_methodDef;
249249
} m_synthetic;
250250
};
251-
};
252251

252+
// cDAC accesses fields via ILCodeVersioningState.m_activeVersion
253+
template<typename T> friend struct ::cdac_data;
254+
};
253255

254256
class NativeCodeVersionNode
255257
{
@@ -316,6 +318,16 @@ class NativeCodeVersionNode
316318
IsActiveChildFlag = 1
317319
};
318320
DWORD m_flags;
321+
322+
template<typename T> friend struct ::cdac_data;
323+
};
324+
325+
template<>
326+
struct cdac_data<NativeCodeVersionNode>
327+
{
328+
static constexpr size_t Next = offsetof(NativeCodeVersionNode, m_pNextMethodDescSibling);
329+
static constexpr size_t MethodDesc = offsetof(NativeCodeVersionNode, m_pMethodDesc);
330+
static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode);
319331
};
320332

321333
class NativeCodeVersionCollection
@@ -473,6 +485,15 @@ class MethodDescVersioningState
473485
BYTE m_flags;
474486
NativeCodeVersionId m_nextId;
475487
PTR_NativeCodeVersionNode m_pFirstVersionNode;
488+
489+
template<typename T> friend struct ::cdac_data;
490+
};
491+
492+
template<>
493+
struct cdac_data<MethodDescVersioningState>
494+
{
495+
static constexpr size_t NativeCodeVersionNode = offsetof(MethodDescVersioningState, m_pFirstVersionNode);
496+
static constexpr size_t Flags = offsetof(MethodDescVersioningState, m_flags);
476497
};
477498

478499
class ILCodeVersioningState
@@ -505,6 +526,18 @@ class ILCodeVersioningState
505526
PTR_ILCodeVersionNode m_pFirstVersionNode;
506527
PTR_Module m_pModule;
507528
mdMethodDef m_methodDef;
529+
530+
template<typename T> friend struct ::cdac_data;
531+
};
532+
533+
template<>
534+
struct cdac_data<ILCodeVersioningState>
535+
{
536+
static constexpr size_t Node = offsetof(ILCodeVersioningState, m_pFirstVersionNode);
537+
static constexpr size_t ActiveVersionKind = offsetof(ILCodeVersioningState, m_activeVersion.m_storageKind);
538+
static constexpr size_t ActiveVersionNode = offsetof(ILCodeVersioningState, m_activeVersion.m_pVersionNode);
539+
static constexpr size_t ActiveVersionModule = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_pModule);
540+
static constexpr size_t ActiveVersionMethodDef = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_methodDef);
508541
};
509542

510543
class CodeVersionManager

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

+4
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ internal abstract class ContractRegistry
4343
/// Gets an instance of the ExecutionManager contract for the target.
4444
/// </summary>
4545
public abstract IExecutionManager ExecutionManager { get; }
46+
/// <summary>
47+
/// Gets an instance of the CodeVersions contract for the target.
48+
/// </summary>
49+
public abstract ICodeVersions CodeVersions { get; }
4650
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
7+
8+
internal interface ICodeVersions : IContract
9+
{
10+
static string IContract.Name { get; } = nameof(CodeVersions);
11+
12+
public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) => throw new NotImplementedException();
13+
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc) => throw new NotImplementedException();
14+
15+
public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException();
16+
17+
public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException();
18+
19+
}
20+
21+
internal struct NativeCodeVersionHandle
22+
{
23+
// no public constructors
24+
internal readonly TargetPointer MethodDescAddress;
25+
internal readonly TargetPointer CodeVersionNodeAddress;
26+
internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress)
27+
{
28+
if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null)
29+
{
30+
throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null");
31+
}
32+
MethodDescAddress = methodDescAddress;
33+
CodeVersionNodeAddress = codeVersionNodeAddress;
34+
}
35+
36+
internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null);
37+
public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null;
38+
39+
}
40+
41+
internal readonly struct CodeVersions : ICodeVersions
42+
{
43+
// throws NotImplementedException for all methods
44+
}

0 commit comments

Comments
 (0)