From ea480744d614888c81e0b249995fc957a1e0a539 Mon Sep 17 00:00:00 2001 From: Bruce Forstall Date: Wed, 19 Apr 2017 13:37:34 -0700 Subject: [PATCH] Add code to measure dynamic noway_assert usage Set COMPlus_JitMeasureNowayAssert=1 to enable measurement. Set COMPlus_JitMeasureNowayAssertFile to a path/file to send output to a file instead of stdout. Code is under `#if MEASURE_NOWAY` which is enabled for DEBUG only, though it can be manually enabled for non-DEBUG builds. Commit migrated from https://github.com/dotnet/coreclr/commit/c2469deded28f16e3302bf339d8a3a67a03c2086 --- src/coreclr/src/jit/compiler.cpp | 169 ++++++++++++++++++++++++++++++++++ src/coreclr/src/jit/compiler.h | 4 + src/coreclr/src/jit/error.h | 24 ++++- src/coreclr/src/jit/jitconfigvalues.h | 6 ++ 4 files changed, 201 insertions(+), 2 deletions(-) diff --git a/src/coreclr/src/jit/compiler.cpp b/src/coreclr/src/jit/compiler.cpp index 14b2aba..1c24b93 100644 --- a/src/coreclr/src/jit/compiler.cpp +++ b/src/coreclr/src/jit/compiler.cpp @@ -1134,6 +1134,170 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, return useType; } +/////////////////////////////////////////////////////////////////////////////// +// +// MEASURE_NOWAY: code to measure and rank dynamic occurences of noway_assert. +// (Just the appearances of noway_assert, whether the assert is true or false.) +// This might help characterize the cost of noway_assert in non-DEBUG builds, +// or determine which noway_assert should be simple DEBUG-only asserts. +// +/////////////////////////////////////////////////////////////////////////////// + +#if MEASURE_NOWAY + +struct FileLine +{ + char* m_file; + unsigned m_line; + char* m_condStr; + + FileLine() : m_file(nullptr), m_line(0), m_condStr(nullptr) + { + } + + FileLine(const char* file, unsigned line, const char* condStr) : m_line(line) + { + size_t newSize = (strlen(file) + 1) * sizeof(char); + m_file = (char*)HostAllocator::getHostAllocator()->Alloc(newSize); + strcpy_s(m_file, newSize, file); + + newSize = (strlen(condStr) + 1) * sizeof(char); + m_condStr = (char*)HostAllocator::getHostAllocator()->Alloc(newSize); + strcpy_s(m_condStr, newSize, condStr); + } + + FileLine(const FileLine& other) + { + m_file = other.m_file; + m_line = other.m_line; + m_condStr = other.m_condStr; + } + + // GetHashCode() and Equals() are needed by SimplerHashTable + + static unsigned GetHashCode(FileLine fl) + { + assert(fl.m_file != nullptr); + unsigned code = fl.m_line; + for (const char* p = fl.m_file; *p != '\0'; p++) + { + code += *p; + } + // Could also add condStr. + return code; + } + + static bool Equals(FileLine fl1, FileLine fl2) + { + return (fl1.m_line == fl2.m_line) && (0 == strcmp(fl1.m_file, fl2.m_file)); + } +}; + +typedef SimplerHashTable FileLineToCountMap; +FileLineToCountMap* NowayAssertMap; + +void Compiler::RecordNowayAssert(const char* filename, unsigned line, const char* condStr) +{ + if (NowayAssertMap == nullptr) + { + NowayAssertMap = new (HostAllocator::getHostAllocator()) FileLineToCountMap(HostAllocator::getHostAllocator()); + } + FileLine fl(filename, line, condStr); + size_t* pCount = NowayAssertMap->LookupPointer(fl); + if (pCount == nullptr) + { + NowayAssertMap->Set(fl, 1); + } + else + { + ++(*pCount); + } +} + +void RecordNowayAssertGlobal(const char* filename, unsigned line, const char* condStr) +{ + if ((JitConfig.JitMeasureNowayAssert() == 1) && (JitTls::GetCompiler() != nullptr)) + { + JitTls::GetCompiler()->RecordNowayAssert(filename, line, condStr); + } +} + +struct NowayAssertCountMap +{ + size_t count; + FileLine fl; + + NowayAssertCountMap() : count(0) + { + } + + static int __cdecl compare(const void* elem1, const void* elem2) + { + NowayAssertCountMap* e1 = (NowayAssertCountMap*)elem1; + NowayAssertCountMap* e2 = (NowayAssertCountMap*)elem2; + return (int)((ssize_t)e2->count - (ssize_t)e1->count); // sort in descending order + } +}; + +void DisplayNowayAssertMap() +{ + if (NowayAssertMap != nullptr) + { + FILE* fout; + + LPCWSTR strJitMeasureNowayAssertFile = JitConfig.JitMeasureNowayAssertFile(); + if (strJitMeasureNowayAssertFile != nullptr) + { + fout = _wfopen(strJitMeasureNowayAssertFile, W("a")); + if (fout == nullptr) + { + fprintf(jitstdout, "Failed to open JitMeasureNowayAssertFile \"%ws\"\n", strJitMeasureNowayAssertFile); + return; + } + } + else + { + fout = jitstdout; + } + + // Iterate noway assert map, create sorted table by occurrence, dump it. + unsigned count = NowayAssertMap->GetCount(); + NowayAssertCountMap* nacp = new NowayAssertCountMap[count]; + unsigned i = 0; + + for (FileLineToCountMap::KeyIterator iter = NowayAssertMap->Begin(), end = NowayAssertMap->End(); + !iter.Equal(end); ++iter) + { + nacp[i].count = iter.GetValue(); + nacp[i].fl = iter.Get(); + ++i; + } + + qsort(nacp, count, sizeof(nacp[0]), NowayAssertCountMap::compare); + + if (fout == jitstdout) + { + // Don't output the header if writing to a file, since we'll be appending to existing dumps in that case. + fprintf(fout, "\nnoway_assert counts:\n"); + fprintf(fout, "count, file, line, text\n"); + } + + for (i = 0; i < count; i++) + { + fprintf(fout, "%u, %s, %u, \"%s\"\n", nacp[i].count, nacp[i].fl.m_file, nacp[i].fl.m_line, + nacp[i].fl.m_condStr); + } + + if (fout != jitstdout) + { + fclose(fout); + fout = nullptr; + } + } +} + +#endif // MEASURE_NOWAY + /***************************************************************************** * variables to keep track of how many iterations we go in a dataflow pass */ @@ -1222,6 +1386,10 @@ void Compiler::compShutdown() } #endif // ALT_JIT +#if MEASURE_NOWAY + DisplayNowayAssertMap(); +#endif // MEASURE_NOWAY + ArenaAllocator::shutdown(); /* Shut down the emitter */ @@ -2414,6 +2582,7 @@ bool Compiler::compShouldThrowOnNoway( #ifdef FEATURE_TRACELOGGING compJitTelemetry.NotifyNowayAssert(filename, line); #endif + // In min opts, we don't want the noway assert to go through the exception // path. Instead we want it to just silently go through codegen for // compat reasons. diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 1ba059a..998b647 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -9071,6 +9071,10 @@ public: // Is the compilation in a full trust context? bool compIsFullTrust(); +#if MEASURE_NOWAY + void RecordNowayAssert(const char* filename, unsigned line, const char* condStr); +#endif // MEASURE_NOWAY + #ifndef FEATURE_TRACELOGGING // Should we actually fire the noway assert body and the exception handler? bool compShouldThrowOnNoway(); diff --git a/src/coreclr/src/jit/error.h b/src/coreclr/src/jit/error.h index 0535601..78f24ad 100644 --- a/src/coreclr/src/jit/error.h +++ b/src/coreclr/src/jit/error.h @@ -80,7 +80,25 @@ extern void noWayAssertBodyConditional( ); extern void noWayAssertBodyConditional(const char* cond, const char* file, unsigned line); +// Define MEASURE_NOWAY to 1 to enable code to count and rank individual noway_assert calls by occurrence. +// These asserts would be dynamically executed, but not necessarily fail. The provides some insight into +// the dynamic prevalence of these (if not a direct measure of their cost), which exist in non-DEBUG as +// well as DEBUG builds. #ifdef DEBUG +#define MEASURE_NOWAY 1 +#else // !DEBUG +#define MEASURE_NOWAY 0 +#endif // !DEBUG + +#if MEASURE_NOWAY +extern void RecordNowayAssertGlobal(const char* filename, unsigned line, const char* condStr); +#define RECORD_NOWAY_ASSERT(condStr) RecordNowayAssertGlobal(__FILE__, __LINE__, condStr); +#else +#define RECORD_NOWAY_ASSERT(condStr) +#endif + +#ifdef DEBUG + #define NO_WAY(msg) (debugError(msg, __FILE__, __LINE__), noWay()) // Used for fallback stress mode #define NO_WAY_NOASSERT(msg) noWay() @@ -90,6 +108,7 @@ extern void noWayAssertBodyConditional(const char* cond, const char* file, unsig #define noway_assert(cond) \ do \ { \ + RECORD_NOWAY_ASSERT(#cond) \ if (!(cond)) \ { \ noWayAssertBodyConditional(#cond, __FILE__, __LINE__); \ @@ -99,7 +118,7 @@ extern void noWayAssertBodyConditional(const char* cond, const char* file, unsig #define NOWAY_MSG(msg) noWayAssertBodyConditional(msg, __FILE__, __LINE__) -#else +#else // !DEBUG #define NO_WAY(msg) noWay() #define BADCODE(msg) badCode() @@ -114,6 +133,7 @@ extern void noWayAssertBodyConditional(const char* cond, const char* file, unsig #define noway_assert(cond) \ do \ { \ + RECORD_NOWAY_ASSERT(#cond) \ if (!(cond)) \ { \ noWayAssertBodyConditional(NOWAY_ASSERT_BODY_ARGUMENTS); \ @@ -123,7 +143,7 @@ extern void noWayAssertBodyConditional(const char* cond, const char* file, unsig #define NOWAY_MSG(msg) noWayAssertBodyConditional(NOWAY_ASSERT_BODY_ARGUMENTS) -#endif +#endif // !DEBUG // IMPL_LIMITATION is called when we encounter valid IL that is not // supported by our current implementation because of various diff --git a/src/coreclr/src/jit/jitconfigvalues.h b/src/coreclr/src/jit/jitconfigvalues.h index 624ad1a..3657696a 100644 --- a/src/coreclr/src/jit/jitconfigvalues.h +++ b/src/coreclr/src/jit/jitconfigvalues.h @@ -279,6 +279,12 @@ CONFIG_STRING(JitTimeLogCsv, W("JitTimeLogCsv")) // If set, gather JIT throughpu // mode must be used in internal retail builds. CONFIG_STRING(TailCallOpt, W("TailCallOpt")) +CONFIG_INTEGER(JitMeasureNowayAssert, W("JitMeasureNowayAssert"), 0) // Set to 1 to measure noway_assert usage. Only + // valid if MEASURE_NOWAY is defined. +CONFIG_STRING(JitMeasureNowayAssertFile, + W("JitMeasureNowayAssertFile")) // Set to file to write noway_assert usage to a file (if not + // set: stdout). Only valid if MEASURE_NOWAY is defined. + #if defined(DEBUG) || defined(INLINE_DATA) CONFIG_INTEGER(JitInlineDumpData, W("JitInlineDumpData"), 0) CONFIG_INTEGER(JitInlineDumpXml, W("JitInlineDumpXml"), 0) // 1 = full xml (all methods), 2 = minimal xml (only method -- 2.7.4