[cDAC] Implement GetTieredVersions for cDAC#126164
[cDAC] Implement GetTieredVersions for cDAC#126164barosiak wants to merge 2 commits intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
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 exposeEEConfig+ debugger control globals needed for tier classification. - Implement
SOSDacImpl.GetTieredVersionsusing the new contract APIs and map contract tiers to the legacyDacpTieredVersionData.OptimizationTiervalues. - 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; |
There was a problem hiding this comment.
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.
| : (NativeCodeVersionOptimizationTier) nativeCodeVersionNode.OptimizationTier; | |
| : (NativeCodeVersionOptimizationTier) nativeCodeVersionNode.OptimizationTier.Value; |
|
|
||
| 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); |
There was a problem hiding this comment.
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.
| MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); | ||
| MethodImplAttributes implAttrs = methodDef.ImplAttributes; | ||
| isMiNoOptimization = (implAttrs & MethodImplAttributes.NoOptimization) != 0; | ||
| } | ||
| else if (!isNoMetadata) |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
We do not care about these things.
| 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); |
There was a problem hiding this comment.
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.
| return TargetPointer.Null; | ||
| } | ||
|
|
||
| NativeCodeVersionOptimizationTier IRuntimeTypeSystem.GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) |
There was a problem hiding this comment.
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.
| { | ||
| if (methodDesc == 0 || cNativeCodeAddrs == 0 || pcNativeCodeAddrs == null) | ||
| { | ||
| return HResults.E_INVALIDARG; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
| 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); |
There was a problem hiding this comment.
Add validation for the data in the array
|
|
||
| *pcNativeCodeAddrs = 0; | ||
| int hr = HResults.S_OK; | ||
| #if FEATURE_REJIT |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
runtime/src/coreclr/vm/comcallablewrapper.h
Line 752 in 67495bc
| 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 |
There was a problem hiding this comment.
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
|
Please update the docs for the contracts you updated |
| return !isNoMetadata && isMiNoOptimization; | ||
| } | ||
|
|
||
| private bool IsJitOptimizationDisabledForAllMethodsInChunk(MethodDescHandle methodDescHandle) |
There was a problem hiding this comment.
| 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); |
|
#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? |
Summary
Implement
ISOSDacInterface5.GetTieredVersionsfor 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
OptimizationTieronNativeCodeVersionNodeandMethodDescCodeData, addEEConfigdata type (JitMinOpts,GenDebuggable,TieredCompilation_DefaultTier), and exposeCORDebuggerControlFlagsglobal pointer.Contracts & abstractions:
ICodeVersions—GetOptimizationTier,NativeCodeVersionOptimizationTierenumIRuntimeTypeSystem—GetMethodDescOptimizationTier,GetInitialOptimizationTier,IsEligibleForTieredCompilation,IsJitOptimizationDisabled,DebuggerControlFlagenumILoader—IsReadyToRun,GetDebuggerInfoBits,ProfilerDisableOptflag,DebuggerAssemblyControlFlagsenumLegacy layer: Full
SOSDacImpl.GetTieredVersionsimplementation with newly addedDacpTieredVersionDatastruct and#if DEBUGvalidation against the legacy impl.Data: New
EEConfig.cs, optionalOptimizationTierfield inMethodDescCodeDataandNativeCodeVersionNode.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.