Merge pull request #6467 from briansull/multireg-return2
[platform/upstream/coreclr.git] / src / jit / error.cpp
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
7 XX                                                                           XX
8 XX                           error.cpp                                       XX
9 XX                                                                           XX
10 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
11 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
12 */
13
14 #include "jitpch.h"
15 #ifdef _MSC_VER
16 #pragma hdrstop
17 #endif
18 #include "compiler.h"
19
20 #if MEASURE_FATAL
21 unsigned fatal_badCode;
22 unsigned fatal_noWay;
23 unsigned fatal_NOMEM;
24 unsigned fatal_noWayAssertBody;
25 #ifdef DEBUG
26 unsigned fatal_noWayAssertBodyArgs;
27 #endif // DEBUG
28 unsigned fatal_NYI;
29 #endif // MEASURE_FATAL
30
31 /*****************************************************************************/
32 void DECLSPEC_NORETURN fatal(int errCode)
33 {
34 #ifdef DEBUG
35     if (errCode != CORJIT_SKIPPED) // Don't stop on NYI: use COMPlus_AltJitAssertOnNYI for that.
36     {
37         if (JitConfig.DebugBreakOnVerificationFailure())
38         {
39             DebugBreak();
40         }
41     }
42 #endif // DEBUG
43
44     ULONG_PTR exceptArg = errCode;
45     RaiseException(FATAL_JIT_EXCEPTION, EXCEPTION_NONCONTINUABLE, 1, &exceptArg);
46     UNREACHABLE();
47 }
48
49 /*****************************************************************************/
50 void DECLSPEC_NORETURN badCode()
51 {
52 #if MEASURE_FATAL
53     fatal_badCode += 1;
54 #endif // MEASURE_FATAL
55
56     fatal(CORJIT_BADCODE);
57 }
58
59 /*****************************************************************************/
60 void DECLSPEC_NORETURN noWay()
61 {
62 #if MEASURE_FATAL
63     fatal_noWay += 1;
64 #endif // MEASURE_FATAL
65
66     fatal(CORJIT_INTERNALERROR);
67 }
68
69 /*****************************************************************************/
70 void DECLSPEC_NORETURN NOMEM()
71 {
72 #if MEASURE_FATAL
73     fatal_NOMEM += 1;
74 #endif // MEASURE_FATAL
75
76     fatal(CORJIT_OUTOFMEM);
77 }
78
79 /*****************************************************************************/
80 void DECLSPEC_NORETURN noWayAssertBody()
81 {
82 #if MEASURE_FATAL
83     fatal_noWayAssertBody += 1;
84 #endif // MEASURE_FATAL
85
86 #ifndef DEBUG
87     // Even in retail, if we hit a noway, and we have this variable set, we don't want to fall back
88     // to MinOpts, which might hide a regression. Instead, hit a breakpoint (and crash). We don't
89     // have the assert code to fall back on here.
90     // The debug path goes through this function also, to do the call to 'fatal'.
91     // This kind of noway is hit for unreached().
92     if (JitConfig.JitEnableNoWayAssert())
93     {
94         DebugBreak();
95     }
96 #endif // !DEBUG
97
98     fatal(CORJIT_RECOVERABLEERROR);
99 }
100
101
102 inline static bool ShouldThrowOnNoway(
103 #ifdef FEATURE_TRACELOGGING
104         const char* filename, unsigned line
105 #endif
106 )
107 {
108     return JitTls::GetCompiler() == NULL ||
109         JitTls::GetCompiler()->compShouldThrowOnNoway(
110 #ifdef FEATURE_TRACELOGGING
111             filename, line
112 #endif
113         );
114 }
115
116 /*****************************************************************************/
117 void noWayAssertBodyConditional(
118 #ifdef FEATURE_TRACELOGGING
119     const char* filename, unsigned line
120 #endif
121 )
122 {
123 #ifdef FEATURE_TRACELOGGING
124     if (ShouldThrowOnNoway(filename, line))
125 #else
126     if (ShouldThrowOnNoway())
127 #endif // FEATURE_TRACELOGGING
128     {
129         noWayAssertBody();
130     }
131 }
132
133 #if !defined(_TARGET_X86_) || !defined(LEGACY_BACKEND)
134
135 /*****************************************************************************/
136 void notYetImplemented(const char * msg, const char * filename, unsigned line)
137 {
138 #if FUNC_INFO_LOGGING
139 #ifdef DEBUG
140     LogEnv* env = JitTls::GetLogEnv();
141     if (env != NULL)
142     {
143         const Compiler* const pCompiler = env->compiler;
144         if (pCompiler->verbose)
145         {
146             printf("\n\n%s - NYI (%s:%d - %s)\n", pCompiler->info.compFullName,
147                 filename,
148                 line,
149                 msg);
150         }
151     }
152     if (Compiler::compJitFuncInfoFile != NULL)
153     {
154         fprintf(Compiler::compJitFuncInfoFile, "%s - NYI (%s:%d - %s)\n",
155             (env == NULL) ? "UNKNOWN" : env->compiler->info.compFullName,
156             filename,
157             line,
158             msg);
159         fflush(Compiler::compJitFuncInfoFile);
160     }
161 #else // !DEBUG
162     if (Compiler::compJitFuncInfoFile != NULL)
163     {
164         fprintf(Compiler::compJitFuncInfoFile, "NYI (%s:%d - %s)\n",
165             filename,
166             line,
167             msg);
168         fflush(Compiler::compJitFuncInfoFile);
169     }
170 #endif // !DEBUG
171 #endif // FUNC_INFO_LOGGING
172
173 #ifdef DEBUG
174     Compiler* pCompiler = JitTls::GetCompiler();
175     if (pCompiler != nullptr)
176     {
177         // Assume we're within a compFunctionTrace boundary, which might not be true.
178         pCompiler->compFunctionTraceEnd(nullptr, 0, true);
179     }
180 #endif // DEBUG 
181
182     DWORD value = JitConfig.AltJitAssertOnNYI();
183
184     // 0 means just silently skip
185     // If we are in retail builds, assume ignore
186     // 1 means popup the assert (abort=abort, retry=debugger, ignore=skip)
187     // 2 means silently don't skip (same as 3 for retail)
188     // 3 means popup the assert (abort=abort, retry=debugger, ignore=don't skip)
189     if (value & 1)
190     {
191 #ifdef DEBUG
192         assertAbort(msg, filename, line);
193 #endif
194     }
195
196     if ((value & 2) == 0)
197     {
198 #if MEASURE_FATAL
199         fatal_NYI += 1;
200 #endif // MEASURE_FATAL
201
202         fatal(CORJIT_SKIPPED);
203     }
204 }
205
206 #endif // #if !defined(_TARGET_X86_) || !defined(LEGACY_BACKEND)
207
208 /*****************************************************************************/
209 LONG __JITfilter(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam)
210 {
211    DWORD exceptCode = pExceptionPointers->ExceptionRecord->ExceptionCode;
212
213     if (exceptCode ==  FATAL_JIT_EXCEPTION)
214     {
215         ErrorTrapParam * pParam = (ErrorTrapParam *)lpvParam;
216
217         assert(pExceptionPointers->ExceptionRecord->NumberParameters == 1);
218         pParam->errc = (int)pExceptionPointers->ExceptionRecord->ExceptionInformation[0];
219
220         ICorJitInfo * jitInfo = pParam->jitInfo;
221
222         if (jitInfo != NULL)
223             jitInfo->reportFatalError((CorJitResult)pParam->errc);
224
225         return EXCEPTION_EXECUTE_HANDLER;
226     }
227
228     return EXCEPTION_CONTINUE_SEARCH;
229 }
230
231 /*****************************************************************************/
232 #ifdef DEBUG
233
234 DWORD getBreakOnBadCode()
235 {
236     return JitConfig.JitBreakOnBadCode();
237 }
238
239 /*****************************************************************************/
240 void debugError(const char* msg, const char* file, unsigned line) 
241 {
242     const char* tail = strrchr(file, '\\');
243     if (tail) file = tail+1;
244
245     LogEnv* env = JitTls::GetLogEnv();
246
247     logf(LL_ERROR, "COMPILATION FAILED: file: %s:%d compiling method %s reason %s\n", file, line, env->compiler->info.compFullName, msg);
248
249     // We now only assert when user explicitly set ComPlus_JitRequired=1
250     // If ComPlus_JitRequired is 0 or is not set, we will not assert.
251     if (JitConfig.JitRequired() == 1 || getBreakOnBadCode())
252     {
253             // Don't assert if verification is done.
254         if (!env->compiler->tiVerificationNeeded || getBreakOnBadCode())
255             assertAbort(msg, "NO-FILE", 0);
256     }
257
258     BreakIfDebuggerPresent();
259 }
260
261
262 /*****************************************************************************/
263 LogEnv::LogEnv(ICorJitInfo* aCompHnd)
264     : compHnd(aCompHnd)
265     , compiler(nullptr)
266 {
267 }
268
269 /*****************************************************************************/
270 extern  "C"
271 void  __cdecl   assertAbort(const char *why, const char *file, unsigned line)
272 {
273     const char* msg = why;
274     LogEnv* env = JitTls::GetLogEnv();
275     const int BUFF_SIZE = 8192;
276     char *buff = (char*)alloca(BUFF_SIZE);
277     if (env->compiler) {
278         _snprintf_s(buff, BUFF_SIZE, _TRUNCATE, "Assertion failed '%s' in '%s' (IL size %d)\n", why, env->compiler->info.compFullName, env->compiler->info.compILCodeSize);
279         msg = buff;
280     }
281     printf("");         // null string means flush
282
283 #if FUNC_INFO_LOGGING
284     if (Compiler::compJitFuncInfoFile != NULL)
285     {
286         fprintf(Compiler::compJitFuncInfoFile, "%s - Assertion failed (%s:%d - %s)\n",
287             (env == NULL) ? "UNKNOWN" : env->compiler->info.compFullName,
288             file,
289             line,
290             why);
291     }
292 #endif // FUNC_INFO_LOGGING
293
294     if (env->compHnd->doAssert(file, line, msg))
295         DebugBreak(); 
296
297 #ifdef ALT_JIT
298     // If we hit an assert, and we got here, it's either because the user hit "ignore" on the
299     // dialog pop-up, or they set COMPlus_ContinueOnAssert=1 to not emit a pop-up, but just continue.
300     // If we're an altjit, we have two options: (1) silently continue, as a normal JIT would, probably
301     // leading to additional asserts, or (2) tell the VM that the AltJit wants to skip this function,
302     // thus falling back to the fallback JIT. Setting COMPlus_AltJitSkipOnAssert=1 chooses this "skip"
303     // to the fallback JIT behavior. This is useful when doing ASM diffs, where we only want to see
304     // the first assert for any function, but we don't want to kill the whole ngen process on the
305     // first assert (which would happen if you used COMPlus_NoGuiOnAssert=1 for example).
306     if (JitConfig.AltJitSkipOnAssert() != 0)
307     {
308         fatal(CORJIT_SKIPPED);
309     }
310 #elif defined(_TARGET_ARM64_)
311     // TODO-ARM64-NYI: remove this after the JIT no longer asserts during startup
312     //
313     // When we are bringing up the new Arm64 JIT we set COMPlus_ContinueOnAssert=1 
314     // We only want to hit one assert then we will fall back to the interpreter.
315     //
316     bool interpreterFallback = (JitConfig.InterpreterFallback() != 0);
317
318     if (interpreterFallback)
319     {
320         fatal(CORJIT_SKIPPED);
321     }
322 #endif 
323 }
324
325 /*********************************************************************/
326 BOOL vlogf(unsigned level, const char* fmt, va_list args) 
327 {
328     return JitTls::GetLogEnv()->compHnd->logMsg(level, fmt, args);
329
330
331 int vflogf(FILE* file, const char* fmt, va_list args)
332 {
333     // 0-length string means flush
334     if (fmt[0] == '\0')
335     {
336         fflush(file);
337         return 0;
338     }
339
340     const int BUFF_SIZE = 8192;
341     char buffer[BUFF_SIZE];
342     int written = _vsnprintf_s(&buffer[0], BUFF_SIZE, _TRUNCATE, fmt, args);
343
344     if (JitConfig.JitDumpToDebugger())
345     {
346         OutputDebugStringA(buffer);
347     }
348    
349     // We use fputs here so that this executes as fast a possible
350     fputs(&buffer[0], file);
351     return written;
352 }
353
354 int flogf(FILE* file, const char* fmt, ...)
355 {
356     va_list args;
357     va_start(args, fmt);
358     int written = vflogf(file, fmt, args);
359     va_end(args);
360     return written;
361 }
362
363 /*********************************************************************/
364 int logf(const char* fmt, ...)
365 {
366     va_list args;
367     static bool logToEEfailed = false;
368     int written = 0;
369     //
370     // We remember when the EE failed to log, because vlogf()
371     // is very slow in a checked build.
372     //
373     // If it fails to log an LL_INFO1000 message once 
374     // it will always fail when logging an LL_INFO1000 message.
375     //
376     if (!logToEEfailed)
377     {
378         va_start(args, fmt);
379         if (!vlogf(LL_INFO1000, fmt, args))
380             logToEEfailed = true;
381         va_end(args);
382     }
383     
384     if (logToEEfailed)
385     {
386         // if the EE refuses to log it, we try to send it to stdout
387         va_start(args, fmt);
388         written = vflogf(jitstdout, fmt, args);
389         va_end(args);
390     }
391 #if 0  // Enable this only when you need it
392     else
393     {
394         //
395         // The EE just successfully logged our message
396         //
397         static ConfigDWORD fJitBreakOnDumpToken;
398         DWORD breakOnDumpToken = fJitBreakOnDumpToken.val(CLRConfig::INTERNAL_BreakOnDumpToken);
399         static DWORD forbidEntry = 0;
400         
401         if ((breakOnDumpToken != 0xffffffff) && (forbidEntry == 0)) 
402         {
403             forbidEntry = 1;
404             
405             // Use value of 0 to get the dump
406             static DWORD currentLine = 1;
407             
408             if (currentLine == breakOnDumpToken) 
409             {
410                 assert(!"Dump token reached");
411             }
412             
413             printf("(Token=0x%x) ", currentLine++);
414             forbidEntry = 0;
415         }
416     }
417 #endif // 0
418     va_end(args);
419
420     return written;
421 }
422
423 /*********************************************************************/
424 void gcDump_logf(const char* fmt, ...)
425 {
426     va_list args;
427     static bool logToEEfailed = false;
428     //
429     // We remember when the EE failed to log, because vlogf()
430     // is very slow in a checked build.
431     //
432     // If it fails to log an LL_INFO1000 message once 
433     // it will always fail when logging an LL_INFO1000 message.
434     //
435     if (!logToEEfailed)
436     {
437         va_start(args, fmt);
438         if (!vlogf(LL_INFO1000, fmt, args))
439             logToEEfailed = true;
440         va_end(args);
441     }
442     
443     if (logToEEfailed)
444     {
445         // if the EE refuses to log it, we try to send it to stdout
446         va_start(args, fmt);
447         vflogf(jitstdout, fmt, args);
448         va_end(args);
449     }
450 #if 0  // Enable this only when you need it
451     else
452     {
453         //
454         // The EE just successfully logged our message
455         //
456         static ConfigDWORD fJitBreakOnDumpToken;
457         DWORD breakOnDumpToken = fJitBreakOnDumpToken.val(CLRConfig::INTERNAL_BreakOnDumpToken);
458         static DWORD forbidEntry = 0;
459         
460         if ((breakOnDumpToken != 0xffffffff) && (forbidEntry == 0)) 
461         {
462             forbidEntry = 1;
463             
464             // Use value of 0 to get the dump
465             static DWORD currentLine = 1;
466             
467             if (currentLine == breakOnDumpToken) 
468             {
469                 assert(!"Dump token reached");
470             }
471             
472             printf("(Token=0x%x) ", currentLine++);
473             forbidEntry = 0;
474         }
475     }
476 #endif // 0
477     va_end(args);
478 }
479
480 /*********************************************************************/
481 void logf(unsigned level, const char* fmt, ...)
482 {
483     va_list args;
484     va_start(args, fmt);
485     vlogf(level, fmt, args);
486     va_end(args);
487 }
488
489 void DECLSPEC_NORETURN badCode3(const char* msg, const char* msg2, int arg,
490                                 __in_z const char* file, unsigned line)
491 {
492     const int BUFF_SIZE = 512;
493     char buf1[BUFF_SIZE];
494     char buf2[BUFF_SIZE];
495     sprintf_s(buf1, BUFF_SIZE, "%s%s", msg, msg2);
496     sprintf_s(buf2, BUFF_SIZE, buf1, arg);
497
498     debugError(buf2, file, line);
499     badCode();
500 }
501
502 void noWayAssertAbortHelper(const char * cond, const char * file, unsigned line)
503 {
504     // Show the assert UI.
505     if (JitConfig.JitEnableNoWayAssert())
506     {
507         assertAbort(cond, file, line);
508     }
509 }
510
511 void noWayAssertBodyConditional(const char * cond, const char * file, unsigned line)
512 {
513 #ifdef FEATURE_TRACELOGGING
514     if (ShouldThrowOnNoway(file, line))
515 #else
516     if (ShouldThrowOnNoway())
517 #endif
518     {
519         noWayAssertBody(cond, file, line);
520     }
521     // In CHK we want the assert UI to show up in min-opts.
522     else
523     {
524         noWayAssertAbortHelper(cond, file, line);
525     }
526 }
527
528 void DECLSPEC_NORETURN noWayAssertBody(const char * cond, const char * file, unsigned line)
529 {
530 #if MEASURE_FATAL
531     fatal_noWayAssertBodyArgs += 1;
532 #endif // MEASURE_FATAL
533
534     noWayAssertAbortHelper(cond, file, line);
535     noWayAssertBody();
536 }
537
538 #endif // DEBUG