From 4b2125fd0f978babb5bff8d58375d9894348085b Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 27 May 2021 15:41:11 -0700 Subject: [PATCH] JIT: add option to choose guarded devirt class randomly Add a config setting to randomly choose one of the observed classes for guarded devirtualization, rather than the most likely class. --- src/coreclr/jit/compiler.h | 6 ++ src/coreclr/jit/importer.cpp | 32 +++++++-- src/coreclr/jit/inline.cpp | 9 ++- src/coreclr/jit/inline.h | 2 +- src/coreclr/jit/jitconfigvalues.h | 1 + src/coreclr/jit/likelyclass.cpp | 112 +++++++++++++++++++++++++++++- 6 files changed, 151 insertions(+), 11 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 2272aa126886c..3a8389e795635 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5742,6 +5742,12 @@ class Compiler void fgIncorporateBlockCounts(); void fgIncorporateEdgeCounts(); + CORINFO_CLASS_HANDLE getRandomClass(ICorJitInfo::PgoInstrumentationSchema* schema, + UINT32 countSchemaItems, + BYTE* pInstrumentationData, + int32_t ilOffset, + CLRRandom* random); + public: const char* fgPgoFailReason; bool fgPgoDisabled; diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index d96773def7f1b..9e3d75836154d 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1,4 +1,3 @@ - // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. @@ -21645,8 +21644,31 @@ void Compiler::considerGuardedDevirtualization( unsigned likelihood = 0; unsigned numberOfClasses = 0; - CORINFO_CLASS_HANDLE likelyClass = - getLikelyClass(fgPgoSchema, fgPgoSchemaCount, fgPgoData, ilOffset, &likelihood, &numberOfClasses); + CORINFO_CLASS_HANDLE likelyClass = NO_CLASS_HANDLE; + + bool doRandomDevirt = false; + +#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()); + likelihood = 100; + numberOfClasses = 1; + likelyClass = getRandomClass(fgPgoSchema, fgPgoSchemaCount, fgPgoData, ilOffset, random); + } + else +#endif + { + likelyClass = getLikelyClass(fgPgoSchema, fgPgoSchemaCount, fgPgoData, ilOffset, &likelihood, &numberOfClasses); + } if (likelyClass == NO_CLASS_HANDLE) { @@ -21654,8 +21676,8 @@ void Compiler::considerGuardedDevirtualization( return; } - JITDUMP("Likely class for %p (%s) is %p (%s) [likelihood:%u classes seen:%u]\n", dspPtr(objClass), objClassName, - likelyClass, eeGetClassName(likelyClass), likelihood, numberOfClasses); + JITDUMP("%s class for %p (%s) is %p (%s) [likelihood:%u classes seen:%u]\n", doRandomDevirt ? "Random" : "Likely", + dspPtr(objClass), objClassName, likelyClass, eeGetClassName(likelyClass), likelihood, numberOfClasses); // Todo: a more advanced heuristic using likelihood, number of // classes, and the profile count for this block. diff --git a/src/coreclr/jit/inline.cpp b/src/coreclr/jit/inline.cpp index 828ad60767660..b2db02dcd25c6 100644 --- a/src/coreclr/jit/inline.cpp +++ b/src/coreclr/jit/inline.cpp @@ -1665,6 +1665,9 @@ void InlineStrategy::FinalizeXml(FILE* file) //------------------------------------------------------------------------ // GetRandom: setup or access random state // +// Arguments: +// seed -- seed value to use if not doing random inlines +// // Return Value: // New or pre-existing random state. // @@ -1673,11 +1676,11 @@ void InlineStrategy::FinalizeXml(FILE* file) // specified externally (via stress or policy setting) and partially // specified internally via method hash. -CLRRandom* InlineStrategy::GetRandom() +CLRRandom* InlineStrategy::GetRandom(int optionalSeed) { if (m_Random == nullptr) { - int externalSeed = 0; + int externalSeed = optionalSeed; #ifdef DEBUG @@ -1707,6 +1710,8 @@ CLRRandom* InlineStrategy::GetRandom() int seed = externalSeed ^ internalSeed; + JITDUMP("\n*** Using random seed ext(%u) ^ int(%u) = %u\n", externalSeed, internalSeed, seed); + m_Random = new (m_Compiler, CMK_Inlining) CLRRandom(); m_Random->Init(seed); } diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index e1fb23e80c791..cfa8de36cabff 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -912,7 +912,7 @@ class InlineStrategy } // Set up or access random state (for use by RandomPolicy) - CLRRandom* GetRandom(); + CLRRandom* GetRandom(int optionalSeed = 0); #endif // defined(DEBUG) || defined(INLINE_DATA) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index a5937939a6006..8037e33d7da27 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -469,6 +469,7 @@ CONFIG_INTEGER(JitGuardedDevirtualizationChainLikelihood, W("JitGuardedDevirtual CONFIG_INTEGER(JitGuardedDevirtualizationChainStatements, W("JitGuardedDevirtualizationChainStatements"), 4) #if defined(DEBUG) CONFIG_STRING(JitGuardedDevirtualizationRange, W("JitGuardedDevirtualizationRange")) +CONFIG_INTEGER(JitRandomGuardedDevirtualization, W("JitRandomGuardedDevirtualization"), 0) #endif // DEBUG // Enable insertion of patchpoints into Tier0 methods with loops. diff --git a/src/coreclr/jit/likelyclass.cpp b/src/coreclr/jit/likelyclass.cpp index af5ead05ad883..c1cd25572d1bd 100644 --- a/src/coreclr/jit/likelyclass.cpp +++ b/src/coreclr/jit/likelyclass.cpp @@ -33,7 +33,6 @@ struct LikelyClassHistogramEntry }; // Summarizes a ClassProfile table by forming a Histogram - // struct LikelyClassHistogram { @@ -54,6 +53,13 @@ struct LikelyClassHistogram } }; +//------------------------------------------------------------------------ +// LikelyClassHistogram::LikelyClassHistgram: construct a new histogram +// +// Arguments: +// histogramEntries - pointer to the table portion of a ClassProfile* object (see corjit.h) +// entryCount - number of entries in the table to examine +// LikelyClassHistogram::LikelyClassHistogram(INT_PTR* histogramEntries, unsigned entryCount) { m_unknownTypes = 0; @@ -97,8 +103,29 @@ LikelyClassHistogram::LikelyClassHistogram(INT_PTR* histogramEntries, unsigned e } } -// This is used by the devirtualization logic below, and by crossgen2 when producing the R2R image (to reduce the size -// cost of carrying the type histogram) +//------------------------------------------------------------------------ +// getLikelyClass: find class profile data for an IL offset, and return the most likely class +// +// Arguments: +// schema - profile schema +// countSchemaItems - number of items in the schema +// pInstrumentationData - associated data +// ilOffset - il offset of the callvirt +// pLikelihood - [OUT] likelihood of observing that entry [0...100] +// pNumberOfClasses - [OUT] estimated number of classes seen at runtime +// +// Returns: +// Class handle for the most likely class, or nullptr +// +// Notes: +// A "monomorphic" call site will return likelihood 100 and number of entries = 1. +// +// This is used by the devirtualization logic below, and by crossgen2 when producing +// the R2R image (to reduce the sizecost of carrying the type histogram) +// +// This code can runs without a jit instance present, so JITDUMP and related +// cannot be used. +// extern "C" DLLEXPORT CORINFO_CLASS_HANDLE WINAPI getLikelyClass(ICorJitInfo::PgoInstrumentationSchema* schema, UINT32 countSchemaItems, BYTE* pInstrumentationData, @@ -220,3 +247,82 @@ extern "C" DLLEXPORT CORINFO_CLASS_HANDLE WINAPI getLikelyClass(ICorJitInfo::Pgo // return NULL; } + +//------------------------------------------------------------------------ +// getRandomClass: find class profile data for an IL offset, and return +// one of the possible classes at random +// +// Arguments: +// schema - profile schema +// countSchemaItems - number of items in the schema +// pInstrumentationData - associated data +// ilOffset - il offset of the callvirt +// random - randomness generator +// +// Returns: +// Randomly observed class, or nullptr. +// +CORINFO_CLASS_HANDLE Compiler::getRandomClass(ICorJitInfo::PgoInstrumentationSchema* schema, + UINT32 countSchemaItems, + BYTE* pInstrumentationData, + int32_t ilOffset, + CLRRandom* random) +{ + if (schema == nullptr) + { + return NO_CLASS_HANDLE; + } + + for (COUNT_T i = 0; i < countSchemaItems; i++) + { + if (schema[i].ILOffset != (int32_t)ilOffset) + { + continue; + } + + if ((schema[i].InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::GetLikelyClass) && + (schema[i].Count == 1)) + { + INT_PTR result = *(INT_PTR*)(pInstrumentationData + schema[i].Offset); + if (ICorJitInfo::IsUnknownTypeHandle(result)) + { + return NO_CLASS_HANDLE; + } + else + { + return (CORINFO_CLASS_HANDLE)result; + } + } + + bool isHistogramCount = + (schema[i].InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::TypeHandleHistogramIntCount) || + (schema[i].InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::TypeHandleHistogramLongCount); + + if (isHistogramCount && (schema[i].Count == 1) && ((i + 1) < countSchemaItems) && + (schema[i + 1].InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::TypeHandleHistogramTypeHandle)) + { + // Form a histogram + // + LikelyClassHistogram h((INT_PTR*)(pInstrumentationData + schema[i + 1].Offset), schema[i + 1].Count); + + if (h.countHistogramElements == 0) + { + return NO_CLASS_HANDLE; + } + + // Choose an entry at random. + // + unsigned randomEntryIndex = random->Next(0, h.countHistogramElements - 1); + LikelyClassHistogramEntry randomEntry = h.HistogramEntryAt(randomEntryIndex); + + if (ICorJitInfo::IsUnknownTypeHandle(randomEntry.m_mt)) + { + return NO_CLASS_HANDLE; + } + + return (CORINFO_CLASS_HANDLE)randomEntry.m_mt; + } + } + + return NO_CLASS_HANDLE; +}