Skip to content

[cDAC] Implement GetTieredVersions for cDAC#126164

Open
barosiak wants to merge 2 commits intodotnet:mainfrom
barosiak:barosiak/GetTieredVersions
Open

[cDAC] Implement GetTieredVersions for cDAC#126164
barosiak wants to merge 2 commits intodotnet:mainfrom
barosiak:barosiak/GetTieredVersions

Conversation

@barosiak
Copy link
Member

Summary

Implement ISOSDacInterface5.GetTieredVersions for the cDAC. Each native code version is classified into the correct optimization tier by checking R2R image bounds, tiered-compilation eligibility, and JIT-optimization-disabled state.

Changes

Native (C++): Expose OptimizationTier on NativeCodeVersionNode and MethodDescCodeData, add EEConfig data type (JitMinOpts, GenDebuggable, TieredCompilation_DefaultTier), and expose CORDebuggerControlFlags global pointer.

Contracts & abstractions:

  • ICodeVersionsGetOptimizationTier, NativeCodeVersionOptimizationTier enum
  • IRuntimeTypeSystemGetMethodDescOptimizationTier, GetInitialOptimizationTier, IsEligibleForTieredCompilation, IsJitOptimizationDisabled, DebuggerControlFlag enum
  • ILoaderIsReadyToRun, GetDebuggerInfoBits, ProfilerDisableOpt flag, DebuggerAssemblyControlFlags enum

Legacy layer: Full SOSDacImpl.GetTieredVersions implementation with newly added DacpTieredVersionData struct and #if DEBUG validation against the legacy impl.

Data: New EEConfig.cs, optional OptimizationTier field in MethodDescCodeData and NativeCodeVersionNode.

Test Results

Four new parameterized tests covering explicit handles (all tier values + missing field fallback) and synthetic handles (RTS delegation + initial-tier fallback). Verified end-to-end manually.

@barosiak barosiak requested review from max-charlamb and rcj1 March 26, 2026 19:07
@barosiak barosiak self-assigned this Mar 26, 2026
Copilot AI review requested due to automatic review settings March 26, 2026 19:07
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements ISOSDacInterface5.GetTieredVersions in the cDAC by exposing optimization-tier data through contracts/metadata and using it to classify native code versions (R2R vs tiered compilation tiers vs minopts/optimized fallbacks).

Changes:

  • Add optimization tier plumbing to contracts/data models (NativeCodeVersionNode, MethodDescCodeData) and expose EEConfig + debugger control globals needed for tier classification.
  • Implement SOSDacImpl.GetTieredVersions using the new contract APIs and map contract tiers to the legacy DacpTieredVersionData.OptimizationTier values.
  • Add unit tests for ICodeVersions.GetOptimizationTier (explicit/synthetic handles + missing-field fallback).

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.CodeVersions.cs Extend mock NativeCodeVersionNode layout to optionally include OptimizationTier.
src/native/managed/cdac/tests/CodeVersionsTests.cs Add parameterized tests for ICodeVersions.GetOptimizationTier behaviors.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs Implement ISOSDacInterface5.GetTieredVersions using cDAC contracts and tier mapping logic.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs Introduce DacpTieredVersionData struct and update GetTieredVersions signature to use it.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs Add optional OptimizationTier field reading from the target.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs Add optional OptimizationTier field reading from the target.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs New data model for reading EEConfig tiering/debug/minopts settings from the target.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs Add tier-related RTS APIs: per-method tier, initial tier, eligibility, and JIT-opt-disabled detection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs Add IsReadyToRun and GetDebuggerInfoBits, plus module flag plumbing for profiler opt-disable.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs Add ICodeVersions.GetOptimizationTier implementation (explicit + synthetic).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs Expose new globals: CORDebuggerControlFlags and EEConfig.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs Add DataType.EEConfig.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs Add tiering/JIT-opt-disabled APIs and DebuggerControlFlag enum.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs Add R2R + debugger-info APIs and new module flags/DebuggerAssemblyControlFlags.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs Add GetOptimizationTier API and NativeCodeVersionOptimizationTier enum.
src/coreclr/vm/eeconfig.h Export EEConfig fields via cdac_data<EEConfig> for contract reading.
src/coreclr/vm/datadescriptor/datadescriptor.inc Add CDAC descriptors for EEConfig and new globals/fields (OptimizationTier, EEConfig, CORDebuggerControlFlags).
src/coreclr/vm/codeversion.h Export NativeCodeVersionNode::m_optTier offset via cdac_data.

NativeCodeVersionNode nativeCodeVersionNode = _target.ProcessedData.GetOrAdd<NativeCodeVersionNode>(codeVersionHandle.CodeVersionNodeAddress);
return nativeCodeVersionNode.OptimizationTier is null
? NativeCodeVersionOptimizationTier.OptimizationTierUnknown
: (NativeCodeVersionOptimizationTier) nativeCodeVersionNode.OptimizationTier;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit-handle branch is casting a nullable uint (OptimizationTier is uint?) directly to NativeCodeVersionOptimizationTier. This won’t compile; use the nullable’s Value (after the null check) when performing the cast.

Suggested change
: (NativeCodeVersionOptimizationTier) nativeCodeVersionNode.OptimizationTier;
: (NativeCodeVersionOptimizationTier) nativeCodeVersionNode.OptimizationTier.Value;

Copilot uses AI. Check for mistakes.
Comment on lines +1654 to +1670

Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd<Data.MethodDescCodeData>(codeDataAddress);
return codeData.OptimizationTier is null
? NativeCodeVersionOptimizationTier.OptimizationTierUnknown
: (NativeCodeVersionOptimizationTier)codeData.OptimizationTier;
}

bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
return methodDesc.IsEligibleForTieredCompilation;
}

private bool IsJitOptimizationDisabledForSpecificMethod(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
MethodTable methodTable = GetOrCreateMethodTable(methodDesc);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsJitOptimizationDisabledForSpecificMethod throws when metadata isn’t available (mdReader is null) for non-dynamic methods. In dumps/minidumps this can happen and would cause GetTieredVersions to fail; native behavior treats missing metadata as ‘no NoOptimization flag’ rather than throwing. Consider returning false (or otherwise falling back) instead of throwing here.

Copilot uses AI. Check for mistakes.
Comment on lines +1679 to +1683
MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
MethodImplAttributes implAttrs = methodDef.ImplAttributes;
isMiNoOptimization = (implAttrs & MethodImplAttributes.NoOptimization) != 0;
}
else if (!isNoMetadata)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These reads assume the new globals (CORDebuggerControlFlags / EEConfig) are always present in the target’s contract metadata. ReadGlobalPointer throws if the global isn’t available, which would break tier classification on older runtimes/dumps. Consider using TryReadGlobalPointer (and a safe default) so IsJitOptimizationDisabled can degrade gracefully when the globals aren’t exposed.

Copilot uses AI. Check for mistakes.
Comment on lines +1699 to +1710
Data.EEConfig eeConfig = _target.ProcessedData.GetOrAdd<Data.EEConfig>(eeConfigPtr);

bool corDebuggerAllowJITOpts = debuggerInfoBits.HasFlag(DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS)
|| (corDebuggerControlFlags.HasFlag(DebuggerControlFlag.DBCF_ALLOW_JIT_OPT)
&& !debuggerInfoBits.HasFlag(DebuggerAssemblyControlFlags.DACF_USER_OVERRIDE));
bool profilerDisabledOptimizations = _target.Contracts.Loader.GetFlags(moduleHandle).HasFlag(ModuleFlags.ProfilerDisableOpt);
bool areJITOptimizationsDisabled = !corDebuggerAllowJITOpts || profilerDisabledOptimizations;

return eeConfig.JitMinOpts || eeConfig.GenDebuggable || areJITOptimizationsDisabled;
}

bool IRuntimeTypeSystem.IsJitOptimizationDisabled(MethodDescHandle methodDescHandle)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetInitialOptimizationTier also unconditionally reads the EEConfig global via ReadGlobalPointer/ReadPointer. If the EEConfig global isn’t present in a target’s contract metadata (older runtime/dump), this will throw and bubble up through GetOptimizationTier/GetTieredVersions. Consider TryReadGlobalPointer + fallback (e.g., Optimized or Unknown) to keep the API usable across contract versions.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not care about these things.

Comment on lines +4809 to +4824
int ISOSDacInterface5.GetTieredVersions(ClrDataAddress methodDesc, int rejitId, DacpTieredVersionData* nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs)
{
if (methodDesc == 0 || cNativeCodeAddrs == 0 || pcNativeCodeAddrs == null)
{
return HResults.E_INVALIDARG;
}

*pcNativeCodeAddrs = 0;
int hr = HResults.S_OK;
#if FEATURE_REJIT
try
{
ILoader loader = _target.Contracts.Loader;
ICodeVersions codeVersions = _target.Contracts.CodeVersions;
IReJIT rejitContract = _target.Contracts.ReJIT;
TargetPointer methodDescPtr = methodDesc.ToTargetPointer(_target);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a full ISOSDacInterface5.GetTieredVersions implementation, but there don’t appear to be any unit tests covering the COM surface behavior (tier mapping, R2R detection, truncation/S_FALSE, invalid rejitId). Given there are existing SOSDacInterface* tests in this repo, adding focused tests for GetTieredVersions would help prevent regressions and ensure parity with the legacy DAC mapping logic.

Copilot uses AI. Check for mistakes.
Comment on lines 1645 to +1648
return TargetPointer.Null;
}

NativeCodeVersionOptimizationTier IRuntimeTypeSystem.GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In GetMethodDescOptimizationTier just above this block, the code returns (NativeCodeVersionOptimizationTier)codeData.OptimizationTier, but OptimizationTier is a uint? on MethodDescCodeData. Casting a nullable numeric directly to a non-nullable enum won’t compile; cast codeData.OptimizationTier.Value after the null check.

Copilot uses AI. Check for mistakes.
{
if (methodDesc == 0 || cNativeCodeAddrs == 0 || pcNativeCodeAddrs == null)
{
return HResults.E_INVALIDARG;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls enclose in try-catch and throw an ArgumentException

#region ISOSDacInterface5
int ISOSDacInterface5.GetTieredVersions(ClrDataAddress methodDesc, int rejitId, /*struct DacpTieredVersionData*/ void* nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs)
=> _legacyImpl5 is not null ? _legacyImpl5.GetTieredVersions(methodDesc, rejitId, nativeCodeAddrs, cNativeCodeAddrs, pcNativeCodeAddrs) : HResults.E_NOTIMPL;
int ISOSDacInterface5.GetTieredVersions(ClrDataAddress methodDesc, int rejitId, DacpTieredVersionData* nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int ISOSDacInterface5.GetTieredVersions(ClrDataAddress methodDesc, int rejitId, DacpTieredVersionData* nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs)
int ISOSDacInterface5.GetTieredVersions(ClrDataAddress methodDesc, int rejitId, [In, MarshalUsing(CountElementName = nameof(cNativeCodeAddrs)), Out] DacpTieredVersionData[]? nativeCodeAddrs, int cNativeCodeAddrs, int* pcNativeCodeAddrs)

Change the signature as well to use array marshaling

if (_legacyImpl5 is not null)
{
int hrLocal = _legacyImpl5.GetTieredVersions(methodDesc, rejitId, nativeCodeAddrs, cNativeCodeAddrs, pcNativeCodeAddrs);
Debug.ValidateHResult(hr, hrLocal);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add validation for the data in the array


*pcNativeCodeAddrs = 0;
int hr = HResults.S_OK;
#if FEATURE_REJIT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work. We want to ensure that our contracts only exist when they are relevant, so wrap the contract in an ifdef like in my PR https://github.com/rcj1/runtime/blob/777e93de97a96f0f34c5c31b0be08a68a38b2e91/src/coreclr/vm/datadescriptor/datadescriptor.inc#L1068, and then try to grab the contract. If the feature is not active, you will get a NotImplementedException which you can catch and then just return early.

public enum DebuggerAssemblyControlFlags
{
DACF_USER_OVERRIDE = 0x01,
DACF_ALLOW_JIT_OPTS = 0x02,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding these flags that mirror enums, please add comments in the native enum of the format [cDAC] [insert whichever contract depends on it] : cDAC depends on this value. See

Slot_Basic = 0, // [cDAC] [BuiltInCOM]: Contract depends on this value

Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits
EditAndContinue = 0x8, // Edit and Continue is enabled for this module
ReflectionEmit = 0x40, // Reflection.Emit was used to create this module
ProfilerDisableOpt = 0x80, // Profiler disabled JIT optimizations when module was loaded
Copy link
Contributor

@rcj1 rcj1 Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with the enum. Since these are not included in the data descriptor we have to ensure that anyone who decides to change these also changes the cDAC contract

@rcj1
Copy link
Contributor

rcj1 commented Mar 26, 2026

Please update the docs for the contracts you updated

return !isNoMetadata && isMiNoOptimization;
}

private bool IsJitOptimizationDisabledForAllMethodsInChunk(MethodDescHandle methodDescHandle)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DebuggerAssemblyControlFlags ILoader.GetDebuggerInfoBits(ModuleHandle handle)
{
Data.Module module = _target.ProcessedData.GetOrAdd<Data.Module>(handle.Address);
return (DebuggerAssemblyControlFlags)((module.Flags & DEBUGGER_INFO_MASK_PRIV) >> DEBUGGER_INFO_SHIFT_PRIV);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be in an enum

@jkotas
Copy link
Member

jkotas commented Mar 27, 2026

#119879 has earlier abandoned version of this change. It has discussion about needing to refactor the runtime implementation before it gets exposed via cDAC. Are we no longer planning to do that?

@rcj1
Copy link
Contributor

rcj1 commented Mar 27, 2026

#119879 has earlier abandoned version of this change. It has discussion about needing to refactor the runtime implementation before it gets exposed via cDAC. Are we no longer planning to do that?

#125243

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants