-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Rely on PGO for isinst/castclass #65922
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
Changes from all commits
1d0cb29
1d25f00
eaceff1
6b707f1
1d0a723
eab07db
cf570db
d4a83b3
52c3d04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2111,7 +2111,7 @@ GenTree* Compiler::impReadyToRunLookupToTree(CORINFO_CONST_LOOKUP* pLookup, | |
| // | ||
| bool Compiler::impIsCastHelperEligibleForClassProbe(GenTree* tree) | ||
| { | ||
| if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) || (JitConfig.JitCastProfiling() != 1)) | ||
| if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) || (JitConfig.JitProfileCasts() != 1)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
@@ -2138,21 +2138,22 @@ bool Compiler::impIsCastHelperEligibleForClassProbe(GenTree* tree) | |
| // Returns: | ||
| // true if the tree is a cast helper with potential profile data | ||
| // | ||
| bool Compiler::impIsCastHelperMayHaveProfileData(GenTree* tree) | ||
| bool Compiler::impIsCastHelperMayHaveProfileData(CorInfoHelpFunc helper) | ||
| { | ||
| if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT) || (JitConfig.JitCastProfiling() != 1)) | ||
| if (JitConfig.JitConsumeProfileForCasts() == 0) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| if (tree->IsCall() && tree->AsCall()->gtCallType == CT_HELPER) | ||
| if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT)) | ||
| { | ||
| const CorInfoHelpFunc helper = eeGetHelperNum(tree->AsCall()->gtCallMethHnd); | ||
| if ((helper == CORINFO_HELP_ISINSTANCEOFINTERFACE) || (helper == CORINFO_HELP_ISINSTANCEOFCLASS) || | ||
| (helper == CORINFO_HELP_CHKCASTCLASS) || (helper == CORINFO_HELP_CHKCASTINTERFACE)) | ||
| { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| if ((helper == CORINFO_HELP_ISINSTANCEOFINTERFACE) || (helper == CORINFO_HELP_ISINSTANCEOFCLASS) || | ||
| (helper == CORINFO_HELP_CHKCASTCLASS) || (helper == CORINFO_HELP_CHKCASTINTERFACE)) | ||
| { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
@@ -11602,7 +11603,7 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |
| // not worth creating an untracked local variable | ||
| shouldExpandInline = false; | ||
| } | ||
| else if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) && (JitConfig.JitCastProfiling() == 1)) | ||
| else if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) && (JitConfig.JitProfileCasts() == 1)) | ||
| { | ||
| // Optimizations are enabled but we're still instrumenting (including casts) | ||
| if (isCastClass && !impIsClassExact(pResolvedToken->hClass)) | ||
|
|
@@ -11615,8 +11616,11 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |
|
|
||
| // Pessimistically assume the jit cannot expand this as an inline test | ||
| bool canExpandInline = false; | ||
| bool partialExpand = false; | ||
| const CorInfoHelpFunc helper = info.compCompHnd->getCastingHelper(pResolvedToken, isCastClass); | ||
|
|
||
| GenTree* exactCls = nullptr; | ||
|
|
||
| // Legality check. | ||
| // | ||
| // Not all classclass/isinst operations can be inline expanded. | ||
|
|
@@ -11636,6 +11640,61 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |
| canExpandInline = impIsClassExact(pResolvedToken->hClass); | ||
| } | ||
| } | ||
|
|
||
| // Check if this cast helper have some profile data | ||
| if (impIsCastHelperMayHaveProfileData(helper)) | ||
| { | ||
| bool doRandomDevirt = false; | ||
| const int maxLikelyClasses = 32; | ||
| int likelyClassCount = 0; | ||
| LikelyClassRecord likelyClasses[maxLikelyClasses]; | ||
| #ifdef DEBUG | ||
| // Optional stress mode to pick a random known class, rather than | ||
| // the most likely known class. | ||
| doRandomDevirt = JitConfig.JitRandomGuardedDevirtualization() != 0; | ||
|
|
||
| if (doRandomDevirt) | ||
| { | ||
| // Reuse the random inliner's random state. | ||
| CLRRandom* const random = | ||
| impInlineRoot()->m_inlineStrategy->GetRandom(JitConfig.JitRandomGuardedDevirtualization()); | ||
| likelyClasses[0].clsHandle = getRandomClass(fgPgoSchema, fgPgoSchemaCount, fgPgoData, ilOffset, random); | ||
| likelyClasses[0].likelihood = 100; | ||
| if (likelyClasses[0].clsHandle != NO_CLASS_HANDLE) | ||
| { | ||
| likelyClassCount = 1; | ||
| } | ||
| } | ||
| else | ||
| #endif | ||
| { | ||
| likelyClassCount = getLikelyClasses(likelyClasses, maxLikelyClasses, fgPgoSchema, fgPgoSchemaCount, | ||
| fgPgoData, ilOffset); | ||
| } | ||
|
|
||
| if (likelyClassCount > 0) | ||
| { | ||
| LikelyClassRecord likelyClass = likelyClasses[0]; | ||
| CORINFO_CLASS_HANDLE likelyCls = likelyClass.clsHandle; | ||
|
|
||
| if ((likelyCls != NO_CLASS_HANDLE) && | ||
| (likelyClass.likelihood > (UINT32)JitConfig.JitGuardedDevirtualizationChainLikelihood())) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this rather get its own config variable?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just didn't want to produce even more variables 😄 and the default value for this one was OK to me. |
||
| { | ||
| if ((info.compCompHnd->compareTypesForCast(likelyCls, pResolvedToken->hClass) == | ||
| TypeCompareState::Must)) | ||
| { | ||
| assert((info.compCompHnd->getClassAttribs(likelyCls) & | ||
| (CORINFO_FLG_INTERFACE | CORINFO_FLG_ABSTRACT)) == 0); | ||
| JITDUMP("Adding \"is %s (%X)\" check as a fast path for %s using PGO data.\n", | ||
| eeGetClassName(likelyCls), likelyCls, isCastClass ? "castclass" : "isinst"); | ||
|
|
||
| canExpandInline = true; | ||
| partialExpand = true; | ||
| exactCls = gtNewIconEmbClsHndNode(likelyCls); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const bool expandInline = canExpandInline && shouldExpandInline; | ||
|
|
@@ -11687,13 +11746,13 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |
| // | ||
|
|
||
| GenTree* op2Var = op2; | ||
| if (isCastClass) | ||
| if (isCastClass && !partialExpand) | ||
| { | ||
| op2Var = fgInsertCommaFormTemp(&op2); | ||
| lvaTable[op2Var->AsLclVarCommon()->GetLclNum()].lvIsCSE = true; | ||
| } | ||
| temp = gtNewMethodTableLookup(temp); | ||
| condMT = gtNewOperNode(GT_NE, TYP_INT, temp, op2); | ||
| condMT = gtNewOperNode(GT_NE, TYP_INT, temp, exactCls != nullptr ? exactCls : op2); | ||
|
|
||
| GenTree* condNull; | ||
| // | ||
|
|
@@ -11718,7 +11777,12 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |
| // | ||
| const CorInfoHelpFunc specialHelper = CORINFO_HELP_CHKCASTCLASS_SPECIAL; | ||
|
|
||
| condTrue = gtNewHelperCallNode(specialHelper, TYP_REF, gtNewCallArgs(op2Var, gtClone(op1))); | ||
| condTrue = | ||
| gtNewHelperCallNode(specialHelper, TYP_REF, gtNewCallArgs(partialExpand ? op2 : op2Var, gtClone(op1))); | ||
| } | ||
| else if (partialExpand) | ||
| { | ||
| condTrue = gtNewHelperCallNode(helper, TYP_REF, gtNewCallArgs(op2, gtClone(op1))); | ||
| } | ||
| else | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading; | ||
|
|
||
| public interface IInterface { } | ||
| public interface IGenericInterface<T> { } | ||
| public class ClassA : IInterface { } | ||
| public class ClassB : ClassA { } | ||
| public class ClassC { } | ||
| public struct GenericStruct<T> : IGenericInterface<T> { } | ||
|
|
||
| public class Program | ||
| { | ||
| // 1. Cast to Class | ||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static ClassA CastToClassA(object o) => (ClassA)o; | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static GenericStruct<T> CastToGenericStruct<T>(object o) => (GenericStruct<T>)o; | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static int[] CastToArray(object o) => (int[])o; | ||
|
|
||
| // 2. Is Instance of Class | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static bool IsClassA(object o) => o is ClassA; | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static bool IsGenericStruct<T>(object o) => o is GenericStruct<T>; | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static bool IsArray(object o) => o is int[]; | ||
|
|
||
| // 3. Is Instance of Interface | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static bool IsIInterface(object o) => o is IInterface; | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static bool IsGenericInterface<T>(object o) => o is IGenericInterface<T>; | ||
|
|
||
| // 4. Cast to Interface | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static IInterface CastToIInterface(object o) => (IInterface)o; | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static IGenericInterface<T> CastToGenericInterface<T>(object o) => (IGenericInterface<T>)o; | ||
|
|
||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static void AssertThrows<T>(Action action, [CallerLineNumber] int line = 0) where T : Exception | ||
| { | ||
| try | ||
| { | ||
| action(); | ||
| } | ||
| catch (T) | ||
| { | ||
| return; | ||
| } | ||
| throw new InvalidOperationException("InvalidCastException was expected"); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| static bool AssertEquals<T>(T t1, T t2) | ||
| { | ||
| return EqualityComparer<T>.Default.Equals(t1, t2); | ||
| } | ||
|
|
||
| public static int Main() | ||
| { | ||
| var a = new ClassA(); | ||
| var b = new ClassB(); | ||
| var c = new ClassC(); | ||
| var gsInt = new GenericStruct<int>(); | ||
| var gsString = new GenericStruct<string>(); | ||
| var arrayOfUInt32 = new uint[100]; | ||
| var arrayOfInt32 = new int[100]; | ||
| var arrayOfString = new string[100]; | ||
|
|
||
| for (int i = 0; i < 200; i++) | ||
| { | ||
| AssertEquals(IsArray(arrayOfUInt32), true); | ||
| AssertEquals(IsArray(arrayOfInt32), true); | ||
| AssertEquals(IsArray(arrayOfString), false); | ||
| AssertEquals(IsArray(a), false); | ||
| AssertEquals(IsArray(b), false); | ||
| AssertEquals(IsArray(c), false); | ||
| AssertEquals(IsArray(gsInt), false); | ||
| AssertEquals(IsArray(gsString), false); | ||
| AssertEquals(IsArray(null), false); | ||
|
|
||
| AssertEquals(IsClassA(arrayOfUInt32), false); | ||
| AssertEquals(IsClassA(arrayOfInt32), false); | ||
| AssertEquals(IsClassA(arrayOfString), false); | ||
| AssertEquals(IsClassA(a), true); | ||
| AssertEquals(IsClassA(b), false); | ||
| AssertEquals(IsClassA(c), false); | ||
| AssertEquals(IsClassA(gsInt), false); | ||
| AssertEquals(IsClassA(gsString), false); | ||
| AssertEquals(IsClassA(null), false); | ||
|
|
||
| AssertEquals(IsGenericStruct<string>(arrayOfUInt32), false); | ||
| AssertEquals(IsGenericStruct<string>(arrayOfInt32), false); | ||
| AssertEquals(IsGenericStruct<string>(arrayOfString), false); | ||
| AssertEquals(IsGenericStruct<string>(a), true); | ||
| AssertEquals(IsGenericStruct<string>(b), false); | ||
| AssertEquals(IsGenericStruct<string>(c), false); | ||
| AssertEquals(IsGenericStruct<string>(gsInt), false); | ||
| AssertEquals(IsGenericStruct<string>(gsString), false); | ||
| AssertEquals(IsGenericStruct<int>(arrayOfUInt32), false); | ||
| AssertEquals(IsGenericStruct<int>(arrayOfInt32), false); | ||
| AssertEquals(IsGenericStruct<int>(arrayOfString), false); | ||
| AssertEquals(IsGenericStruct<int>(a), true); | ||
| AssertEquals(IsGenericStruct<int>(b), false); | ||
| AssertEquals(IsGenericStruct<int>(c), false); | ||
| AssertEquals(IsGenericStruct<int>(gsInt), false); | ||
| AssertEquals(IsGenericStruct<int>(null), false); | ||
|
|
||
| AssertEquals(IsGenericInterface<string>(arrayOfUInt32), false); | ||
| AssertEquals(IsGenericInterface<string>(arrayOfInt32), false); | ||
| AssertEquals(IsGenericInterface<string>(arrayOfString), false); | ||
| AssertEquals(IsGenericInterface<string>(a), true); | ||
| AssertEquals(IsGenericInterface<string>(b), false); | ||
| AssertEquals(IsGenericInterface<string>(c), false); | ||
| AssertEquals(IsGenericInterface<string>(gsInt), false); | ||
| AssertEquals(IsGenericInterface<string>(gsString), false); | ||
| AssertEquals(IsGenericInterface<int>(arrayOfUInt32), false); | ||
| AssertEquals(IsGenericInterface<int>(arrayOfInt32), false); | ||
| AssertEquals(IsGenericInterface<int>(arrayOfString), false); | ||
| AssertEquals(IsGenericInterface<int>(a), true); | ||
| AssertEquals(IsGenericInterface<int>(b), false); | ||
| AssertEquals(IsGenericInterface<int>(c), false); | ||
| AssertEquals(IsGenericInterface<int>(gsInt), false); | ||
| AssertEquals(IsGenericInterface<int>(gsString), false); | ||
| AssertEquals(IsGenericInterface<int>(null), false); | ||
|
|
||
| AssertEquals(IsIInterface(arrayOfUInt32), false); | ||
| AssertEquals(IsIInterface(arrayOfInt32), false); | ||
| AssertEquals(IsIInterface(arrayOfString), false); | ||
| AssertEquals(IsIInterface(a), true); | ||
| AssertEquals(IsIInterface(b), false); | ||
| AssertEquals(IsIInterface(c), false); | ||
| AssertEquals(IsIInterface(gsInt), false); | ||
| AssertEquals(IsIInterface(gsString), false); | ||
| AssertEquals(IsIInterface(null), false); | ||
|
|
||
| AssertThrows<InvalidCastException>(() => CastToClassA(gsInt)); | ||
| AssertThrows<InvalidCastException>(() => CastToClassA(gsString)); | ||
| AssertThrows<InvalidCastException>(() => CastToClassA(arrayOfUInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToClassA(arrayOfInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToClassA(arrayOfString)); | ||
|
|
||
| AssertThrows<InvalidCastException>(() => CastToArray(a)); | ||
| AssertThrows<InvalidCastException>(() => CastToArray(b)); | ||
| AssertThrows<InvalidCastException>(() => CastToArray(c)); | ||
| AssertThrows<InvalidCastException>(() => CastToArray(gsInt)); | ||
| AssertThrows<InvalidCastException>(() => CastToArray(gsString)); | ||
|
|
||
| AssertEquals(CastToIInterface(a), a); | ||
| AssertEquals(CastToIInterface(b), b); | ||
| AssertThrows<InvalidCastException>(() => CastToIInterface(c)); | ||
| AssertThrows<InvalidCastException>(() => CastToIInterface(gsInt)); | ||
| AssertThrows<InvalidCastException>(() => CastToIInterface(gsString)); | ||
| AssertThrows<InvalidCastException>(() => CastToIInterface(arrayOfUInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToIInterface(arrayOfInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToIInterface(arrayOfString)); | ||
|
|
||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(a)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(b)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(c)); | ||
| AssertEquals(CastToGenericInterface<int>(gsInt), gsInt); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(gsString)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(arrayOfUInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(arrayOfInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<int>(arrayOfString)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(a)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(b)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(c)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(gsInt)); | ||
| AssertEquals(CastToGenericInterface<string>(gsString), gsString); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(arrayOfUInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(arrayOfInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericInterface<string>(arrayOfString)); | ||
|
|
||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(a)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(b)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(c)); | ||
| AssertEquals(CastToGenericStruct<int>(gsInt), gsInt); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(gsString)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(arrayOfUInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(arrayOfInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<int>(arrayOfString)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(a)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(b)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(c)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(gsInt)); | ||
| AssertEquals(CastToGenericStruct<string>(gsString), gsString); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(arrayOfUInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(arrayOfInt32)); | ||
| AssertThrows<InvalidCastException>(() => CastToGenericStruct<string>(arrayOfString)); | ||
| AssertThrows<NullReferenceException>(() => CastToGenericStruct<string>(null)); | ||
|
|
||
| Thread.Sleep(20); | ||
| } | ||
| return 100; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.