summaryrefslogtreecommitdiff
path: root/src/jit/error.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/error.cpp')
-rw-r--r--src/jit/error.cpp536
1 files changed, 536 insertions, 0 deletions
diff --git a/src/jit/error.cpp b/src/jit/error.cpp
new file mode 100644
index 0000000000..71c3301045
--- /dev/null
+++ b/src/jit/error.cpp
@@ -0,0 +1,536 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX error.cpp XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+#include "compiler.h"
+
+#if MEASURE_FATAL
+unsigned fatal_badCode;
+unsigned fatal_noWay;
+unsigned fatal_NOMEM;
+unsigned fatal_noWayAssertBody;
+#ifdef DEBUG
+unsigned fatal_noWayAssertBodyArgs;
+#endif // DEBUG
+unsigned fatal_NYI;
+#endif // MEASURE_FATAL
+
+/*****************************************************************************/
+void DECLSPEC_NORETURN fatal(int errCode)
+{
+#ifdef DEBUG
+ if (errCode != CORJIT_SKIPPED) // Don't stop on NYI: use COMPlus_AltJitAssertOnNYI for that.
+ {
+ if (JitConfig.DebugBreakOnVerificationFailure())
+ {
+ DebugBreak();
+ }
+ }
+#endif // DEBUG
+
+ ULONG_PTR exceptArg = errCode;
+ RaiseException(FATAL_JIT_EXCEPTION, EXCEPTION_NONCONTINUABLE, 1, &exceptArg);
+ UNREACHABLE();
+}
+
+/*****************************************************************************/
+void DECLSPEC_NORETURN badCode()
+{
+#if MEASURE_FATAL
+ fatal_badCode += 1;
+#endif // MEASURE_FATAL
+
+ fatal(CORJIT_BADCODE);
+}
+
+/*****************************************************************************/
+void DECLSPEC_NORETURN noWay()
+{
+#if MEASURE_FATAL
+ fatal_noWay += 1;
+#endif // MEASURE_FATAL
+
+ fatal(CORJIT_INTERNALERROR);
+}
+
+/*****************************************************************************/
+void DECLSPEC_NORETURN NOMEM()
+{
+#if MEASURE_FATAL
+ fatal_NOMEM += 1;
+#endif // MEASURE_FATAL
+
+ fatal(CORJIT_OUTOFMEM);
+}
+
+/*****************************************************************************/
+void DECLSPEC_NORETURN noWayAssertBody()
+{
+#if MEASURE_FATAL
+ fatal_noWayAssertBody += 1;
+#endif // MEASURE_FATAL
+
+#ifndef DEBUG
+ // Even in retail, if we hit a noway, and we have this variable set, we don't want to fall back
+ // to MinOpts, which might hide a regression. Instead, hit a breakpoint (and crash). We don't
+ // have the assert code to fall back on here.
+ // The debug path goes through this function also, to do the call to 'fatal'.
+ // This kind of noway is hit for unreached().
+ if (JitConfig.JitEnableNoWayAssert())
+ {
+ DebugBreak();
+ }
+#endif // !DEBUG
+
+ fatal(CORJIT_RECOVERABLEERROR);
+}
+
+inline static bool ShouldThrowOnNoway(
+#ifdef FEATURE_TRACELOGGING
+ const char* filename, unsigned line
+#endif
+ )
+{
+ return JitTls::GetCompiler() == nullptr ||
+ JitTls::GetCompiler()->compShouldThrowOnNoway(
+#ifdef FEATURE_TRACELOGGING
+ filename, line
+#endif
+ );
+}
+
+/*****************************************************************************/
+void noWayAssertBodyConditional(
+#ifdef FEATURE_TRACELOGGING
+ const char* filename, unsigned line
+#endif
+ )
+{
+#ifdef FEATURE_TRACELOGGING
+ if (ShouldThrowOnNoway(filename, line))
+#else
+ if (ShouldThrowOnNoway())
+#endif // FEATURE_TRACELOGGING
+ {
+ noWayAssertBody();
+ }
+}
+
+#if !defined(_TARGET_X86_) || !defined(LEGACY_BACKEND)
+
+/*****************************************************************************/
+void notYetImplemented(const char* msg, const char* filename, unsigned line)
+{
+#if FUNC_INFO_LOGGING
+#ifdef DEBUG
+ LogEnv* env = JitTls::GetLogEnv();
+ if (env != nullptr)
+ {
+ const Compiler* const pCompiler = env->compiler;
+ if (pCompiler->verbose)
+ {
+ printf("\n\n%s - NYI (%s:%d - %s)\n", pCompiler->info.compFullName, filename, line, msg);
+ }
+ }
+ if (Compiler::compJitFuncInfoFile != nullptr)
+ {
+ fprintf(Compiler::compJitFuncInfoFile, "%s - NYI (%s:%d - %s)\n",
+ (env == nullptr) ? "UNKNOWN" : env->compiler->info.compFullName, filename, line, msg);
+ fflush(Compiler::compJitFuncInfoFile);
+ }
+#else // !DEBUG
+ if (Compiler::compJitFuncInfoFile != nullptr)
+ {
+ fprintf(Compiler::compJitFuncInfoFile, "NYI (%s:%d - %s)\n", filename, line, msg);
+ fflush(Compiler::compJitFuncInfoFile);
+ }
+#endif // !DEBUG
+#endif // FUNC_INFO_LOGGING
+
+#ifdef DEBUG
+ Compiler* pCompiler = JitTls::GetCompiler();
+ if (pCompiler != nullptr)
+ {
+ // Assume we're within a compFunctionTrace boundary, which might not be true.
+ pCompiler->compFunctionTraceEnd(nullptr, 0, true);
+ }
+#endif // DEBUG
+
+ DWORD value = JitConfig.AltJitAssertOnNYI();
+
+ // 0 means just silently skip
+ // If we are in retail builds, assume ignore
+ // 1 means popup the assert (abort=abort, retry=debugger, ignore=skip)
+ // 2 means silently don't skip (same as 3 for retail)
+ // 3 means popup the assert (abort=abort, retry=debugger, ignore=don't skip)
+ if (value & 1)
+ {
+#ifdef DEBUG
+ assertAbort(msg, filename, line);
+#endif
+ }
+
+ if ((value & 2) == 0)
+ {
+#if MEASURE_FATAL
+ fatal_NYI += 1;
+#endif // MEASURE_FATAL
+
+ fatal(CORJIT_SKIPPED);
+ }
+}
+
+#endif // #if !defined(_TARGET_X86_) || !defined(LEGACY_BACKEND)
+
+/*****************************************************************************/
+LONG __JITfilter(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam)
+{
+ DWORD exceptCode = pExceptionPointers->ExceptionRecord->ExceptionCode;
+
+ if (exceptCode == FATAL_JIT_EXCEPTION)
+ {
+ ErrorTrapParam* pParam = (ErrorTrapParam*)lpvParam;
+
+ assert(pExceptionPointers->ExceptionRecord->NumberParameters == 1);
+ pParam->errc = (int)pExceptionPointers->ExceptionRecord->ExceptionInformation[0];
+
+ ICorJitInfo* jitInfo = pParam->jitInfo;
+
+ if (jitInfo != nullptr)
+ {
+ jitInfo->reportFatalError((CorJitResult)pParam->errc);
+ }
+
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+/*****************************************************************************/
+#ifdef DEBUG
+
+DWORD getBreakOnBadCode()
+{
+ return JitConfig.JitBreakOnBadCode();
+}
+
+/*****************************************************************************/
+void debugError(const char* msg, const char* file, unsigned line)
+{
+ const char* tail = strrchr(file, '\\');
+ if (tail)
+ {
+ file = tail + 1;
+ }
+
+ LogEnv* env = JitTls::GetLogEnv();
+
+ logf(LL_ERROR, "COMPILATION FAILED: file: %s:%d compiling method %s reason %s\n", file, line,
+ env->compiler->info.compFullName, msg);
+
+ // We now only assert when user explicitly set ComPlus_JitRequired=1
+ // If ComPlus_JitRequired is 0 or is not set, we will not assert.
+ if (JitConfig.JitRequired() == 1 || getBreakOnBadCode())
+ {
+ // Don't assert if verification is done.
+ if (!env->compiler->tiVerificationNeeded || getBreakOnBadCode())
+ {
+ assertAbort(msg, "NO-FILE", 0);
+ }
+ }
+
+ BreakIfDebuggerPresent();
+}
+
+/*****************************************************************************/
+LogEnv::LogEnv(ICorJitInfo* aCompHnd) : compHnd(aCompHnd), compiler(nullptr)
+{
+}
+
+/*****************************************************************************/
+extern "C" void __cdecl assertAbort(const char* why, const char* file, unsigned line)
+{
+ const char* msg = why;
+ LogEnv* env = JitTls::GetLogEnv();
+ const int BUFF_SIZE = 8192;
+ char* buff = (char*)alloca(BUFF_SIZE);
+ if (env->compiler)
+ {
+ _snprintf_s(buff, BUFF_SIZE, _TRUNCATE, "Assertion failed '%s' in '%s' (IL size %d)\n", why,
+ env->compiler->info.compFullName, env->compiler->info.compILCodeSize);
+ msg = buff;
+ }
+ printf(""); // null string means flush
+
+#if FUNC_INFO_LOGGING
+ if (Compiler::compJitFuncInfoFile != nullptr)
+ {
+ fprintf(Compiler::compJitFuncInfoFile, "%s - Assertion failed (%s:%d - %s)\n",
+ (env == nullptr) ? "UNKNOWN" : env->compiler->info.compFullName, file, line, why);
+ }
+#endif // FUNC_INFO_LOGGING
+
+ if (env->compHnd->doAssert(file, line, msg))
+ {
+ DebugBreak();
+ }
+
+#ifdef ALT_JIT
+ // If we hit an assert, and we got here, it's either because the user hit "ignore" on the
+ // dialog pop-up, or they set COMPlus_ContinueOnAssert=1 to not emit a pop-up, but just continue.
+ // If we're an altjit, we have two options: (1) silently continue, as a normal JIT would, probably
+ // leading to additional asserts, or (2) tell the VM that the AltJit wants to skip this function,
+ // thus falling back to the fallback JIT. Setting COMPlus_AltJitSkipOnAssert=1 chooses this "skip"
+ // to the fallback JIT behavior. This is useful when doing ASM diffs, where we only want to see
+ // the first assert for any function, but we don't want to kill the whole ngen process on the
+ // first assert (which would happen if you used COMPlus_NoGuiOnAssert=1 for example).
+ if (JitConfig.AltJitSkipOnAssert() != 0)
+ {
+ fatal(CORJIT_SKIPPED);
+ }
+#elif defined(_TARGET_ARM64_)
+ // TODO-ARM64-NYI: remove this after the JIT no longer asserts during startup
+ //
+ // When we are bringing up the new Arm64 JIT we set COMPlus_ContinueOnAssert=1
+ // We only want to hit one assert then we will fall back to the interpreter.
+ //
+ bool interpreterFallback = (JitConfig.InterpreterFallback() != 0);
+
+ if (interpreterFallback)
+ {
+ fatal(CORJIT_SKIPPED);
+ }
+#endif
+}
+
+/*********************************************************************/
+BOOL vlogf(unsigned level, const char* fmt, va_list args)
+{
+ return JitTls::GetLogEnv()->compHnd->logMsg(level, fmt, args);
+}
+
+int vflogf(FILE* file, const char* fmt, va_list args)
+{
+ // 0-length string means flush
+ if (fmt[0] == '\0')
+ {
+ fflush(file);
+ return 0;
+ }
+
+ const int BUFF_SIZE = 8192;
+ char buffer[BUFF_SIZE];
+ int written = _vsnprintf_s(&buffer[0], BUFF_SIZE, _TRUNCATE, fmt, args);
+
+ if (JitConfig.JitDumpToDebugger())
+ {
+ OutputDebugStringA(buffer);
+ }
+
+ // We use fputs here so that this executes as fast a possible
+ fputs(&buffer[0], file);
+ return written;
+}
+
+int flogf(FILE* file, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ int written = vflogf(file, fmt, args);
+ va_end(args);
+ return written;
+}
+
+/*********************************************************************/
+int logf(const char* fmt, ...)
+{
+ va_list args;
+ static bool logToEEfailed = false;
+ int written = 0;
+ //
+ // We remember when the EE failed to log, because vlogf()
+ // is very slow in a checked build.
+ //
+ // If it fails to log an LL_INFO1000 message once
+ // it will always fail when logging an LL_INFO1000 message.
+ //
+ if (!logToEEfailed)
+ {
+ va_start(args, fmt);
+ if (!vlogf(LL_INFO1000, fmt, args))
+ {
+ logToEEfailed = true;
+ }
+ va_end(args);
+ }
+
+ if (logToEEfailed)
+ {
+ // if the EE refuses to log it, we try to send it to stdout
+ va_start(args, fmt);
+ written = vflogf(jitstdout, fmt, args);
+ va_end(args);
+ }
+#if 0 // Enable this only when you need it
+ else
+ {
+ //
+ // The EE just successfully logged our message
+ //
+ static ConfigDWORD fJitBreakOnDumpToken;
+ DWORD breakOnDumpToken = fJitBreakOnDumpToken.val(CLRConfig::INTERNAL_BreakOnDumpToken);
+ static DWORD forbidEntry = 0;
+
+ if ((breakOnDumpToken != 0xffffffff) && (forbidEntry == 0))
+ {
+ forbidEntry = 1;
+
+ // Use value of 0 to get the dump
+ static DWORD currentLine = 1;
+
+ if (currentLine == breakOnDumpToken)
+ {
+ assert(!"Dump token reached");
+ }
+
+ printf("(Token=0x%x) ", currentLine++);
+ forbidEntry = 0;
+ }
+ }
+#endif // 0
+ va_end(args);
+
+ return written;
+}
+
+/*********************************************************************/
+void gcDump_logf(const char* fmt, ...)
+{
+ va_list args;
+ static bool logToEEfailed = false;
+ //
+ // We remember when the EE failed to log, because vlogf()
+ // is very slow in a checked build.
+ //
+ // If it fails to log an LL_INFO1000 message once
+ // it will always fail when logging an LL_INFO1000 message.
+ //
+ if (!logToEEfailed)
+ {
+ va_start(args, fmt);
+ if (!vlogf(LL_INFO1000, fmt, args))
+ {
+ logToEEfailed = true;
+ }
+ va_end(args);
+ }
+
+ if (logToEEfailed)
+ {
+ // if the EE refuses to log it, we try to send it to stdout
+ va_start(args, fmt);
+ vflogf(jitstdout, fmt, args);
+ va_end(args);
+ }
+#if 0 // Enable this only when you need it
+ else
+ {
+ //
+ // The EE just successfully logged our message
+ //
+ static ConfigDWORD fJitBreakOnDumpToken;
+ DWORD breakOnDumpToken = fJitBreakOnDumpToken.val(CLRConfig::INTERNAL_BreakOnDumpToken);
+ static DWORD forbidEntry = 0;
+
+ if ((breakOnDumpToken != 0xffffffff) && (forbidEntry == 0))
+ {
+ forbidEntry = 1;
+
+ // Use value of 0 to get the dump
+ static DWORD currentLine = 1;
+
+ if (currentLine == breakOnDumpToken)
+ {
+ assert(!"Dump token reached");
+ }
+
+ printf("(Token=0x%x) ", currentLine++);
+ forbidEntry = 0;
+ }
+ }
+#endif // 0
+ va_end(args);
+}
+
+/*********************************************************************/
+void logf(unsigned level, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vlogf(level, fmt, args);
+ va_end(args);
+}
+
+void DECLSPEC_NORETURN badCode3(const char* msg, const char* msg2, int arg, __in_z const char* file, unsigned line)
+{
+ const int BUFF_SIZE = 512;
+ char buf1[BUFF_SIZE];
+ char buf2[BUFF_SIZE];
+ sprintf_s(buf1, BUFF_SIZE, "%s%s", msg, msg2);
+ sprintf_s(buf2, BUFF_SIZE, buf1, arg);
+
+ debugError(buf2, file, line);
+ badCode();
+}
+
+void noWayAssertAbortHelper(const char* cond, const char* file, unsigned line)
+{
+ // Show the assert UI.
+ if (JitConfig.JitEnableNoWayAssert())
+ {
+ assertAbort(cond, file, line);
+ }
+}
+
+void noWayAssertBodyConditional(const char* cond, const char* file, unsigned line)
+{
+#ifdef FEATURE_TRACELOGGING
+ if (ShouldThrowOnNoway(file, line))
+#else
+ if (ShouldThrowOnNoway())
+#endif
+ {
+ noWayAssertBody(cond, file, line);
+ }
+ // In CHK we want the assert UI to show up in min-opts.
+ else
+ {
+ noWayAssertAbortHelper(cond, file, line);
+ }
+}
+
+void DECLSPEC_NORETURN noWayAssertBody(const char* cond, const char* file, unsigned line)
+{
+#if MEASURE_FATAL
+ fatal_noWayAssertBodyArgs += 1;
+#endif // MEASURE_FATAL
+
+ noWayAssertAbortHelper(cond, file, line);
+ noWayAssertBody();
+}
+
+#endif // DEBUG