@@ -7,15 +7,25 @@ namespace BenchmarkDotNet.Portability
77{
88 // Implementation is based on article https://medium.com/@meriffa/net-core-concepts-tiered-compilation-10f7da3a29c7
99 // documentation https://learn.microsoft.com/en-us/dotnet/core/runtime-config/compilation
10- // and source https://github.com/dotnet/runtime/blob/71c30b405516b1fe774a1bfdbc43cd804468568f/src/coreclr/vm/eeconfig.cpp
10+ // and source https://github.com/dotnet/runtime/blob/3fb6fbb3efaa7f2ae5e76bf235615d7f70005201/src/coreclr/vm/eeconfig.cpp
11+ // https://github.com/dotnet/runtime/blob/3fb6fbb3efaa7f2ae5e76bf235615d7f70005201/src/coreclr/jit/jitconfigvalues.h
1112 internal static class JitInfo
1213 {
13- public const string MinOptsEnv = "JITMinOpts" ;
14- public const string TieredCompilationEnv = "TieredCompilation" ;
15- public const string DynamicPGOEnv = "TieredPGO" ;
16- public const string AggressiveTieringEnv = "TC_AggressiveTiering" ;
17- public const string CallCountThresholdEnv = "TC_CallCountThreshold" ;
18- public const string CallCountingDelayMsEnv = "TC_CallCountingDelayMs" ;
14+ public const string EnvCallCountingDelayMs = "TC_CallCountingDelayMs" ;
15+
16+ private const string EnvMinOpts = "JITMinOpts" ;
17+ private const string EnvTieredCompilation = "TieredCompilation" ;
18+ private const string EnvQuickJit = "TC_QuickJit" ;
19+ private const string EnvPGO = "TieredPGO" ;
20+ private const string EnvCallCountThreshold = "TC_CallCountThreshold" ;
21+ private const string EnvAggressiveTiering = "TC_AggressiveTiering" ;
22+ private const string EnvOSR = "TC_OnStackReplacement" ;
23+
24+ private const string KnobTieredCompilation = "System.Runtime.TieredCompilation" ;
25+ private const string KnobQuickJit = "System.Runtime.TieredCompilation.QuickJit" ;
26+ private const string KnobPGO = "System.Runtime.TieredPGO" ;
27+ private const string KnobCallCountThreshold = "System.Runtime.TieredCompilation.CallCountThreshold" ;
28+ private const string KnobCallCountingDelayMs = "System.Runtime.TieredCompilation.CallCountingDelayMs" ;
1929
2030 // .Net 5 and older uses COMPlus_ prefix,
2131 // .Net 6+ uses DOTNET_ prefix, but still supports legacy COMPlus_.
@@ -40,34 +50,71 @@ private static bool IsKnobDisabled(string name)
4050 private static bool TryParseKnob ( string name , out int value )
4151 => int . TryParse ( AppContext . GetData ( name ) as string , out value ) ;
4252
53+ private static bool IsEnabled ( string envName , string knobName )
54+ => IsEnvVarEnabled ( envName ) || IsKnobEnabled ( knobName ) ;
55+
56+ private static bool IsDisabled ( string envName , string knobName )
57+ => IsEnvVarDisabled ( envName ) || IsKnobDisabled ( knobName ) ;
58+
4359 /// <summary>
4460 /// Is tiered JIT enabled?
4561 /// </summary>
4662 public static readonly bool IsTiered =
4763 IsNetCore
4864 // JITMinOpts disables tiered compilation (all methods are effectively tier0 instead of tier1).
49- && ! IsEnvVarEnabled ( MinOptsEnv )
65+ && ! IsEnvVarEnabled ( EnvMinOpts )
5066 && ( ( CoreRuntime . TryGetVersion ( out var version ) && version . Major >= 3 )
5167 // Enabled by default in netcoreapp3.0+, check if it's disabled.
52- ? ! IsEnvVarDisabled ( TieredCompilationEnv ) && ! IsKnobDisabled ( "System.Runtime.TieredCompilation" )
68+ ? ! IsDisabled ( EnvTieredCompilation , KnobTieredCompilation )
5369 // Disabled by default in netcoreapp2.X, check if it's enabled.
54- : IsEnvVarEnabled ( TieredCompilationEnv ) || IsKnobEnabled ( "System.Runtime.TieredCompilation" ) ) ;
70+ : IsEnabled ( EnvTieredCompilation , KnobTieredCompilation ) ) ;
5571
5672 /// <summary>
57- /// Is tiered JIT enabled with dynamic profile-guided optimization (tier0 instrumented)?
73+ /// The maximum numbers of jit tiers that a method may be promoted through. This is the maximum number of jit tiers - 1.
5874 /// </summary>
59- public static readonly bool IsDPGO =
60- IsTiered
61- // Added experimentally in .Net 6
62- && Environment . Version . Major >= 6
63- // Disabled if QuickJit is disabled in .Net 7+.
64- && ( Environment . Version . Major < 7 || ( ! IsEnvVarDisabled ( "TC_QuickJit" ) && ! IsKnobDisabled ( "System.Runtime.TieredCompilation.QuickJit" ) ) )
65- && ( Environment . Version . Major >= 8
66- // Enabled by default in .Net 8, check if it's disabled
67- ? ! IsEnvVarDisabled ( DynamicPGOEnv ) && ! IsKnobDisabled ( "System.Runtime.TieredPGO" )
68- // Disabled by default in earlier versions, check if it's enabled.
69- : IsEnvVarEnabled ( DynamicPGOEnv ) || IsKnobEnabled ( "System.Runtime.TieredPGO" ) ) ;
75+ public static readonly int MaxTierPromotions = GetMaxTierPromotions ( ) ;
7076
77+ private static int GetMaxTierPromotions ( )
78+ {
79+ if ( ! IsTiered )
80+ {
81+ return 0 ;
82+ }
83+ // Tier1
84+ int maxPromotions = 1 ;
85+ if ( GetIsDPGO ( ) )
86+ {
87+ // Tier0 instrumented
88+ ++ maxPromotions ;
89+ }
90+ if ( GetIsOSR ( ) )
91+ {
92+ // On-stack-replacement *shouldn't* interfere with promotion velocity, but there is a bug where OSR may cause a method to be tier0 instrumented twice.
93+ // https://github.com/dotnet/runtime/issues/117787#issuecomment-3090771091
94+ ++ maxPromotions ;
95+ }
96+ return maxPromotions ;
97+
98+ static bool GetIsDPGO ( ) =>
99+ // Added experimentally in .Net 6.
100+ Environment . Version . Major >= 6
101+ // Disabled if QuickJit is disabled in .Net 7+.
102+ && ( Environment . Version . Major < 7 || ! IsDisabled ( EnvQuickJit , KnobQuickJit ) )
103+ && ( Environment . Version . Major >= 8
104+ // Enabled by default in .Net 8, check if it's disabled.
105+ ? ! IsDisabled ( EnvPGO , KnobPGO )
106+ // Disabled by default in earlier versions, check if it's enabled.
107+ : IsEnabled ( EnvPGO , KnobPGO ) ) ;
108+
109+ static bool GetIsOSR ( ) =>
110+ // Added experimentally in .Net 5.
111+ Environment . Version . Major >= 5
112+ && ( Environment . Version . Major >= 7
113+ // Enabled by default in .Net 7, check if it's disabled.
114+ ? ! IsEnvVarDisabled ( EnvOSR )
115+ // Disabled by default in earlier versions, check if it's enabled.
116+ : IsEnvVarEnabled ( EnvOSR ) ) ;
117+ }
71118
72119 /// <summary>
73120 /// The number of times a method must be called before it will be eligible for the next JIT tier.
@@ -81,16 +128,16 @@ private static int GetTieredCallCountThreshold()
81128 return 0 ;
82129 }
83130 // AggressiveTiering was added in .Net 5.
84- if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( AggressiveTieringEnv ) )
131+ if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( EnvAggressiveTiering ) )
85132 {
86133 return 1 ;
87134 }
88- if ( TryParseEnvVar ( CallCountThresholdEnv , out int callCountThreshold ) )
135+ if ( TryParseEnvVar ( EnvCallCountThreshold , out int callCountThreshold ) )
89136 {
90137 return callCountThreshold ;
91138 }
92139 // CallCountThreshold was added as a knob in .Net 8.
93- if ( Environment . Version . Major >= 8 && TryParseKnob ( "System.Runtime.TieredCompilation.CallCountThreshold" , out callCountThreshold ) )
140+ if ( Environment . Version . Major >= 8 && TryParseKnob ( KnobCallCountThreshold , out callCountThreshold ) )
94141 {
95142 return callCountThreshold ;
96143 }
@@ -110,16 +157,16 @@ private static TimeSpan GetTieredDelay()
110157 return TimeSpan . Zero ;
111158 }
112159 // AggressiveTiering was added in .Net 5.
113- if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( AggressiveTieringEnv ) )
160+ if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( EnvAggressiveTiering ) )
114161 {
115162 return TimeSpan . Zero ;
116163 }
117- if ( TryParseEnvVar ( CallCountingDelayMsEnv , out int callCountDelay ) )
164+ if ( TryParseEnvVar ( EnvCallCountingDelayMs , out int callCountDelay ) )
118165 {
119166 return TimeSpan . FromMilliseconds ( callCountDelay ) ;
120167 }
121168 // CallCountingDelayMs was added as a knob in .Net 8.
122- if ( Environment . Version . Major >= 8 && TryParseKnob ( "System.Runtime.TieredCompilation.CallCountingDelayMs" , out callCountDelay ) )
169+ if ( Environment . Version . Major >= 8 && TryParseKnob ( KnobCallCountingDelayMs , out callCountDelay ) )
123170 {
124171 return TimeSpan . FromMilliseconds ( callCountDelay ) ;
125172 }
0 commit comments