Skip to content

Commit 1ba22eb

Browse files
authored
Allow jit to disregard PGO; PGO changes for SPMI and MCS (#49551)
* COMPlus_JitDisablePGO will now block the jit from using profile data. Useful for investigations and (someday) bug triage. Also disable GDV if we're disabling PGO * `-base_jit_option` and `-diff_jit_option` can be used to pass options to superpmy.py asmdiffs. * Fix SPMI file name computation to better handle cases where the directory is a relative path. * Enhance `mcs -jitflags` output to describe how many method contexts have PGO data, and which kinds of data they hold. Useful for validating that an SPMI collection done via dynamic PGO has actually collected an interesting set of jit requests.
1 parent 4e3deb5 commit 1ba22eb

File tree

11 files changed

+207
-53
lines changed

11 files changed

+207
-53
lines changed

src/coreclr/ToolBox/superpmi/mcs/verbjitflags.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ int verbJitFlags::DoWork(const char* nameOfInput)
2424
mc->repGetJitFlags(&corJitFlags, sizeof(corJitFlags));
2525
unsigned long long rawFlags = corJitFlags.GetFlagsRaw();
2626

27+
// We co-opt unused flag bits to note if there's pgo data,
28+
// and if so, what kind
29+
//
30+
bool hasEdgeProfile = false;
31+
bool hasClassProfile = false;
32+
if (mc->hasPgoData(hasEdgeProfile, hasClassProfile))
33+
{
34+
rawFlags |= 1ULL << (EXTRA_JIT_FLAGS::HAS_PGO);
35+
36+
if (hasEdgeProfile)
37+
{
38+
rawFlags |= 1ULL << (EXTRA_JIT_FLAGS::HAS_EDGE_PROFILE);
39+
}
40+
41+
if (hasClassProfile)
42+
{
43+
rawFlags |= 1ULL << (EXTRA_JIT_FLAGS::HAS_CLASS_PROFILE);
44+
}
45+
}
46+
2747
int index = flagMap.GetIndex(rawFlags);
2848
if (index == -1)
2949
{

src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6765,6 +6765,44 @@ int MethodContext::dumpMD5HashToBuffer(BYTE* pBuffer, int bufLen, char* hash, in
67656765
return m_hash.HashBuffer(pBuffer, bufLen, hash, hashLen);
67666766
}
67676767

6768+
bool MethodContext::hasPgoData(bool& hasEdgeProfile, bool& hasClassProfile)
6769+
{
6770+
hasEdgeProfile = false;
6771+
hasClassProfile = false;
6772+
6773+
// Obtain the Method Info structure for this method
6774+
CORINFO_METHOD_INFO info;
6775+
unsigned flags = 0;
6776+
repCompileMethod(&info, &flags);
6777+
6778+
if ((GetPgoInstrumentationResults != nullptr) &&
6779+
(GetPgoInstrumentationResults->GetIndex(CastHandle(info.ftn)) != -1))
6780+
{
6781+
ICorJitInfo::PgoInstrumentationSchema* schema = nullptr;
6782+
UINT32 schemaCount = 0;
6783+
BYTE* schemaData = nullptr;
6784+
HRESULT pgoHR = repGetPgoInstrumentationResults(info.ftn, &schema, &schemaCount, &schemaData);
6785+
6786+
if (SUCCEEDED(pgoHR))
6787+
{
6788+
for (UINT32 i = 0; i < schemaCount; i++)
6789+
{
6790+
hasEdgeProfile |= (schema[i].InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::EdgeIntCount);
6791+
hasClassProfile |= (schema[i].InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::TypeHandleHistogramCount);
6792+
6793+
if (hasEdgeProfile && hasClassProfile)
6794+
{
6795+
break;
6796+
}
6797+
}
6798+
6799+
return true;
6800+
}
6801+
}
6802+
6803+
return false;
6804+
}
6805+
67686806
MethodContext::Environment MethodContext::cloneEnvironment()
67696807
{
67706808
MethodContext::Environment env;

src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ const char* toString(CorInfoType cit);
4343

4444
#define METHOD_IDENTITY_INFO_SIZE 0x10000 // We assume that the METHOD_IDENTITY_INFO_SIZE will not exceed 64KB
4545

46+
// Special "jit flags" for noting some method context features
47+
48+
enum EXTRA_JIT_FLAGS
49+
{
50+
HAS_PGO = 63,
51+
HAS_EDGE_PROFILE = 62,
52+
HAS_CLASS_PROFILE = 61
53+
};
54+
55+
// Asserts to catch changes in corjit flags definitions.
56+
57+
static_assert((int)EXTRA_JIT_FLAGS::HAS_PGO == (int)CORJIT_FLAGS::CorJitFlag::CORJIT_FLAG_UNUSED36, "Jit Flags Mismatch");
58+
static_assert((int)EXTRA_JIT_FLAGS::HAS_EDGE_PROFILE == (int)CORJIT_FLAGS::CorJitFlag::CORJIT_FLAG_UNUSED35, "Jit Flags Mismatch");
59+
static_assert((int)EXTRA_JIT_FLAGS::HAS_CLASS_PROFILE == (int)CORJIT_FLAGS::CorJitFlag::CORJIT_FLAG_UNUSED34, "Jit Flags Mismatch");
60+
4661
class MethodContext
4762
{
4863
public:
@@ -82,6 +97,8 @@ class MethodContext
8297
int dumpMethodIdentityInfoToBuffer(char* buff, int len, bool ignoreMethodName = false, CORINFO_METHOD_INFO* optInfo = nullptr, unsigned optFlags = 0);
8398
int dumpMethodMD5HashToBuffer(char* buff, int len, bool ignoreMethodName = false, CORINFO_METHOD_INFO* optInfo = nullptr, unsigned optFlags = 0);
8499

100+
bool hasPgoData(bool& hasEdgeProfile, bool& hasClassProfile);
101+
85102
void recGlobalContext(const MethodContext& other);
86103

87104
void dmpEnvironment(DWORD key, const Agnostic_Environment& value);

src/coreclr/ToolBox/superpmi/superpmi-shared/spmidumphelper.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ std::string SpmiDumpHelper::DumpJitFlags(unsigned long long flags)
279279

280280
AddFlag(NO_INLINING);
281281

282+
// "Extra jit flag" support
283+
//
284+
AddFlagNumeric(HAS_PGO, EXTRA_JIT_FLAGS::HAS_PGO);
285+
AddFlagNumeric(HAS_EDGE_PROFILE, EXTRA_JIT_FLAGS::HAS_EDGE_PROFILE);
286+
AddFlagNumeric(HAS_CLASS_PROFILE, EXTRA_JIT_FLAGS::HAS_CLASS_PROFILE);
287+
282288
#undef AddFlag
283289
#undef AddFlagNumeric
284290

src/coreclr/ToolBox/superpmi/superpmi-shared/spmiutil.cpp

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -156,58 +156,76 @@ void ReplaceIllegalCharacters(WCHAR* fileName)
156156
// All lengths in this function exclude the terminal NULL.
157157
WCHAR* GetResultFileName(const WCHAR* folderPath, const WCHAR* fileName, const WCHAR* extension)
158158
{
159-
const size_t folderPathLength = wcslen(folderPath);
160-
const size_t fileNameLength = wcslen(fileName);
161159
const size_t extensionLength = wcslen(extension);
162-
const size_t maxPathLength = MAX_PATH - 50; // subtract 50 because excel doesn't like paths longer then 230.
160+
const size_t fileNameLength = wcslen(fileName);
163161
const size_t randomStringLength = 8;
162+
const size_t maxPathLength = MAX_PATH - 50;
163+
bool appendRandomString = false;
164164

165-
size_t fullPathLength = folderPathLength + 1 + extensionLength;
166-
bool appendRandomString = false;
165+
// See how long the folder part is, and start building the file path with the folder part.
166+
//
167+
WCHAR* fullPath = new WCHAR[MAX_PATH];
168+
fullPath[0] = W('\0');
169+
const size_t folderPathLength = GetFullPathNameW(folderPath, MAX_PATH, (LPWSTR)fullPath, NULL);
167170

168-
if (fileNameLength > 0)
169-
{
170-
fullPathLength += fileNameLength;
171-
}
172-
else
171+
if (folderPathLength == 0)
173172
{
174-
fullPathLength += randomStringLength;
175-
appendRandomString = true;
173+
LogError("GetResultFileName - can't resolve folder path '%ws'", folderPath);
174+
return nullptr;
176175
}
177176

178-
size_t charsToDelete = 0;
177+
// Account for the folder, directory separator and extension.
178+
//
179+
size_t fullPathLength = folderPathLength + 1 + extensionLength;
179180

180-
if (fullPathLength > maxPathLength)
181+
// If we won't have room for a minimal file name part, bail.
182+
//
183+
if ((fullPathLength + randomStringLength) > maxPathLength)
181184
{
182-
// The path name is too long; creating the file will fail. This can happen because we use the command line,
183-
// which for ngen includes lots of environment variables, for example.
184-
// Shorten the file name and add a random string to the end to avoid collisions.
185+
LogError("GetResultFileName - folder path '%ws' length + minimal file name exceeds limit %d", fullPath, maxPathLength);
186+
return nullptr;
187+
}
185188

186-
charsToDelete = fullPathLength - maxPathLength + randomStringLength;
189+
// Now figure out the file name part.
190+
//
191+
const size_t maxFileNameLength = maxPathLength - fullPathLength;
192+
size_t usableFileNameLength = 0;
187193

188-
if (fileNameLength >= charsToDelete)
189-
{
190-
appendRandomString = true;
191-
fullPathLength = maxPathLength;
192-
}
193-
else
194-
{
195-
LogError("GetResultFileName - path to the output file is too long '%ws\\%ws.%ws(%d)'", folderPath, fileName, extension, fullPathLength);
196-
return nullptr;
197-
}
194+
if (fileNameLength == 0)
195+
{
196+
// No file name provided. Use random string.
197+
//
198+
fullPathLength += randomStringLength;
199+
appendRandomString = true;
200+
}
201+
else if (fileNameLength < maxFileNameLength)
202+
{
203+
// Reasonable length file name, use as is.
204+
//
205+
usableFileNameLength = fileNameLength;
206+
fullPathLength += fileNameLength;
207+
appendRandomString = false;
208+
}
209+
else
210+
{
211+
// Overly long file name, truncate and add random string.
212+
//
213+
usableFileNameLength = maxFileNameLength - randomStringLength;
214+
fullPathLength += maxFileNameLength;
215+
appendRandomString = true;
198216
}
199217

200-
WCHAR* fullPath = new WCHAR[fullPathLength + 1];
201-
fullPath[0] = W('\0');
202-
wcsncat_s(fullPath, fullPathLength + 1, folderPath, folderPathLength);
218+
// Append the file name part
219+
//
203220
wcsncat_s(fullPath, fullPathLength + 1, DIRECTORY_SEPARATOR_STR_W, 1);
221+
wcsncat_s(fullPath, fullPathLength + 1, fileName, usableFileNameLength);
204222

205-
if (fileNameLength > charsToDelete)
206-
{
207-
wcsncat_s(fullPath, fullPathLength + 1, fileName, fileNameLength - charsToDelete);
208-
ReplaceIllegalCharacters(fullPath + folderPathLength + 1);
209-
}
223+
// Clean up anything in the file part that can't be in a file name.
224+
//
225+
ReplaceIllegalCharacters(fullPath + folderPathLength + 1);
210226

227+
// Append random string, if we're using it.
228+
//
211229
if (appendRandomString)
212230
{
213231
unsigned randomNumber = 0;
@@ -223,6 +241,8 @@ WCHAR* GetResultFileName(const WCHAR* folderPath, const WCHAR* fileName, const W
223241
wcsncat_s(fullPath, fullPathLength + 1, randomString, randomStringLength);
224242
}
225243

244+
// Append extension
245+
//
226246
wcsncat_s(fullPath, fullPathLength + 1, extension, extensionLength);
227247

228248
return fullPath;

src/coreclr/jit/codegencommon.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,9 +2207,9 @@ void CodeGen::genGenerateMachineCode()
22072207
compiler->fgHaveValidEdgeWeights ? "valid" : "invalid", compiler->fgCalledCount);
22082208
}
22092209

2210-
if (compiler->fgProfileData_ILSizeMismatch)
2210+
if (compiler->fgPgoFailReason != nullptr)
22112211
{
2212-
printf("; discarded IBC profile data due to mismatch in ILSize\n");
2212+
printf("; %s\n", compiler->fgPgoFailReason);
22132213
}
22142214

22152215
if (compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT))

src/coreclr/jit/compiler.cpp

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2830,11 +2830,12 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
28302830

28312831
// Profile data
28322832
//
2833-
fgPgoSchema = nullptr;
2834-
fgPgoData = nullptr;
2835-
fgPgoSchemaCount = 0;
2836-
fgPgoQueryResult = E_FAIL;
2837-
fgProfileData_ILSizeMismatch = false;
2833+
fgPgoSchema = nullptr;
2834+
fgPgoData = nullptr;
2835+
fgPgoSchemaCount = 0;
2836+
fgPgoQueryResult = E_FAIL;
2837+
fgPgoFailReason = nullptr;
2838+
28382839
if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT))
28392840
{
28402841
fgPgoQueryResult = info.compCompHnd->getPgoInstrumentationResults(info.compMethodHnd, &fgPgoSchema,
@@ -2846,12 +2847,22 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
28462847
//
28472848
// We will discard the IBC data in this case
28482849
//
2849-
if (FAILED(fgPgoQueryResult) && (fgPgoSchema != nullptr))
2850+
if (FAILED(fgPgoQueryResult))
2851+
{
2852+
fgPgoFailReason = (fgPgoSchema != nullptr) ? "No matching PGO data" : "No PGO data";
2853+
fgPgoData = nullptr;
2854+
fgPgoSchema = nullptr;
2855+
}
2856+
// Optionally, discard the profile data.
2857+
//
2858+
else if (JitConfig.JitDisablePGO() != 0)
28502859
{
2851-
fgProfileData_ILSizeMismatch = true;
2852-
fgPgoData = nullptr;
2853-
fgPgoSchema = nullptr;
2860+
fgPgoFailReason = "PGO data available, but JitDisablePGO != 0";
2861+
fgPgoQueryResult = E_FAIL;
2862+
fgPgoData = nullptr;
2863+
fgPgoSchema = nullptr;
28542864
}
2865+
28552866
#ifdef DEBUG
28562867
// A successful result implies a non-NULL fgPgoSchema
28572868
//
@@ -3364,9 +3375,9 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
33643375
printf("OPTIONS: optimized using profile data\n");
33653376
}
33663377

3367-
if (fgProfileData_ILSizeMismatch)
3378+
if (fgPgoFailReason != nullptr)
33683379
{
3369-
printf("OPTIONS: discarded IBC profile data due to mismatch in ILSize\n");
3380+
printf("OPTIONS: %s\n", fgPgoFailReason);
33703381
}
33713382

33723383
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))

src/coreclr/jit/compiler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5636,7 +5636,7 @@ class Compiler
56365636
void fgIncorporateEdgeCounts();
56375637

56385638
public:
5639-
bool fgProfileData_ILSizeMismatch;
5639+
const char* fgPgoFailReason;
56405640
ICorJitInfo::PgoInstrumentationSchema* fgPgoSchema;
56415641
BYTE* fgPgoData;
56425642
UINT32 fgPgoSchemaCount;

src/coreclr/jit/importer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21504,6 +21504,15 @@ void Compiler::considerGuardedDevirtualization(
2150421504

2150521505
JITDUMP("Considering guarded devirtualization\n");
2150621506

21507+
// We currently only get likely class guesses when there is PGO data. So if we've disabled
21508+
// PGO, just bail out.
21509+
21510+
if (JitConfig.JitDisablePGO() != 0)
21511+
{
21512+
JITDUMP("Not guessing for class; pgo disabled\n");
21513+
return;
21514+
}
21515+
2150721516
// See if there's a likely guess for the class.
2150821517
//
2150921518
const unsigned likelihoodThreshold = isInterface ? 25 : 30;

src/coreclr/jit/jitconfigvalues.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ CONFIG_INTEGER(JitMinimalPrejitProfiling, W("JitMinimalPrejitProfiling"), 0)
462462
CONFIG_INTEGER(JitClassProfiling, W("JitClassProfiling"), 1)
463463
CONFIG_INTEGER(JitEdgeProfiling, W("JitEdgeProfiling"), 1)
464464

465+
// Profile consumption options
466+
CONFIG_INTEGER(JitDisablePGO, W("JitDisablePGO"), 0) // Ignore pgo data
467+
465468
// Control when Virtual Calls are expanded
466469
CONFIG_INTEGER(JitExpandCallsEarly, W("JitExpandCallsEarly"), 1) // Expand Call targets early (in the global morph
467470
// phase)

0 commit comments

Comments
 (0)