Permalink
Fetching contributors…
Cannot retrieve contributors at this time
11089 lines (9410 sloc) 357 KB
// 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 Compiler XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif // _MSC_VER
#include "hostallocator.h"
#include "emit.h"
#include "ssabuilder.h"
#include "valuenum.h"
#include "rangecheck.h"
#include "lower.h"
#include "stacklevelsetter.h"
#include "jittelemetry.h"
#if defined(DEBUG)
// Column settings for COMPlus_JitDumpIR. We could(should) make these programmable.
#define COLUMN_OPCODE 30
#define COLUMN_OPERANDS (COLUMN_OPCODE + 25)
#define COLUMN_KINDS 110
#define COLUMN_FLAGS (COLUMN_KINDS + 32)
#endif
#if defined(DEBUG)
unsigned Compiler::jitTotalMethodCompiled = 0;
#endif // defined(DEBUG)
#if defined(DEBUG)
LONG Compiler::jitNestingLevel = 0;
#endif // defined(DEBUG)
#ifdef ALT_JIT
// static
bool Compiler::s_pAltJitExcludeAssembliesListInitialized = false;
AssemblyNamesList2* Compiler::s_pAltJitExcludeAssembliesList = nullptr;
#endif // ALT_JIT
#ifdef DEBUG
// static
bool Compiler::s_pJitDisasmIncludeAssembliesListInitialized = false;
AssemblyNamesList2* Compiler::s_pJitDisasmIncludeAssembliesList = nullptr;
#endif // DEBUG
/*****************************************************************************
*
* Little helpers to grab the current cycle counter value; this is done
* differently based on target architecture, host toolchain, etc. The
* main thing is to keep the overhead absolutely minimal; in fact, on
* x86/x64 we use RDTSC even though it's not thread-safe; GetThreadCycles
* (which is monotonous) is just too expensive.
*/
#ifdef FEATURE_JIT_METHOD_PERF
#if defined(_HOST_X86_) || defined(_HOST_AMD64_)
#if defined(_MSC_VER)
#include <intrin.h>
inline bool _our_GetThreadCycles(unsigned __int64* cycleOut)
{
*cycleOut = __rdtsc();
return true;
}
#elif defined(__clang__)
inline bool _our_GetThreadCycles(unsigned __int64* cycleOut)
{
uint32_t hi, lo;
__asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi));
*cycleOut = (static_cast<unsigned __int64>(hi) << 32) | static_cast<unsigned __int64>(lo);
return true;
}
#else // neither _MSC_VER nor __clang__
// The following *might* work - might as well try.
#define _our_GetThreadCycles(cp) GetThreadCycles(cp)
#endif
#elif defined(_HOST_ARM_) || defined(_HOST_ARM64_)
// If this doesn't work please see ../gc/gc.cpp for additional ARM
// info (and possible solutions).
#define _our_GetThreadCycles(cp) GetThreadCycles(cp)
#else // not x86/x64 and not ARM
// Don't know what this target is, but let's give it a try; if
// someone really wants to make this work, please add the right
// code here.
#define _our_GetThreadCycles(cp) GetThreadCycles(cp)
#endif // which host OS
#endif // FEATURE_JIT_METHOD_PERF
/*****************************************************************************/
inline unsigned getCurTime()
{
SYSTEMTIME tim;
GetSystemTime(&tim);
return (((tim.wHour * 60) + tim.wMinute) * 60 + tim.wSecond) * 1000 + tim.wMilliseconds;
}
/*****************************************************************************/
#ifdef DEBUG
/*****************************************************************************/
static FILE* jitSrcFilePtr;
static unsigned jitCurSrcLine;
void Compiler::JitLogEE(unsigned level, const char* fmt, ...)
{
va_list args;
if (verbose)
{
va_start(args, fmt);
vflogf(jitstdout, fmt, args);
va_end(args);
}
va_start(args, fmt);
vlogf(level, fmt, args);
va_end(args);
}
void Compiler::compDspSrcLinesByLineNum(unsigned line, bool seek)
{
if (!jitSrcFilePtr)
{
return;
}
if (jitCurSrcLine == line)
{
return;
}
if (jitCurSrcLine > line)
{
if (!seek)
{
return;
}
if (fseek(jitSrcFilePtr, 0, SEEK_SET) != 0)
{
printf("Compiler::compDspSrcLinesByLineNum: fseek returned an error.\n");
}
jitCurSrcLine = 0;
}
if (!seek)
{
printf(";\n");
}
do
{
char temp[128];
size_t llen;
if (!fgets(temp, sizeof(temp), jitSrcFilePtr))
{
return;
}
if (seek)
{
continue;
}
llen = strlen(temp);
if (llen && temp[llen - 1] == '\n')
{
temp[llen - 1] = 0;
}
printf("; %s\n", temp);
} while (++jitCurSrcLine < line);
if (!seek)
{
printf(";\n");
}
}
/*****************************************************************************/
void Compiler::compDspSrcLinesByNativeIP(UNATIVE_OFFSET curIP)
{
static IPmappingDsc* nextMappingDsc;
static unsigned lastLine;
if (!opts.dspLines)
{
return;
}
if (curIP == 0)
{
if (genIPmappingList)
{
nextMappingDsc = genIPmappingList;
lastLine = jitGetILoffs(nextMappingDsc->ipmdILoffsx);
unsigned firstLine = jitGetILoffs(nextMappingDsc->ipmdILoffsx);
unsigned earlierLine = (firstLine < 5) ? 0 : firstLine - 5;
compDspSrcLinesByLineNum(earlierLine, true); // display previous 5 lines
compDspSrcLinesByLineNum(firstLine, false);
}
else
{
nextMappingDsc = nullptr;
}
return;
}
if (nextMappingDsc)
{
UNATIVE_OFFSET offset = nextMappingDsc->ipmdNativeLoc.CodeOffset(genEmitter);
if (offset <= curIP)
{
IL_OFFSET nextOffs = jitGetILoffs(nextMappingDsc->ipmdILoffsx);
if (lastLine < nextOffs)
{
compDspSrcLinesByLineNum(nextOffs);
}
else
{
// This offset corresponds to a previous line. Rewind to that line
compDspSrcLinesByLineNum(nextOffs - 2, true);
compDspSrcLinesByLineNum(nextOffs);
}
lastLine = nextOffs;
nextMappingDsc = nextMappingDsc->ipmdNext;
}
}
}
/*****************************************************************************/
#endif // DEBUG
/*****************************************************************************/
#if defined(DEBUG) || MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || DISPLAY_SIZES || CALL_ARG_STATS
static unsigned genMethodCnt; // total number of methods JIT'ted
unsigned genMethodICnt; // number of interruptible methods
unsigned genMethodNCnt; // number of non-interruptible methods
static unsigned genSmallMethodsNeedingExtraMemoryCnt = 0;
#endif
/*****************************************************************************/
#if MEASURE_NODE_SIZE
NodeSizeStats genNodeSizeStats;
NodeSizeStats genNodeSizeStatsPerFunc;
unsigned genTreeNcntHistBuckets[] = {10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000, 5000, 10000, 0};
Histogram genTreeNcntHist(genTreeNcntHistBuckets);
unsigned genTreeNsizHistBuckets[] = {1000, 5000, 10000, 50000, 100000, 500000, 1000000, 0};
Histogram genTreeNsizHist(genTreeNsizHistBuckets);
#endif // MEASURE_NODE_SIZE
/*****************************************************************************/
#if MEASURE_MEM_ALLOC
unsigned memAllocHistBuckets[] = {64, 128, 192, 256, 512, 1024, 4096, 8192, 0};
Histogram memAllocHist(memAllocHistBuckets);
unsigned memUsedHistBuckets[] = {16, 32, 64, 128, 192, 256, 512, 1024, 4096, 8192, 0};
Histogram memUsedHist(memUsedHistBuckets);
#endif // MEASURE_MEM_ALLOC
/*****************************************************************************
*
* Variables to keep track of total code amounts.
*/
#if DISPLAY_SIZES
size_t grossVMsize; // Total IL code size
size_t grossNCsize; // Native code + data size
size_t totalNCsize; // Native code + data + GC info size (TODO-Cleanup: GC info size only accurate for JIT32_GCENCODER)
size_t gcHeaderISize; // GC header size: interruptible methods
size_t gcPtrMapISize; // GC pointer map size: interruptible methods
size_t gcHeaderNSize; // GC header size: non-interruptible methods
size_t gcPtrMapNSize; // GC pointer map size: non-interruptible methods
#endif // DISPLAY_SIZES
/*****************************************************************************
*
* Variables to keep track of argument counts.
*/
#if CALL_ARG_STATS
unsigned argTotalCalls;
unsigned argHelperCalls;
unsigned argStaticCalls;
unsigned argNonVirtualCalls;
unsigned argVirtualCalls;
unsigned argTotalArgs; // total number of args for all calls (including objectPtr)
unsigned argTotalDWordArgs;
unsigned argTotalLongArgs;
unsigned argTotalFloatArgs;
unsigned argTotalDoubleArgs;
unsigned argTotalRegArgs;
unsigned argTotalTemps;
unsigned argTotalLclVar;
unsigned argTotalDeferred;
unsigned argTotalConst;
unsigned argTotalObjPtr;
unsigned argTotalGTF_ASGinArgs;
unsigned argMaxTempsPerMethod;
unsigned argCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argCntTable(argCntBuckets);
unsigned argDWordCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argDWordCntTable(argDWordCntBuckets);
unsigned argDWordLngCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argDWordLngCntTable(argDWordLngCntBuckets);
unsigned argTempsCntBuckets[] = {0, 1, 2, 3, 4, 5, 6, 10, 0};
Histogram argTempsCntTable(argTempsCntBuckets);
#endif // CALL_ARG_STATS
/*****************************************************************************
*
* Variables to keep track of basic block counts.
*/
#if COUNT_BASIC_BLOCKS
// --------------------------------------------------
// Basic block count frequency table:
// --------------------------------------------------
// <= 1 ===> 26872 count ( 56% of total)
// 2 .. 2 ===> 669 count ( 58% of total)
// 3 .. 3 ===> 4687 count ( 68% of total)
// 4 .. 5 ===> 5101 count ( 78% of total)
// 6 .. 10 ===> 5575 count ( 90% of total)
// 11 .. 20 ===> 3028 count ( 97% of total)
// 21 .. 50 ===> 1108 count ( 99% of total)
// 51 .. 100 ===> 182 count ( 99% of total)
// 101 .. 1000 ===> 34 count (100% of total)
// 1001 .. 10000 ===> 0 count (100% of total)
// --------------------------------------------------
unsigned bbCntBuckets[] = {1, 2, 3, 5, 10, 20, 50, 100, 1000, 10000, 0};
Histogram bbCntTable(bbCntBuckets);
/* Histogram for the IL opcode size of methods with a single basic block */
unsigned bbSizeBuckets[] = {1, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 0};
Histogram bbOneBBSizeTable(bbSizeBuckets);
#endif // COUNT_BASIC_BLOCKS
/*****************************************************************************
*
* Used by optFindNaturalLoops to gather statistical information such as
* - total number of natural loops
* - number of loops with 1, 2, ... exit conditions
* - number of loops that have an iterator (for like)
* - number of loops that have a constant iterator
*/
#if COUNT_LOOPS
unsigned totalLoopMethods; // counts the total number of methods that have natural loops
unsigned maxLoopsPerMethod; // counts the maximum number of loops a method has
unsigned totalLoopOverflows; // # of methods that identified more loops than we can represent
unsigned totalLoopCount; // counts the total number of natural loops
unsigned totalUnnatLoopCount; // counts the total number of (not-necessarily natural) loops
unsigned totalUnnatLoopOverflows; // # of methods that identified more unnatural loops than we can represent
unsigned iterLoopCount; // counts the # of loops with an iterator (for like)
unsigned simpleTestLoopCount; // counts the # of loops with an iterator and a simple loop condition (iter < const)
unsigned constIterLoopCount; // counts the # of loops with a constant iterator (for like)
bool hasMethodLoops; // flag to keep track if we already counted a method as having loops
unsigned loopsThisMethod; // counts the number of loops in the current method
bool loopOverflowThisMethod; // True if we exceeded the max # of loops in the method.
/* Histogram for number of loops in a method */
unsigned loopCountBuckets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0};
Histogram loopCountTable(loopCountBuckets);
/* Histogram for number of loop exits */
unsigned loopExitCountBuckets[] = {0, 1, 2, 3, 4, 5, 6, 0};
Histogram loopExitCountTable(loopExitCountBuckets);
#endif // COUNT_LOOPS
//------------------------------------------------------------------------
// getJitGCType: Given the VM's CorInfoGCType convert it to the JIT's var_types
//
// Arguments:
// gcType - an enum value that originally came from an element
// of the BYTE[] returned from getClassGClayout()
//
// Return Value:
// The corresponsing enum value from the JIT's var_types
//
// Notes:
// The gcLayout of each field of a struct is returned from getClassGClayout()
// as a BYTE[] but each BYTE element is actually a CorInfoGCType value
// Note when we 'know' that there is only one element in theis array
// the JIT will often pass the address of a single BYTE, instead of a BYTE[]
//
var_types Compiler::getJitGCType(BYTE gcType)
{
var_types result = TYP_UNKNOWN;
CorInfoGCType corInfoType = (CorInfoGCType)gcType;
if (corInfoType == TYPE_GC_NONE)
{
result = TYP_I_IMPL;
}
else if (corInfoType == TYPE_GC_REF)
{
result = TYP_REF;
}
else if (corInfoType == TYPE_GC_BYREF)
{
result = TYP_BYREF;
}
else
{
noway_assert(!"Bad value of 'gcType'");
}
return result;
}
#if FEATURE_MULTIREG_ARGS
//---------------------------------------------------------------------------
// getStructGcPtrsFromOp: Given a GenTree node of TYP_STRUCT that represents
// a pass by value argument, return the gcPtr layout
// for the pointers sized fields
// Arguments:
// op - the operand of TYP_STRUCT that is passed by value
// gcPtrsOut - an array of BYTES that are written by this method
// they will contain the VM's CorInfoGCType values
// for each pointer sized field
// Return Value:
// Two [or more] values are written into the gcPtrs array
//
// Note that for ARM64 there will always be exactly two pointer sized fields
void Compiler::getStructGcPtrsFromOp(GenTree* op, BYTE* gcPtrsOut)
{
assert(op->TypeGet() == TYP_STRUCT);
#ifdef _TARGET_ARM64_
if (op->OperGet() == GT_OBJ)
{
CORINFO_CLASS_HANDLE objClass = op->gtObj.gtClass;
int structSize = info.compCompHnd->getClassSize(objClass);
assert(structSize <= 2 * TARGET_POINTER_SIZE);
BYTE gcPtrsTmp[2] = {TYPE_GC_NONE, TYPE_GC_NONE};
info.compCompHnd->getClassGClayout(objClass, &gcPtrsTmp[0]);
gcPtrsOut[0] = gcPtrsTmp[0];
gcPtrsOut[1] = gcPtrsTmp[1];
}
else if (op->OperGet() == GT_LCL_VAR)
{
GenTreeLclVarCommon* varNode = op->AsLclVarCommon();
unsigned varNum = varNode->gtLclNum;
assert(varNum < lvaCount);
LclVarDsc* varDsc = &lvaTable[varNum];
// At this point any TYP_STRUCT LclVar must be a 16-byte pass by value argument
assert(varDsc->lvSize() == 2 * TARGET_POINTER_SIZE);
gcPtrsOut[0] = varDsc->lvGcLayout[0];
gcPtrsOut[1] = varDsc->lvGcLayout[1];
}
else
#endif
{
noway_assert(!"Unsupported Oper for getStructGcPtrsFromOp");
}
}
#endif // FEATURE_MULTIREG_ARGS
#ifdef ARM_SOFTFP
//---------------------------------------------------------------------------
// IsSingleFloat32Struct:
// Check if the given struct type contains only one float32 value type
//
// Arguments:
// clsHnd - the handle for the struct type
//
// Return Value:
// true if the given struct type contains only one float32 value type,
// false otherwise.
//
bool Compiler::isSingleFloat32Struct(CORINFO_CLASS_HANDLE clsHnd)
{
for (;;)
{
// all of class chain must be of value type and must have only one field
if (!info.compCompHnd->isValueClass(clsHnd) || info.compCompHnd->getClassNumInstanceFields(clsHnd) != 1)
{
return false;
}
CORINFO_CLASS_HANDLE* pClsHnd = &clsHnd;
CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(clsHnd, 0);
CorInfoType fieldType = info.compCompHnd->getFieldType(fldHnd, pClsHnd);
switch (fieldType)
{
case CORINFO_TYPE_VALUECLASS:
clsHnd = *pClsHnd;
break;
case CORINFO_TYPE_FLOAT:
return true;
default:
return false;
}
}
}
#endif // ARM_SOFTFP
//-----------------------------------------------------------------------------
// getPrimitiveTypeForStruct:
// Get the "primitive" type that is is used for a struct
// of size 'structSize'.
// We examine 'clsHnd' to check the GC layout of the struct and
// return TYP_REF for structs that simply wrap an object.
// If the struct is a one element HFA, we will return the
// proper floating point type.
//
// Arguments:
// structSize - the size of the struct type, cannot be zero
// clsHnd - the handle for the struct type, used when may have
// an HFA or if we need the GC layout for an object ref.
//
// Return Value:
// The primitive type (i.e. byte, short, int, long, ref, float, double)
// used to pass or return structs of this size.
// If we shouldn't use a "primitive" type then TYP_UNKNOWN is returned.
// Notes:
// For 32-bit targets (X86/ARM32) the 64-bit TYP_LONG type is not
// considered a primitive type by this method.
// So a struct that wraps a 'long' is passed and returned in the
// same way as any other 8-byte struct
// For ARM32 if we have an HFA struct that wraps a 64-bit double
// we will return TYP_DOUBLE.
//
var_types Compiler::getPrimitiveTypeForStruct(unsigned structSize, CORINFO_CLASS_HANDLE clsHnd, bool isVarArg)
{
assert(structSize != 0);
var_types useType;
switch (structSize)
{
case 1:
useType = TYP_BYTE;
break;
case 2:
useType = TYP_SHORT;
break;
#if !defined(_TARGET_XARCH_) || defined(UNIX_AMD64_ABI)
case 3:
useType = TYP_INT;
break;
#endif // !_TARGET_XARCH_ || UNIX_AMD64_ABI
#ifdef _TARGET_64BIT_
case 4:
if (IsHfa(clsHnd))
{
// A structSize of 4 with IsHfa, it must be an HFA of one float
useType = TYP_FLOAT;
}
else
{
useType = TYP_INT;
}
break;
#if !defined(_TARGET_XARCH_) || defined(UNIX_AMD64_ABI)
case 5:
case 6:
case 7:
useType = TYP_I_IMPL;
break;
#endif // !_TARGET_XARCH_ || UNIX_AMD64_ABI
#endif // _TARGET_64BIT_
case TARGET_POINTER_SIZE:
#ifdef ARM_SOFTFP
// For ARM_SOFTFP, HFA is unsupported so we need to check in another way
// This matters only for size-4 struct cause bigger structs would be processed with RetBuf
if (isSingleFloat32Struct(clsHnd))
#else // !ARM_SOFTFP
if (IsHfa(clsHnd)
#if defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
// Arm64 Windows VarArg methods arguments will not
// classify HFA types, they will need to be treated
// as if they are not HFA types.
&& !isVarArg
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
)
#endif // ARM_SOFTFP
{
#ifdef _TARGET_64BIT_
var_types hfaType = GetHfaType(clsHnd);
// A structSize of 8 with IsHfa, we have two possiblities:
// An HFA of one double or an HFA of two floats
//
// Check and exclude the case of an HFA of two floats
if (hfaType == TYP_DOUBLE)
{
// We have an HFA of one double
useType = TYP_DOUBLE;
}
else
{
assert(hfaType == TYP_FLOAT);
// We have an HFA of two floats
// This should be passed or returned in two FP registers
useType = TYP_UNKNOWN;
}
#else // a 32BIT target
// A structSize of 4 with IsHfa, it must be an HFA of one float
useType = TYP_FLOAT;
#endif // _TARGET_64BIT_
}
else
{
BYTE gcPtr = 0;
// Check if this pointer-sized struct is wrapping a GC object
info.compCompHnd->getClassGClayout(clsHnd, &gcPtr);
useType = getJitGCType(gcPtr);
}
break;
#ifdef _TARGET_ARM_
case 8:
if (IsHfa(clsHnd))
{
var_types hfaType = GetHfaType(clsHnd);
// A structSize of 8 with IsHfa, we have two possiblities:
// An HFA of one double or an HFA of two floats
//
// Check and exclude the case of an HFA of two floats
if (hfaType == TYP_DOUBLE)
{
// We have an HFA of one double
useType = TYP_DOUBLE;
}
else
{
assert(hfaType == TYP_FLOAT);
// We have an HFA of two floats
// This should be passed or returned in two FP registers
useType = TYP_UNKNOWN;
}
}
else
{
// We don't have an HFA
useType = TYP_UNKNOWN;
}
break;
#endif // _TARGET_ARM_
default:
useType = TYP_UNKNOWN;
break;
}
return useType;
}
//-----------------------------------------------------------------------------
// getArgTypeForStruct:
// Get the type that is used to pass values of the given struct type.
// If you have already retrieved the struct size then it should be
// passed as the optional third argument, as this allows us to avoid
// an extra call to getClassSize(clsHnd)
//
// Arguments:
// clsHnd - the handle for the struct type
// wbPassStruct - An "out" argument with information about how
// the struct is to be passed
// isVarArg - is vararg, used to ignore HFA types for Arm64 windows varargs
// structSize - the size of the struct type,
// or zero if we should call getClassSize(clsHnd)
//
// Return Value:
// For wbPassStruct you can pass a 'nullptr' and nothing will be written
// or returned for that out parameter.
// When *wbPassStruct is SPK_PrimitiveType this method's return value
// is the primitive type used to pass the struct.
// When *wbPassStruct is SPK_ByReference this method's return value
// is always TYP_UNKNOWN and the struct type is passed by reference to a copy
// When *wbPassStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value
// is always TYP_STRUCT and the struct type is passed by value either
// using multiple registers or on the stack.
//
// Assumptions:
// The size must be the size of the given type.
// The given class handle must be for a value type (struct).
//
// Notes:
// About HFA types:
// When the clsHnd is a one element HFA type we return the appropriate
// floating point primitive type and *wbPassStruct is SPK_PrimitiveType
// If there are two or more elements in the HFA type then the this method's
// return value is TYP_STRUCT and *wbPassStruct is SPK_ByValueAsHfa
//
var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
structPassingKind* wbPassStruct,
bool isVarArg,
unsigned structSize /* = 0 */)
{
var_types useType = TYP_UNKNOWN;
structPassingKind howToPassStruct = SPK_Unknown; // We must change this before we return
if (structSize == 0)
{
structSize = info.compCompHnd->getClassSize(clsHnd);
}
assert(structSize > 0);
// Determine if we can pass the struct as a primitive type.
// Note that on x86 we never pass structs as primitive types (unless the VM unwraps them for us).
#ifndef _TARGET_X86_
#ifdef UNIX_AMD64_ABI
// An 8-byte struct may need to be passed in a floating point register
// So we always consult the struct "Classifier" routine
//
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
eeGetSystemVAmd64PassStructInRegisterDescriptor(clsHnd, &structDesc);
if (structDesc.passedInRegisters && (structDesc.eightByteCount != 1))
{
// We can't pass this as a primitive type.
}
else if (structDesc.eightByteClassifications[0] == SystemVClassificationTypeSSE)
{
// If this is passed as a floating type, use that.
// Otherwise, we'll use the general case - we don't want to use the "EightByteType"
// directly, because it returns `TYP_INT` for any integral type <= 4 bytes, and
// we need to preserve small types.
useType = GetEightByteType(structDesc, 0);
}
else
#endif // UNIX_AMD64_ABI
// The largest primitive type is 8 bytes (TYP_DOUBLE)
// so we can skip calling getPrimitiveTypeForStruct when we
// have a struct that is larger than that.
//
if (structSize <= sizeof(double))
{
// We set the "primitive" useType based upon the structSize
// and also examine the clsHnd to see if it is an HFA of count one
useType = getPrimitiveTypeForStruct(structSize, clsHnd, isVarArg);
}
#endif // !_TARGET_X86_
// Did we change this struct type into a simple "primitive" type?
//
if (useType != TYP_UNKNOWN)
{
// Yes, we should use the "primitive" type in 'useType'
howToPassStruct = SPK_PrimitiveType;
}
else // We can't replace the struct with a "primitive" type
{
// See if we can pass this struct by value, possibly in multiple registers
// or if we should pass it by reference to a copy
//
if (structSize <= MAX_PASS_MULTIREG_BYTES)
{
// Structs that are HFA's are passed by value in multiple registers
if (IsHfa(clsHnd)
#if defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
&& !isVarArg // Arm64 Windows VarArg methods arguments will not
// classify HFA types, they will need to be treated
// as if they are not HFA types.
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
)
{
// HFA's of count one should have been handled by getPrimitiveTypeForStruct
assert(GetHfaCount(clsHnd) >= 2);
// setup wbPassType and useType indicate that this is passed by value as an HFA
// using multiple registers
// (when all of the parameters registers are used, then the stack will be used)
howToPassStruct = SPK_ByValueAsHfa;
useType = TYP_STRUCT;
}
else // Not an HFA struct type
{
#ifdef UNIX_AMD64_ABI
// The case of (structDesc.eightByteCount == 1) should have already been handled
if ((structDesc.eightByteCount > 1) || !structDesc.passedInRegisters)
{
// setup wbPassType and useType indicate that this is passed by value in multiple registers
// (when all of the parameters registers are used, then the stack will be used)
howToPassStruct = SPK_ByValue;
useType = TYP_STRUCT;
}
else
{
assert(structDesc.eightByteCount == 0);
// Otherwise we pass this struct by reference to a copy
// setup wbPassType and useType indicate that this is passed using one register
// (by reference to a copy)
howToPassStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
}
#elif defined(_TARGET_ARM64_)
// Structs that are pointer sized or smaller should have been handled by getPrimitiveTypeForStruct
assert(structSize > TARGET_POINTER_SIZE);
// On ARM64 structs that are 9-16 bytes are passed by value in multiple registers
//
if (structSize <= (TARGET_POINTER_SIZE * 2))
{
// setup wbPassType and useType indicate that this is passed by value in multiple registers
// (when all of the parameters registers are used, then the stack will be used)
howToPassStruct = SPK_ByValue;
useType = TYP_STRUCT;
}
else // a structSize that is 17-32 bytes in size
{
// Otherwise we pass this struct by reference to a copy
// setup wbPassType and useType indicate that this is passed using one register
// (by reference to a copy)
howToPassStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
}
#elif defined(_TARGET_X86_) || defined(_TARGET_ARM_)
// Otherwise we pass this struct by value on the stack
// setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI
howToPassStruct = SPK_ByValue;
useType = TYP_STRUCT;
#else // _TARGET_XXX_
noway_assert(!"Unhandled TARGET in getArgTypeForStruct (with FEATURE_MULTIREG_ARGS=1)");
#endif // _TARGET_XXX_
}
}
else // (structSize > MAX_PASS_MULTIREG_BYTES)
{
// We have a (large) struct that can't be replaced with a "primitive" type
// and can't be passed in multiple registers
CLANG_FORMAT_COMMENT_ANCHOR;
#if defined(_TARGET_X86_) || defined(_TARGET_ARM_)
// Otherwise we pass this struct by value on the stack
// setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI
howToPassStruct = SPK_ByValue;
useType = TYP_STRUCT;
#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
// Otherwise we pass this struct by reference to a copy
// setup wbPassType and useType indicate that this is passed using one register (by reference to a copy)
howToPassStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
#else // _TARGET_XXX_
noway_assert(!"Unhandled TARGET in getArgTypeForStruct");
#endif // _TARGET_XXX_
}
}
// 'howToPassStruct' must be set to one of the valid values before we return
assert(howToPassStruct != SPK_Unknown);
if (wbPassStruct != nullptr)
{
*wbPassStruct = howToPassStruct;
}
return useType;
}
//-----------------------------------------------------------------------------
// getReturnTypeForStruct:
// Get the type that is used to return values of the given struct type.
// If you have already retrieved the struct size then it should be
// passed as the optional third argument, as this allows us to avoid
// an extra call to getClassSize(clsHnd)
//
// Arguments:
// clsHnd - the handle for the struct type
// wbReturnStruct - An "out" argument with information about how
// the struct is to be returned
// structSize - the size of the struct type,
// or zero if we should call getClassSize(clsHnd)
//
// Return Value:
// For wbReturnStruct you can pass a 'nullptr' and nothing will be written
// or returned for that out parameter.
// When *wbReturnStruct is SPK_PrimitiveType this method's return value
// is the primitive type used to return the struct.
// When *wbReturnStruct is SPK_ByReference this method's return value
// is always TYP_UNKNOWN and the struct type is returned using a return buffer
// When *wbReturnStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value
// is always TYP_STRUCT and the struct type is returned using multiple registers.
//
// Assumptions:
// The size must be the size of the given type.
// The given class handle must be for a value type (struct).
//
// Notes:
// About HFA types:
// When the clsHnd is a one element HFA type then this method's return
// value is the appropriate floating point primitive type and
// *wbReturnStruct is SPK_PrimitiveType.
// If there are two or more elements in the HFA type and the target supports
// multireg return types then the return value is TYP_STRUCT and
// *wbReturnStruct is SPK_ByValueAsHfa.
// Additionally if there are two or more elements in the HFA type and
// the target doesn't support multreg return types then it is treated
// as if it wasn't an HFA type.
// About returning TYP_STRUCT:
// Whenever this method's return value is TYP_STRUCT it always means
// that multiple registers are used to return this struct.
//
var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
structPassingKind* wbReturnStruct /* = nullptr */,
unsigned structSize /* = 0 */)
{
var_types useType = TYP_UNKNOWN;
structPassingKind howToReturnStruct = SPK_Unknown; // We must change this before we return
bool canReturnInRegister = true;
assert(clsHnd != NO_CLASS_HANDLE);
if (structSize == 0)
{
structSize = info.compCompHnd->getClassSize(clsHnd);
}
assert(structSize > 0);
#ifdef UNIX_AMD64_ABI
// An 8-byte struct may need to be returned in a floating point register
// So we always consult the struct "Classifier" routine
//
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
eeGetSystemVAmd64PassStructInRegisterDescriptor(clsHnd, &structDesc);
if (structDesc.eightByteCount == 1)
{
assert(structSize <= sizeof(double));
assert(structDesc.passedInRegisters);
if (structDesc.eightByteClassifications[0] == SystemVClassificationTypeSSE)
{
// If this is returned as a floating type, use that.
// Otherwise, leave as TYP_UNKONWN and we'll sort things out below.
useType = GetEightByteType(structDesc, 0);
howToReturnStruct = SPK_PrimitiveType;
}
}
else
{
// Return classification is not always size based...
canReturnInRegister = structDesc.passedInRegisters;
}
#endif // UNIX_AMD64_ABI
// Check for cases where a small struct is returned in a register
// via a primitive type.
//
// The largest primitive type is 8 bytes (TYP_DOUBLE)
// so we can skip calling getPrimitiveTypeForStruct when we
// have a struct that is larger than that.
if (canReturnInRegister && (useType == TYP_UNKNOWN) && (structSize <= sizeof(double)))
{
// We set the "primitive" useType based upon the structSize
// and also examine the clsHnd to see if it is an HFA of count one
//
// The ABI for struct returns in varArg methods, is same as the normal case,
// so pass false for isVararg
useType = getPrimitiveTypeForStruct(structSize, clsHnd, /*isVararg=*/false);
if (useType != TYP_UNKNOWN)
{
if (structSize == genTypeSize(useType))
{
// Currently: 1, 2, 4, or 8 byte structs
howToReturnStruct = SPK_PrimitiveType;
}
else
{
// Currently: 3, 5, 6, or 7 byte structs
assert(structSize < genTypeSize(useType));
howToReturnStruct = SPK_EnclosingType;
}
}
}
#ifdef _TARGET_64BIT_
// Note this handles an odd case when FEATURE_MULTIREG_RET is disabled and HFAs are enabled
//
// getPrimitiveTypeForStruct will return TYP_UNKNOWN for a struct that is an HFA of two floats
// because when HFA are enabled, normally we would use two FP registers to pass or return it
//
// But if we don't have support for multiple register return types, we have to change this.
// Since we what we have an 8-byte struct (float + float) we change useType to TYP_I_IMPL
// so that the struct is returned instead using an 8-byte integer register.
//
if ((FEATURE_MULTIREG_RET == 0) && (useType == TYP_UNKNOWN) && (structSize == (2 * sizeof(float))) && IsHfa(clsHnd))
{
useType = TYP_I_IMPL;
howToReturnStruct = SPK_PrimitiveType;
}
#endif
// Did we change this struct type into a simple "primitive" type?
if (useType != TYP_UNKNOWN)
{
// If so, we should have already set howToReturnStruct, too.
assert(howToReturnStruct != SPK_Unknown);
}
else // We can't replace the struct with a "primitive" type
{
// See if we can return this struct by value, possibly in multiple registers
// or if we should return it using a return buffer register
//
if ((FEATURE_MULTIREG_RET == 1) && (structSize <= MAX_RET_MULTIREG_BYTES))
{
// Structs that are HFA's are returned in multiple registers
if (IsHfa(clsHnd))
{
// HFA's of count one should have been handled by getPrimitiveTypeForStruct
assert(GetHfaCount(clsHnd) >= 2);
// setup wbPassType and useType indicate that this is returned by value as an HFA
// using multiple registers
howToReturnStruct = SPK_ByValueAsHfa;
useType = TYP_STRUCT;
}
else // Not an HFA struct type
{
#ifdef UNIX_AMD64_ABI
// The case of (structDesc.eightByteCount == 1) should have already been handled
if (structDesc.eightByteCount > 1)
{
// setup wbPassType and useType indicate that this is returned by value in multiple registers
howToReturnStruct = SPK_ByValue;
useType = TYP_STRUCT;
assert(structDesc.passedInRegisters == true);
}
else
{
assert(structDesc.eightByteCount == 0);
// Otherwise we return this struct using a return buffer
// setup wbPassType and useType indicate that this is return using a return buffer register
// (reference to a return buffer)
howToReturnStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
assert(structDesc.passedInRegisters == false);
}
#elif defined(_TARGET_ARM64_)
// Structs that are pointer sized or smaller should have been handled by getPrimitiveTypeForStruct
assert(structSize > TARGET_POINTER_SIZE);
// On ARM64 structs that are 9-16 bytes are returned by value in multiple registers
//
if (structSize <= (TARGET_POINTER_SIZE * 2))
{
// setup wbPassType and useType indicate that this is return by value in multiple registers
howToReturnStruct = SPK_ByValue;
useType = TYP_STRUCT;
}
else // a structSize that is 17-32 bytes in size
{
// Otherwise we return this struct using a return buffer
// setup wbPassType and useType indicate that this is returned using a return buffer register
// (reference to a return buffer)
howToReturnStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
}
#elif defined(_TARGET_ARM_) || defined(_TARGET_X86_)
// Otherwise we return this struct using a return buffer
// setup wbPassType and useType indicate that this is returned using a return buffer register
// (reference to a return buffer)
howToReturnStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
#else // _TARGET_XXX_
noway_assert(!"Unhandled TARGET in getReturnTypeForStruct (with FEATURE_MULTIREG_ARGS=1)");
#endif // _TARGET_XXX_
}
}
else // (structSize > MAX_RET_MULTIREG_BYTES) || (FEATURE_MULTIREG_RET == 0)
{
// We have a (large) struct that can't be replaced with a "primitive" type
// and can't be returned in multiple registers
// We return this struct using a return buffer register
// setup wbPassType and useType indicate that this is returned using a return buffer register
// (reference to a return buffer)
howToReturnStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
}
}
// 'howToReturnStruct' must be set to one of the valid values before we return
assert(howToReturnStruct != SPK_Unknown);
if (wbReturnStruct != nullptr)
{
*wbReturnStruct = howToReturnStruct;
}
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 = HostAllocator::getHostAllocator().allocate<char>(newSize);
strcpy_s(m_file, newSize, file);
newSize = (strlen(condStr) + 1) * sizeof(char);
m_condStr = HostAllocator::getHostAllocator().allocate<char>(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 JitHashTable
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 JitHashTable<FileLine, FileLine, size_t, HostAllocator> 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
*/
#if DATAFLOW_ITER
unsigned CSEiterCount; // counts the # of iteration for the CSE dataflow
unsigned CFiterCount; // counts the # of iteration for the Const Folding dataflow
#endif // DATAFLOW_ITER
#if MEASURE_BLOCK_SIZE
size_t genFlowNodeSize;
size_t genFlowNodeCnt;
#endif // MEASURE_BLOCK_SIZE
/*****************************************************************************/
// We keep track of methods we've already compiled.
/*****************************************************************************
* Declare the statics
*/
#ifdef DEBUG
/* static */
unsigned Compiler::s_compMethodsCount = 0; // to produce unique label names
#endif
#if MEASURE_MEM_ALLOC
/* static */
bool Compiler::s_dspMemStats = false;
#endif
#ifndef PROFILING_SUPPORTED
const bool Compiler::Options::compNoPInvokeInlineCB = false;
#endif
/*****************************************************************************
*
* One time initialization code
*/
/* static */
void Compiler::compStartup()
{
#if DISPLAY_SIZES
grossVMsize = grossNCsize = totalNCsize = 0;
#endif // DISPLAY_SIZES
/* Initialize the table of tree node sizes */
GenTree::InitNodeSize();
#ifdef JIT32_GCENCODER
// Initialize the GC encoder lookup table
GCInfo::gcInitEncoderLookupTable();
#endif
/* Initialize the emitter */
emitter::emitInit();
// Static vars of ValueNumStore
ValueNumStore::InitValueNumStoreStatics();
compDisplayStaticSizes(jitstdout);
}
/*****************************************************************************
*
* One time finalization code
*/
/* static */
void Compiler::compShutdown()
{
#ifdef ALT_JIT
if (s_pAltJitExcludeAssembliesList != nullptr)
{
s_pAltJitExcludeAssembliesList->~AssemblyNamesList2(); // call the destructor
s_pAltJitExcludeAssembliesList = nullptr;
}
#endif // ALT_JIT
#ifdef DEBUG
if (s_pJitDisasmIncludeAssembliesList != nullptr)
{
s_pJitDisasmIncludeAssembliesList->~AssemblyNamesList2(); // call the destructor
s_pJitDisasmIncludeAssembliesList = nullptr;
}
#endif // DEBUG
#if MEASURE_NOWAY
DisplayNowayAssertMap();
#endif // MEASURE_NOWAY
/* Shut down the emitter */
emitter::emitDone();
#if defined(DEBUG) || defined(INLINE_DATA)
// Finish reading and/or writing inline xml
InlineStrategy::FinalizeXml();
#endif // defined(DEBUG) || defined(INLINE_DATA)
#if defined(DEBUG) || MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || DISPLAY_SIZES || CALL_ARG_STATS
if (genMethodCnt == 0)
{
return;
}
#endif
#if NODEBASH_STATS
GenTree::ReportOperBashing(jitstdout);
#endif
// Where should we write our statistics output?
FILE* fout = jitstdout;
#ifdef FEATURE_JIT_METHOD_PERF
if (compJitTimeLogFilename != nullptr)
{
FILE* jitTimeLogFile = _wfopen(compJitTimeLogFilename, W("a"));
if (jitTimeLogFile != nullptr)
{
CompTimeSummaryInfo::s_compTimeSummary.Print(jitTimeLogFile);
fclose(jitTimeLogFile);
}
}
#endif // FEATURE_JIT_METHOD_PERF
#if COUNT_AST_OPERS
// Add up all the counts so that we can show percentages of total
unsigned gtc = 0;
for (unsigned op = 0; op < GT_COUNT; op++)
gtc += GenTree::s_gtNodeCounts[op];
if (gtc > 0)
{
unsigned rem_total = gtc;
unsigned rem_large = 0;
unsigned rem_small = 0;
unsigned tot_large = 0;
unsigned tot_small = 0;
fprintf(fout, "\nGenTree operator counts (approximate):\n\n");
for (unsigned op = 0; op < GT_COUNT; op++)
{
unsigned siz = GenTree::s_gtTrueSizes[op];
unsigned cnt = GenTree::s_gtNodeCounts[op];
double pct = 100.0 * cnt / gtc;
if (siz > TREE_NODE_SZ_SMALL)
tot_large += cnt;
else
tot_small += cnt;
// Let's not show anything below a threshold
if (pct >= 0.5)
{
fprintf(fout, " GT_%-17s %7u (%4.1lf%%) %3u bytes each\n", GenTree::OpName((genTreeOps)op), cnt,
pct, siz);
rem_total -= cnt;
}
else
{
if (siz > TREE_NODE_SZ_SMALL)
rem_large += cnt;
else
rem_small += cnt;
}
}
if (rem_total > 0)
{
fprintf(fout, " All other GT_xxx ... %7u (%4.1lf%%) ... %4.1lf%% small + %4.1lf%% large\n", rem_total,
100.0 * rem_total / gtc, 100.0 * rem_small / gtc, 100.0 * rem_large / gtc);
}
fprintf(fout, " -----------------------------------------------------\n");
fprintf(fout, " Total ....... %11u --ALL-- ... %4.1lf%% small + %4.1lf%% large\n", gtc,
100.0 * tot_small / gtc, 100.0 * tot_large / gtc);
fprintf(fout, "\n");
}
#endif // COUNT_AST_OPERS
#if DISPLAY_SIZES
if (grossVMsize && grossNCsize)
{
fprintf(fout, "\n");
fprintf(fout, "--------------------------------------\n");
fprintf(fout, "Function and GC info size stats\n");
fprintf(fout, "--------------------------------------\n");
fprintf(fout, "[%7u VM, %8u %6s %4u%%] %s\n", grossVMsize, grossNCsize, Target::g_tgtCPUName,
100 * grossNCsize / grossVMsize, "Total (excluding GC info)");
fprintf(fout, "[%7u VM, %8u %6s %4u%%] %s\n", grossVMsize, totalNCsize, Target::g_tgtCPUName,
100 * totalNCsize / grossVMsize, "Total (including GC info)");
if (gcHeaderISize || gcHeaderNSize)
{
fprintf(fout, "\n");
fprintf(fout, "GC tables : [%7uI,%7uN] %7u byt (%u%% of IL, %u%% of %s).\n",
gcHeaderISize + gcPtrMapISize, gcHeaderNSize + gcPtrMapNSize, totalNCsize - grossNCsize,
100 * (totalNCsize - grossNCsize) / grossVMsize, 100 * (totalNCsize - grossNCsize) / grossNCsize,
Target::g_tgtCPUName);
fprintf(fout, "GC headers : [%7uI,%7uN] %7u byt, [%4.1fI,%4.1fN] %4.1f byt/meth\n", gcHeaderISize,
gcHeaderNSize, gcHeaderISize + gcHeaderNSize, (float)gcHeaderISize / (genMethodICnt + 0.001),
(float)gcHeaderNSize / (genMethodNCnt + 0.001),
(float)(gcHeaderISize + gcHeaderNSize) / genMethodCnt);
fprintf(fout, "GC ptr maps : [%7uI,%7uN] %7u byt, [%4.1fI,%4.1fN] %4.1f byt/meth\n", gcPtrMapISize,
gcPtrMapNSize, gcPtrMapISize + gcPtrMapNSize, (float)gcPtrMapISize / (genMethodICnt + 0.001),
(float)gcPtrMapNSize / (genMethodNCnt + 0.001),
(float)(gcPtrMapISize + gcPtrMapNSize) / genMethodCnt);
}
else
{
fprintf(fout, "\n");
fprintf(fout, "GC tables take up %u bytes (%u%% of instr, %u%% of %6s code).\n",
totalNCsize - grossNCsize, 100 * (totalNCsize - grossNCsize) / grossVMsize,
100 * (totalNCsize - grossNCsize) / grossNCsize, Target::g_tgtCPUName);
}
#ifdef DEBUG
#if DOUBLE_ALIGN
fprintf(fout, "%u out of %u methods generated with double-aligned stack\n",
Compiler::s_lvaDoubleAlignedProcsCount, genMethodCnt);
#endif
#endif
}
#endif // DISPLAY_SIZES
#if CALL_ARG_STATS
compDispCallArgStats(fout);
#endif
#if COUNT_BASIC_BLOCKS
fprintf(fout, "--------------------------------------------------\n");
fprintf(fout, "Basic block count frequency table:\n");
fprintf(fout, "--------------------------------------------------\n");
bbCntTable.dump(fout);
fprintf(fout, "--------------------------------------------------\n");
fprintf(fout, "\n");
fprintf(fout, "--------------------------------------------------\n");
fprintf(fout, "IL method size frequency table for methods with a single basic block:\n");
fprintf(fout, "--------------------------------------------------\n");
bbOneBBSizeTable.dump(fout);
fprintf(fout, "--------------------------------------------------\n");
#endif // COUNT_BASIC_BLOCKS
#if COUNT_LOOPS
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Loop stats\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Total number of methods with loops is %5u\n", totalLoopMethods);
fprintf(fout, "Total number of loops is %5u\n", totalLoopCount);
fprintf(fout, "Maximum number of loops per method is %5u\n", maxLoopsPerMethod);
fprintf(fout, "# of methods overflowing nat loop table is %5u\n", totalLoopOverflows);
fprintf(fout, "Total number of 'unnatural' loops is %5u\n", totalUnnatLoopCount);
fprintf(fout, "# of methods overflowing unnat loop limit is %5u\n", totalUnnatLoopOverflows);
fprintf(fout, "Total number of loops with an iterator is %5u\n", iterLoopCount);
fprintf(fout, "Total number of loops with a simple iterator is %5u\n", simpleTestLoopCount);
fprintf(fout, "Total number of loops with a constant iterator is %5u\n", constIterLoopCount);
fprintf(fout, "--------------------------------------------------\n");
fprintf(fout, "Loop count frequency table:\n");
fprintf(fout, "--------------------------------------------------\n");
loopCountTable.dump(fout);
fprintf(fout, "--------------------------------------------------\n");
fprintf(fout, "Loop exit count frequency table:\n");
fprintf(fout, "--------------------------------------------------\n");
loopExitCountTable.dump(fout);
fprintf(fout, "--------------------------------------------------\n");
#endif // COUNT_LOOPS
#if DATAFLOW_ITER
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Total number of iterations in the CSE dataflow loop is %5u\n", CSEiterCount);
fprintf(fout, "Total number of iterations in the CF dataflow loop is %5u\n", CFiterCount);
#endif // DATAFLOW_ITER
#if MEASURE_NODE_SIZE
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "GenTree node allocation stats\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Allocated %6I64u tree nodes (%7I64u bytes total, avg %4I64u bytes per method)\n",
genNodeSizeStats.genTreeNodeCnt, genNodeSizeStats.genTreeNodeSize,
genNodeSizeStats.genTreeNodeSize / genMethodCnt);
fprintf(fout, "Allocated %7I64u bytes of unused tree node space (%3.2f%%)\n",
genNodeSizeStats.genTreeNodeSize - genNodeSizeStats.genTreeNodeActualSize,
(float)(100 * (genNodeSizeStats.genTreeNodeSize - genNodeSizeStats.genTreeNodeActualSize)) /
genNodeSizeStats.genTreeNodeSize);
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Distribution of per-method GenTree node counts:\n");
genTreeNcntHist.dump(fout);
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Distribution of per-method GenTree node allocations (in bytes):\n");
genTreeNsizHist.dump(fout);
#endif // MEASURE_NODE_SIZE
#if MEASURE_BLOCK_SIZE
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "BasicBlock and flowList/BasicBlockList allocation stats\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Allocated %6u basic blocks (%7u bytes total, avg %4u bytes per method)\n", BasicBlock::s_Count,
BasicBlock::s_Size, BasicBlock::s_Size / genMethodCnt);
fprintf(fout, "Allocated %6u flow nodes (%7u bytes total, avg %4u bytes per method)\n", genFlowNodeCnt,
genFlowNodeSize, genFlowNodeSize / genMethodCnt);
#endif // MEASURE_BLOCK_SIZE
#if MEASURE_MEM_ALLOC
if (s_dspMemStats)
{
fprintf(fout, "\nAll allocations:\n");
ArenaAllocator::dumpAggregateMemStats(jitstdout);
fprintf(fout, "\nLargest method:\n");
ArenaAllocator::dumpMaxMemStats(jitstdout);
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Distribution of total memory allocated per method (in KB):\n");
memAllocHist.dump(fout);
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Distribution of total memory used per method (in KB):\n");
memUsedHist.dump(fout);
}
#endif // MEASURE_MEM_ALLOC
#if LOOP_HOIST_STATS
#ifdef DEBUG // Always display loop stats in retail
if (JitConfig.DisplayLoopHoistStats() != 0)
#endif // DEBUG
{
PrintAggregateLoopHoistStats(jitstdout);
}
#endif // LOOP_HOIST_STATS
#if MEASURE_PTRTAB_SIZE
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "GC pointer table stats\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Reg pointer descriptor size (internal): %8u (avg %4u per method)\n", GCInfo::s_gcRegPtrDscSize,
GCInfo::s_gcRegPtrDscSize / genMethodCnt);
fprintf(fout, "Total pointer table size: %8u (avg %4u per method)\n", GCInfo::s_gcTotalPtrTabSize,
GCInfo::s_gcTotalPtrTabSize / genMethodCnt);
#endif // MEASURE_PTRTAB_SIZE
#if MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || MEASURE_PTRTAB_SIZE || DISPLAY_SIZES
if (genMethodCnt != 0)
{
fprintf(fout, "\n");
fprintf(fout, "A total of %6u methods compiled", genMethodCnt);
#if DISPLAY_SIZES
if (genMethodICnt || genMethodNCnt)
{
fprintf(fout, " (%u interruptible, %u non-interruptible)", genMethodICnt, genMethodNCnt);
}
#endif // DISPLAY_SIZES
fprintf(fout, ".\n");
}
#endif // MEASURE_NODE_SIZE || MEASURE_BLOCK_SIZE || MEASURE_PTRTAB_SIZE || DISPLAY_SIZES
#if EMITTER_STATS
emitterStats(fout);
#endif
#if MEASURE_FATAL
fprintf(fout, "\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, "Fatal errors stats\n");
fprintf(fout, "---------------------------------------------------\n");
fprintf(fout, " badCode: %u\n", fatal_badCode);
fprintf(fout, " noWay: %u\n", fatal_noWay);
fprintf(fout, " NOMEM: %u\n", fatal_NOMEM);
fprintf(fout, " noWayAssertBody: %u\n", fatal_noWayAssertBody);
#ifdef DEBUG
fprintf(fout, " noWayAssertBodyArgs: %u\n", fatal_noWayAssertBodyArgs);
#endif // DEBUG
fprintf(fout, " NYI: %u\n", fatal_NYI);
#endif // MEASURE_FATAL
}
/*****************************************************************************
* Display static data structure sizes.
*/
/* static */
void Compiler::compDisplayStaticSizes(FILE* fout)
{
#if MEASURE_NODE_SIZE
GenTree::DumpNodeSizes(fout);
#endif
#if MEASURE_BLOCK_SIZE
BasicBlock* bbDummy = nullptr;
fprintf(fout, "\n");
fprintf(fout, "Offset / size of bbNext = %3u / %3u\n", offsetof(BasicBlock, bbNext),
sizeof(bbDummy->bbNext));
fprintf(fout, "Offset / size of bbNum = %3u / %3u\n", offsetof(BasicBlock, bbNum),
sizeof(bbDummy->bbNum));
fprintf(fout, "Offset / size of bbPostOrderNum = %3u / %3u\n", offsetof(BasicBlock, bbPostOrderNum),
sizeof(bbDummy->bbPostOrderNum));
fprintf(fout, "Offset / size of bbRefs = %3u / %3u\n", offsetof(BasicBlock, bbRefs),
sizeof(bbDummy->bbRefs));
fprintf(fout, "Offset / size of bbFlags = %3u / %3u\n", offsetof(BasicBlock, bbFlags),
sizeof(bbDummy->bbFlags));
fprintf(fout, "Offset / size of bbWeight = %3u / %3u\n", offsetof(BasicBlock, bbWeight),
sizeof(bbDummy->bbWeight));
fprintf(fout, "Offset / size of bbJumpKind = %3u / %3u\n", offsetof(BasicBlock, bbJumpKind),
sizeof(bbDummy->bbJumpKind));
fprintf(fout, "Offset / size of bbJumpOffs = %3u / %3u\n", offsetof(BasicBlock, bbJumpOffs),
sizeof(bbDummy->bbJumpOffs));
fprintf(fout, "Offset / size of bbJumpDest = %3u / %3u\n", offsetof(BasicBlock, bbJumpDest),
sizeof(bbDummy->bbJumpDest));
fprintf(fout, "Offset / size of bbJumpSwt = %3u / %3u\n", offsetof(BasicBlock, bbJumpSwt),
sizeof(bbDummy->bbJumpSwt));
fprintf(fout, "Offset / size of bbEntryState = %3u / %3u\n", offsetof(BasicBlock, bbEntryState),
sizeof(bbDummy->bbEntryState));
fprintf(fout, "Offset / size of bbStkTempsIn = %3u / %3u\n", offsetof(BasicBlock, bbStkTempsIn),
sizeof(bbDummy->bbStkTempsIn));
fprintf(fout, "Offset / size of bbStkTempsOut = %3u / %3u\n", offsetof(BasicBlock, bbStkTempsOut),
sizeof(bbDummy->bbStkTempsOut));
fprintf(fout, "Offset / size of bbTryIndex = %3u / %3u\n", offsetof(BasicBlock, bbTryIndex),
sizeof(bbDummy->bbTryIndex));
fprintf(fout, "Offset / size of bbHndIndex = %3u / %3u\n", offsetof(BasicBlock, bbHndIndex),
sizeof(bbDummy->bbHndIndex));
fprintf(fout, "Offset / size of bbCatchTyp = %3u / %3u\n", offsetof(BasicBlock, bbCatchTyp),
sizeof(bbDummy->bbCatchTyp));
fprintf(fout, "Offset / size of bbStkDepth = %3u / %3u\n", offsetof(BasicBlock, bbStkDepth),
sizeof(bbDummy->bbStkDepth));
fprintf(fout, "Offset / size of bbFPinVars = %3u / %3u\n", offsetof(BasicBlock, bbFPinVars),
sizeof(bbDummy->bbFPinVars));
fprintf(fout, "Offset / size of bbPreds = %3u / %3u\n", offsetof(BasicBlock, bbPreds),
sizeof(bbDummy->bbPreds));
fprintf(fout, "Offset / size of bbReach = %3u / %3u\n", offsetof(BasicBlock, bbReach),
sizeof(bbDummy->bbReach));
fprintf(fout, "Offset / size of bbIDom = %3u / %3u\n", offsetof(BasicBlock, bbIDom),
sizeof(bbDummy->bbIDom));
fprintf(fout, "Offset / size of bbDfsNum = %3u / %3u\n", offsetof(BasicBlock, bbDfsNum),
sizeof(bbDummy->bbDfsNum));
fprintf(fout, "Offset / size of bbCodeOffs = %3u / %3u\n", offsetof(BasicBlock, bbCodeOffs),
sizeof(bbDummy->bbCodeOffs));
fprintf(fout, "Offset / size of bbCodeOffsEnd = %3u / %3u\n", offsetof(BasicBlock, bbCodeOffsEnd),
sizeof(bbDummy->bbCodeOffsEnd));
fprintf(fout, "Offset / size of bbVarUse = %3u / %3u\n", offsetof(BasicBlock, bbVarUse),
sizeof(bbDummy->bbVarUse));
fprintf(fout, "Offset / size of bbVarDef = %3u / %3u\n", offsetof(BasicBlock, bbVarDef),
sizeof(bbDummy->bbVarDef));
fprintf(fout, "Offset / size of bbLiveIn = %3u / %3u\n", offsetof(BasicBlock, bbLiveIn),
sizeof(bbDummy->bbLiveIn));
fprintf(fout, "Offset / size of bbLiveOut = %3u / %3u\n", offsetof(BasicBlock, bbLiveOut),
sizeof(bbDummy->bbLiveOut));
fprintf(fout, "Offset / size of bbMemorySsaPhiFunc = %3u / %3u\n", offsetof(BasicBlock, bbMemorySsaPhiFunc),
sizeof(bbDummy->bbMemorySsaPhiFunc));
fprintf(fout, "Offset / size of bbMemorySsaNumIn = %3u / %3u\n", offsetof(BasicBlock, bbMemorySsaNumIn),
sizeof(bbDummy->bbMemorySsaNumIn));
fprintf(fout, "Offset / size of bbMemorySsaNumOut = %3u / %3u\n", offsetof(BasicBlock, bbMemorySsaNumOut),
sizeof(bbDummy->bbMemorySsaNumOut));
fprintf(fout, "Offset / size of bbScope = %3u / %3u\n", offsetof(BasicBlock, bbScope),
sizeof(bbDummy->bbScope));
fprintf(fout, "Offset / size of bbCseGen = %3u / %3u\n", offsetof(BasicBlock, bbCseGen),
sizeof(bbDummy->bbCseGen));
fprintf(fout, "Offset / size of bbCseIn = %3u / %3u\n", offsetof(BasicBlock, bbCseIn),
sizeof(bbDummy->bbCseIn));
fprintf(fout, "Offset / size of bbCseOut = %3u / %3u\n", offsetof(BasicBlock, bbCseOut),
sizeof(bbDummy->bbCseOut));
fprintf(fout, "Offset / size of bbEmitCookie = %3u / %3u\n", offsetof(BasicBlock, bbEmitCookie),
sizeof(bbDummy->bbEmitCookie));
#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
fprintf(fout, "Offset / size of bbUnwindNopEmitCookie = %3u / %3u\n", offsetof(BasicBlock, bbUnwindNopEmitCookie),
sizeof(bbDummy->bbUnwindNopEmitCookie));
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
#ifdef VERIFIER
fprintf(fout, "Offset / size of bbStackIn = %3u / %3u\n", offsetof(BasicBlock, bbStackIn),
sizeof(bbDummy->bbStackIn));
fprintf(fout, "Offset / size of bbStackOut = %3u / %3u\n", offsetof(BasicBlock, bbStackOut),
sizeof(bbDummy->bbStackOut));
fprintf(fout, "Offset / size of bbTypesIn = %3u / %3u\n", offsetof(BasicBlock, bbTypesIn),
sizeof(bbDummy->bbTypesIn));
fprintf(fout, "Offset / size of bbTypesOut = %3u / %3u\n", offsetof(BasicBlock, bbTypesOut),
sizeof(bbDummy->bbTypesOut));
#endif // VERIFIER
#ifdef DEBUG
fprintf(fout, "Offset / size of bbLoopNum = %3u / %3u\n", offsetof(BasicBlock, bbLoopNum),
sizeof(bbDummy->bbLoopNum));
#endif // DEBUG
fprintf(fout, "\n");
fprintf(fout, "Size of BasicBlock = %3u\n", sizeof(BasicBlock));
#endif // MEASURE_BLOCK_SIZE
#if EMITTER_STATS
emitterStaticStats(fout);
#endif
}
/*****************************************************************************
*
* Constructor
*/
void Compiler::compInit(ArenaAllocator* pAlloc, InlineInfo* inlineInfo)
{
assert(pAlloc);
compArenaAllocator = pAlloc;
// Inlinee Compile object will only be allocated when needed for the 1st time.
InlineeCompiler = nullptr;
// Set the inline info.
impInlineInfo = inlineInfo;
eeInfoInitialized = false;
compDoAggressiveInlining = false;
if (compIsForInlining())
{
m_inlineStrategy = nullptr;
compInlineResult = inlineInfo->inlineResult;
}
else
{
m_inlineStrategy = new (this, CMK_Inlining) InlineStrategy(this);
compInlineResult = nullptr;
}
#ifdef FEATURE_TRACELOGGING
// Make sure JIT telemetry is initialized as soon as allocations can be made
// but no later than a point where noway_asserts can be thrown.
// 1. JIT telemetry could allocate some objects internally.
// 2. NowayAsserts are tracked through telemetry.
// Note: JIT telemetry could gather data when compiler is not fully initialized.
// So you have to initialize the compiler variables you use for telemetry.
assert((unsigned)PHASE_PRE_IMPORT == 0);
previousCompletedPhase = PHASE_PRE_IMPORT;
info.compILCodeSize = 0;
info.compMethodHnd = nullptr;
compJitTelemetry.Initialize(this);
#endif
#ifdef DEBUG
bRangeAllowStress = false;
#endif
fgInit();
lvaInit();
if (!compIsForInlining())
{
codeGen = getCodeGenerator(this);
optInit();
hashBv::Init(this);
compVarScopeMap = nullptr;
// If this method were a real constructor for Compiler, these would
// become method initializations.
impPendingBlockMembers = JitExpandArray<BYTE>(getAllocator());
impSpillCliquePredMembers = JitExpandArray<BYTE>(getAllocator());
impSpillCliqueSuccMembers = JitExpandArray<BYTE>(getAllocator());
lvMemoryPerSsaData = SsaDefArray<SsaMemDef>();
//
// Initialize all the per-method statistics gathering data structures.
//
optLoopsCloned = 0;
#if LOOP_HOIST_STATS
m_loopsConsidered = 0;
m_curLoopHasHoistedExpression = false;
m_loopsWithHoistedExpressions = 0;
m_totalHoistedExpressions = 0;
#endif // LOOP_HOIST_STATS
#if MEASURE_NODE_SIZE
genNodeSizeStatsPerFunc.Init();
#endif // MEASURE_NODE_SIZE
}
else
{
codeGen = nullptr;
}
compJmpOpUsed = false;
compLongUsed = false;
compTailCallUsed = false;
compLocallocUsed = false;
compLocallocOptimized = false;
compQmarkRationalized = false;
compQmarkUsed = false;
compFloatingPointUsed = false;
compUnsafeCastUsed = false;
compNeedsGSSecurityCookie = false;
compGSReorderStackLayout = false;
#if STACK_PROBES
compStackProbePrologDone = false;
#endif
compGeneratingProlog = false;
compGeneratingEpilog = false;
compLSRADone = false;
compRationalIRForm = false;
#ifdef DEBUG
compCodeGenDone = false;
compRegSetCheckLevel = 0;
opts.compMinOptsIsUsed = false;
#endif
opts.compMinOptsIsSet = false;
// Used by fgFindJumpTargets for inlining heuristics.
opts.instrCount = 0;
// Used to track when we should consider running EarlyProp
optMethodFlags = 0;
#ifdef DEBUG
m_nodeTestData = nullptr;
m_loopHoistCSEClass = FIRST_LOOP_HOIST_CSE_CLASS;
#endif
m_switchDescMap = nullptr;
m_blockToEHPreds = nullptr;
m_fieldSeqStore = nullptr;
m_zeroOffsetFieldMap = nullptr;
m_arrayInfoMap = nullptr;
m_refAnyClass = nullptr;
for (MemoryKind memoryKind : allMemoryKinds())
{
m_memorySsaMap[memoryKind] = nullptr;
}
#ifdef DEBUG
if (!compIsForInlining())
{
compDoComponentUnitTestsOnce();
}
#endif // DEBUG
vnStore = nullptr;
m_opAsgnVarDefSsaNums = nullptr;
fgSsaPassesCompleted = 0;
fgVNPassesCompleted = 0;
// check that HelperCallProperties are initialized
assert(s_helperCallProperties.IsPure(CORINFO_HELP_GETSHARED_GCSTATIC_BASE));
assert(!s_helperCallProperties.IsPure(CORINFO_HELP_GETFIELDOBJ)); // quick sanity check
// We start with the flow graph in tree-order
fgOrder = FGOrderTree;
#ifdef FEATURE_SIMD
m_simdHandleCache = nullptr;
#endif // FEATURE_SIMD
compUsesThrowHelper = false;
}
/*****************************************************************************
*
* Destructor
*/
void Compiler::compDone()
{
}
void* Compiler::compGetHelperFtn(CorInfoHelpFunc ftnNum, /* IN */
void** ppIndirection) /* OUT */
{
void* addr;
if (info.compMatchedVM)
{
addr = info.compCompHnd->getHelperFtn(ftnNum, ppIndirection);
}
else
{
// If we don't have a matched VM, we won't get valid results when asking for a helper function.
addr = (void*)0xCA11CA11; // "callcall"
}
return addr;
}
unsigned Compiler::compGetTypeSize(CorInfoType cit, CORINFO_CLASS_HANDLE clsHnd)
{
var_types sigType = genActualType(JITtype2varType(cit));
unsigned sigSize;
sigSize = genTypeSize(sigType);
if (cit == CORINFO_TYPE_VALUECLASS)
{
sigSize = info.compCompHnd->getClassSize(clsHnd);
}
else if (cit == CORINFO_TYPE_REFANY)
{
sigSize = 2 * TARGET_POINTER_SIZE;
}
return sigSize;
}
#ifdef DEBUG
static bool DidComponentUnitTests = false;
void Compiler::compDoComponentUnitTestsOnce()
{
if (!JitConfig.RunComponentUnitTests())
{
return;
}
if (!DidComponentUnitTests)
{
DidComponentUnitTests = true;
ValueNumStore::RunTests(this);
BitSetSupport::TestSuite(getAllocatorDebugOnly());
}
}
//------------------------------------------------------------------------
// compGetJitDefaultFill:
//
// Return Value:
// An unsigned char value used to initizalize memory allocated by the JIT.
// The default value is taken from COMPLUS_JitDefaultFill, if is not set
// the value will be 0xdd. When JitStress is active a random value based
// on the method hash is used.
//
// Notes:
// Note that we can't use small values like zero, because we have some
// asserts that can fire for such values.
//
unsigned char Compiler::compGetJitDefaultFill()
{
unsigned char defaultFill = (unsigned char)JitConfig.JitDefaultFill();
if ((this != nullptr) && (compStressCompile(STRESS_GENERIC_VARN, 50)))
{
unsigned temp;
temp = info.compMethodHash();
temp = (temp >> 16) ^ temp;
temp = (temp >> 8) ^ temp;
temp = temp & 0xff;
// asserts like this: assert(!IsUninitialized(stkLvl));
// mean that small values for defaultFill are problematic
// so we make the value larger in that case.
if (temp < 0x20)
{
temp |= 0x80;
}
defaultFill = (unsigned char)temp;
}
return defaultFill;
}
#endif // DEBUG
/*****************************************************************************/
#ifdef DEBUG
/*****************************************************************************/
VarName Compiler::compVarName(regNumber reg, bool isFloatReg)
{
if (isFloatReg)
{
assert(genIsValidFloatReg(reg));
}
else
{
assert(genIsValidReg(reg));
}
if ((info.compVarScopesCount > 0) && compCurBB && opts.varNames)
{
unsigned lclNum;
LclVarDsc* varDsc;
/* Look for the matching register */
for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
{
/* If the variable is not in a register, or not in the register we're looking for, quit. */
/* Also, if it is a compiler generated variable (i.e. slot# > info.compVarScopesCount), don't bother. */
if ((varDsc->lvRegister != 0) && (varDsc->lvRegNum == reg) && (varDsc->IsFloatRegType() || !isFloatReg) &&
(varDsc->lvSlotNum < info.compVarScopesCount))
{
/* check if variable in that register is live */
if (VarSetOps::IsMember(this, compCurLife, varDsc->lvVarIndex))
{
/* variable is live - find the corresponding slot */
VarScopeDsc* varScope =
compFindLocalVar(varDsc->lvSlotNum, compCurBB->bbCodeOffs, compCurBB->bbCodeOffsEnd);
if (varScope)
{
return varScope->vsdName;
}
}
}
}
}
return nullptr;
}
const char* Compiler::compRegVarName(regNumber reg, bool displayVar, bool isFloatReg)
{
#ifdef _TARGET_ARM_
isFloatReg = genIsValidFloatReg(reg);
#endif
if (displayVar && (reg != REG_NA))
{
VarName varName = compVarName(reg, isFloatReg);
if (varName)
{
const int NAME_VAR_REG_BUFFER_LEN = 4 + 256 + 1;
static char nameVarReg[2][NAME_VAR_REG_BUFFER_LEN]; // to avoid overwriting the buffer when have 2
// consecutive calls before printing
static int index = 0; // for circular index into the name array
index = (index + 1) % 2; // circular reuse of index
sprintf_s(nameVarReg[index], NAME_VAR_REG_BUFFER_LEN, "%s'%s'", getRegName(reg, isFloatReg),
VarNameToStr(varName));
return nameVarReg[index];
}
}
/* no debug info required or no variable in that register
-> return standard name */
return getRegName(reg, isFloatReg);
}
const char* Compiler::compRegNameForSize(regNumber reg, size_t size)
{
if (size == 0 || size >= 4)
{
return compRegVarName(reg, true);
}
// clang-format off
static
const char * sizeNames[][2] =
{
{ "al", "ax" },
{ "cl", "cx" },
{ "dl", "dx" },
{ "bl", "bx" },
#ifdef _TARGET_AMD64_
{ "spl", "sp" }, // ESP
{ "bpl", "bp" }, // EBP
{ "sil", "si" }, // ESI
{ "dil", "di" }, // EDI
{ "r8b", "r8w" },
{ "r9b", "r9w" },
{ "r10b", "r10w" },
{ "r11b", "r11w" },
{ "r12b", "r12w" },
{ "r13b", "r13w" },
{ "r14b", "r14w" },
{ "r15b", "r15w" },
#endif // _TARGET_AMD64_
};
// clang-format on
assert(isByteReg(reg));
assert(genRegMask(reg) & RBM_BYTE_REGS);
assert(size == 1 || size == 2);
return sizeNames[reg][size - 1];
}
const char* Compiler::compFPregVarName(unsigned fpReg, bool displayVar)
{
const int NAME_VAR_REG_BUFFER_LEN = 4 + 256 + 1;
static char nameVarReg[2][NAME_VAR_REG_BUFFER_LEN]; // to avoid overwriting the buffer when have 2 consecutive calls
// before printing
static int index = 0; // for circular index into the name array
index = (index + 1) % 2; // circular reuse of index
/* no debug info required or no variable in that register
-> return standard name */
sprintf_s(nameVarReg[index], NAME_VAR_REG_BUFFER_LEN, "ST(%d)", fpReg);
return nameVarReg[index];
}
const char* Compiler::compLocalVarName(unsigned varNum, unsigned offs)
{
unsigned i;
VarScopeDsc* t;
for (i = 0, t = info.compVarScopes; i < info.compVarScopesCount; i++, t++)
{
if (t->vsdVarNum != varNum)
{
continue;
}
if (offs >= t->vsdLifeBeg && offs < t->vsdLifeEnd)
{
return VarNameToStr(t->vsdName);
}
}
return nullptr;
}
/*****************************************************************************/
#endif // DEBUG
/*****************************************************************************/
#ifdef _TARGET_XARCH_
static bool configEnableISA(InstructionSet isa)
{
#ifdef DEBUG
switch (isa)
{
case InstructionSet_SSE:
return JitConfig.EnableSSE() != 0;
case InstructionSet_SSE2:
return JitConfig.EnableSSE2() != 0;
case InstructionSet_SSE3:
return JitConfig.EnableSSE3() != 0;
case InstructionSet_SSSE3:
return JitConfig.EnableSSSE3() != 0;
case InstructionSet_SSE41:
return JitConfig.EnableSSE41() != 0;
case InstructionSet_SSE42:
return JitConfig.EnableSSE42() != 0;
case InstructionSet_AVX:
return JitConfig.EnableAVX() != 0;
case InstructionSet_FMA:
return JitConfig.EnableFMA() != 0;
case InstructionSet_AVX2:
return JitConfig.EnableAVX2() != 0;
case InstructionSet_AES:
return JitConfig.EnableAES() != 0;
case InstructionSet_BMI1:
return JitConfig.EnableBMI1() != 0;
case InstructionSet_BMI2:
return JitConfig.EnableBMI2() != 0;
case InstructionSet_LZCNT:
return JitConfig.EnableLZCNT() != 0;
case InstructionSet_PCLMULQDQ:
return JitConfig.EnablePCLMULQDQ() != 0;
case InstructionSet_POPCNT:
return JitConfig.EnablePOPCNT() != 0;
default:
return false;
}
#else
// We have a retail config switch that can disable instruction sets reliant on the VEX encoding
switch (isa)
{
case InstructionSet_AVX:
case InstructionSet_FMA:
case InstructionSet_AVX2:
case InstructionSet_BMI1:
case InstructionSet_BMI2:
return JitConfig.EnableAVX() != 0;
default:
return true;
}
#endif
}
#endif // _TARGET_XARCH_
void Compiler::compSetProcessor()
{
const JitFlags& jitFlags = *opts.jitFlags;
#if defined(_TARGET_ARM_)
info.genCPU = CPU_ARM;
#elif defined(_TARGET_AMD64_)
info.genCPU = CPU_X64;
#elif defined(_TARGET_X86_)
if (jitFlags.IsSet(JitFlags::JIT_FLAG_TARGET_P4))
info.genCPU = CPU_X86_PENTIUM_4;
else
info.genCPU = CPU_X86;
#endif
//
// Processor specific optimizations
//
CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef _TARGET_AMD64_
opts.compUseFCOMI = false;
opts.compUseCMOV = true;
#elif defined(_TARGET_X86_)
opts.compUseFCOMI = jitFlags.IsSet(JitFlags::JIT_FLAG_USE_FCOMI);
opts.compUseCMOV = jitFlags.IsSet(JitFlags::JIT_FLAG_USE_CMOV);
#ifdef DEBUG
if (opts.compUseFCOMI)
opts.compUseFCOMI = !compStressCompile(STRESS_USE_FCOMI, 50);
if (opts.compUseCMOV)
opts.compUseCMOV = !compStressCompile(STRESS_USE_CMOV, 50);
#endif // DEBUG
#endif // _TARGET_X86_
// Instruction set flags for Intel hardware intrinsics
#ifdef _TARGET_XARCH_
opts.compSupportsISA = 0;
if (!jitFlags.IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if (configEnableISA(InstructionSet_SSE))
{
opts.setSupportedISA(InstructionSet_SSE);
}
if (configEnableISA(InstructionSet_SSE2))
{
opts.setSupportedISA(InstructionSet_SSE2);
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_AES))
{
if (configEnableISA(InstructionSet_AES))
{
opts.setSupportedISA(InstructionSet_AES);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_LZCNT))
{
if (configEnableISA(InstructionSet_LZCNT))
{
opts.setSupportedISA(InstructionSet_LZCNT);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_PCLMULQDQ))
{
if (configEnableISA(InstructionSet_PCLMULQDQ))
{
opts.setSupportedISA(InstructionSet_PCLMULQDQ);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_POPCNT))
{
if (configEnableISA(InstructionSet_POPCNT))
{
opts.setSupportedISA(InstructionSet_POPCNT);
}
}
// There are currently two sets of flags that control SSE3 through SSE4.2 support:
// These are the general EnableSSE3_4 flag and the individual ISA flags. We need to
// check both for any given ISA.
if (JitConfig.EnableSSE3_4())
{
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_SSE3))
{
if (configEnableISA(InstructionSet_SSE3))
{
opts.setSupportedISA(InstructionSet_SSE3);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_SSE41))
{
if (configEnableISA(InstructionSet_SSE41))
{
opts.setSupportedISA(InstructionSet_SSE41);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_SSE42))
{
if (configEnableISA(InstructionSet_SSE42))
{
opts.setSupportedISA(InstructionSet_SSE42);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_SSSE3))
{
if (configEnableISA(InstructionSet_SSSE3))
{
opts.setSupportedISA(InstructionSet_SSSE3);
}
}
}
// There are currently two sets of flags that control instruction sets that require the VEX encoding:
// These are the general EnableAVX flag and the individual ISA flags. We need to
// check both for any given isa.
if (JitConfig.EnableAVX())
{
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_AVX))
{
if (configEnableISA(InstructionSet_AVX))
{
opts.setSupportedISA(InstructionSet_AVX);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_FMA))
{
if (configEnableISA(InstructionSet_FMA))
{
opts.setSupportedISA(InstructionSet_FMA);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_AVX2))
{
if (configEnableISA(InstructionSet_AVX2))
{
opts.setSupportedISA(InstructionSet_AVX2);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_BMI1))
{
if (configEnableISA(InstructionSet_BMI1))
{
opts.setSupportedISA(InstructionSet_BMI1);
}
}
if (jitFlags.IsSet(JitFlags::JIT_FLAG_USE_BMI2))
{
if (configEnableISA(InstructionSet_BMI2))
{
opts.setSupportedISA(InstructionSet_BMI2);
}
}
}
}
if (!compIsForInlining())
{
if (canUseVexEncoding())
{
codeGen->getEmitter()->SetUseVEXEncoding(true);
// Assume each JITted method does not contain AVX instruction at first
codeGen->getEmitter()->SetContainsAVX(false);
codeGen->getEmitter()->SetContains256bitAVX(false);
}
else if (compSupports(InstructionSet_SSSE3) || compSupports(InstructionSet_SSE41) ||
compSupports(InstructionSet_SSE42))
{
// Emitter::UseSSE4 controls whether we support the 4-byte encoding for certain
// instructions. We need to check if either is supported independently, since
// it is currently possible to enable/disable them separately.
codeGen->getEmitter()->SetUseSSE4(true);
}
}
#endif
#if defined(_TARGET_ARM64_)
// There is no JitFlag for Base instructions handle manually
opts.setSupportedISA(InstructionSet_Base);
#define HARDWARE_INTRINSIC_CLASS(flag, isa) \
if (jitFlags.IsSet(JitFlags::flag)) \
opts.setSupportedISA(InstructionSet_##isa);
#include "hwintrinsiclistArm64.h"
#endif
}
#ifdef PROFILING_SUPPORTED
// A Dummy routine to receive Enter/Leave/Tailcall profiler callbacks.
// These are used when complus_JitEltHookEnabled=1
#ifdef _TARGET_AMD64_
void DummyProfilerELTStub(UINT_PTR ProfilerHandle, UINT_PTR callerSP)
{
return;
}
#else //! _TARGET_AMD64_
void DummyProfilerELTStub(UINT_PTR ProfilerHandle)
{
return;
}
#endif //!_TARGET_AMD64_
#endif // PROFILING_SUPPORTED
bool Compiler::compIsFullTrust()
{
return (info.compCompHnd->canSkipMethodVerification(info.compMethodHnd) == CORINFO_VERIFICATION_CAN_SKIP);
}
bool Compiler::compShouldThrowOnNoway(
#ifdef FEATURE_TRACELOGGING
const char* filename, unsigned line
#endif
)
{
#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.
// If we are not in full trust, we should always fire for security.
return !opts.MinOpts() || !compIsFullTrust();
}
// ConfigInteger does not offer an option for decimal flags. Any numbers are interpreted as hex.
// I could add the decimal option to ConfigInteger or I could write a function to reinterpret this
// value as the user intended.
unsigned ReinterpretHexAsDecimal(unsigned in)
{
// ex: in: 0x100 returns: 100
unsigned result = 0;
unsigned index = 1;
// default value
if (in == INT_MAX)
{
return in;
}
while (in)
{
unsigned digit = in % 16;
in >>= 4;
assert(digit < 10);
result += digit * index;
index *= 10;
}
return result;
}
void Compiler::compInitOptions(JitFlags* jitFlags)
{
#ifdef UNIX_AMD64_ABI
opts.compNeedToAlignFrame = false;
#endif // UNIX_AMD64_ABI
memset(&opts, 0, sizeof(opts));
if (compIsForInlining())
{
// The following flags are lost when inlining. (They are removed in
// Compiler::fgInvokeInlineeCompiler().)
assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT));
assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR));
assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE));
assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC));
assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO));
assert(jitFlags->IsSet(JitFlags::JIT_FLAG_SKIP_VERIFICATION));
}
opts.jitFlags = jitFlags;
opts.compFlags = CLFLG_MAXOPT; // Default value is for full optimization
if (jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_CODE) || jitFlags->IsSet(JitFlags::JIT_FLAG_MIN_OPT) ||
jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0))
{
opts.compFlags = CLFLG_MINOPT;
}
// Don't optimize .cctors (except prejit) or if we're an inlinee
else if (!jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT) && ((info.compFlags & FLG_CCTOR) == FLG_CCTOR) &&
!compIsForInlining())
{
opts.compFlags = CLFLG_MINOPT;
}
// Default value is to generate a blend of size and speed optimizations
//
opts.compCodeOpt = BLENDED_CODE;
// If the EE sets SIZE_OPT or if we are compiling a Class constructor
// we will optimize for code size at the expense of speed
//
if (jitFlags->IsSet(JitFlags::JIT_FLAG_SIZE_OPT) || ((info.compFlags & FLG_CCTOR) == FLG_CCTOR))
{
opts.compCodeOpt = SMALL_CODE;
}
//
// If the EE sets SPEED_OPT we will optimize for speed at the expense of code size
//
else if (jitFlags->IsSet(JitFlags::JIT_FLAG_SPEED_OPT) ||
(jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1) && !jitFlags->IsSet(JitFlags::JIT_FLAG_MIN_OPT)))
{
opts.compCodeOpt = FAST_CODE;
assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_SIZE_OPT));
}
//-------------------------------------------------------------------------
opts.compDbgCode = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_CODE);
opts.compDbgInfo = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO);
opts.compDbgEnC = jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC);
#if REGEN_SHORTCUTS || REGEN_CALLPAT
// We never want to have debugging enabled when regenerating GC encoding patterns
opts.compDbgCode = false;
opts.compDbgInfo = false;
opts.compDbgEnC = false;
#endif
compSetProcessor();
#ifdef DEBUG
opts.dspOrder = false;
if (compIsForInlining())
{
verbose = impInlineInfo->InlinerCompiler->verbose;
}
else
{
verbose = false;
codeGen->setVerbose(false);
}
verboseTrees = verbose && shouldUseVerboseTrees();
verboseSsa = verbose && shouldUseVerboseSsa();
asciiTrees = shouldDumpASCIITrees();
opts.dspDiffable = compIsForInlining() ? impInlineInfo->InlinerCompiler->opts.dspDiffable : false;
#endif
opts.compNeedSecurityCheck = false;
opts.altJit = false;
#if defined(LATE_DISASM) && !defined(DEBUG)
// For non-debug builds with the late disassembler built in, we currently always do late disassembly
// (we have no way to determine when not to, since we don't have class/method names).
// In the DEBUG case, this is initialized to false, below.
opts.doLateDisasm = true;
#endif
#ifdef DEBUG
const JitConfigValues::MethodSet* pfAltJit;
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
pfAltJit = &JitConfig.AltJitNgen();
}
else
{
pfAltJit = &JitConfig.AltJit();
}
#ifdef ALT_JIT
if (pfAltJit->contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.altJit = true;
}
unsigned altJitLimit = ReinterpretHexAsDecimal(JitConfig.AltJitLimit());
if (altJitLimit > 0 && Compiler::jitTotalMethodCompiled >= altJitLimit)
{
opts.altJit = false;
}
#endif // ALT_JIT
#else // !DEBUG
const char* altJitVal;
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
altJitVal = JitConfig.AltJitNgen().list();
}
else
{
altJitVal = JitConfig.AltJit().list();
}
#ifdef ALT_JIT
// In release mode, you either get all methods or no methods. You must use "*" as the parameter, or we ignore it.
// You don't get to give a regular expression of methods to match.
// (Partially, this is because we haven't computed and stored the method and class name except in debug, and it
// might be expensive to do so.)
if ((altJitVal != nullptr) && (strcmp(altJitVal, "*") == 0))
{
opts.altJit = true;
}
#endif // ALT_JIT
#endif // !DEBUG
#ifdef ALT_JIT
// Take care of COMPlus_AltJitExcludeAssemblies.
if (opts.altJit)
{
// First, initialize the AltJitExcludeAssemblies list, but only do it once.
if (!s_pAltJitExcludeAssembliesListInitialized)
{
const wchar_t* wszAltJitExcludeAssemblyList = JitConfig.AltJitExcludeAssemblies();
if (wszAltJitExcludeAssemblyList != nullptr)
{
// NOTE: The Assembly name list is allocated in the process heap, not in the no-release heap, which is
// reclaimed
// for every compilation. This is ok because we only allocate once, due to the static.
s_pAltJitExcludeAssembliesList = new (HostAllocator::getHostAllocator())
AssemblyNamesList2(wszAltJitExcludeAssemblyList, HostAllocator::getHostAllocator());
}
s_pAltJitExcludeAssembliesListInitialized = true;
}
if (s_pAltJitExcludeAssembliesList != nullptr)
{
// We have an exclusion list. See if this method is in an assembly that is on the list.
// Note that we check this for every method, since we might inline across modules, and
// if the inlinee module is on the list, we don't want to use the altjit for it.
const char* methodAssemblyName = info.compCompHnd->getAssemblyName(
info.compCompHnd->getModuleAssembly(info.compCompHnd->getClassModule(info.compClassHnd)));
if (s_pAltJitExcludeAssembliesList->IsInList(methodAssemblyName))
{
opts.altJit = false;
}
}
}
#endif // ALT_JIT
#ifdef DEBUG
bool altJitConfig = !pfAltJit->isEmpty();
// If we have a non-empty AltJit config then we change all of these other
// config values to refer only to the AltJit. Otherwise, a lot of COMPlus_* variables
// would apply to both the altjit and the normal JIT, but we only care about
// debugging the altjit if the COMPlus_AltJit configuration is set.
//
if (compIsForImportOnly() && (!altJitConfig || opts.altJit))
{
if (JitConfig.JitImportBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
assert(!"JitImportBreak reached");
}
}
bool verboseDump = false;
bool dumpIR = false;
bool dumpIRTypes = false;
bool dumpIRLocals = false;
bool dumpIRRegs = false;
bool dumpIRSsa = false;
bool dumpIRValnums = false;
bool dumpIRCosts = false;
bool dumpIRFlags = false;
bool dumpIRKinds = false;
bool dumpIRNodes = false;
bool dumpIRNoLists = false;
bool dumpIRNoLeafs = false;
bool dumpIRNoStmts = false;
bool dumpIRTrees = false;
bool dumpIRLinear = false;
bool dumpIRDataflow = false;
bool dumpIRBlockHeaders = false;
bool dumpIRExit = false;
LPCWSTR dumpIRPhase = nullptr;
LPCWSTR dumpIRFormat = nullptr;
if (!altJitConfig || opts.altJit)
{
LPCWSTR dumpIRFormat = nullptr;
// We should only enable 'verboseDump' when we are actually compiling a matching method
// and not enable it when we are just considering inlining a matching method.
//
if (!compIsForInlining())
{
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if (JitConfig.NgenDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
verboseDump = true;
}
unsigned ngenHashDumpVal = (unsigned)JitConfig.NgenHashDump();
if ((ngenHashDumpVal != (DWORD)-1) && (ngenHashDumpVal == info.compMethodHash()))
{
verboseDump = true;
}
if (JitConfig.NgenDumpIR().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
dumpIR = true;
}
unsigned ngenHashDumpIRVal = (unsigned)JitConfig.NgenHashDumpIR();
if ((ngenHashDumpIRVal != (DWORD)-1) && (ngenHashDumpIRVal == info.compMethodHash()))
{
dumpIR = true;
}
dumpIRFormat = JitConfig.NgenDumpIRFormat();
dumpIRPhase = JitConfig.NgenDumpIRPhase();
}
else
{
if (JitConfig.JitDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
verboseDump = true;
}
unsigned jitHashDumpVal = (unsigned)JitConfig.JitHashDump();
if ((jitHashDumpVal != (DWORD)-1) && (jitHashDumpVal == info.compMethodHash()))
{
verboseDump = true;
}
if (JitConfig.JitDumpIR().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
dumpIR = true;
}
unsigned jitHashDumpIRVal = (unsigned)JitConfig.JitHashDumpIR();
if ((jitHashDumpIRVal != (DWORD)-1) && (jitHashDumpIRVal == info.compMethodHash()))
{
dumpIR = true;
}
dumpIRFormat = JitConfig.JitDumpIRFormat();
dumpIRPhase = JitConfig.JitDumpIRPhase();
}
}
if (dumpIRPhase == nullptr)
{
dumpIRPhase = W("*");
}
this->dumpIRPhase = dumpIRPhase;
if (dumpIRFormat != nullptr)
{
this->dumpIRFormat = dumpIRFormat;
}
dumpIRTrees = false;
dumpIRLinear = true;
if (dumpIRFormat != nullptr)
{
for (LPCWSTR p = dumpIRFormat; (*p != 0);)
{
for (; (*p != 0); p++)
{
if (*p != L' ')
{
break;
}
}
if (*p == 0)
{
break;
}
static bool dumpedHelp = false;
if ((*p == L'?') && (!dumpedHelp))
{
printf("*******************************************************************************\n");
printf("\n");
dFormatIR();
printf("\n");
printf("\n");
printf("Available specifiers (comma separated):\n");
printf("\n");
printf("? dump out value of COMPlus_JitDumpIRFormat and this list of values\n");
printf("\n");
printf("linear linear IR dump (default)\n");
printf("tree tree IR dump (traditional)\n");
printf("mixed intermingle tree dump with linear IR dump\n");
printf("\n");
printf("dataflow use data flow form of linear IR dump\n");
printf("structural use structural form of linear IR dump\n");
printf("all implies structural, include everything\n");
printf("\n");
printf("kinds include tree node kinds in dump, example: \"kinds=[LEAF][LOCAL]\"\n");
printf("flags include tree node flags in dump, example: \"flags=[CALL][GLOB_REF]\" \n");
printf("types includes tree node types in dump, example: \".int\"\n");
printf("locals include local numbers and tracking numbers in dump, example: \"(V3,T1)\"\n");
printf("regs include register assignments in dump, example: \"(rdx)\"\n");
printf("ssa include SSA numbers in dump, example: \"<d:3>\" or \"<u:3>\"\n");
printf("valnums include Value numbers in dump, example: \"<v:$c4>\" or \"<v:$c4,$c5>\"\n");
printf("\n");
printf("nolist exclude GT_LIST nodes from dump\n");
printf("noleafs exclude LEAF nodes from dump (fold into operations)\n");
printf("nostmts exclude GT_STMTS from dump (unless required by dependencies)\n");
printf("\n");
printf("blkhdrs include block headers\n");
printf("exit exit program after last phase dump (used with single method)\n");
printf("\n");
printf("*******************************************************************************\n");
dumpedHelp = true;
}
if (wcsncmp(p, W("types"), 5) == 0)
{
dumpIRTypes = true;
}
if (wcsncmp(p, W("locals"), 6) == 0)
{
dumpIRLocals = true;
}
if (wcsncmp(p, W("regs"), 4) == 0)
{
dumpIRRegs = true;
}
if (wcsncmp(p, W("ssa"), 3) == 0)
{
dumpIRSsa = true;
}
if (wcsncmp(p, W("valnums"), 7) == 0)
{
dumpIRValnums = true;
}
if (wcsncmp(p, W("costs"), 5) == 0)
{
dumpIRCosts = true;
}
if (wcsncmp(p, W("flags"), 5) == 0)
{
dumpIRFlags = true;
}
if (wcsncmp(p, W("kinds"), 5) == 0)
{
dumpIRKinds = true;
}
if (wcsncmp(p, W("nodes"), 5) == 0)
{
dumpIRNodes = true;
}
if (wcsncmp(p, W("exit"), 4) == 0)
{
dumpIRExit = true;
}
if (wcsncmp(p, W("nolists"), 7) == 0)
{
dumpIRNoLists = true;
}
if (wcsncmp(p, W("noleafs"), 7) == 0)
{
dumpIRNoLeafs = true;
}
if (wcsncmp(p, W("nostmts"), 7) == 0)
{
dumpIRNoStmts = true;
}
if (wcsncmp(p, W("trees"), 5) == 0)
{
dumpIRTrees = true;
dumpIRLinear = false;
}
if (wcsncmp(p, W("structural"), 10) == 0)
{
dumpIRLinear = true;
dumpIRNoStmts = false;
dumpIRNoLeafs = false;
dumpIRNoLists = false;
}
if (wcsncmp(p, W("all"), 3) == 0)
{
dumpIRLinear = true;
dumpIRKinds = true;
dumpIRFlags = true;
dumpIRTypes = true;
dumpIRLocals = true;
dumpIRRegs = true;
dumpIRSsa = true;
dumpIRValnums = true;
dumpIRCosts = true;
dumpIRNoStmts = false;
dumpIRNoLeafs = false;
dumpIRNoLists = false;
}
if (wcsncmp(p, W("linear"), 6) == 0)
{
dumpIRTrees = false;
dumpIRLinear = true;
}
if (wcsncmp(p, W("mixed"), 5) == 0)
{
dumpIRTrees = true;
dumpIRLinear = true;
}
if (wcsncmp(p, W("dataflow"), 8) == 0)
{
dumpIRDataflow = true;
dumpIRNoLeafs = true;
dumpIRNoLists = true;
dumpIRNoStmts = true;
}
if (wcsncmp(p, W("blkhdrs"), 7) == 0)
{
dumpIRBlockHeaders = true;
}
for (; (*p != 0); p++)
{
if (*p == L',')
{
p++;
break;
}
}
}
}
}
if (verboseDump)
{
verbose = true;
}
if (dumpIR)
{
this->dumpIR = true;
}
if (dumpIRTypes)
{
this->dumpIRTypes = true;
}
if (dumpIRLocals)
{
this->dumpIRLocals = true;
}
if (dumpIRRegs)
{
this->dumpIRRegs = true;
}
if (dumpIRSsa)
{
this->dumpIRSsa = true;
}
if (dumpIRValnums)
{
this->dumpIRValnums = true;
}
if (dumpIRCosts)
{
this->dumpIRCosts = true;
}
if (dumpIRFlags)
{
this->dumpIRFlags = true;
}
if (dumpIRKinds)
{
this->dumpIRKinds = true;
}
if (dumpIRNodes)
{
this->dumpIRNodes = true;
}
if (dumpIRNoLists)
{
this->dumpIRNoLists = true;
}
if (dumpIRNoLeafs)
{
this->dumpIRNoLeafs = true;
}
if (dumpIRNoLeafs && dumpIRDataflow)
{
this->dumpIRDataflow = true;
}
if (dumpIRNoStmts)
{
this->dumpIRNoStmts = true;
}
if (dumpIRTrees)
{
this->dumpIRTrees = true;
}
if (dumpIRLinear)
{
this->dumpIRLinear = true;
}
if (dumpIRBlockHeaders)
{
this->dumpIRBlockHeaders = true;
}
if (dumpIRExit)
{
this->dumpIRExit = true;
}
#endif // DEBUG
#ifdef FEATURE_SIMD
// Minimum bar for availing SIMD benefits is SSE2 on AMD64/x86.
featureSIMD = jitFlags->IsSet(JitFlags::JIT_FLAG_FEATURE_SIMD);
setUsesSIMDTypes(false);
#endif // FEATURE_SIMD
if (compIsForImportOnly())
{
return;
}
#if FEATURE_TAILCALL_OPT
// By default opportunistic tail call optimization is enabled.
// Recognition is done in the importer so this must be set for
// inlinees as well.
opts.compTailCallOpt = true;
#endif // FEATURE_TAILCALL_OPT
if (compIsForInlining())
{
return;
}
// The rest of the opts fields that we initialize here
// should only be used when we generate code for the method
// They should not be used when importing or inlining
CLANG_FORMAT_COMMENT_ANCHOR;
#if FEATURE_TAILCALL_OPT
opts.compTailCallLoopOpt = true;
#endif // FEATURE_TAILCALL_OPT
opts.genFPorder = true;
opts.genFPopt = true;
opts.instrCount = 0;
opts.lvRefCount = 0;
#ifdef PROFILING_SUPPORTED
opts.compJitELTHookEnabled = false;
#endif // PROFILING_SUPPORTED
#ifdef DEBUG
opts.dspInstrs = false;
opts.dspEmit = false;
opts.dspLines = false;
opts.varNames = false;
opts.dmpHex = false;
opts.disAsm = false;
opts.disAsmSpilled = false;
opts.disDiffable = false;
opts.dspCode = false;
opts.dspEHTable = false;
opts.dspDebugInfo = false;
opts.dspGCtbls = false;
opts.disAsm2 = false;
opts.dspUnwind = false;
opts.compLongAddress = false;
opts.optRepeat = false;
#ifdef LATE_DISASM
opts.doLateDisasm = false;
#endif // LATE_DISASM
compDebugBreak = false;
// If we have a non-empty AltJit config then we change all of these other
// config values to refer only to the AltJit.
//
if (!altJitConfig || opts.altJit)
{
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
if ((JitConfig.NgenOrder() & 1) == 1)
{
opts.dspOrder = true;
}
if (JitConfig.NgenGCDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.dspGCtbls = true;
}
if (JitConfig.NgenDisasm().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.disAsm = true;
}
if (JitConfig.NgenDisasm().contains("SPILLED", nullptr, nullptr))
{
opts.disAsmSpilled = true;
}
if (JitConfig.NgenUnwindDump().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.dspUnwind = true;
}
if (JitConfig.NgenEHDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.dspEHTable = true;
}
if (JitConfig.NgenDebugDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.dspDebugInfo = true;
}
}
else
{
bool disEnabled = true;
// Setup assembly name list for disassembly, if not already set up.
if (!s_pJitDisasmIncludeAssembliesListInitialized)
{
const wchar_t* assemblyNameList = JitConfig.JitDisasmAssemblies();
if (assemblyNameList != nullptr)
{
s_pJitDisasmIncludeAssembliesList = new (HostAllocator::getHostAllocator())
AssemblyNamesList2(assemblyNameList, HostAllocator::getHostAllocator());
}
s_pJitDisasmIncludeAssembliesListInitialized = true;
}
// If we have an assembly name list for disassembly, also check this method's assembly.
if (s_pJitDisasmIncludeAssembliesList != nullptr)
{
const char* assemblyName = info.compCompHnd->getAssemblyName(
info.compCompHnd->getModuleAssembly(info.compCompHnd->getClassModule(info.compClassHnd)));
if (!s_pJitDisasmIncludeAssembliesList->IsInList(assemblyName))
{
disEnabled = false;
}
}
if (disEnabled)
{
if ((JitConfig.JitOrder() & 1) == 1)
{
opts.dspOrder = true;
}
if (JitConfig.JitGCDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.dspGCtbls = true;
}
if (JitConfig.JitDisasm().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.disAsm = true;
}
if (JitConfig.JitDisasm().contains("SPILLED", nullptr, nullptr))
{
opts.disAsmSpilled = true;
}
if (JitConfig.JitUnwindDump().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.dspUnwind = true;
}
if (JitConfig.JitEHDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.dspEHTable = true;
}
if (JitConfig.JitDebugDump().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.dspDebugInfo = true;
}
}
}
#ifdef LATE_DISASM
if (JitConfig.JitLateDisasm().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
opts.doLateDisasm = true;
#endif // LATE_DISASM
// This one applies to both Ngen/Jit Disasm output: COMPlus_JitDiffableDasm=1
if (JitConfig.DiffableDasm() != 0)
{
opts.disDiffable = true;
opts.dspDiffable = true;
}
if (JitConfig.JitLongAddress() != 0)
{
opts.compLongAddress = true;
}
if (JitConfig.JitOptRepeat().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
opts.optRepeat = true;
}
}
if (verboseDump)
{
opts.dspCode = true;
opts.dspEHTable = true;
opts.dspGCtbls = true;
opts.disAsm2 = true;
opts.dspUnwind = true;
verbose = true;
verboseTrees = shouldUseVerboseTrees();
verboseSsa = shouldUseVerboseSsa();
codeGen->setVerbose(true);
}
treesBeforeAfterMorph = (JitConfig.TreesBeforeAfterMorph() == 1);
morphNum = 0; // Initialize the morphed-trees counting.
expensiveDebugCheckLevel = JitConfig.JitExpensiveDebugCheckLevel();
if (expensiveDebugCheckLevel == 0)
{
// If we're in a stress mode that modifies the flowgraph, make 1 the default.
if (fgStressBBProf() || compStressCompile(STRESS_DO_WHILE_LOOPS, 30))
{
expensiveDebugCheckLevel = 1;
}
}
if (verbose)
{
printf("****** START compiling %s (MethodHash=%08x)\n", info.compFullName, info.compMethodHash());
printf("Generating code for %s %s\n", Target::g_tgtPlatformName, Target::g_tgtCPUName);
printf(""); // in our logic this causes a flush
}
if (JitConfig.JitBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
assert(!"JitBreak reached");
}
unsigned jitHashBreakVal = (unsigned)JitConfig.JitHashBreak();
if ((jitHashBreakVal != (DWORD)-1) && (jitHashBreakVal == info.compMethodHash()))
{
assert(!"JitHashBreak reached");
}
if (verbose ||
JitConfig.JitDebugBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args) ||
JitConfig.JitBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
compDebugBreak = true;
}
memset(compActiveStressModes, 0, sizeof(compActiveStressModes));
#endif // DEBUG
//-------------------------------------------------------------------------
#ifdef DEBUG
assert(!codeGen->isGCTypeFixed());
opts.compGcChecks = (JitConfig.JitGCChecks() != 0) || compStressCompile(STRESS_GENERIC_VARN, 5);
enum
{
STACK_CHECK_ON_RETURN = 0x1,
STACK_CHECK_ON_CALL = 0x2,
STACK_CHECK_ALL = 0x3,
};
DWORD dwJitStackChecks = JitConfig.JitStackChecks();
if (compStressCompile(STRESS_GENERIC_VARN, 5))
{
dwJitStackChecks = STACK_CHECK_ALL;
}
opts.compStackCheckOnRet = (dwJitStackChecks & DWORD(STACK_CHECK_ON_RETURN)) != 0;
opts.compStackCheckOnCall = (dwJitStackChecks & DWORD(STACK_CHECK_ON_CALL)) != 0;
#endif
#if MEASURE_MEM_ALLOC
s_dspMemStats = (JitConfig.DisplayMemStats() != 0);
#endif
#ifdef PROFILING_SUPPORTED
opts.compNoPInvokeInlineCB = jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_NO_PINVOKE_INLINE);
// Cache the profiler handle
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE))
{
BOOL hookNeeded;
BOOL indirected;
info.compCompHnd->GetProfilingHandle(&hookNeeded, &compProfilerMethHnd, &indirected);
compProfilerHookNeeded = !!hookNeeded;
compProfilerMethHndIndirected = !!indirected;
}
else
{
compProfilerHookNeeded = false;
compProfilerMethHnd = nullptr;
compProfilerMethHndIndirected = false;
}
// Honour COMPlus_JitELTHookEnabled only if VM has not asked us to generate profiler
// hooks in the first place. That is, override VM only if it hasn't asked for a
// profiler callback for this method.
if (!compProfilerHookNeeded && (JitConfig.JitELTHookEnabled() != 0))
{
opts.compJitELTHookEnabled = true;
}
// TBD: Exclude PInvoke stubs
if (opts.compJitELTHookEnabled)
{
compProfilerMethHnd = (void*)DummyProfilerELTStub;
compProfilerMethHndIndirected = false;
}
#endif // PROFILING_SUPPORTED
#if FEATURE_TAILCALL_OPT
const wchar_t* strTailCallOpt = JitConfig.TailCallOpt();
if (strTailCallOpt != nullptr)
{
opts.compTailCallOpt = (UINT)_wtoi(strTailCallOpt) != 0;
}
if (JitConfig.TailCallLoopOpt() == 0)
{
opts.compTailCallLoopOpt = false;
}
#endif
opts.compScopeInfo = opts.compDbgInfo;
#ifdef LATE_DISASM
codeGen->getDisAssembler().disOpenForLateDisAsm(info.compMethodName, info.compClassName,
info.compMethodInfo->args.pSig);
#endif
//-------------------------------------------------------------------------
opts.compReloc = jitFlags->IsSet(JitFlags::JIT_FLAG_RELOC);
#ifdef DEBUG
#if defined(_TARGET_XARCH_)
// Whether encoding of absolute addr as PC-rel offset is enabled
opts.compEnablePCRelAddr = (JitConfig.EnablePCRelAddr() != 0);
#endif
#endif // DEBUG
opts.compProcedureSplitting = jitFlags->IsSet(JitFlags::JIT_FLAG_PROCSPLIT);
#ifdef _TARGET_ARM64_
// TODO-ARM64-NYI: enable hot/cold splitting
opts.compProcedureSplitting = false;
#endif // _TARGET_ARM64_
#ifdef DEBUG
opts.compProcedureSplittingEH = opts.compProcedureSplitting;
#endif // DEBUG
if (opts.compProcedureSplitting)
{
// Note that opts.compdbgCode is true under ngen for checked assemblies!
opts.compProcedureSplitting = !opts.compDbgCode;
#ifdef DEBUG
// JitForceProcedureSplitting is used to force procedure splitting on checked assemblies.
// This is useful for debugging on a checked build. Note that we still only do procedure
// splitting in the zapper.
if (JitConfig.JitForceProcedureSplitting().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.compProcedureSplitting = true;
}
// JitNoProcedureSplitting will always disable procedure splitting.
if (JitConfig.JitNoProcedureSplitting().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.compProcedureSplitting = false;
}
//
// JitNoProcedureSplittingEH will disable procedure splitting in functions with EH.
if (JitConfig.JitNoProcedureSplittingEH().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.compProcedureSplittingEH = false;
}
#endif
}
fgProfileBuffer = nullptr;
fgProfileData_ILSizeMismatch = false;
fgNumProfileRuns = 0;
if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT))
{
assert(!compIsForInlining());
HRESULT hr;
hr = info.compCompHnd->getBBProfileData(info.compMethodHnd, &fgProfileBufferCount, &fgProfileBuffer,
&fgNumProfileRuns);
// a failed result that also has a non-NULL fgProfileBuffer
// indicates that the ILSize for the method no longer matches
// the ILSize for the method when profile data was collected.
//
// We will discard the IBC data in this case
//
if (FAILED(hr) && (fgProfileBuffer != nullptr))
{
fgProfileData_ILSizeMismatch = true;
fgProfileBuffer = nullptr;
}
#ifdef DEBUG
// A successful result implies a non-NULL fgProfileBuffer
//
if (SUCCEEDED(hr))
{
assert(fgProfileBuffer != nullptr);
}
// A failed result implies a NULL fgProfileBuffer
// see implementation of Compiler::fgHaveProfileData()
//
if (FAILED(hr))
{
assert(fgProfileBuffer == nullptr);
}
#endif
}
opts.compNeedStackProbes = false;
#ifdef DEBUG
if (JitConfig.StackProbesOverride() != 0 || compStressCompile(STRESS_GENERIC_VARN, 5))
{
opts.compNeedStackProbes = true;
}
#endif
#ifdef DEBUG
// Now, set compMaxUncheckedOffsetForNullObject for STRESS_NULL_OBJECT_CHECK
if (compStressCompile(STRESS_NULL_OBJECT_CHECK, 30))
{
compMaxUncheckedOffsetForNullObject = (size_t)JitConfig.JitMaxUncheckedOffset();
if (verbose)
{
printf("STRESS_NULL_OBJECT_CHECK: compMaxUncheckedOffsetForNullObject=0x%X\n",
compMaxUncheckedOffsetForNullObject);
}
}
if (verbose)
{
printf("OPTIONS: compCodeOpt = %s\n",
(opts.compCodeOpt == BLENDED_CODE)
? "BLENDED_CODE"
: (opts.compCodeOpt == SMALL_CODE) ? "SMALL_CODE"
: (opts.compCodeOpt == FAST_CODE) ? "FAST_CODE" : "UNKNOWN_CODE");
printf("OPTIONS: compDbgCode = %s\n", dspBool(opts.compDbgCode));
printf("OPTIONS: compDbgInfo = %s\n", dspBool(opts.compDbgInfo));
printf("OPTIONS: compDbgEnC = %s\n", dspBool(opts.compDbgEnC));
printf("OPTIONS: compProcedureSplitting = %s\n", dspBool(opts.compProcedureSplitting));
printf("OPTIONS: compProcedureSplittingEH = %s\n", dspBool(opts.compProcedureSplittingEH));
if (jitFlags->IsSet(JitFlags::JIT_FLAG_BBOPT) && fgHaveProfileData())
{
printf("OPTIONS: using real profile data\n");
}
if (fgProfileData_ILSizeMismatch)
{
printf("OPTIONS: discarded IBC profile data due to mismatch in ILSize\n");
}
if (jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
{
printf("OPTIONS: Jit invoked for ngen\n");
}
printf("OPTIONS: Stack probing is %s\n", opts.compNeedStackProbes ? "ENABLED" : "DISABLED");
}
#endif
opts.compGCPollType = GCPOLL_NONE;
if (jitFlags->IsSet(JitFlags::JIT_FLAG_GCPOLL_CALLS))
{
opts.compGCPollType = GCPOLL_CALL;
}
else if (jitFlags->IsSet(JitFlags::JIT_FLAG_GCPOLL_INLINE))
{
// make sure that the EE didn't set both flags.
assert(opts.compGCPollType == GCPOLL_NONE);
opts.compGCPollType = GCPOLL_INLINE;
}
#ifdef PROFILING_SUPPORTED
#ifdef UNIX_AMD64_ABI
if (compIsProfilerHookNeeded())
{
opts.compNeedToAlignFrame = true;
}
#endif // UNIX_AMD64_ABI
#endif
}
#ifdef DEBUG
bool Compiler::compJitHaltMethod()
{
/* This method returns true when we use an INS_BREAKPOINT to allow us to step into the generated native code */
/* Note that this these two "Jit" environment variables also work for ngen images */
if (JitConfig.JitHalt().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args))
{
return true;
}
/* Use this Hash variant when there are a lot of method with the same name and different signatures */
unsigned fJitHashHaltVal = (unsigned)JitConfig.JitHashHalt();
if ((fJitHashHaltVal != (unsigned)-1) && (fJitHashHaltVal == info.compMethodHash()))
{
return true;
}
return false;
}
/*****************************************************************************
* Should we use a "stress-mode" for the given stressArea. We have different
* areas to allow the areas to be mixed in different combinations in
* different methods.
* 'weight' indicates how often (as a percentage) the area should be stressed.
* It should reflect the usefulness:overhead ratio.
*/
const LPCWSTR Compiler::s_compStressModeNames[STRESS_COUNT + 1] = {
#define STRESS_MODE(mode) W("STRESS_") W(#mode),
STRESS_MODES
#undef STRESS_MODE
};
bool Compiler::compStressCompile(compStressArea stressArea, unsigned weight)
{
unsigned hash;
DWORD stressLevel;