Add code to measure dynamic noway_assert usage
authorBruce Forstall <brucefo@microsoft.com>
Wed, 19 Apr 2017 20:37:34 +0000 (13:37 -0700)
committerBruce Forstall <brucefo@microsoft.com>
Tue, 25 Apr 2017 06:50:29 +0000 (23:50 -0700)
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.

src/jit/compiler.cpp
src/jit/compiler.h
src/jit/error.h
src/jit/jitconfigvalues.h

index 14b2aba..1c24b93 100644 (file)
@@ -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<FileLine, FileLine, size_t, JitSimplerHashBehavior> 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.
index 1ba059a..998b647 100644 (file)
@@ -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();
index 0535601..78f24ad 100644 (file)
@@ -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
index 624ad1a..3657696 100644 (file)
@@ -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