Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cDAC] Implement ReJIT portion of SOSDacImpl::GetMethodDescData #109936

Merged
merged 11 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 81 additions & 116 deletions docs/design/datacontracts/CodeVersions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,48 @@ This contract encapsulates support for [code versioning](../features/code-versio

## APIs of contract

```csharp
internal readonly struct ILCodeVersionHandle
{
public static ILCodeVersionHandle Invalid;

public bool IsValid;
}
```

```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;

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

```csharp
// Return a handle to the active version of the IL code for a given method descriptor
public virtual ILCodeVersionHandle GetActiveILCodeVersion(TargetPointer methodDesc);
// Return a handle to the IL code version representing the given native code version
public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle codeVersionHandle);
// Return all of the IL code versions for a given method descriptor
public virtual IEnumerable<ILCodeVersionHandle> GetILCodeVersions(TargetPointer methodDesc);

// 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);
// Return a handle to the active version of the native code for a given method descriptor and IL code version. The IL code version and method descriptor must represent the same method
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle);

// 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);
```
### Extension Methods
```csharp
// Return a handle to the active version of the native code for a given method descriptor
public static NativeCodeVersionHandle GetActiveNativeCodeVersion(this ICodeVersions, TargetPointer methodDesc);
```

## Version 1

Expand All @@ -52,11 +61,13 @@ Data descriptors used:
| NativeCodeVersionNode | NativeCode | indicates an explicit native code version node |
| NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below |
| NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL code version |
| ILCodeVersioningState | FirstVersionNode | pointer to the first `ILCodeVersionNode` |
| ILCodeVersioningState | ActiveVersionKind | an `ILCodeVersionKind` value indicating which fields of the active version are value |
| ILCodeVersioningState | ActiveVersionNode | if the active version is explicit, the NativeCodeVersionNode for the active version |
| ILCodeVersioningState | ActiveVersionModule | if the active version is synthetic or unknown, the pointer to the Module that defines the method |
| ILCodeVersioningState | ActiveVersionMethodDef | if the active version is synthetic or unknown, the MethodDef token for the method |
| ILCodeVersionNode | VersionId | Version ID of the node |
| ILCodeVersionNode | Next | Pointer to the next `ILCodeVersionNode`|

The flag indicates that the default version of the code for a method desc is active:
```csharp
Expand Down Expand Up @@ -93,6 +104,51 @@ Contracts used:
| Loader |
| RuntimeTypeSystem |

### Finding active ILCodeVersion for a method
```csharp
public virtual ILCodeVersionHandle GetActiveILCodeVersion(TargetPointer methodDesc);
```
1. Check if the method has an `ILCodeVersioningState`.
2. If the method does not have an `ILCodeVersioningState`, the synthetic ILCodeVersion must be active. Return the synthetic ILCodeVersion for the method.
3. Otherwise, read the active ILCodeVersion off of the `ILCodeVersioningState`.

### Finding ILCodeVersion from a NativeCodeVersion
```csharp
public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle nativeCodeVersionHandle);
```
1. If `nativeCodeVersionHandle` is invalid, return an invalid `ILCodeVersionHandle`.
2. If `nativeCodeVersionHandle` is synthetic, the corresponding ILCodeVersion must also be synthetic; return the synthetic ILCodeVersion for the method.
3. Search the linked list of ILCodeVersions for one with the matching ILVersionId. Return the ILCodeVersion if found. Otherwise return invalid.

### Finding all of the ILCodeVersions for a method
```csharp
IEnumerable<ILCodeVersionHandle> ICodeVersions.GetILCodeVersions(TargetPointer methodDesc)
{
// CodeVersionManager::GetILCodeVersions
GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken);

ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);

// always add the synthetic version
yield return new ILCodeVersionHandle(module, methodDefToken, TargetPointer.Null);

// if explicit versions exist, iterate linked list and return them
if (ilVersionStateAddress != TargetPointer.Null)
{
Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd<Data.ILCodeVersioningState>(ilVersionStateAddress);
TargetPointer nodePointer = ilState.FirstVersionNode;
while (nodePointer != TargetPointer.Null)
{
Data.ILCodeVersionNode current = _target.ProcessedData.GetOrAdd<Data.ILCodeVersionNode>(nodePointer);
yield return new ILCodeVersionHandle(TargetPointer.Null, 0, nodePointer);
nodePointer = current.Next;
}
}
}
```

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

```csharp
Expand All @@ -113,7 +169,7 @@ NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointe
MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress);
if (!rts.IsVersionable(md))
{
return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null);
return NativeCodeVersion.OfSynthetic(methodDescAddress);
}
else
{
Expand All @@ -128,7 +184,7 @@ NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, Target
TargetCodePointer firstNativeCode = rts.GetNativeCode(md);
if (firstNativeCode == startAddress)
{
NativeCodeVersionHandle first = new NativeCodeVersionHandle(md.Address, TargetPointer.Null);
NativeCodeVersionHandle first = NativeCodeVersionHandle.OfSynthetic(md.Address);
return first;
}

Expand All @@ -154,114 +210,23 @@ NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescH
Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd<Data.NativeCodeVersionNode>(currentAddress);
if (predicate(current))
{
return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress);
return NativeCodeVersionHandle.OfExplicit(currentAddress);
}
currentAddress = current.Next;
}
return NativeCodeVersionHandle.Invalid;
}
```

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

### Finding the active native code version of an ILCodeVersion for a method descriptor
```csharp
NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc)
{
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);
}

ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState)
{
switch ((ILCodeVersionKind)ilState.ActiveVersionKind)
{
case ILCodeVersionKind.Explicit:
return new ILCodeVersionHandle(module: TargetPointer.Null, methodDef: 0, ilState.ActiveVersionNode);
case ILCodeVersionKind.Synthetic:
case ILCodeVersionKind.Unknown:
return new ILCodeVersionHandle(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef, TargetPointer.Null);
default:
throw new InvalidOperationException($"Unknown ILCodeVersionKind {ilState.ActiveVersionKind}");
}
}

ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition)
{
ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefinition, out var _);
if (ilVersionStateAddress == TargetPointer.Null)
{
return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null);
}
Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd<Data.ILCodeVersioningState>(ilVersionStateAddress);
return ILCodeVersionHandleFromState(ilState);
}

bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion)
{
if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null)
{
MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress);
TargetPointer versioningStateAddress = _target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md);
if (versioningStateAddress == TargetPointer.Null)
{
return true;
}
Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd<Data.MethodDescVersioningState>(versioningStateAddress);
MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags;
return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag);
}
else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null)
{
uint flags = _target.Read<uint>(nativeCodeVersion.CodeVersionNodeAddress + /* NativeCodVersionNode::Flags offset*/)
return ((NativeCodeVersionNodeFlags)flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild);
}
else
{
throw new ArgumentException("Invalid NativeCodeVersionHandle");
}
}

NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress)
{
TargetNUInt? ilVersionId = default;
if (methodDefActiveVersion.Module != TargetPointer.Null)
{
NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null);
if (IsActiveNativeCodeVersion(provisionalHandle))
{
return provisionalHandle;
}
}
else
{
// Get the explicit IL code version
Debug.Assert(methodDefActiveVersion.ILCodeVersionNode != TargetPointer.Null);
ilVersionId = _target.ReadNUint(methodDefActiveVersion.ILCodeVersionNode + /* ILCodeVersionNode::VersionId offset */);
}

// Iterate through versioning state nodes and return the active one, matching any IL code version
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress);
return FindFirstCodeVersion(rts, md, (codeVersion) =>
{
return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value)
&& ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild);
});
}
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle);
```

1. If `ilCodeVersionHandle` is invalid, return invalid.
2. If `ilCodeVersionHandle` is synthetic, the active native code version could be synthetic. Check if the method's synthetic NativeCodeVersion is active. If it is, return that NativeCodeVersion.
3. Search the linked list of NativeCodeVersions for one with the active flag and the relevent ILVersionId. If found return that node. Otherwise return invalid.

### Determining whether a method descriptor supports code versioning

```csharp
Expand Down
84 changes: 84 additions & 0 deletions docs/design/datacontracts/ReJIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@ This contract encapsulates support for [ReJIT](../features/code-versioning.md) i

## APIs of contract

```csharp
public enum RejitState
{
Requested,
Active
}
```

```csharp
bool IsEnabled();

RejitState GetRejitState(ILCodeVersionHandle codeVersionHandle);

TargetNUInt GetRejitId(ILCodeVersionHandle codeVersionHandle);

IEnumerable<TargetNUInt> GetRejitIds(TargetPointer methodDesc)
```

## Version 1
Expand All @@ -14,6 +28,8 @@ Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| ProfControlBlock | GlobalEventMask | an `ICorProfiler` `COR_PRF_MONITOR` value |
| ILCodeVersionNode | VersionId | `ILCodeVersion` ReJIT ID
| ILCodeVersionNode | RejitState | a `RejitFlags` value |

Global variables used:
| Global Name | Type | Purpose |
Expand All @@ -23,6 +39,7 @@ Global variables used:
Contracts used:
| Contract Name |
| --- |
| CodeVersions |

```csharp
// see src/coreclr/inc/corprof.idl
Expand All @@ -32,6 +49,21 @@ private enum COR_PRF_MONITOR
COR_PRF_ENABLE_REJIT = 0x00040000,
}

// see src/coreclr/vm/codeversion.h
[Flags]
public enum RejitFlags : uint
{
kStateRequested = 0x00000000,

kStateGettingReJITParameters = 0x00000001,

kStateActive = 0x00000002,

kStateMask = 0x0000000F,

kSuppressParams = 0x80000000
}

bool IsEnabled()
{
TargetPointer address = target.ReadGlobalPointer("ProfilerControlBlock");
Expand All @@ -40,4 +72,56 @@ bool IsEnabled()
bool clrConfigEnabledReJit = /* host process does not have environment variable DOTNET_ProfAPI_ReJitOnAttach set to 0 */;
return profEnabledReJIT || clrConfigEnabledReJIT;
}

RejitState GetRejitState(ILCodeVersionHandle codeVersion)
{
// ILCodeVersion::GetRejitState
if (codeVersion is not explicit)
{
// for non explicit ILCodeVersions, ReJITState is always kStateActive
return RejitState.Active;
}
else
{
// ILCodeVersionNode::GetRejitState
ILCodeVersionNode codeVersionNode = AsNode(codeVersion);
return ((RejitFlags)ilCodeVersionNode.RejitState & RejitFlags.kStateMask) switch
{
RejitFlags.kStateRequested => RejitState.Requested,
RejitFlags.kStateActive => RejitState.Active,
_ => throw new NotImplementedException($"Unknown ReJIT state: {ilCodeVersionNode.RejitState}"),
};
}
}

TargetNUInt GetRejitId(ILCodeVersionHandle codeVersion)
{
// ILCodeVersion::GetVersionId
if (codeVersion is not explicit)
{
// for non explicit ILCodeVersions, ReJITId is always 0
return new TargetNUInt(0);
}
else
{
// ILCodeVersionNode::GetVersionId
ILCodeVersionNode codeVersionNode = AsNode(codeVersion);
return codeVersionNode.VersionId;
}
}

IEnumerable<TargetNUInt> GetRejitIds(TargetPointer methodDesc)
{
// ReJitManager::GetReJITIDs
ICodeVersions cv = _target.Contracts.CodeVersions;
IEnumerable<ILCodeVersionHandle> ilCodeVersions = cv.GetILCodeVersions(methodDesc);

foreach (ILCodeVersionHandle ilCodeVersionHandle in ilCodeVersions)
{
if (GetRejitState(ilCodeVersionHandle) == RejitState.Active)
{
yield return GetRejitId(ilCodeVersionHandle);
}
}
}
```
Loading
Loading