Skip to content

Commit

Permalink
[cdac] Add a CodeVersions contract
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lambdageek committed Oct 17, 2024
1 parent 1b8f744 commit ac1bb9f
Show file tree
Hide file tree
Showing 21 changed files with 686 additions and 3 deletions.
149 changes: 149 additions & 0 deletions docs/design/datacontracts/CodeVersions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Contract CodeVersions

This contract encapsulates support for [code versioning](../features/code-versioning.md) in the runtime.

## APIs of contract

```csharp
internal struct NativeCodeVersionHandle
{
// no public constructors
internal readonly TargetPointer MethodDescAddress;
internal readonly TargetPointer CodeVersionNodeAddress;
internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress)
{
if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null)
{
throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null");
}
MethodDescAddress = methodDescAddress;
CodeVersionNodeAddress = codeVersionNodeAddress;
}

internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null);
public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null;
}
```

```csharp
// Return a handle to the version of the native code that includes the given instruction pointer
public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip);
// Return a handle to the active version of the native code for a given method descriptor
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc);

// returns true if the given method descriptor supports multiple code versions
public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc);

// Return the instruction pointer corresponding to the start of the given native code version
public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle);
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| MethodDescVersioningState | ? | ? |
| NativeCodeVersionNode | ? | ? |
| ILCodeVersioningState | ? | ? |


Global variables used:
| Global Name | Type | Purpose |
| --- | --- | --- |

Contracts used:
| Contract Name |
| --- |
| ExecutionManager |
| Loader |
| RuntimeTypeSystem |

### Finding the start of a specific native code version

```csharp
NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip)
{
Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager;
EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip);
if (!info.HasValue)
{
return NativeCodeVersionHandle.Invalid;
}
TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value);
if (methodDescAddress == TargetPointer.Null)
{
return NativeCodeVersionHandle.Invalid;
}
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress);
if (!rts.IsVersionable(md))
{
return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null);
}
else
{
TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value);
return GetSpecificNativeCodeVersion(md, startAddress);
}
}

private NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress)
{
TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md);
if (methodDescVersioningStateAddress == TargetPointer.Null)
{
return NativeCodeVersionHandle.Invalid;
}
Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd<Data.MethodDescVersioningState>(methodDescVersioningStateAddress);
// CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc, PCODE startAddress)
return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) =>
{
return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress;
});
}

private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func<Data.NativeCodeVersionNode, bool> predicate)
{
// NativeCodeVersion::Next, heavily inlined
TargetPointer currentAddress = versioningState.NativeCodeVersionNode;
while (currentAddress != TargetPointer.Null)
{
Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd<Data.NativeCodeVersionNode>(currentAddress);
if (predicate(current))
{
return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress);
}
currentAddress = current.Next;
}
return NativeCodeVersionHandle.Invalid;
}
```

### Finding the active native code version of a method descriptor

```csharp
NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc)
{
// CodeVersionManager::GetActiveILCodeVersion
// then ILCodeVersion::GetActiveNativeCodeVersion
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
MethodDescHandle md = rts.GetMethodDescHandle(methodDesc);
TargetPointer mtAddr = rts.GetMethodTable(md);
TypeHandle typeHandle = rts.GetTypeHandle(mtAddr);
TargetPointer module = rts.GetModule(typeHandle);
uint methodDefToken = rts.GetMethodToken(md);
ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken);
if (!methodDefActiveVersion.IsValid)
{
return NativeCodeVersionHandle.Invalid;
}
return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc);
}
```

**FIXME**

### Determining whether a method descriptor supports code versioning

**TODO**
6 changes: 6 additions & 0 deletions docs/design/datacontracts/Loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ TargetPointer GetLoaderAllocator(ModuleHandle handle);
TargetPointer GetThunkHeap(ModuleHandle handle);
TargetPointer GetILBase(ModuleHandle handle);
ModuleLookupTables GetLookupTables(ModuleHandle handle);
TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags);
```

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

``` csharp
ModuleHandle GetModuleHandle(TargetPointer modulePointer)
Expand Down Expand Up @@ -110,3 +114,5 @@ ModuleLookupTables GetLookupTables(ModuleHandle handle)
Module::MethodDefToILCodeVersioningState */));
}
```

**TODO* pseudocode for IsCollectibleLoaderAllocator and LookupTableMap element lookup
25 changes: 24 additions & 1 deletion docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ partial interface IRuntimeTypeSystem : IContract
// Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime
// A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod
public virtual bool IsILStub(MethodDescHandle methodDesc);

// Return true if a MethodDesc is in a collectible module
public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc);

// Return true if a MethodDesc supports mulitiple code versions
public virtual bool IsVersionable(MethodDescHandle methodDesc);

// Return a pointer to the IL versioning state of the MethodDesc
public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc);

// Get an instruction pointer that can be called to cause the MethodDesc to be executed
public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc);

}
```

Expand Down Expand Up @@ -607,6 +620,7 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t
| `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. |
| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` |

**TODO** MethodDesc code pointers additions

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
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.
Expand All @@ -631,6 +645,15 @@ We depend on the following data descriptors:
| `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` |
| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc |

**TODO** MethodDesc code pointers additions

The contract depends on the following other contracts

| Contract |
| --- |
| Loader |
| ReJIT |
| CodeVersions |

And the following enumeration definitions

Expand Down Expand Up @@ -821,4 +844,4 @@ And the various apis are implemented with the following algorithms
return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub);
}
```
**TODO(cdac)**
**TODO(cdac)** additional code pointers methods on MethodDesc
1 change: 1 addition & 0 deletions src/coreclr/debug/runtimeinfo/contracts.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// cdac-build-tool can take multiple "-c contract_file" arguments
// so to conditionally include contracts, put additional contracts in a separate file
{
"CodeVersions": 1,
"DacStreams": 1,
"EcmaMetadata" : 1,
"Exception": 1,
Expand Down
24 changes: 24 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ CDAC_TYPE_END(Module)

CDAC_TYPE_BEGIN(ModuleLookupMap)
CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, TableData, offsetof(LookupMapBase, pTable))
CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, Next, offsetof(LookupMapBase, pNext))
CDAC_TYPE_FIELD(ModuleLookupMap, /*uint32*/, Count, offsetof(LookupMapBase, dwCount))
CDAC_TYPE_FIELD(ModuleLookupMap, /*nuint*/, SupportedFlagsMask, offsetof(LookupMapBase, supportedFlags))
CDAC_TYPE_END(ModuleLookupMap)

// RuntimeTypeSystem
Expand Down Expand Up @@ -344,6 +347,12 @@ CDAC_TYPE_BEGIN(CodePointer)
CDAC_TYPE_SIZE(sizeof(PCODE))
CDAC_TYPE_END(CodePointer)

CDAC_TYPE_BEGIN(MethodDescVersioningState)
CDAC_TYPE_INDETERMINATE(MethodDescVersioningState)
CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, cdac_data<MethodDescVersioningState>::NativeCodeVersionNode)
CDAC_TYPE_FIELD(MethodDescVersioningState, /*uint8*/, Flags, cdac_data<MethodDescVersioningState>::Flags)
CDAC_TYPE_END(MethodDescVersioningState)

CDAC_TYPE_BEGIN(RangeSectionMap)
CDAC_TYPE_INDETERMINATE(RangeSectionMap)
CDAC_TYPE_FIELD(RangeSectionMap, /*pointer*/, TopLevelData, cdac_data<RangeSectionMap>::TopLevelData)
Expand Down Expand Up @@ -381,6 +390,21 @@ CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, MapBase, offsetof(HeapList, mapBa
CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, HeaderMap, offsetof(HeapList, pHdrMap))
CDAC_TYPE_END(CodeHeapListNode)

CDAC_TYPE_BEGIN(ILCodeVersioningState)
CDAC_TYPE_INDETERMINATE(ILCodeVersioningState)
CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, Node, cdac_data<ILCodeVersioningState>::Node)
CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionKind, cdac_data<ILCodeVersioningState>::ActiveVersionKind)
CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionNode, cdac_data<ILCodeVersioningState>::ActiveVersionNode)
CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionModule, cdac_data<ILCodeVersioningState>::ActiveVersionModule)
CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionMethodDef, cdac_data<ILCodeVersioningState>::ActiveVersionMethodDef)
CDAC_TYPE_END(ILCodeVersioningState)

CDAC_TYPE_BEGIN(NativeCodeVersionNode)
CDAC_TYPE_INDETERMINATE(NativeCodeVersionNode)
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, Next, cdac_data<NativeCodeVersionNode>::Next)
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data<NativeCodeVersionNode>::MethodDesc)
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, NativeCode, cdac_data<NativeCodeVersionNode>::NativeCode)
CDAC_TYPE_END(NativeCodeVersionNode)
CDAC_TYPES_END()

CDAC_GLOBALS_BEGIN()
Expand Down
35 changes: 34 additions & 1 deletion src/coreclr/vm/codeversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,10 @@ class ILCodeVersion
mdMethodDef m_methodDef;
} m_synthetic;
};
};

// cDAC accesses fields via ILCodeVersioningState.m_activeVersion
template<typename T> friend struct ::cdac_data;
};

class NativeCodeVersionNode
{
Expand Down Expand Up @@ -316,6 +318,16 @@ class NativeCodeVersionNode
IsActiveChildFlag = 1
};
DWORD m_flags;

template<typename T> friend struct ::cdac_data;
};

template<>
struct cdac_data<NativeCodeVersionNode>
{
static constexpr size_t Next = offsetof(NativeCodeVersionNode, m_pNextMethodDescSibling);
static constexpr size_t MethodDesc = offsetof(NativeCodeVersionNode, m_pMethodDesc);
static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode);
};

class NativeCodeVersionCollection
Expand Down Expand Up @@ -473,6 +485,15 @@ class MethodDescVersioningState
BYTE m_flags;
NativeCodeVersionId m_nextId;
PTR_NativeCodeVersionNode m_pFirstVersionNode;

template<typename T> friend struct ::cdac_data;
};

template<>
struct cdac_data<MethodDescVersioningState>
{
static constexpr size_t NativeCodeVersionNode = offsetof(MethodDescVersioningState, m_pFirstVersionNode);
static constexpr size_t Flags = offsetof(MethodDescVersioningState, m_flags);
};

class ILCodeVersioningState
Expand Down Expand Up @@ -505,6 +526,18 @@ class ILCodeVersioningState
PTR_ILCodeVersionNode m_pFirstVersionNode;
PTR_Module m_pModule;
mdMethodDef m_methodDef;

template<typename T> friend struct ::cdac_data;
};

template<>
struct cdac_data<ILCodeVersioningState>
{
static constexpr size_t Node = offsetof(ILCodeVersioningState, m_pFirstVersionNode);
static constexpr size_t ActiveVersionKind = offsetof(ILCodeVersioningState, m_activeVersion.m_storageKind);
static constexpr size_t ActiveVersionNode = offsetof(ILCodeVersioningState, m_activeVersion.m_pVersionNode);
static constexpr size_t ActiveVersionModule = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_pModule);
static constexpr size_t ActiveVersionMethodDef = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_methodDef);
};

class CodeVersionManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ internal abstract class ContractRegistry
/// Gets an instance of the ExecutionManager contract for the target.
/// </summary>
public abstract IExecutionManager ExecutionManager { get; }
/// <summary>
/// Gets an instance of the CodeVersions contract for the target.
/// </summary>
public abstract ICodeVersions CodeVersions { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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.Contracts;

internal interface ICodeVersions : IContract
{
static string IContract.Name { get; } = nameof(CodeVersions);

public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) => throw new NotImplementedException();
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc) => throw new NotImplementedException();

public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException();

public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException();

}

internal struct NativeCodeVersionHandle
{
// no public constructors
internal readonly TargetPointer MethodDescAddress;
internal readonly TargetPointer CodeVersionNodeAddress;
internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress)
{
if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null)
{
throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null");
}
MethodDescAddress = methodDescAddress;
CodeVersionNodeAddress = codeVersionNodeAddress;
}

internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null);
public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null;

}

internal readonly struct CodeVersions : ICodeVersions
{
// throws NotImplementedException for all methods
}
Loading

0 comments on commit ac1bb9f

Please sign in to comment.