Permalink
Fetching contributors…
Cannot retrieve contributors at this time
4837 lines (4118 sloc) 144 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 Inline functions XX
XX XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
#ifndef _COMPILER_HPP_
#define _COMPILER_HPP_
#include "emit.h" // for emitter::emitAddLabel
#include "bitvec.h"
#include "compilerbitsettraits.hpp"
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX XX
XX Miscellaneous utility functions. Some of these are defined in Utils.cpp XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
/*****************************************************************************/
/*****************************************************************************/
inline bool getInlinePInvokeEnabled()
{
#ifdef DEBUG
return JitConfig.JitPInvokeEnabled() && !JitConfig.StressCOMCall();
#else
return true;
#endif
}
inline bool getInlinePInvokeCheckEnabled()
{
#ifdef DEBUG
return JitConfig.JitPInvokeCheckEnabled() != 0;
#else
return false;
#endif
}
// Enforce float narrowing for buggy compilers (notably preWhidbey VC)
inline float forceCastToFloat(double d)
{
Volatile<float> f = (float)d;
return f;
}
// Enforce UInt32 narrowing for buggy compilers (notably Whidbey Beta 2 LKG)
inline UINT32 forceCastToUInt32(double d)
{
Volatile<UINT32> u = (UINT32)d;
return u;
}
enum RoundLevel
{
ROUND_NEVER = 0, // Never round
ROUND_CMP_CONST = 1, // Round values compared against constants
ROUND_CMP = 2, // Round comparands and return values
ROUND_ALWAYS = 3, // Round always
COUNT_ROUND_LEVEL,
DEFAULT_ROUND_LEVEL = ROUND_NEVER
};
inline RoundLevel getRoundFloatLevel()
{
#ifdef DEBUG
return (RoundLevel)JitConfig.JitRoundFloat();
#else
return DEFAULT_ROUND_LEVEL;
#endif
}
/*****************************************************************************/
/*****************************************************************************
*
* Return the lowest bit that is set
*/
template <typename T>
inline T genFindLowestBit(T value)
{
return (value & (0 - value));
}
/*****************************************************************************/
/*****************************************************************************
*
* Return the highest bit that is set (that is, a mask that includes just the highest bit).
* TODO-ARM64-Throughput: we should convert these to use the _BitScanReverse() / _BitScanReverse64()
* compiler intrinsics, but our CRT header file intrin.h doesn't define these for ARM64 yet.
*/
inline unsigned int genFindHighestBit(unsigned int mask)
{
assert(mask != 0);
unsigned int bit = 1U << ((sizeof(unsigned int) * 8) - 1); // start looking at the top
while ((bit & mask) == 0)
{
bit >>= 1;
}
return bit;
}
inline unsigned __int64 genFindHighestBit(unsigned __int64 mask)
{
assert(mask != 0);
unsigned __int64 bit = 1ULL << ((sizeof(unsigned __int64) * 8) - 1); // start looking at the top
while ((bit & mask) == 0)
{
bit >>= 1;
}
return bit;
}
#if 0
// TODO-ARM64-Cleanup: These should probably be the implementation, when intrin.h is updated for ARM64
inline
unsigned int genFindHighestBit(unsigned int mask)
{
assert(mask != 0);
unsigned int index;
_BitScanReverse(&index, mask);
return 1L << index;
}
inline
unsigned __int64 genFindHighestBit(unsigned __int64 mask)
{
assert(mask != 0);
unsigned int index;
_BitScanReverse64(&index, mask);
return 1LL << index;
}
#endif // 0
/*****************************************************************************
*
* Return true if the given 64-bit value has exactly zero or one bits set.
*/
template <typename T>
inline BOOL genMaxOneBit(T value)
{
return (value & (value - 1)) == 0;
}
/*****************************************************************************
*
* Return true if the given 32-bit value has exactly zero or one bits set.
*/
inline BOOL genMaxOneBit(unsigned value)
{
return (value & (value - 1)) == 0;
}
/*****************************************************************************
*
* Return true if the given 64-bit value has exactly one bit set.
*/
template <typename T>
inline bool genExactlyOneBit(T value)
{
return ((value != 0) && genMaxOneBit(value));
}
/*****************************************************************************
*
* Return true if the given 32-bit value has exactly zero or one bits set.
*/
inline bool genExactlyOneBit(unsigned value)
{
return ((value != 0) && genMaxOneBit(value));
}
/*****************************************************************************
*
* Given a value that has exactly one bit set, return the position of that
* bit, in other words return the logarithm in base 2 of the given value.
*/
inline unsigned genLog2(unsigned value)
{
return BitPosition(value);
}
// Given an unsigned 64-bit value, returns the lower 32-bits in unsigned format
//
inline unsigned ulo32(unsigned __int64 value)
{
return static_cast<unsigned>(value);
}
// Given an unsigned 64-bit value, returns the upper 32-bits in unsigned format
//
inline unsigned uhi32(unsigned __int64 value)
{
return static_cast<unsigned>(value >> 32);
}
/*****************************************************************************
*
* Given a value that has exactly one bit set, return the position of that
* bit, in other words return the logarithm in base 2 of the given value.
*/
inline unsigned genLog2(unsigned __int64 value)
{
unsigned lo32 = ulo32(value);
unsigned hi32 = uhi32(value);
if (lo32 != 0)
{
assert(hi32 == 0);
return genLog2(lo32);
}
else
{
return genLog2(hi32) + 32;
}
}
/*****************************************************************************
*
* Return the lowest bit that is set in the given register mask.
*/
inline regMaskTP genFindLowestReg(regMaskTP value)
{
return (regMaskTP)genFindLowestBit(value);
}
/*****************************************************************************
*
* A rather simple routine that counts the number of bits in a given number.
*/
template <typename T>
inline unsigned genCountBits(T bits)
{
unsigned cnt = 0;
while (bits)
{
cnt++;
bits -= genFindLowestBit(bits);
}
return cnt;
}
/*****************************************************************************
*
* Given 3 masks value, end, start, returns the bits of value between start
* and end (exclusive).
*
* value[bitNum(end) - 1, bitNum(start) + 1]
*/
inline unsigned __int64 BitsBetween(unsigned __int64 value, unsigned __int64 end, unsigned __int64 start)
{
assert(start != 0);
assert(start < end);
assert((start & (start - 1)) == 0);
assert((end & (end - 1)) == 0);
return value & ~((start - 1) | start) & // Ones to the left of set bit in the start mask.
(end - 1); // Ones to the right of set bit in the end mask.
}
/*****************************************************************************/
inline bool jitIsScaleIndexMul(size_t val)
{
switch (val)
{
case 1:
case 2:
case 4:
case 8:
return true;
default:
return false;
}
}
// Returns "tree" iff "val" is a valid addressing mode scale shift amount on
// the target architecture.
inline bool jitIsScaleIndexShift(ssize_t val)
{
// It happens that this is the right test for all our current targets: x86, x64 and ARM.
// This test would become target-dependent if we added a new target with a different constraint.
return 0 < val && val < 4;
}
/*****************************************************************************
* Returns true if value is between [start..end).
* The comparison is inclusive of start, exclusive of end.
*/
/* static */
inline bool Compiler::jitIsBetween(unsigned value, unsigned start, unsigned end)
{
return start <= value && value < end;
}
/*****************************************************************************
* Returns true if value is between [start..end].
* The comparison is inclusive of both start and end.
*/
/* static */
inline bool Compiler::jitIsBetweenInclusive(unsigned value, unsigned start, unsigned end)
{
return start <= value && value <= end;
}
/******************************************************************************************
* Return the EH descriptor for the given region index.
*/
inline EHblkDsc* Compiler::ehGetDsc(unsigned regionIndex)
{
assert(regionIndex < compHndBBtabCount);
return &compHndBBtab[regionIndex];
}
/******************************************************************************************
* Return the EH descriptor index of the enclosing try, for the given region index.
*/
inline unsigned Compiler::ehGetEnclosingTryIndex(unsigned regionIndex)
{
return ehGetDsc(regionIndex)->ebdEnclosingTryIndex;
}
/******************************************************************************************
* Return the EH descriptor index of the enclosing handler, for the given region index.
*/
inline unsigned Compiler::ehGetEnclosingHndIndex(unsigned regionIndex)
{
return ehGetDsc(regionIndex)->ebdEnclosingHndIndex;
}
/******************************************************************************************
* Return the EH index given a region descriptor.
*/
inline unsigned Compiler::ehGetIndex(EHblkDsc* ehDsc)
{
assert(compHndBBtab <= ehDsc && ehDsc < compHndBBtab + compHndBBtabCount);
return (unsigned)(ehDsc - compHndBBtab);
}
/******************************************************************************************
* Return the EH descriptor for the most nested 'try' region this BasicBlock is a member of
* (or nullptr if this block is not in a 'try' region).
*/
inline EHblkDsc* Compiler::ehGetBlockTryDsc(BasicBlock* block)
{
if (!block->hasTryIndex())
{
return nullptr;
}
return ehGetDsc(block->getTryIndex());
}
/******************************************************************************************
* Return the EH descriptor for the most nested filter or handler region this BasicBlock is a member of
* (or nullptr if this block is not in a filter or handler region).
*/
inline EHblkDsc* Compiler::ehGetBlockHndDsc(BasicBlock* block)
{
if (!block->hasHndIndex())
{
return nullptr;
}
return ehGetDsc(block->getHndIndex());
}
#if FEATURE_EH_FUNCLETS
/*****************************************************************************
* Get the FuncInfoDsc for the funclet we are currently generating code for.
* This is only valid during codegen.
*
*/
inline FuncInfoDsc* Compiler::funCurrentFunc()
{
return funGetFunc(compCurrFuncIdx);
}
/*****************************************************************************
* Change which funclet we are currently generating code for.
* This is only valid after funclets are created.
*
*/
inline void Compiler::funSetCurrentFunc(unsigned funcIdx)
{
assert(fgFuncletsCreated);
assert(FitsIn<unsigned short>(funcIdx));
noway_assert(funcIdx < compFuncInfoCount);
compCurrFuncIdx = (unsigned short)funcIdx;
}
/*****************************************************************************
* Get the FuncInfoDsc for the given funclet.
* This is only valid after funclets are created.
*
*/
inline FuncInfoDsc* Compiler::funGetFunc(unsigned funcIdx)
{
assert(fgFuncletsCreated);
assert(funcIdx < compFuncInfoCount);
return &compFuncInfos[funcIdx];
}
/*****************************************************************************
* Get the funcIdx for the EH funclet that begins with block.
* This is only valid after funclets are created.
* It is only valid for blocks marked with BBF_FUNCLET_BEG because
* otherwise we would have to do a more expensive check to determine
* if this should return the filter funclet or the filter handler funclet.
*
*/
inline unsigned Compiler::funGetFuncIdx(BasicBlock* block)
{
assert(fgFuncletsCreated);
assert(block->bbFlags & BBF_FUNCLET_BEG);
EHblkDsc* eh = ehGetDsc(block->getHndIndex());
unsigned int funcIdx = eh->ebdFuncIndex;
if (eh->ebdHndBeg != block)
{
// If this is a filter EH clause, but we want the funclet
// for the filter (not the filter handler), it is the previous one
noway_assert(eh->HasFilter());
noway_assert(eh->ebdFilter == block);
assert(funGetFunc(funcIdx)->funKind == FUNC_HANDLER);
assert(funGetFunc(funcIdx)->funEHIndex == funGetFunc(funcIdx - 1)->funEHIndex);
assert(funGetFunc(funcIdx - 1)->funKind == FUNC_FILTER);
funcIdx--;
}
return funcIdx;
}
#else // !FEATURE_EH_FUNCLETS
/*****************************************************************************
* Get the FuncInfoDsc for the funclet we are currently generating code for.
* This is only valid during codegen. For non-funclet platforms, this is
* always the root function.
*
*/
inline FuncInfoDsc* Compiler::funCurrentFunc()
{
return &compFuncInfoRoot;
}
/*****************************************************************************
* Change which funclet we are currently generating code for.
* This is only valid after funclets are created.
*
*/
inline void Compiler::funSetCurrentFunc(unsigned funcIdx)
{
assert(funcIdx == 0);
}
/*****************************************************************************
* Get the FuncInfoDsc for the givven funclet.
* This is only valid after funclets are created.
*
*/
inline FuncInfoDsc* Compiler::funGetFunc(unsigned funcIdx)
{
assert(funcIdx == 0);
return &compFuncInfoRoot;
}
/*****************************************************************************
* No funclets, so always 0.
*
*/
inline unsigned Compiler::funGetFuncIdx(BasicBlock* block)
{
return 0;
}
#endif // !FEATURE_EH_FUNCLETS
//------------------------------------------------------------------------------
// genRegNumFromMask : Maps a single register mask to a register number.
//
// Arguments:
// mask - the register mask
//
// Return Value:
// The number of the register contained in the mask.
//
// Assumptions:
// The mask contains one and only one register.
inline regNumber genRegNumFromMask(regMaskTP mask)
{
assert(mask != 0); // Must have one bit set, so can't have a mask of zero
/* Convert the mask to a register number */
regNumber regNum = (regNumber)genLog2(mask);
/* Make sure we got it right */
assert(genRegMask(regNum) == mask);
return regNum;
}
//------------------------------------------------------------------------------
// genSmallTypeCanRepresentValue: Checks if a value can be represented by a given small type.
//
// Arguments:
// value - the value to check
// type - the type
//
// Return Value:
// True if the value is representable, false otherwise.
inline bool genSmallTypeCanRepresentValue(var_types type, ssize_t value)
{
switch (type)
{
case TYP_UBYTE:
case TYP_BOOL:
return FitsIn<UINT8>(value);
case TYP_BYTE:
return FitsIn<INT8>(value);
case TYP_USHORT:
return FitsIn<UINT16>(value);
case TYP_SHORT:
return FitsIn<INT16>(value);
default:
unreached();
}
}
/*****************************************************************************
*
* Return the size in bytes of the given type.
*/
extern const BYTE genTypeSizes[TYP_COUNT];
template <class T>
inline unsigned genTypeSize(T type)
{
assert((unsigned)TypeGet(type) < _countof(genTypeSizes));
return genTypeSizes[TypeGet(type)];
}
/*****************************************************************************
*
* Return the "stack slot count" of the given type.
* returns 1 for 32-bit types and 2 for 64-bit types.
*/
extern const BYTE genTypeStSzs[TYP_COUNT];
inline unsigned genTypeStSz(var_types type)
{
assert((unsigned)type < _countof(genTypeStSzs));
return genTypeStSzs[type];
}
/*****************************************************************************
*
* Return the number of registers required to hold a value of the given type.
*/
/*****************************************************************************
*
* The following function maps a 'precise' type to an actual type as seen
* by the VM (for example, 'byte' maps to 'int').
*/
extern const BYTE genActualTypes[TYP_COUNT];
inline var_types genActualType(var_types type)
{
/* Spot check to make certain the table is in synch with the enum */
assert(genActualTypes[TYP_DOUBLE] == TYP_DOUBLE);
assert(genActualTypes[TYP_REF] == TYP_REF);
assert((unsigned)type < sizeof(genActualTypes));
return (var_types)genActualTypes[type];
}
/*****************************************************************************/
inline var_types genUnsignedType(var_types type)
{
/* Force signed types into corresponding unsigned type */
switch (type)
{
case TYP_BYTE:
type = TYP_UBYTE;
break;
case TYP_SHORT:
type = TYP_USHORT;
break;
case TYP_INT:
type = TYP_UINT;
break;
case TYP_LONG:
type = TYP_ULONG;
break;
default:
break;
}
return type;
}
/*****************************************************************************/
inline var_types genSignedType(var_types type)
{
/* Force non-small unsigned type into corresponding signed type */
/* Note that we leave the small types alone */
switch (type)
{
case TYP_UINT:
type = TYP_INT;
break;
case TYP_ULONG:
type = TYP_LONG;
break;
default:
break;
}
return type;
}
/*****************************************************************************
* Can this type be passed as a parameter in a register?
*/
inline bool isRegParamType(var_types type)
{
#if defined(_TARGET_X86_)
return (type <= TYP_INT || type == TYP_REF || type == TYP_BYREF);
#else // !_TARGET_X86_
return true;
#endif // !_TARGET_X86_
}
#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
/*****************************************************************************/
// Returns true if 'type' is a struct that can be enregistered for call args
// or can be returned by value in multiple registers.
// if 'type' is not a struct the return value will be false.
//
// Arguments:
// type - the basic jit var_type for the item being queried
// typeClass - the handle for the struct when 'type' is TYP_STRUCT
// typeSize - Out param (if non-null) is updated with the size of 'type'.
// forReturn - this is true when we asking about a GT_RETURN context;
// this is false when we are asking about an argument context
// isVarArg - whether or not this is a vararg fixed arg or variable argument
// - if so on arm64 windows getArgTypeForStruct will ignore HFA
// - types
//
inline bool Compiler::VarTypeIsMultiByteAndCanEnreg(
var_types type, CORINFO_CLASS_HANDLE typeClass, unsigned* typeSize, bool forReturn, bool isVarArg)
{
bool result = false;
unsigned size = 0;
if (varTypeIsStruct(type))
{
size = info.compCompHnd->getClassSize(typeClass);
if (forReturn)
{
structPassingKind howToReturnStruct;
type = getReturnTypeForStruct(typeClass, &howToReturnStruct, size);
}
else
{
structPassingKind howToPassStruct;
type = getArgTypeForStruct(typeClass, &howToPassStruct, isVarArg, size);
}
if (type != TYP_UNKNOWN)
{
result = true;
}
}
else
{
size = genTypeSize(type);
}
if (typeSize != nullptr)
{
*typeSize = size;
}
return result;
}
#endif //_TARGET_AMD64_ || _TARGET_ARM64_
/*****************************************************************************/
#ifdef DEBUG
inline const char* varTypeGCstring(var_types type)
{
switch (type)
{
case TYP_REF:
return "gcr";
case TYP_BYREF:
return "byr";
default:
return "non";
}
}
#endif
/*****************************************************************************/
const char* varTypeName(var_types);
/*****************************************************************************
*
* Helpers to pull big-endian values out of a byte stream.
*/
inline unsigned genGetU1(const BYTE* addr)
{
return addr[0];
}
inline signed genGetI1(const BYTE* addr)
{
return (signed char)addr[0];
}
inline unsigned genGetU2(const BYTE* addr)
{
return (addr[0] << 8) | addr[1];
}
inline signed genGetI2(const BYTE* addr)
{
return (signed short)((addr[0] << 8) | addr[1]);
}
inline unsigned genGetU4(const BYTE* addr)
{
return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];
}
/*****************************************************************************/
// Helpers to pull little-endian values out of a byte stream.
inline unsigned __int8 getU1LittleEndian(const BYTE* ptr)
{
return *(UNALIGNED unsigned __int8*)ptr;
}
inline unsigned __int16 getU2LittleEndian(const BYTE* ptr)
{
return GET_UNALIGNED_VAL16(ptr);
}
inline unsigned __int32 getU4LittleEndian(const BYTE* ptr)
{
return GET_UNALIGNED_VAL32(ptr);
}
inline signed __int8 getI1LittleEndian(const BYTE* ptr)
{
return *(UNALIGNED signed __int8*)ptr;
}
inline signed __int16 getI2LittleEndian(const BYTE* ptr)
{
return GET_UNALIGNED_VAL16(ptr);
}
inline signed __int32 getI4LittleEndian(const BYTE* ptr)
{
return GET_UNALIGNED_VAL32(ptr);
}
inline signed __int64 getI8LittleEndian(const BYTE* ptr)
{
return GET_UNALIGNED_VAL64(ptr);
}
inline float getR4LittleEndian(const BYTE* ptr)
{
__int32 val = getI4LittleEndian(ptr);
return *(float*)&val;
}
inline double getR8LittleEndian(const BYTE* ptr)
{
__int64 val = getI8LittleEndian(ptr);
return *(double*)&val;
}
/*****************************************************************************
*
* Return the normalized index to use in the EXPSET_TP for the CSE with
* the given CSE index.
* Each GenTree has the following field:
* signed char gtCSEnum; // 0 or the CSE index (negated if def)
* So zero is reserved to mean this node is not a CSE
* and postive values indicate CSE uses and negative values indicate CSE defs.
* The caller of this method must pass a non-zero postive value.
* This precondition is checked by the assert on the first line of this method.
*/
inline unsigned int genCSEnum2bit(unsigned index)
{
assert((index > 0) && (index <= EXPSET_SZ));
return (index - 1);
}
#ifdef DEBUG
const char* genES2str(BitVecTraits* traits, EXPSET_TP set);
const char* refCntWtd2str(unsigned refCntWtd);
#endif
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX GenTree XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
void* GenTree::operator new(size_t sz, Compiler* comp, genTreeOps oper)
{
#if SMALL_TREE_NODES
size_t size = GenTree::s_gtNodeSizes[oper];
#else
size_t size = TREE_NODE_SZ_LARGE;
#endif
#if MEASURE_NODE_SIZE
genNodeSizeStats.genTreeNodeCnt += 1;
genNodeSizeStats.genTreeNodeSize += size;
genNodeSizeStats.genTreeNodeActualSize += sz;
genNodeSizeStatsPerFunc.genTreeNodeCnt += 1;
genNodeSizeStatsPerFunc.genTreeNodeSize += size;
genNodeSizeStatsPerFunc.genTreeNodeActualSize += sz;
#endif // MEASURE_NODE_SIZE
assert(size >= sz);
return comp->getAllocator(CMK_ASTNode).allocate<char>(size);
}
// GenTree constructor
inline GenTree::GenTree(genTreeOps oper, var_types type DEBUGARG(bool largeNode))
{
gtOper = oper;
gtType = type;
gtFlags = 0;
gtLIRFlags = 0;
#ifdef DEBUG
gtDebugFlags = 0;
#endif // DEBUG
#if FEATURE_ANYCSE
gtCSEnum = NO_CSE;
#endif // FEATURE_ANYCSE
#if ASSERTION_PROP
ClearAssertion();
#endif
gtNext = nullptr;
gtPrev = nullptr;
gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
INDEBUG(gtCostsInitialized = false;)
#ifdef DEBUG
#if SMALL_TREE_NODES
size_t size = GenTree::s_gtNodeSizes[oper];
if (size == TREE_NODE_SZ_SMALL && !largeNode)
{
gtDebugFlags |= GTF_DEBUG_NODE_SMALL;
}
else if (size == TREE_NODE_SZ_LARGE || largeNode)
{
gtDebugFlags |= GTF_DEBUG_NODE_LARGE;
}
else
{
assert(!"bogus node size");
}
#endif
#endif
#if COUNT_AST_OPERS
InterlockedIncrement(&s_gtNodeCounts[oper]);
#endif
#ifdef DEBUG
gtSeqNum = 0;
gtTreeID = JitTls::GetCompiler()->compGenTreeID++;
gtVNPair.SetBoth(ValueNumStore::NoVN);
gtRegTag = GT_REGTAG_NONE;
gtOperSave = GT_NONE;
#endif
}
/*****************************************************************************/
inline GenTreeStmt* Compiler::gtNewStmt(GenTree* expr, IL_OFFSETX offset)
{
/* NOTE - GT_STMT is now a small node in retail */
GenTreeStmt* stmt = new (this, GT_STMT) GenTreeStmt(expr, offset);
return stmt;
}
/*****************************************************************************/
inline GenTree* Compiler::gtNewOperNode(genTreeOps oper, var_types type, GenTree* op1, bool doSimplifications)
{
assert((GenTree::OperKind(oper) & (GTK_UNOP | GTK_BINOP)) != 0);
assert((GenTree::OperKind(oper) & GTK_EXOP) ==
0); // Can't use this to construct any types that extend unary/binary operator.
assert(op1 != nullptr || oper == GT_PHI || oper == GT_RETFILT || oper == GT_NOP ||
(oper == GT_RETURN && type == TYP_VOID));
if (doSimplifications)
{
// We do some simplifications here.
// If this gets to be too many, try a switch...
// TODO-Cleanup: With the factoring out of array bounds checks, it should not be the
// case that we need to check for the array index case here, but without this check
// we get failures (see for example jit\Directed\Languages\Python\test_methods_d.exe)
if (oper == GT_IND)
{
// IND(ADDR(IND(x)) == IND(x)
if (op1->gtOper == GT_ADDR)
{
if (op1->gtOp.gtOp1->gtOper == GT_IND && (op1->gtOp.gtOp1->gtFlags & GTF_IND_ARR_INDEX) == 0)
{
op1 = op1->gtOp.gtOp1->gtOp.gtOp1;
}
}
}
else if (oper == GT_ADDR)
{
// if "x" is not an array index, ADDR(IND(x)) == x
if (op1->gtOper == GT_IND && (op1->gtFlags & GTF_IND_ARR_INDEX) == 0)
{
return op1->gtOp.gtOp1;
}
}
}
GenTree* node = new (this, oper) GenTreeOp(oper, type, op1, nullptr);
//
// the GT_ADDR of a Local Variable implies GTF_ADDR_ONSTACK
//
if ((oper == GT_ADDR) && (op1->OperGet() == GT_LCL_VAR))
{
node->gtFlags |= GTF_ADDR_ONSTACK;
}
return node;
}
// Returns an opcode that is of the largest node size in use.
inline genTreeOps LargeOpOpcode()
{
#if SMALL_TREE_NODES
// Allocate a large node
assert(GenTree::s_gtNodeSizes[GT_CALL] == TREE_NODE_SZ_LARGE);
#endif
return GT_CALL;
}
/******************************************************************************
*
* Use to create nodes which may later be morphed to another (big) operator
*/
inline GenTree* Compiler::gtNewLargeOperNode(genTreeOps oper, var_types type, GenTree* op1, GenTree* op2)
{
assert((GenTree::OperKind(oper) & (GTK_UNOP | GTK_BINOP)) != 0);
assert((GenTree::OperKind(oper) & GTK_EXOP) ==
0); // Can't use this to construct any types that extend unary/binary operator.
#if SMALL_TREE_NODES
// Allocate a large node
assert(GenTree::s_gtNodeSizes[oper] == TREE_NODE_SZ_SMALL);
GenTree* node = new (this, LargeOpOpcode()) GenTreeOp(oper, type, op1, op2 DEBUGARG(/*largeNode*/ true));
#else
GenTree* node = new (this, oper) GenTreeOp(oper, type, op1, op2);
#endif
return node;
}
/*****************************************************************************
*
* allocates a integer constant entry that represents a handle (something
* that may need to be fixed up).
*/
inline GenTree* Compiler::gtNewIconHandleNode(size_t value, unsigned flags, FieldSeqNode* fields)
{
GenTree* node;
assert((flags & (GTF_ICON_HDL_MASK | GTF_ICON_FIELD_OFF)) != 0);
// Interpret "fields == NULL" as "not a field."
if (fields == nullptr)
{
fields = FieldSeqStore::NotAField();
}
#if defined(LATE_DISASM)
node = new (this, LargeOpOpcode()) GenTreeIntCon(TYP_I_IMPL, value, fields DEBUGARG(/*largeNode*/ true));
#else
node = new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, value, fields);
#endif
node->gtFlags |= flags;
return node;
}
/*****************************************************************************
*
* It may not be allowed to embed HANDLEs directly into the JITed code (for eg,
* as arguments to JIT helpers). Get a corresponding value that can be embedded.
* These are versions for each specific type of HANDLE
*/
inline GenTree* Compiler::gtNewIconEmbScpHndNode(CORINFO_MODULE_HANDLE scpHnd)
{
void *embedScpHnd, *pEmbedScpHnd;
embedScpHnd = (void*)info.compCompHnd->embedModuleHandle(scpHnd, &pEmbedScpHnd);
assert((!embedScpHnd) != (!pEmbedScpHnd));
return gtNewIconEmbHndNode(embedScpHnd, pEmbedScpHnd, GTF_ICON_SCOPE_HDL, scpHnd);
}
//-----------------------------------------------------------------------------
inline GenTree* Compiler::gtNewIconEmbClsHndNode(CORINFO_CLASS_HANDLE clsHnd)
{
void *embedClsHnd, *pEmbedClsHnd;
embedClsHnd = (void*)info.compCompHnd->embedClassHandle(clsHnd, &pEmbedClsHnd);
assert((!embedClsHnd) != (!pEmbedClsHnd));
return gtNewIconEmbHndNode(embedClsHnd, pEmbedClsHnd, GTF_ICON_CLASS_HDL, clsHnd);
}
//-----------------------------------------------------------------------------
inline GenTree* Compiler::gtNewIconEmbMethHndNode(CORINFO_METHOD_HANDLE methHnd)
{
void *embedMethHnd, *pEmbedMethHnd;
embedMethHnd = (void*)info.compCompHnd->embedMethodHandle(methHnd, &pEmbedMethHnd);
assert((!embedMethHnd) != (!pEmbedMethHnd));
return gtNewIconEmbHndNode(embedMethHnd, pEmbedMethHnd, GTF_ICON_METHOD_HDL, methHnd);
}
//-----------------------------------------------------------------------------
inline GenTree* Compiler::gtNewIconEmbFldHndNode(CORINFO_FIELD_HANDLE fldHnd)
{
void *embedFldHnd, *pEmbedFldHnd;
embedFldHnd = (void*)info.compCompHnd->embedFieldHandle(fldHnd, &pEmbedFldHnd);
assert((!embedFldHnd) != (!pEmbedFldHnd));
return gtNewIconEmbHndNode(embedFldHnd, pEmbedFldHnd, GTF_ICON_FIELD_HDL, fldHnd);
}
/*****************************************************************************/
//------------------------------------------------------------------------------
// gtNewHelperCallNode : Helper to create a call helper node.
//
//
// Arguments:
// helper - Call helper
// type - Type of the node
// args - Call args
//
// Return Value:
// New CT_HELPER node
inline GenTreeCall* Compiler::gtNewHelperCallNode(unsigned helper, var_types type, GenTreeArgList* args)
{
unsigned flags = s_helperCallProperties.NoThrow((CorInfoHelpFunc)helper) ? 0 : GTF_EXCEPT;
GenTreeCall* result = gtNewCallNode(CT_HELPER, eeFindHelper(helper), type, args);
result->gtFlags |= flags;
#if DEBUG
// Helper calls are never candidates.
result->gtInlineObservation = InlineObservation::CALLSITE_IS_CALL_TO_HELPER;
#endif
return result;
}
//------------------------------------------------------------------------
// gtNewAllocObjNode: A little helper to create an object allocation node.
//
// Arguments:
// helper - Value returned by ICorJitInfo::getNewHelper
// clsHnd - Corresponding class handle
// type - Tree return type (e.g. TYP_REF)
// op1 - Node containing an address of VtablePtr
//
// Return Value:
// Returns GT_ALLOCOBJ node that will be later morphed into an
// allocation helper call or local variable allocation on the stack.
inline GenTree* Compiler::gtNewAllocObjNode(unsigned int helper,
CORINFO_CLASS_HANDLE clsHnd,
var_types type,
GenTree* op1)
{
GenTree* node = new (this, GT_ALLOCOBJ) GenTreeAllocObj(type, helper, clsHnd, op1);
return node;
}
//------------------------------------------------------------------------
// gtNewRuntimeLookup: Helper to create a runtime lookup node
//
// Arguments:
// hnd - generic handle being looked up
// hndTyp - type of the generic handle
// tree - tree for the lookup
//
// Return Value:
// New GenTreeRuntimeLookup node.
inline GenTree* Compiler::gtNewRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfoGenericHandleType hndTyp, GenTree* tree)
{
assert(tree != nullptr);
GenTree* node = new (this, GT_RUNTIMELOOKUP) GenTreeRuntimeLookup(hnd, hndTyp, tree);
return node;
}
/*****************************************************************************/
inline GenTree* Compiler::gtNewCodeRef(BasicBlock* block)
{
GenTree* node = new (this, GT_LABEL) GenTreeLabel(block);
return node;
}
/*****************************************************************************
*
* A little helper to create a data member reference node.
*/
inline GenTree* Compiler::gtNewFieldRef(var_types typ, CORINFO_FIELD_HANDLE fldHnd, GenTree* obj, DWORD offset)
{
#if SMALL_TREE_NODES
/* 'GT_FIELD' nodes may later get transformed into 'GT_IND' */
assert(GenTree::s_gtNodeSizes[GT_IND] <= GenTree::s_gtNodeSizes[GT_FIELD]);
GenTree* tree = new (this, GT_FIELD) GenTreeField(typ);
#else
GenTree* tree = new (this, GT_FIELD) GenTreeField(typ);
#endif
tree->gtField.gtFldObj = obj;
tree->gtField.gtFldHnd = fldHnd;
tree->gtField.gtFldOffset = offset;
#ifdef FEATURE_READYTORUN_COMPILER
tree->gtField.gtFieldLookup.addr = nullptr;
#endif
// If "obj" is the address of a local, note that a field of that struct local has been accessed.
if (obj != nullptr && obj->OperGet() == GT_ADDR && varTypeIsStruct(obj->gtOp.gtOp1) &&
obj->gtOp.gtOp1->OperGet() == GT_LCL_VAR)
{
unsigned lclNum = obj->gtOp.gtOp1->gtLclVarCommon.gtLclNum;
lvaTable[lclNum].lvFieldAccessed = 1;
#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
// These structs are passed by reference; we should probably be able to treat these
// as non-global refs, but downstream logic expects these to be marked this way.
if (lvaTable[lclNum].lvIsParam)
{
tree->gtFlags |= GTF_GLOB_REF;
}
#endif // defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
}
else
{
tree->gtFlags |= GTF_GLOB_REF;
}
return tree;
}
/*****************************************************************************
*
* A little helper to create an array index node.
*/
inline GenTree* Compiler::gtNewIndexRef(var_types typ, GenTree* arrayOp, GenTree* indexOp)
{
GenTreeIndex* gtIndx = new (this, GT_INDEX) GenTreeIndex(typ, arrayOp, indexOp, genTypeSize(typ));
return gtIndx;
}
//------------------------------------------------------------------------------
// gtNewArrLen : Helper to create an array length node.
//
//
// Arguments:
// typ - Type of the node
// arrayOp - Array node
// lenOffset - Offset of the length field
//
// Return Value:
// New GT_ARR_LENGTH node
inline GenTreeArrLen* Compiler::gtNewArrLen(var_types typ, GenTree* arrayOp, int lenOffset)
{
GenTreeArrLen* arrLen = new (this, GT_ARR_LENGTH) GenTreeArrLen(typ, arrayOp, lenOffset);
static_assert_no_msg(GTF_ARRLEN_NONFAULTING == GTF_IND_NONFAULTING);
arrLen->SetIndirExceptionFlags(this);
return arrLen;
}
//------------------------------------------------------------------------------
// gtNewIndir : Helper to create an indirection node.
//
// Arguments:
// typ - Type of the node
// addr - Address of the indirection
//
// Return Value:
// New GT_IND node
inline GenTree* Compiler::gtNewIndir(var_types typ, GenTree* addr)
{
GenTree* indir = gtNewOperNode(GT_IND, typ, addr);
indir->SetIndirExceptionFlags(this);
return indir;
}
/*****************************************************************************
*
* Create (and check for) a "nothing" node, i.e. a node that doesn't produce
* any code. We currently use a "nop" node of type void for this purpose.
*/
inline GenTree* Compiler::gtNewNothingNode()
{
return new (this, GT_NOP) GenTreeOp(GT_NOP, TYP_VOID);
}
/*****************************************************************************/
inline bool GenTree::IsNothingNode() const
{
return (gtOper == GT_NOP && gtType == TYP_VOID);
}
/*****************************************************************************
*
* Change the given node to a NOP - May be later changed to a GT_COMMA
*
*****************************************************************************/
inline void GenTree::gtBashToNOP()
{
ChangeOper(GT_NOP);
gtType = TYP_VOID;
gtOp.gtOp1 = gtOp.gtOp2 = nullptr;
gtFlags &= ~(GTF_ALL_EFFECT | GTF_REVERSE_OPS);
}
// return new arg placeholder node. Does not do anything but has a type associated
// with it so we can keep track of register arguments in lists associated w/ call nodes
inline GenTree* Compiler::gtNewArgPlaceHolderNode(var_types type, CORINFO_CLASS_HANDLE clsHnd)
{
GenTree* node = new (this, GT_ARGPLACE) GenTreeArgPlace(type, clsHnd);
return node;
}
/*****************************************************************************/
inline GenTree* Compiler::gtUnusedValNode(GenTree* expr)
{
return gtNewOperNode(GT_COMMA, TYP_VOID, expr, gtNewNothingNode());
}
/*****************************************************************************
*
* A wrapper for gtSetEvalOrder and gtComputeFPlvls
* Necessary because the FP levels may need to be re-computed if we reverse
* operands
*/
inline void Compiler::gtSetStmtInfo(GenTree* stmt)
{
assert(stmt->gtOper == GT_STMT);
GenTree* expr = stmt->gtStmt.gtStmtExpr;
/* Recursively process the expression */
gtSetEvalOrder(expr);
// Set the statement to have the same costs as the top node of the tree.
stmt->CopyCosts(expr);
}
/*****************************************************************************/
#if SMALL_TREE_NODES
/*****************************************************************************/
inline void GenTree::SetOper(genTreeOps oper, ValueNumberUpdate vnUpdate)
{
assert(((gtDebugFlags & GTF_DEBUG_NODE_SMALL) != 0) != ((gtDebugFlags & GTF_DEBUG_NODE_LARGE) != 0));
/* Make sure the node isn't too small for the new operator */
assert(GenTree::s_gtNodeSizes[gtOper] == TREE_NODE_SZ_SMALL ||
GenTree::s_gtNodeSizes[gtOper] == TREE_NODE_SZ_LARGE);
assert(GenTree::s_gtNodeSizes[oper] == TREE_NODE_SZ_SMALL || GenTree::s_gtNodeSizes[oper] == TREE_NODE_SZ_LARGE);
assert(GenTree::s_gtNodeSizes[oper] == TREE_NODE_SZ_SMALL || (gtDebugFlags & GTF_DEBUG_NODE_LARGE));
#if defined(_HOST_64BIT_) && !defined(_TARGET_64BIT_)
if (gtOper == GT_CNS_LNG && oper == GT_CNS_INT)
{
// When casting from LONG to INT, we need to force cast of the value,
// if the host architecture represents INT and LONG with the same data size.
gtLngCon.gtLconVal = (INT64)(INT32)gtLngCon.gtLconVal;
}
#endif // defined(_HOST_64BIT_) && !defined(_TARGET_64BIT_)
SetOperRaw(oper);
#ifdef DEBUG
// Maintain the invariant that unary operators always have NULL gtOp2.
// If we ever start explicitly allocating GenTreeUnOp nodes, we wouldn't be
// able to do that (but if we did, we'd have to have a check in gtOp -- perhaps
// a gtUnOp...)
if (OperKind(oper) == GTK_UNOP)
{
gtOp.gtOp2 = nullptr;
}
#endif // DEBUG
#if DEBUGGABLE_GENTREE
// Until we eliminate SetOper/ChangeOper, we also change the vtable of the node, so that
// it shows up correctly in the debugger.
SetVtableForOper(oper);
#endif // DEBUGGABLE_GENTREE
if (oper == GT_CNS_INT)
{
gtIntCon.gtFieldSeq = nullptr;
}
#if defined(_TARGET_ARM_)
if (oper == GT_MUL_LONG)
{
// We sometimes bash GT_MUL to GT_MUL_LONG, which converts it from GenTreeOp to GenTreeMultiRegOp.
gtMultiRegOp.gtOtherReg = REG_NA;
gtMultiRegOp.ClearOtherRegFlags();
}
#endif
if (vnUpdate == CLEAR_VN)
{
// Clear the ValueNum field as well.
gtVNPair.SetBoth(ValueNumStore::NoVN);
}
}
inline GenTreeCast* Compiler::gtNewCastNode(var_types typ, GenTree* op1, bool fromUnsigned, var_types castType)
{
GenTreeCast* res = new (this, GT_CAST) GenTreeCast(typ, op1, fromUnsigned, castType);
return res;
}
inline GenTreeCast* Compiler::gtNewCastNodeL(var_types typ, GenTree* op1, bool fromUnsigned, var_types castType)
{
/* Some casts get transformed into 'GT_CALL' or 'GT_IND' nodes */
assert(GenTree::s_gtNodeSizes[GT_CALL] >= GenTree::s_gtNodeSizes[GT_CAST]);
assert(GenTree::s_gtNodeSizes[GT_CALL] >= GenTree::s_gtNodeSizes[GT_IND]);
/* Make a big node first and then change it to be GT_CAST */
GenTreeCast* res =
new (this, LargeOpOpcode()) GenTreeCast(typ, op1, fromUnsigned, castType DEBUGARG(/*largeNode*/ true));
return res;
}
/*****************************************************************************/
#else // SMALL_TREE_NODES
/*****************************************************************************/
inline void GenTree::InitNodeSize()
{
}
inline void GenTree::SetOper(genTreeOps oper, ValueNumberUpdate vnUpdate)
{
SetOperRaw(oper);
if (vnUpdate == CLEAR_VN)
{
// Clear the ValueNum field.
gtVNPair.SetBoth(ValueNumStore::NoVN);
}
}
inline void GenTree::ReplaceWith(GenTree* src)
{
RecordOperBashing(OperGet(), src->OperGet()); // nop unless NODEBASH_STATS is enabled
*this = *src;
#ifdef DEBUG
gtSeqNum = 0;
#endif
}
inline GenTree* Compiler::gtNewCastNode(var_types typ, GenTree* op1, var_types castType)
{
GenTree* tree = gtNewOperNode(GT_CAST, typ, op1);
tree->gtCast.gtCastType = castType;
}
inline GenTree* Compiler::gtNewCastNodeL(var_types typ, GenTree* op1, var_types castType)
{
return gtNewCastNode(typ, op1, castType);
}
/*****************************************************************************/
#endif // SMALL_TREE_NODES
/*****************************************************************************/
/*****************************************************************************/
inline void GenTree::SetOperRaw(genTreeOps oper)
{
// Please do not do anything here other than assign to gtOper (debug-only
// code is OK, but should be kept to a minimum).
RecordOperBashing(OperGet(), oper); // nop unless NODEBASH_STATS is enabled
gtOper = oper;
}
inline void GenTree::SetOperResetFlags(genTreeOps oper)
{
SetOper(oper);
gtFlags &= GTF_NODE_MASK;
}
inline void GenTree::ChangeOperConst(genTreeOps oper)
{
#ifdef _TARGET_64BIT_
assert(oper != GT_CNS_LNG); // We should never see a GT_CNS_LNG for a 64-bit target!
#endif
assert(OperIsConst(oper)); // use ChangeOper() instead
SetOperResetFlags(oper);
// Some constant subtypes have additional fields that must be initialized.
if (oper == GT_CNS_INT)
{
gtIntCon.gtFieldSeq = FieldSeqStore::NotAField();
}
}
inline void GenTree::ChangeOper(genTreeOps oper, ValueNumberUpdate vnUpdate)
{
assert(!OperIsConst(oper)); // use ChangeOperLeaf() instead
unsigned mask = GTF_COMMON_MASK;
if (this->OperIsIndirOrArrLength() && OperIsIndirOrArrLength(oper))
{
mask |= GTF_IND_NONFAULTING;
}
SetOper(oper, vnUpdate);
gtFlags &= mask;
// Do "oper"-specific initializations...
switch (oper)
{
case GT_LCL_FLD:
gtLclFld.gtLclOffs = 0;
gtLclFld.gtFieldSeq = FieldSeqStore::NotAField();
break;
default:
break;
}
}
inline void GenTree::ChangeOperUnchecked(genTreeOps oper)
{
unsigned mask = GTF_COMMON_MASK;
if (this->OperIsIndirOrArrLength() && OperIsIndirOrArrLength(oper))
{
mask |= GTF_IND_NONFAULTING;
}
SetOperRaw(oper); // Trust the caller and don't use SetOper()
gtFlags &= mask;
}
/*****************************************************************************
* Returns true if the node is &var (created by ldarga and ldloca)
*/
inline bool GenTree::IsVarAddr() const
{
if (gtOper == GT_ADDR)
{
if (gtFlags & GTF_ADDR_ONSTACK)
{
assert((gtType == TYP_BYREF) || (gtType == TYP_I_IMPL));
return true;
}
}
return false;
}
/*****************************************************************************
*
* Returns true if the node is of the "ovf" variety, for example, add.ovf.i1.
* + gtOverflow() can only be called for valid operators (that is, we know it is one
* of the operators which may have GTF_OVERFLOW set).
* + gtOverflowEx() is more expensive, and should be called only if gtOper may be
* an operator for which GTF_OVERFLOW is invalid.
*/
inline bool GenTree::gtOverflow() const
{
assert(OperMayOverflow());
if ((gtFlags & GTF_OVERFLOW) != 0)
{
assert(varTypeIsIntegral(TypeGet()));
return true;
}
else
{
return false;
}
}
inline bool GenTree::gtOverflowEx() const
{
return OperMayOverflow() && gtOverflow();
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX LclVarsInfo XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
inline bool Compiler::lvaHaveManyLocals() const
{
return (lvaCount >= lclMAX_TRACKED);
}
/*****************************************************************************
*
* Allocate a temporary variable or a set of temp variables.
*/
inline unsigned Compiler::lvaGrabTemp(bool shortLifetime DEBUGARG(const char* reason))
{
if (compIsForInlining())
{
// Grab the temp using Inliner's Compiler instance.
Compiler* pComp = impInlineInfo->InlinerCompiler; // The Compiler instance for the caller (i.e. the inliner)
if (pComp->lvaHaveManyLocals())
{
// Don't create more LclVar with inlining
compInlineResult->NoteFatal(InlineObservation::CALLSITE_TOO_MANY_LOCALS);
}
unsigned tmpNum = pComp->lvaGrabTemp(shortLifetime DEBUGARG(reason));
lvaTable = pComp->lvaTable;
lvaCount = pComp->lvaCount;
lvaTableCnt = pComp->lvaTableCnt;
return tmpNum;
}
// You cannot allocate more space after frame layout!
noway_assert(lvaDoneFrameLayout < Compiler::TENTATIVE_FRAME_LAYOUT);
/* Check if the lvaTable has to be grown */
if (lvaCount + 1 > lvaTableCnt)
{
unsigned newLvaTableCnt = lvaCount + (lvaCount / 2) + 1;
// Check for overflow
if (newLvaTableCnt <= lvaCount)
{
IMPL_LIMITATION("too many locals");
}
LclVarDsc* newLvaTable = getAllocator(CMK_LvaTable).allocate<LclVarDsc>(newLvaTableCnt);
memcpy(newLvaTable, lvaTable, lvaCount * sizeof(*lvaTable));
memset(newLvaTable + lvaCount, 0, (newLvaTableCnt - lvaCount) * sizeof(*lvaTable));
for (unsigned i = lvaCount; i < newLvaTableCnt; i++)
{
new (&newLvaTable[i], jitstd::placement_t()) LclVarDsc(); // call the constructor.
}
#ifdef DEBUG
// Fill the old table with junks. So to detect the un-intended use.
memset(lvaTable, JitConfig.JitDefaultFill(), lvaCount * sizeof(*lvaTable));
#endif
lvaTableCnt = newLvaTableCnt;
lvaTable = newLvaTable;
}
const unsigned tempNum = lvaCount;
lvaCount++;
// Initialize lvType, lvIsTemp and lvOnFrame
lvaTable[tempNum].lvType = TYP_UNDEF;
lvaTable[tempNum].lvIsTemp = shortLifetime;
lvaTable[tempNum].lvOnFrame = true;
// If we've started normal ref counting, bump the ref count of this
// local, as we no longer do any incremental counting, and we presume
// this new local will be referenced.
if (lvaLocalVarRefCounted())
{
if (opts.MinOpts() || opts.compDbgCode)
{
lvaTable[tempNum].lvImplicitlyReferenced = 1;
}
else
{
lvaTable[tempNum].setLvRefCnt(1);
lvaTable[tempNum].setLvRefCntWtd(BB_UNITY_WEIGHT);
}
}
#ifdef DEBUG
if (verbose)
{
printf("\nlvaGrabTemp returning %d (", tempNum);
gtDispLclVar(tempNum, false);
printf(")%s called for %s.\n", shortLifetime ? "" : " (a long lifetime temp)", reason);
}
#endif // DEBUG
return tempNum;
}
inline unsigned Compiler::lvaGrabTemps(unsigned cnt DEBUGARG(const char* reason))
{
if (compIsForInlining())
{
// Grab the temps using Inliner's Compiler instance.
unsigned tmpNum = impInlineInfo->InlinerCompiler->lvaGrabTemps(cnt DEBUGARG(reason));
lvaTable = impInlineInfo->InlinerCompiler->lvaTable;
lvaCount = impInlineInfo->InlinerCompiler->lvaCount;
lvaTableCnt = impInlineInfo->InlinerCompiler->lvaTableCnt;
return tmpNum;
}
#ifdef DEBUG
if (verbose)
{
printf("\nlvaGrabTemps(%d) returning %d..%d (long lifetime temps) called for %s", cnt, lvaCount,
lvaCount + cnt - 1, reason);
}
#endif
// Could handle this...
assert(!lvaLocalVarRefCounted());
// You cannot allocate more space after frame layout!
noway_assert(lvaDoneFrameLayout < Compiler::TENTATIVE_FRAME_LAYOUT);
/* Check if the lvaTable has to be grown */
if (lvaCount + cnt > lvaTableCnt)
{
unsigned newLvaTableCnt = lvaCount + max(lvaCount / 2 + 1, cnt);
// Check for overflow
if (newLvaTableCnt <= lvaCount)
{
IMPL_LIMITATION("too many locals");
}
LclVarDsc* newLvaTable = getAllocator(CMK_LvaTable).allocate<LclVarDsc>(newLvaTableCnt);
memcpy(newLvaTable, lvaTable, lvaCount * sizeof(*lvaTable));
memset(newLvaTable + lvaCount, 0, (newLvaTableCnt - lvaCount) * sizeof(*lvaTable));
for (unsigned i = lvaCount; i < newLvaTableCnt; i++)
{
new (&newLvaTable[i], jitstd::placement_t()) LclVarDsc(); // call the constructor.
}
#ifdef DEBUG
// Fill the old table with junks. So to detect the un-intended use.
memset(lvaTable, JitConfig.JitDefaultFill(), lvaCount * sizeof(*lvaTable));
#endif
lvaTableCnt = newLvaTableCnt;
lvaTable = newLvaTable;
}
unsigned tempNum = lvaCount;
while (cnt--)
{
lvaTable[lvaCount].lvType = TYP_UNDEF; // Initialize lvType, lvIsTemp and lvOnFrame
lvaTable[lvaCount].lvIsTemp = false;
lvaTable[lvaCount].lvOnFrame = true;
lvaCount++;
}
return tempNum;
}
/*****************************************************************************
*
* Allocate a temporary variable which is implicitly used by code-gen
* There will be no explicit references to the temp, and so it needs to
* be forced to be kept alive, and not be optimized away.
*/
inline unsigned Compiler::lvaGrabTempWithImplicitUse(bool shortLifetime DEBUGARG(const char* reason))
{
if (compIsForInlining())
{
// Grab the temp using Inliner's Compiler instance.
unsigned tmpNum = impInlineInfo->InlinerCompiler->lvaGrabTempWithImplicitUse(shortLifetime DEBUGARG(reason));
lvaTable = impInlineInfo->InlinerCompiler->lvaTable;
lvaCount = impInlineInfo->InlinerCompiler->lvaCount;
lvaTableCnt = impInlineInfo->InlinerCompiler->lvaTableCnt;
return tmpNum;
}
unsigned lclNum = lvaGrabTemp(shortLifetime DEBUGARG(reason));
LclVarDsc* varDsc = &lvaTable[lclNum];
// This will prevent it from being optimized away
// TODO-CQ: We shouldn't have to go as far as to declare these
// address-exposed -- DoNotEnregister should suffice?
lvaSetVarAddrExposed(lclNum);
// Note the implicit use
varDsc->lvImplicitlyReferenced = 1;
return lclNum;
}
/*****************************************************************************
*
* Increment the ref counts for a local variable
*/
inline void LclVarDsc::incRefCnts(BasicBlock::weight_t weight, Compiler* comp, RefCountState state, bool propagate)
{
// In minopts and debug codegen, we don't maintain normal ref counts.
if ((state == RCS_NORMAL) && (comp->opts.MinOpts() || comp->opts.compDbgCode))
{
// Note, at least, that there is at least one reference.
lvImplicitlyReferenced = 1;
return;
}
Compiler::lvaPromotionType promotionType = DUMMY_INIT(Compiler::PROMOTION_TYPE_NONE);
if (varTypeIsStruct(lvType))
{
promotionType = comp->lvaGetPromotionType(this);
}
//
// Increment counts on the local itself.
//
if (lvType != TYP_STRUCT || promotionType != Compiler::PROMOTION_TYPE_INDEPENDENT)
{
//
// Increment lvRefCnt
//
int newRefCnt = lvRefCnt(state) + 1;
if (newRefCnt == (unsigned short)newRefCnt) // lvRefCnt is an "unsigned short". Don't overflow it.
{
setLvRefCnt((unsigned short)newRefCnt, state);
}
//
// Increment lvRefCntWtd
//
if (weight != 0)
{
// We double the weight of internal temps
//
if (lvIsTemp && (weight * 2 > weight))
{
weight *= 2;
}
unsigned newWeight = lvRefCntWtd(state) + weight;
if (newWeight >= lvRefCntWtd(state))
{ // lvRefCntWtd is an "unsigned". Don't overflow it
setLvRefCntWtd(newWeight, state);
}
else
{ // On overflow we assign ULONG_MAX
setLvRefCntWtd(ULONG_MAX, state);
}
}
}
if (varTypeIsStruct(lvType) && propagate)
{
// For promoted struct locals, increment lvRefCnt on its field locals as well.
if (promotionType == Compiler::PROMOTION_TYPE_INDEPENDENT ||
promotionType == Compiler::PROMOTION_TYPE_DEPENDENT)
{
for (unsigned i = lvFieldLclStart; i < lvFieldLclStart + lvFieldCnt; ++i)
{
comp->lvaTable[i].incRefCnts(weight, comp, state, false); // Don't propagate
}
}
}
if (lvIsStructField && propagate)
{
// Depending on the promotion type, increment the ref count for the parent struct as well.
promotionType = comp->lvaGetParentPromotionType(this);
LclVarDsc* parentvarDsc = &comp->lvaTable[lvParentLcl];
assert(!parentvarDsc->lvRegStruct);
if (promotionType == Compiler::PROMOTION_TYPE_DEPENDENT)
{
parentvarDsc->incRefCnts(weight, comp, state, false); // Don't propagate
}
}
#ifdef DEBUG
if (comp->verbose)
{
unsigned varNum = (unsigned)(this - comp->lvaTable);
assert(&comp->lvaTable[varNum] == this);
printf("New refCnts for V%02u: refCnt = %2u, refCntWtd = %s\n", varNum, lvRefCnt(state),
refCntWtd2str(lvRefCntWtd(state)));
}
#endif
}
/*****************************************************************************
*
* The following returns the mask of all tracked locals
* referenced in a statement.
*/
inline VARSET_VALRET_TP Compiler::lvaStmtLclMask(GenTree* stmt)
{
GenTree* tree;
unsigned varNum;
LclVarDsc* varDsc;
VARSET_TP lclMask(VarSetOps::MakeEmpty(this));
assert(stmt->gtOper == GT_STMT);
assert(fgStmtListThreaded);
for (tree = stmt->gtStmt.gtStmtList; tree; tree = tree->gtNext)
{
if (tree->gtOper != GT_LCL_VAR)
{
continue;
}
varNum = tree->gtLclVarCommon.gtLclNum;
assert(varNum < lvaCount);
varDsc = lvaTable + varNum;
if (!varDsc->lvTracked)
{
continue;
}
VarSetOps::UnionD(this, lclMask, VarSetOps::MakeSingleton(this, varDsc->lvVarIndex));
}
return lclMask;
}
/*****************************************************************************
* Returns true if the lvType is a TYP_REF or a TYP_BYREF.
* When the lvType is a TYP_STRUCT it searches the GC layout
* of the struct and returns true iff it contains a GC ref.
*/
inline bool Compiler::lvaTypeIsGC(unsigned varNum)
{
if (lvaTable[varNum].TypeGet() == TYP_STRUCT)
{
assert(lvaTable[varNum].lvGcLayout != nullptr); // bits are intialized
return (lvaTable[varNum].lvStructGcCount != 0);
}
return (varTypeIsGC(lvaTable[varNum].TypeGet()));
}
/*****************************************************************************
Is this a synchronized instance method? If so, we will need to report "this"
in the GC information, so that the EE can release the object lock
in case of an exception
We also need to report "this" and keep it alive for all shared generic
code that gets the actual generic context from the "this" pointer and
has exception handlers.
For example, if List<T>::m() is shared between T = object and T = string,
then inside m() an exception handler "catch E<T>" needs to be able to fetch
the 'this' pointer to find out what 'T' is in order to tell if we
should catch the exception or not.
*/
inline bool Compiler::lvaKeepAliveAndReportThis()
{
if (info.compIsStatic || lvaTable[0].TypeGet() != TYP_REF)
{
return false;
}
const bool genericsContextIsThis = (info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_THIS) != 0;
#ifdef JIT32_GCENCODER
if (info.compFlags & CORINFO_FLG_SYNCH)
return true;
if (genericsContextIsThis)
{
// TODO: Check if any of the exception clauses are
// typed using a generic type. Else, we do not need to report this.
if (info.compXcptnsCount > 0)
return true;
if (opts.compDbgCode)
return true;
if (lvaGenericsContextUseCount > 0)
{
JITDUMP("Reporting this as generic context: %u refs\n", lvaGenericsContextUseCount);
return true;
}
}
#else // !JIT32_GCENCODER
// If the generics context is the this pointer we need to report it if either
// the VM requires us to keep the generics context alive or it is used in a look-up.
// We keep it alive in the lookup scenario, even when the VM didn't ask us to,
// because collectible types need the generics context when gc-ing.
if (genericsContextIsThis)
{
const bool isUsed = lvaGenericsContextUseCount > 0;
const bool mustKeep = (info.compMethodInfo->options & CORINFO_GENERICS_CTXT_KEEP_ALIVE) != 0;
if (isUsed || mustKeep)
{
JITDUMP("Reporting this as generic context: %u refs%s\n", lvaGenericsContextUseCount,
mustKeep ? ", must keep" : "");
return true;
}
}
#endif
return false;
}
/*****************************************************************************
Similar to lvaKeepAliveAndReportThis
*/
inline bool Compiler::lvaReportParamTypeArg()
{
if (info.compMethodInfo->options & (CORINFO_GENERICS_CTXT_FROM_METHODDESC | CORINFO_GENERICS_CTXT_FROM_METHODTABLE))
{
assert(info.compTypeCtxtArg != -1);
// If the VM requires us to keep the generics context alive and report it (for example, if any catch
// clause catches a type that uses a generic parameter of this method) this flag will be set.
if (info.compMethodInfo->options & CORINFO_GENERICS_CTXT_KEEP_ALIVE)
{
return true;
}
// Otherwise, if an exact type parameter is needed in the body, report the generics context.
// We do this because collectible types needs the generics context when gc-ing.
if (lvaGenericsContextUseCount > 0)
{
return true;
}
}
// Otherwise, we don't need to report it -- the generics context parameter is unused.
return false;
}
//*****************************************************************************
inline int Compiler::lvaCachedGenericContextArgOffset()
{
assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT);
return lvaCachedGenericContextArgOffs;
}
/*****************************************************************************
*
* Return the stack framed offset of the given variable; set *FPbased to
* true if the variable is addressed off of FP, false if it's addressed
* off of SP. Note that 'varNum' can be a negated spill-temporary var index.
*
* mustBeFPBased - strong about whether the base reg is FP. But it is also
* strong about not being FPBased after FINAL_FRAME_LAYOUT. i.e.,
* it enforces SP based.
*
* addrModeOffset - is the addressing mode offset, for example: v02 + 0x10
* So, V02 itself is at offset sp + 0x10 and then addrModeOffset is what gets
* added beyond that.
*/
inline
#ifdef _TARGET_ARM_
int
Compiler::lvaFrameAddress(int varNum, bool mustBeFPBased, regNumber* pBaseReg, int addrModeOffset)
#else
int
Compiler::lvaFrameAddress(int varNum, bool* pFPbased)
#endif
{
assert(lvaDoneFrameLayout != NO_FRAME_LAYOUT);
int offset;
bool FPbased;
bool fConservative = false;
var_types type = TYP_UNDEF;
if (varNum >= 0)
{
LclVarDsc* varDsc;
assert((unsigned)varNum < lvaCount);
varDsc = lvaTable + varNum;
type = varDsc->TypeGet();
bool isPrespilledArg = false;
#if defined(_TARGET_ARM_) && defined(PROFILING_SUPPORTED)
isPrespilledArg = varDsc->lvIsParam && compIsProfilerHookNeeded() &&
lvaIsPreSpilled(varNum, codeGen->regSet.rsMaskPreSpillRegs(false));
#endif
// If we have finished with register allocation, and this isn't a stack-based local,
// check that this has a valid stack location.
if (lvaDoneFrameLayout > REGALLOC_FRAME_LAYOUT && !varDsc->lvOnFrame)
{
#ifdef _TARGET_AMD64_
#ifndef UNIX_AMD64_ABI
// On amd64, every param has a stack location, except on Unix-like systems.
assert(varDsc->lvIsParam);
#endif // UNIX_AMD64_ABI
#else // !_TARGET_AMD64_
// For other targets, a stack parameter that is enregistered or prespilled
// for profiling on ARM will have a stack location.
assert((varDsc->lvIsParam && !varDsc->lvIsRegArg) || isPrespilledArg);
#endif // !_TARGET_AMD64_
}
FPbased = varDsc->lvFramePointerBased;
#ifdef DEBUG
#if FEATURE_FIXED_OUT_ARGS
if ((unsigned)varNum == lvaOutgoingArgSpaceVar)
{
assert(FPbased == false);
}
else
#endif
{
#if DOUBLE_ALIGN
assert(FPbased == (isFramePointerUsed() || (genDoubleAlign() && varDsc->lvIsParam && !varDsc->lvIsRegArg)));
#else
#ifdef _TARGET_X86_
assert(FPbased == isFramePointerUsed());
#endif
#endif
}
#endif // DEBUG
offset = varDsc->lvStkOffs;
}
else // Its a spill-temp
{
FPbased = isFramePointerUsed();
if (lvaDoneFrameLayout == Compiler::FINAL_FRAME_LAYOUT)
{
TempDsc* tmpDsc = codeGen->regSet.tmpFindNum(varNum);
// The temp might be in use, since this might be during code generation.
if (tmpDsc == nullptr)
{
tmpDsc = codeGen->regSet.tmpFindNum(varNum, RegSet::TEMP_USAGE_USED);
}
assert(tmpDsc != nullptr);
offset = tmpDsc->tdTempOffs();
type = tmpDsc->tdTempType();
}
else
{
// This value is an estimate until we calculate the
// offset after the final frame layout
// ---------------------------------------------------
// : :
// +-------------------------+ base --+
// | LR, ++N for ARM | | frameBaseOffset (= N)
// +-------------------------+ |
// | R11, ++N for ARM | <---FP |
// +-------------------------+ --+
// | compCalleeRegsPushed - N| | lclFrameOffset
// +-------------------------+ --+
// | lclVars | |
// +-------------------------+ |
// | tmp[MAX_SPILL_TEMP] | |
// | tmp[1] | |
// | tmp[0] | | compLclFrameSize
// +-------------------------+ |
// | outgoingArgSpaceSize | |
// +-------------------------+ --+
// | | <---SP
// : :
// ---------------------------------------------------
type = compFloatingPointUsed ? TYP_FLOAT : TYP_INT;
fConservative = true;
if (!FPbased)
{
// Worst case stack based offset.
CLANG_FORMAT_COMMENT_ANCHOR;
#if FEATURE_FIXED_OUT_ARGS
int outGoingArgSpaceSize = lvaOutgoingArgSpaceSize;
#else
int outGoingArgSpaceSize = 0;
#endif
offset = outGoingArgSpaceSize + max(-varNum * TARGET_POINTER_SIZE, (int)lvaGetMaxSpillTempSize());
}
else
{
// Worst case FP based offset.
CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef _TARGET_ARM_
offset = codeGen->genCallerSPtoInitialSPdelta() - codeGen->genCallerSPtoFPdelta();
#else
offset = -(codeGen->genTotalFrameSize());
#endif
}
}
}
#ifdef _TARGET_ARM_
if (FPbased)
{
if (mustBeFPBased)
{
*pBaseReg = REG_FPBASE;
}
// Change the Frame Pointer (R11)-based addressing to the SP-based addressing when possible because
// it generates smaller code on ARM. See frame picture above for the math.
else
{
// If it is the final frame layout phase, we don't have a choice, we should stick
// to either FP based or SP based that we decided in the earlier phase. Because
// we have already selected the instruction. MinOpts will always reserve R10, so
// for MinOpts always use SP-based offsets, using R10 as necessary, for simplicity.
int spOffset = fConservative ? compLclFrameSize : offset + codeGen->genSPtoFPdelta();
int actualOffset = spOffset + addrModeOffset;
int encodingLimitUpper = varTypeIsFloating(type) ? 0x3FC : 0xFFF;
int encodingLimitLower = varTypeIsFloating(type) ? -0x3FC : -0xFF;
// Use SP-based encoding. During encoding, we'll pick the best encoding for the actual offset we have.
if (opts.MinOpts() || (actualOffset <= encodingLimitUpper))
{
offset = spOffset;
*pBaseReg = compLocallocUsed ? REG_SAVED_LOCALLOC_SP : REG_SPBASE;
}
// Use Frame Pointer (R11)-based encoding.
else if ((encodingLimitLower <= offset) && (offset <= encodingLimitUpper))
{
*pBaseReg = REG_FPBASE;
}
// Otherwise, use SP-based encoding. This is either (1) a small positive offset using a single movw,
// (2) a large offset using movw/movt. In either case, we must have already reserved
// the "reserved register", which will get used during encoding.
else
{
offset = spOffset;
*pBaseReg = compLocallocUsed ? REG_SAVED_LOCALLOC_SP : REG_SPBASE;
}
}
}
else
{
*pBaseReg = REG_SPBASE;
}
#else
*pFPbased = FPbased;
#endif
return offset;
}
inline bool Compiler::lvaIsParameter(unsigned varNum)
{
LclVarDsc* varDsc;
assert(varNum < lvaCount);
varDsc = lvaTable + varNum;
return varDsc->lvIsParam;
}
inline bool Compiler::lvaIsRegArgument(unsigned varNum)
{
LclVarDsc* varDsc;
assert(varNum < lvaCount);
varDsc = lvaTable + varNum;
return varDsc->lvIsRegArg;
}
inline BOOL Compiler::lvaIsOriginalThisArg(unsigned varNum)
{
assert(varNum < lvaCount);
BOOL isOriginalThisArg = (varNum == info.compThisArg) && (info.compIsStatic == false);
#ifdef DEBUG
if (isOriginalThisArg)
{
LclVarDsc* varDsc = lvaTable + varNum;
// Should never write to or take the address of the original 'this' arg
CLANG_FORMAT_COMMENT_ANCHOR;
#ifndef JIT32_GCENCODER
// With the general encoder/decoder, when the original 'this' arg is needed as a generics context param, we
// copy to a new local, and mark the original as DoNotEnregister, to
// ensure that it is stack-allocated. It should not be the case that the original one can be modified -- it
// should not be written to, or address-exposed.
assert(!varDsc->lvHasILStoreOp &&
(!varDsc->lvAddrExposed || ((info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_THIS) != 0)));
#else
assert(!varDsc->lvHasILStoreOp && !varDsc->lvAddrExposed);
#endif
}
#endif
return isOriginalThisArg;
}
inline BOOL Compiler::lvaIsOriginalThisReadOnly()
{
return lvaArg0Var == info.compThisArg;
}
/*****************************************************************************
*
* The following is used to detect the cases where the same local variable#
* is used both as a long/double value and a 32-bit value and/or both as an
* integer/address and a float value.
*/
/* static */ inline unsigned Compiler::lvaTypeRefMask(var_types type)
{
const static BYTE lvaTypeRefMasks[] = {
#define DEF_TP(tn, nm, jitType, verType, sz, sze, asze, st, al, tf, howUsed) howUsed,
#include "typelist.h"
#undef DEF_TP
};
assert((unsigned)type < sizeof(lvaTypeRefMasks));
assert(lvaTypeRefMasks[type] != 0);
return lvaTypeRefMasks[type];
}
/*****************************************************************************
*
* The following is used to detect the cases where the same local variable#
* is used both as a long/double value and a 32-bit value and/or both as an
* integer/address and a float value.
*/
inline var_types Compiler::lvaGetActualType(unsigned lclNum)
{
return genActualType(lvaGetRealType(lclNum));
}
inline var_types Compiler::lvaGetRealType(unsigned lclNum)
{
return lvaTable[lclNum].TypeGet();
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX Importer XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
inline unsigned Compiler::compMapILargNum(unsigned ILargNum)
{
assert(ILargNum < info.compILargsCount || tiVerificationNeeded);
// Note that this works because if compRetBuffArg/compTypeCtxtArg/lvVarargsHandleArg are not present
// they will be BAD_VAR_NUM (MAX_UINT), which is larger than any variable number.
if (ILargNum >= info.compRetBuffArg)
{
ILargNum++;
assert(ILargNum < info.compLocalsCount || tiVerificationNeeded); // compLocals count already adjusted.
}
if (ILargNum >= (unsigned)info.compTypeCtxtArg)
{
ILargNum++;
assert(ILargNum < info.compLocalsCount || tiVerificationNeeded); // compLocals count already adjusted.
}
if (ILargNum >= (unsigned)lvaVarargsHandleArg)
{
ILargNum++;
assert(ILargNum < info.compLocalsCount || tiVerificationNeeded); // compLocals count already adjusted.
}
assert(ILargNum < info.compArgsCount || tiVerificationNeeded);
return (ILargNum);
}
//------------------------------------------------------------------------
// Compiler::mangleVarArgsType: Retype float types to their corresponding
// : int/long types.
//
// Notes:
//
// The mangling of types will only occur for incoming vararg fixed arguments
// on windows arm|64 or on armel (softFP).
//
// NO-OP for all other cases.
//
inline var_types Compiler::mangleVarArgsType(var_types type)
{
#if defined(_TARGET_ARMARCH_)
if (opts.compUseSoftFP
#if defined(_TARGET_WINDOWS_)
|| info.compIsVarArgs
#endif // defined(_TARGET_WINDOWS_)
)
{
switch (type)
{
case TYP_FLOAT:
return TYP_INT;
case TYP_DOUBLE:
return TYP_LONG;
default:
break;
}
}
#endif // defined(_TARGET_ARMARCH_)
return type;
}
// For CORECLR there is no vararg on System V systems.
#if FEATURE_VARARG
inline regNumber Compiler::getCallArgIntRegister(regNumber floatReg)
{
#ifdef _TARGET_AMD64_
switch (floatReg)
{
case REG_XMM0:
return REG_RCX;
case REG_XMM1:
return REG_RDX;
case REG_XMM2:
return REG_R8;
case REG_XMM3:
return REG_R9;
default:
unreached();
}
#else // !_TARGET_AMD64_
// How will float args be passed for RyuJIT/x86?
NYI("getCallArgIntRegister for RyuJIT/x86");
return REG_NA;
#endif // !_TARGET_AMD64_
}
inline regNumber Compiler::getCallArgFloatRegister(regNumber intReg)
{
#ifdef _TARGET_AMD64_
switch (intReg)
{
case REG_RCX:
return REG_XMM0;
case REG_RDX:
return REG_XMM1;
case REG_R8:
return REG_XMM2;
case REG_R9:
return REG_XMM3;
default:
unreached();
}
#else // !_TARGET_AMD64_
// How will float args be passed for RyuJIT/x86?
NYI("getCallArgFloatRegister for RyuJIT/x86");
return REG_NA;
#endif // !_TARGET_AMD64_
}
#endif // FEATURE_VARARG
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX Register Allocator XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
/*****************************************************************************/
inline bool rpCanAsgOperWithoutReg(GenTree* op, bool lclvar)
{
var_types type;
switch (op->OperGet())
{
case GT_CNS_LNG:
case GT_CNS_INT:
return true;
case GT_LCL_VAR:
type = genActualType(op->TypeGet());
if (lclvar && ((type == TYP_INT) || (type == TYP_REF) || (type == TYP_BYREF)))
{
return true;
}
break;
default:
break;
}
return false;
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX XX
XX FlowGraph XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
inline bool Compiler::compCanEncodePtrArgCntMax()
{
#ifdef JIT32_GCENCODER
// DDB 204533:
// The GC encoding for fully interruptible methods does not
// support more than 1023 pushed arguments, so we have to
// use a partially interruptible GC info/encoding.
//
return (fgPtrArgCntMax < MAX_PTRARG_OFS);
#else // JIT32_GCENCODER
return true;
#endif
}
/*****************************************************************************
*
* Call the given function pointer for all nodes in the tree. The 'visitor'
* fn should return one of the following values:
*
* WALK_ABORT stop walking and return immediately
* WALK_CONTINUE continue walking
* WALK_SKIP_SUBTREES don't walk any subtrees of the node just visited
*
* computeStack - true if we want to make stack visible to callback function
*/
inline Compiler::fgWalkResult Compiler::fgWalkTreePre(
GenTree** pTree, fgWalkPreFn* visitor, void* callBackData, bool lclVarsOnly, bool computeStack)
{
fgWalkData walkData;
walkData.compiler = this;
walkData.wtprVisitorFn = visitor;
walkData.pCallbackData = callBackData;
walkData.parent = nullptr;
walkData.wtprLclsOnly = lclVarsOnly;
#ifdef DEBUG
walkData.printModified = false;
#endif
fgWalkResult result;
if (lclVarsOnly && computeStack)
{
GenericTreeWalker<true, true, false, true, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
else if (lclVarsOnly)
{
GenericTreeWalker<false, true, false, true, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
else if (computeStack)
{
GenericTreeWalker<true, true, false, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
else
{
GenericTreeWalker<false, true, false, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
#ifdef DEBUG
if (verbose && walkData.printModified)
{
gtDispTree(*pTree);
}
#endif
return result;
}
/*****************************************************************************
*
* Same as above, except the tree walk is performed in a depth-first fashion,
* The 'visitor' fn should return one of the following values:
*
* WALK_ABORT stop walking and return immediately
* WALK_CONTINUE continue walking
*
* computeStack - true if we want to make stack visible to callback function
*/
inline Compiler::fgWalkResult Compiler::fgWalkTreePost(GenTree** pTree,
fgWalkPostFn* visitor,
void* callBackData,
bool computeStack)
{
fgWalkData walkData;
walkData.compiler = this;
walkData.wtpoVisitorFn = visitor;
walkData.pCallbackData = callBackData;
walkData.parent = nullptr;
fgWalkResult result;
if (computeStack)
{
GenericTreeWalker<true, false, true, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
else
{
GenericTreeWalker<false, false, true, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
assert(result == WALK_CONTINUE || result == WALK_ABORT);
return result;
}
/*****************************************************************************
*
* Call the given function pointer for all nodes in the tree. The 'visitor'
* fn should return one of the following values:
*
* WALK_ABORT stop walking and return immediately
* WALK_CONTINUE continue walking
* WALK_SKIP_SUBTREES don't walk any subtrees of the node just visited
*/
inline Compiler::fgWalkResult Compiler::fgWalkTree(GenTree** pTree,
fgWalkPreFn* preVisitor,
fgWalkPreFn* postVisitor,
void* callBackData)
{
fgWalkData walkData;
walkData.compiler = this;
walkData.wtprVisitorFn = preVisitor;
walkData.wtpoVisitorFn = postVisitor;
walkData.pCallbackData = callBackData;
walkData.parent = nullptr;
walkData.wtprLclsOnly = false;
#ifdef DEBUG
walkData.printModified = false;
#endif
fgWalkResult result;
assert(preVisitor || postVisitor);
if (preVisitor && postVisitor)
{
GenericTreeWalker<true, true, true, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
else if (preVisitor)
{
GenericTreeWalker<true, true, false, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
else
{
GenericTreeWalker<true, false, true, false, true> walker(&walkData);
result = walker.WalkTree(pTree, nullptr);
}
#ifdef DEBUG
if (verbose && walkData.printModified)
{
gtDispTree(*pTree);
}
#endif
return result;
}
/*****************************************************************************
*
* Has this block been added to throw an inlined exception
* Returns true if the block was added to throw one of:
* range-check exception
* argument exception (used by feature SIMD)
* argument range-check exception (used by feature SIMD)
* divide by zero exception (Not used on X86/X64)
* null reference exception (Not currently used)
* overflow exception
*/
inline bool Compiler::fgIsThrowHlpBlk(BasicBlock* block)
{
if (!fgIsCodeAdded())
{
return false;
}
if (!(block->bbFlags & BBF_INTERNAL) || block->bbJumpKind != BBJ_THROW)
{
return false;
}
GenTree* call = block->lastNode();
#ifdef DEBUG
if (block->IsLIR())
{
LIR::Range& blockRange = LIR::AsRange(block);
for (LIR::Range::ReverseIterator node = blockRange.rbegin(), end = blockRange.rend(); node != end; ++node)
{
if (node->OperGet() == GT_CALL)
{
assert(*node == call);
assert(node == blockRange.rbegin());
break;
}
}
}
#endif
if (!call || (call->gtOper != GT_CALL))
{
return false;
}
if (!((call->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_RNGCHKFAIL)) ||
(call->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_THROWDIVZERO)) ||
(call->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_THROWNULLREF)) ||
(call->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_OVERFLOW))))
{
return false;
}
// We can get to this point for blocks that we didn't create as throw helper blocks
// under stress, with crazy flow graph optimizations. So, walk the fgAddCodeList
// for the final determination.
for (AddCodeDsc* add = fgAddCodeList; add; add = add->acdNext)
{
if (block == add->acdDstBlk)
{
return add->acdKind == SCK_RNGCHK_FAIL || add->acdKind == SCK_DIV_BY_ZERO || add->acdKind == SCK_OVERFLOW ||
add->acdKind == SCK_ARG_EXCPN || add->acdKind == SCK_ARG_RNG_EXCPN;
}
}
// We couldn't find it in the fgAddCodeList
return false;
}
#if !FEATURE_FIXED_OUT_ARGS
/*****************************************************************************
*
* Return the stackLevel of the inserted block that throws exception
* (by calling the EE helper).
*/
inline unsigned Compiler::fgThrowHlpBlkStkLevel(BasicBlock* block)
{
for (AddCodeDsc* add = fgAddCodeList; add; add = add->acdNext)
{
if (block == add->acdDstBlk)
{
// Compute assert cond separately as assert macro cannot have conditional compilation directives.
bool cond =
(add->acdKind == SCK_RNGCHK_FAIL || add->acdKind == SCK_DIV_BY_ZERO || add->acdKind == SCK_OVERFLOW ||
add->acdKind == SCK_ARG_EXCPN || add->acdKind == SCK_ARG_RNG_EXCPN);
assert(cond);
// TODO: bbTgtStkDepth is DEBUG-only.
// Should we use it regularly and avoid this search.
assert(block->bbTgtStkDepth == add->acdStkLvl);
return add->acdStkLvl;
}
}
noway_assert(!"fgThrowHlpBlkStkLevel should only be called if fgIsThrowHlpBlk() is true, but we can't find the "
"block in the fgAddCodeList list");
/* We couldn't find the basic block: it must not have been a throw helper block */
return 0;
}
#endif // !FEATURE_FIXED_OUT_ARGS
/*
Small inline function to change a given block to a throw block.
*/
inline void Compiler::fgConvertBBToThrowBB(BasicBlock* block)
{
// If we're converting a BBJ_CALLFINALLY block to a BBJ_THROW block,
// then mark the subsequent BBJ_ALWAYS block as unreferenced.
if (block->isBBCallAlwaysPair())
{
BasicBlock* leaveBlk = block->bbNext;
noway_assert(leaveBlk->bbJumpKind == BBJ_ALWAYS);
leaveBlk->bbFlags &= ~BBF_DONT_REMOVE;
leaveBlk->bbRefs = 0;
leaveBlk->bbPreds = nullptr;
#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
// This function (fgConvertBBToThrowBB) can be called before the predecessor lists are created (e.g., in
// fgMorph). The fgClearFinallyTargetBit() function to update the BBF_FINALLY_TARGET bit depends on these
// predecessor lists. If there are no predecessor lists, we immediately clear all BBF_FINALLY_TARGET bits
// (to allow subsequent dead code elimination to delete such blocks without asserts), and set a flag to
// recompute them later, before they are required.
if (fgComputePredsDone)
{
fgClearFinallyTargetBit(leaveBlk->bbJumpDest);
}
else
{
fgClearAllFinallyTargetBits();
fgNeedToAddFinallyTargetBits = true;
}
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
}
block->bbJumpKind = BBJ_THROW;
block->bbSetRunRarely(); // any block with a throw is rare
}
/*****************************************************************************
*
* Return true if we've added any new basic blocks.
*/
inline bool Compiler::fgIsCodeAdded()
{
return fgAddCodeModf;
}
/*****************************************************************************
Is the offset too big?
*/
inline bool Compiler::fgIsBigOffset(size_t offset)
{
return (offset > compMaxUncheckedOffsetForNullObject);
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX TempsInfo XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
/*****************************************************************************/
/* static */ inline unsigned RegSet::tmpSlot(unsigned size)
{
noway_assert(size >= sizeof(int));
noway_assert(size <= TEMP_MAX_SIZE);
assert((size % sizeof(int)) == 0);
assert(size < UINT32_MAX);
return size / sizeof(int) - 1;
}
/*****************************************************************************
*
* Finish allocating temps - should be called each time after a pass is made
* over a function body.
*/
inline void RegSet::tmpEnd()
{
#ifdef DEBUG
if (m_rsCompiler->verbose && (tmpCount > 0))
{
printf("%d tmps used\n", tmpCount);
}
#endif // DEBUG
}
/*****************************************************************************
*
* Shuts down the temp-tracking code. Should be called once per function
* compiled.
*/
inline void RegSet::tmpDone()
{
#ifdef DEBUG
unsigned count;
TempDsc* temp;
assert(tmpAllFree());
for (temp = tmpListBeg(), count = temp ? 1 : 0; temp; temp = tmpListNxt(temp), count += temp ? 1 : 0)
{
assert(temp->tdLegalOffset());
}
// Make sure that all the temps were released
assert(count == tmpCount);
assert(tmpGetCount == 0);
#endif // DEBUG
}
#ifdef DEBUG
inline bool Compiler::shouldUseVerboseTrees()
{
return (JitConfig.JitDumpVerboseTrees() == 1);
}
inline bool Compiler::shouldUseVerboseSsa()
{
return (JitConfig.JitDumpVerboseSsa() == 1);
}
//------------------------------------------------------------------------
// shouldDumpASCIITrees: Should we use only ASCII characters for tree dumps?
//
// Notes:
// This is set to default to 1 in clrConfigValues.h
inline bool Compiler::shouldDumpASCIITrees()
{
return (JitConfig.JitDumpASCII() == 1);
}
/*****************************************************************************
* Should we enable JitStress mode?
* 0: No stress
* !=2: Vary stress. Performance will be slightly/moderately degraded
* 2: Check-all stress. Performance will be REALLY horrible
*/
inline DWORD getJitStressLevel()
{
return JitConfig.JitStress();
}
/*****************************************************************************
* Should we do the strict check for non-virtual call to the virtual method?
*/
inline DWORD StrictCheckForNonVirtualCallToVirtualMethod()
{
return JitConfig.JitStrictCheckForNonVirtualCallToVirtualMethod() == 1;
}
#endif // DEBUG
/*****************************************************************************/
/* Map a register argument number ("RegArgNum") to a register number ("RegNum").
* A RegArgNum is in this range:
* [0, MAX_REG_ARG) -- for integer registers
* [0, MAX_FLOAT_REG_ARG) -- for floating point registers
* Note that RegArgNum's are overlapping for integer and floating-point registers,
* while RegNum's are not (for ARM anyway, though for x86, it might be different).
* If we have a fixed return buffer register and are given it's index
* we return the fixed return buffer register
*/
inline regNumber genMapIntRegArgNumToRegNum(unsigned argNum)
{
if (hasFixedRetBuffReg() && (argNum == theFixedRetBuffArgNum()))
{
return theFixedRetBuffReg();
}
assert(argNum < ArrLen(intArgRegs));
return intArgRegs[argNum];
}
inline regNumber genMapFloatRegArgNumToRegNum(unsigned argNum)
{
#ifndef _TARGET_X86_
assert(argNum < ArrLen(fltArgRegs));
return fltArgRegs[argNum];
#else
assert(!"no x86 float arg regs\n");
return REG_NA;
#endif
}
__forceinline regNumber genMapRegArgNumToRegNum(unsigned argNum, var_types type)
{
if (varTypeIsFloating(type))
{
return genMapFloatRegArgNumToRegNum(argNum);
}
else
{
return genMapIntRegArgNumToRegNum(argNum);
}
}
/*****************************************************************************/
/* Map a register argument number ("RegArgNum") to a register mask of the associated register.
* Note that for floating-pointer registers, only the low register for a register pair
* (for a double on ARM) is returned.
*/
inline regMaskTP genMapIntRegArgNumToRegMask(unsigned argNum)
{
assert(argNum < ArrLen(intArgMasks));
return intArgMasks[argNum];
}
inline regMaskTP genMapFloatRegArgNumToRegMask(unsigned argNum)
{
#ifndef _TARGET_X86_
assert(argNum < ArrLen(fltArgMasks));
return fltArgMasks[argNum];
#else
assert(!"no x86 float arg regs\n");
return RBM_NONE;
#endif
}
__forceinline regMaskTP genMapArgNumToRegMask(unsigned argNum, var_types type)
{
regMaskTP result;
if (varTypeIsFloating(type))
{
result = genMapFloatRegArgNumToRegMask(argNum);
#ifdef _TARGET_ARM_
if (type == TYP_DOUBLE)
{
assert((result & RBM_DBL_REGS) != 0);
result |= (result << 1);
}
#endif
}
else
{
result = genMapIntRegArgNumToRegMask(argNum);
}
return result;
}
/*****************************************************************************/
/* Map a register number ("RegNum") to a register argument number ("RegArgNum")
* If we have a fixed return buffer register we return theFixedRetBuffArgNum
*/
inline unsigned genMapIntRegNumToRegArgNum(regNumber regNum)
{
assert(genRegMask(regNum) & fullIntArgRegMask());
switch (regNum)
{
case REG_ARG_0:
return 0;
#if MAX_REG_ARG >= 2
case REG_ARG_1:
return 1;
#if MAX_REG_ARG >= 3
case REG_ARG_2:
return 2;
#if MAX_REG_ARG >= 4
case REG_ARG_3:
return 3;
#if MAX_REG_ARG >= 5
case REG_ARG_4:
return 4;
#if MAX_REG_ARG >= 6
case REG_ARG_5:
return 5;
#if MAX_REG_ARG >= 7
case REG_ARG_6:
return 6;
#if MAX_REG_ARG >= 8
case REG_ARG_7:
return 7;
#endif
#endif
#endif
#endif
#endif
#endif
#endif
default:
// Check for the Arm64 fixed return buffer argument register
if (hasFixedRetBuffReg() && (regNum == theFixedRetBuffReg()))
{
return theFixedRetBuffArgNum();
}
else
{
assert(!"invalid register arg register");
return BAD_VAR_NUM;
}
}
}
inline unsigned genMapFloatRegNumToRegArgNum(regNumber regNum)
{
assert(genRegMask(regNum) & RBM_FLTARG_REGS);
#ifdef _TARGET_ARM_
return regNum - REG_F0;
#elif defined(_TARGET_ARM64_)
return regNum - REG_V0;
#elif defined(UNIX_AMD64_ABI)
return regNum - REG_FLTARG_0;
#else
#if MAX_FLOAT_REG_ARG >= 1
switch (regNum)
{
case REG_FLTARG_0:
return 0;
#if MAX_REG_ARG >= 2
case REG_FLTARG_1:
return 1;
#if MAX_REG_ARG >= 3
case REG_FLTARG_2:
return 2;
#if MAX_REG_ARG >= 4
case REG_FLTARG_3:
return 3;
#if MAX_REG_ARG >= 5
case REG_FLTARG_4:
return 4;
#endif
#endif
#endif
#endif
default:
assert(!"invalid register arg register");
return BAD_VAR_NUM;
}
#else
assert(!"flt reg args not allowed");
return BAD_VAR_NUM;
#endif
#endif // !arm
}
inline unsigned genMapRegNumToRegArgNum(regNumber regNum, var_types type)
{
if (varTypeIsFloating(type))
{
return genMapFloatRegNumToRegArgNum(regNum);
}
else
{
return genMapIntRegNumToRegArgNum(regNum);
}
}
/*****************************************************************************/
/* Return a register mask with the first 'numRegs' argument registers set.
*/
inline regMaskTP genIntAllRegArgMask(unsigned numRegs)
{
assert(numRegs <= MAX_REG_ARG);
regMaskTP result = RBM_NONE;
for (unsigned i = 0; i < numRegs; i++)
{
result |= intArgMasks[i];
}
return result;
}
inline regMaskTP genFltAllRegArgMask(unsigned numRegs)
{
assert(numRegs <= MAX_FLOAT_REG_ARG);
regMaskTP result = RBM_NONE;
for (unsigned i = 0; i < numRegs; i++)
{
result |= fltArgMasks[i];
}
return result;
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX Liveness XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
template <bool ForCodeGen>
inline void Compiler::compUpdateLife(VARSET_VALARG_TP newLife)
{
if (!VarSetOps::Equal(this, compCurLife, newLife))
{
compChangeLife<ForCodeGen>(newLife);
}
#ifdef DEBUG
else
{
if (verbose)
{
printf("Liveness not changing: %s ", VarSetOps::ToString(this, compCurLife));
dumpConvertedVarSet(this, compCurLife);
printf("\n");
}
}
#endif // DEBUG
}
/*****************************************************************************
*
* We stash cookies in basic blocks for the code emitter; this call retrieves
* the cookie associated with the given basic block.
*/
inline void* emitCodeGetCookie(BasicBlock* block)
{
assert(block);
return block->bbEmitCookie;
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX Optimizer XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
#if LOCAL_ASSERTION_PROP
/*****************************************************************************
*
* The following resets the value assignment table
* used only during local assertion prop
*/
inline void Compiler::optAssertionReset(AssertionIndex limit)
{
PREFAST_ASSUME(optAssertionCount <= optMaxAssertionCount);
while (optAssertionCount > limit)
{
AssertionIndex index = optAssertionCount;
AssertionDsc* curAssertion = optGetAssertion(index);
optAssertionCount--;
unsigned lclNum = curAssertion->op1.lcl.lclNum;
assert(lclNum < lvaTableCnt);
BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1);
//
// Find the Copy assertions
//
if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) &&
(curAssertion->op2.kind == O2K_LCLVAR_COPY))
{
//
// op2.lcl.lclNum no longer depends upon this assertion
//
lclNum = curAssertion->op2.lcl.lclNum;
BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1);
}
}
while (optAssertionCount < limit)
{
AssertionIndex index = ++optAssertionCount;
AssertionDsc* curAssertion = optGetAssertion(index);
unsigned lclNum = curAssertion->op1.lcl.lclNum;
BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1);
//
// Check for Copy assertions
//
if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) &&
(curAssertion->op2.kind == O2K_LCLVAR_COPY))
{
//
// op2.lcl.lclNum now depends upon this assertion
//
lclNum = curAssertion->op2.lcl.lclNum;
BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1);
}
}
}
/*****************************************************************************
*
* The following removes the i-th entry in the value assignment table
* used only during local assertion prop
*/
inline void Compiler::optAssertionRemove(AssertionIndex index)
{
assert(index > 0);
assert(index <= optAssertionCount);
PREFAST_ASSUME(optAssertionCount <= optMaxAssertionCount);
AssertionDsc* curAssertion = optGetAssertion(index);
// Two cases to consider if (index == optAssertionCount) then the last
// entry in the table is to be removed and that happens automatically when
// optAssertionCount is decremented and we can just clear the optAssertionDep bits
// The other case is when index < optAssertionCount and here we overwrite the
// index-th entry in the table with the data found at the end of the table
// Since we are reordering the rable the optAssertionDep bits need to be recreated
// using optAssertionReset(0) and optAssertionReset(newAssertionCount) will
// correctly update the optAssertionDep bits
//
if (index == optAssertionCount)
{
unsigned lclNum = curAssertion->op1.lcl.lclNum;
BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1);
//
// Check for Copy assertions
//
if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) &&
(curAssertion->op2.kind == O2K_LCLVAR_COPY))
{
//
// op2.lcl.lclNum no longer depends upon this assertion
//
lclNum = curAssertion->op2.lcl.lclNum;
BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1);
}
optAssertionCount--;
}
else
{
AssertionDsc* lastAssertion = optGetAssertion(optAssertionCount);
AssertionIndex newAssertionCount = optAssertionCount - 1;
optAssertionReset(0); // This make optAssertionCount equal 0
memcpy(curAssertion, // the entry to be removed
lastAssertion, // last entry in the table
sizeof(AssertionDsc));
optAssertionReset(newAssertionCount);
}
}
#endif // LOCAL_ASSERTION_PROP
inline void Compiler::LoopDsc::AddModifiedField(Compiler* comp, CORINFO_FIELD_HANDLE fldHnd)
{
if (lpFieldsModified == nullptr)
{
lpFieldsModified =
new (comp->getAllocatorLoopHoist()) Compiler::LoopDsc::FieldHandleSet(comp->getAllocatorLoopHoist());
}
lpFieldsModified->Set(fldHnd, true);
}
inline void Compiler::LoopDsc::AddModifiedElemType(Compiler* comp, CORINFO_CLASS_HANDLE structHnd)
{
if (lpArrayElemTypesModified == nullptr)
{
lpArrayElemTypesModified =
new (comp->getAllocatorLoopHoist()) Compiler::LoopDsc::ClassHandleSet(comp->getAllocatorLoopHoist());
}
lpArrayElemTypesModified->Set(structHnd, true);
}
inline void Compiler::LoopDsc::VERIFY_lpIterTree()
{
#ifdef DEBUG
assert(lpFlags & LPFLG_ITER);
// iterTree should be "lcl ASG lcl <op> const"
assert(lpIterTree->OperIs(GT_ASG));
GenTree* lhs = lpIterTree->gtOp.gtOp1;
GenTree* rhs = lpIterTree->gtOp.gtOp2;
assert(lhs->OperGet() == GT_LCL_VAR);
switch (rhs->gtOper)
{
case GT_ADD:
case GT_SUB:
case GT_MUL:
case GT_RSH:
case GT_LSH:
break;
default:
assert(!"Unknown operator for loop increment");
}
assert(rhs->gtOp.gtOp1->OperGet() == GT_LCL_VAR);
assert(rhs->gtOp.gtOp1->AsLclVarCommon()->GetLclNum() == lhs->AsLclVarCommon()->GetLclNum());
assert(rhs->gtOp.gtOp2->OperGet() == GT_CNS_INT);
#endif
}
//-----------------------------------------------------------------------------
inline unsigned Compiler::LoopDsc::lpIterVar()
{
VERIFY_lpIterTree();
return lpIterTree->gtOp.gtOp1->gtLclVarCommon.gtLclNum;
}
//-----------------------------------------------------------------------------
inline int Compiler::LoopDsc::lpIterConst()
{
VERIFY_lpIterTree();
GenTree* rhs = lpIterTree->gtOp.gtOp2;
return (int)rhs->gtOp.gtOp2->gtIntCon.gtIconVal;
}
//-----------------------------------------------------------------------------
inline genTreeOps Compiler::LoopDsc::lpIterOper()
{
VERIFY_lpIterTree();
GenTree* rhs = lpIterTree->gtOp.gtOp2;
return rhs->OperGet();
}
inline var_types Compiler::LoopDsc::lpIterOperType()
{
VERIFY_lpIterTree();
var_types type = lpIterTree->TypeGet();
assert(genActualType(type) == TYP_INT);
if ((lpIterTree->gtFlags & GTF_UNSIGNED) && type == TYP_INT)
{
type = TYP_UINT;
}
return type;
}
inline void Compiler::LoopDsc::VERIFY_lpTestTree()
{
#ifdef DEBUG
assert(lpFlags & LPFLG_ITER);
assert(lpTestTree);
genTreeOps oper = lpTestTree->OperGet();
assert(GenTree::OperIsCompare(oper));
GenTree* iterator = nullptr;
GenTree* limit = nullptr;
if ((lpTestTree->gtOp.gtOp2->gtOper == GT_LCL_VAR) && (lpTestTree->gtOp.gtOp2->gtFlags & GTF_VAR_ITERATOR) != 0)
{
iterator = lpTestTree->gtOp.gtOp2;
limit = lpTestTree->gtOp.gtOp1;
}
else if ((lpTestTree->gtOp.gtOp1->gtOper == GT_LCL_VAR) &&
(lpTestTree->gtOp.gtOp1->gtFlags & GTF_VAR_ITERATOR) != 0)
{
iterator = lpTestTree->gtOp.gtOp1;
limit = lpTestTree->gtOp.gtOp2;
}
else
{
// one of the nodes has to be the iterator
assert(false);
}
if (lpFlags & LPFLG_CONST_LIMIT)
{
assert(limit->OperIsConst());
}
if (lpFlags & LPFLG_VAR_LIMIT)
{
assert(limit->OperGet() == GT_LCL_VAR);
}
if (lpFlags & LPFLG_ARRLEN_LIMIT)
{
assert(limit->OperGet() == GT_ARR_LENGTH);
}
#endif
}
//-----------------------------------------------------------------------------
inline bool Compiler::LoopDsc::lpIsReversed()
{
VERIFY_lpTestTree();
return ((lpTestTree->gtOp.gtOp2->gtOper == GT_LCL_VAR) &&
(lpTestTree->gtOp.gtOp2->gtFlags & GTF_VAR_ITERATOR) != 0);
}
//-----------------------------------------------------------------------------
inline genTreeOps Compiler::LoopDsc::lpTestOper()
{
VERIFY_lpTestTree();
genTreeOps op = lpTestTree->OperGet();
return lpIsReversed() ? GenTree::SwapRelop(op) : op;
}
//-----------------------------------------------------------------------------
inline GenTree* Compiler::LoopDsc::lpIterator()
{
VERIFY_lpTestTree();
return lpIsReversed() ? lpTestTree->gtOp.gtOp2 : lpTestTree->gtOp.gtOp1;
}
//-----------------------------------------------------------------------------
inline GenTree* Compiler::LoopDsc::lpLimit()
{
VERIFY_lpTestTree();
return lpIsReversed() ? lpTestTree->gtOp.gtOp1 : lpTestTree->gtOp.gtOp2;
}
//-----------------------------------------------------------------------------
inline int Compiler::LoopDsc::lpConstLimit()
{
VERIFY_lpTestTree();
assert(lpFlags & LPFLG_CONST_LIMIT);
GenTree* limit = lpLimit();
assert(limit->OperIsConst());
return (int)limit->gtIntCon.gtIconVal;
}
//-----------------------------------------------------------------------------
inline unsigned Compiler::LoopDsc::lpVarLimit()
{
VERIFY_lpTestTree();
assert(lpFlags & LPFLG_VAR_LIMIT);
GenTree* limit = lpLimit();
assert(limit->OperGet() == GT_LCL_VAR);
return limit->gtLclVarCommon.gtLclNum;
}
//-----------------------------------------------------------------------------
inline bool Compiler::LoopDsc::lpArrLenLimit(Compiler* comp, ArrIndex* index)
{
VERIFY_lpTestTree();
assert(lpFlags & LPFLG_ARRLEN_LIMIT);
GenTree* limit = lpLimit();
assert(limit->OperGet() == GT_ARR_LENGTH);
// Check if we have a.length or a[i][j].length
if (limit->gtArrLen.ArrRef()->gtOper == GT_LCL_VAR)
{
index->arrLcl = limit->gtArrLen.ArrRef()->gtLclVarCommon.gtLclNum;
index->rank = 0;
return true;
}
// We have a[i].length, extract a[i] pattern.
else if (limit->gtArrLen.ArrRef()->gtOper == GT_COMMA)
{
return comp->optReconstructArrIndex(limit->gtArrLen.ArrRef(), index, BAD_VAR_NUM);
}
return false;
}
/*****************************************************************************
* Is "var" assigned in the loop "lnum" ?
*/
inline bool Compiler::optIsVarAssgLoop(unsigned lnum, unsigned var)
{
assert(lnum < optLoopCount);
if (var < lclMAX_ALLSET_TRACKED)
{
ALLVARSET_TP vs(AllVarSetOps::MakeSingleton(this, var));
return optIsSetAssgLoop(lnum, vs) != 0;
}
else
{
return optIsVarAssigned(optLoopTable[lnum].lpHead->bbNext, optLoopTable[lnum].lpBottom, nullptr, var);
}
}
/*****************************************************************************
* If the tree is a tracked local variable, return its LclVarDsc ptr.
*/
inline LclVarDsc* Compiler::optIsTrackedLocal(GenTree* tree)
{
LclVarDsc* varDsc;
unsigned lclNum;
if (tree->gtOper != GT_LCL_VAR)
{
return nullptr;
}
lclNum = tree->gtLclVarCommon.gtLclNum;
assert(lclNum < lvaCount);
varDsc = lvaTable + lclNum;
/* if variable not tracked, return NULL */
if (!varDsc->lvTracked)
{
return nullptr;
}
return varDsc;
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX XX
XX Optimization activation rules XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
// are we compiling for fast code, or are we compiling for blended code and
// inside a loop?
// We return true for BLENDED_CODE if the Block executes more than BB_LOOP_WEIGHT/2
inline bool Compiler::optFastCodeOrBlendedLoop(BasicBlock::weight_t bbWeight)
{
return (compCodeOpt() == FAST_CODE) ||
((compCodeOpt() == BLENDED_CODE) && (bbWeight > (BB_LOOP_WEIGHT / 2 * BB_UNITY_WEIGHT)));
}
// are we running on a Intel Pentium 4?
inline bool Compiler::optPentium4(void)
{
return (info.genCPU == CPU_X86_PENTIUM_4);
}
// should we use add/sub instead of inc/dec? (faster on P4, but increases size)
inline bool Compiler::optAvoidIncDec(BasicBlock::weight_t bbWeight)
{
return optPentium4() && optFastCodeOrBlendedLoop(bbWeight);
}
// should we try to replace integer multiplication with lea/add/shift sequences?
inline bool Compiler::optAvoidIntMult(void)
{
return (compCodeOpt() != SMALL_CODE);
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX EEInterface XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
extern var_types JITtype2varType(CorInfoType type);
#include "ee_il_dll.hpp"
inline CORINFO_METHOD_HANDLE Compiler::eeFindHelper(unsigned helper)
{
assert(helper < CORINFO_HELP_COUNT);
/* Helpers are marked by the fact that they are odd numbers
* force this to be an odd number (will shift it back to extract) */
return ((CORINFO_METHOD_HANDLE)(size_t)((helper << 2) + 1));
}
inline CorInfoHelpFunc Compiler::eeGetHelperNum(CORINFO_METHOD_HANDLE method)
{
// Helpers are marked by the fact that they are odd numbers
if (!(((size_t)method) & 1))
{
return (CORINFO_HELP_UNDEF);
}
return ((CorInfoHelpFunc)(((size_t)method) >> 2));
}
inline Compiler::fgWalkResult Compiler::CountSharedStaticHelper(GenTree** pTree, fgWalkData* data)
{
if (Compiler::IsSharedStaticHelper(*pTree))
{
int* pCount = (int*)data->pCallbackData;
(*pCount)++;
}
return WALK_CONTINUE;
}
// TODO-Cleanup: Replace calls to IsSharedStaticHelper with new HelperCallProperties
//
inline bool Compiler::IsSharedStaticHelper(GenTree* tree)
{
if (tree->gtOper != GT_CALL || tree->gtCall.gtCallType != CT_HELPER)
{
return false;
}
CorInfoHelpFunc helper = eeGetHelperNum(tree->gtCall.gtCallMethHnd);
bool result1 =
// More helpers being added to IsSharedStaticHelper (that have similar behaviors but are not true
// ShareStaticHelperts)
helper == CORINFO_HELP_STRCNS || helper == CORINFO_HELP_BOX ||
// helpers being added to IsSharedStaticHelper
helper == CORINFO_HELP_GETSTATICFIELDADDR_CONTEXT || helper == CORINFO_HELP_GETSTATICFIELDADDR_TLS ||
helper == CORINFO_HELP_GETGENERICS_GCSTATIC_BASE || helper == CORINFO_HELP_GETGENERICS_NONGCSTATIC_BASE ||
helper == CORINFO_HELP_GETGENERICS_GCTHREADSTATIC_BASE ||
helper == CORINFO_HELP_GETGENERICS_NONGCTHREADSTATIC_BASE ||
helper == CORINFO_HELP_GETSHARED_GCSTATIC_BASE || helper == CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE ||
helper == CORINFO_HELP_GETSHARED_GCSTATIC_BASE_NOCTOR ||
helper == CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_NOCTOR ||
helper == CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS ||
helper == CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_DYNAMICCLASS ||
helper == CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE ||
helper == CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE ||
helper == CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_NOCTOR ||
helper == CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_NOCTOR ||
helper == CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_DYNAMICCLASS ||
helper == CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_DYNAMICCLASS ||
#ifdef FEATURE_READYTORUN_COMPILER
helper == CORINFO_HELP_READYTORUN_STATIC_BASE || helper == CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE ||
#endif
helper == CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS;
#if 0
// See above TODO-Cleanup
bool result2 = s_helperCallProperties.IsPure(helper) && s_helperCallProperties.NonNullReturn(helper);
assert (result1 == result2);
#endif
return result1;
}
inline bool Compiler::IsTreeAlwaysHoistable(GenTree* tree)
{
if (IsSharedStaticHelper(tree))
{
return (GTF_CALL_HOISTABLE & tree->gtFlags) ? true : false;
}
else
{
return false;
}
}
inline bool Compiler::IsGcSafePoint(GenTree* tree)
{
if (tree->IsCall())
{
GenTreeCall* call = tree->AsCall();
if (!call->IsFastTailCall())
{
if (call->gtCallType == CT_INDIRECT)
{
return true;
}
else if (call->gtCallType == CT_USER_FUNC)
{
if ((call->gtCallMoreFlags & GTF_CALL_M_NOGCCHECK) == 0)
{
return true;
}
}
// otherwise we have a CT_HELPER
}
}
return false;
}
//
// Note that we want to have two special FIELD_HANDLES that will both
// be considered non-Data Offset handles
//
// The special values that we use are FLD_GLOBAL_DS and FLD_GLOBAL_FS
//
inline bool jitStaticFldIsGlobAddr(CORINFO_FIELD_HANDLE fldHnd)
{
return (fldHnd == FLD_GLOBAL_DS || fldHnd == FLD_GLOBAL_FS);
}
#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) || defined(FEATURE_TRACELOGGING)
inline bool Compiler::eeIsNativeMethod(CORINFO_METHOD_HANDLE method)
{
return ((((size_t)method) & 0x2) == 0x2);
}
inline CORINFO_METHOD_HANDLE Compiler::eeGetMethodHandleForNative(CORINFO_METHOD_HANDLE method)
{
assert((((size_t)method) & 0x3) == 0x2);
return (CORINFO_METHOD_HANDLE)(((size_t)method) & ~0x3);
}
#endif
inline CORINFO_METHOD_HANDLE Compiler::eeMarkNativeTarget(CORINFO_METHOD_HANDLE method)
{
assert((((size_t)method) & 0x3) == 0);
if (method == nullptr)
{
return method;
}
else
{
return (CORINFO_METHOD_HANDLE)(((size_t)method) | 0x2);
}
}
/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX Compiler XX
XX Inline functions XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
#ifndef DEBUG
inline bool Compiler::compStressCompile(compStressArea stressArea, unsigned weightPercentage)
{
return false;
}
#endif
inline ArenaAllocator* Compiler::compGetArenaAllocator()
{
return compArenaAllocator;
}
inline bool Compiler::compIsProfilerHookNeeded()
{
#ifdef PROFILING_SUPPORTED
return compProfilerHookNeeded
// IL stubs are excluded by VM and we need to do the same even running
// under a complus env hook to generate profiler hooks
|| (opts.compJitELTHookEnabled && !opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB));
#else // !PROFILING_SUPPORTED
return false;
#endif // !PROFILING_SUPPORTED
}
/*****************************************************************************
*
* Check for the special case where the object is the constant 0.
* As we can't even fold the tree (null+fldOffs), we are left with
* op1 and op2 both being a constant. This causes lots of problems.
* We simply grab a temp and assign 0 to it and use it in place of the NULL.
*/
inline GenTree* Compiler::impCheckForNullPointer(GenTree* obj)
{
/* If it is not a GC type, we will be able to fold it.
So don't need to do anything */
if (!varTypeIsGC(obj->TypeGet()))
{
return obj;
}
if (obj->gtOper == GT_CNS_INT)
{
assert(obj->gtType == TYP_REF || obj->gtType == TYP_BYREF);
// We can see non-zero byrefs for RVA statics.
if (obj->gtIntCon.gtIconVal != 0)
{
assert(obj->gtType == TYP_BYREF);
return obj;
}
unsigned tmp = lvaGrabTemp(true DEBUGARG("CheckForNullPointer"));
// We don't need to spill while appending as we are only assigning
// NULL to a freshly-grabbed temp.
impAssignTempGen(tmp, obj, (unsigned)CHECK_SPILL_NONE);
obj = gtNewLclvNode(tmp, obj->gtType);
}
return obj;
}
/*****************************************************************************
*
* Check for the special case where the object is the methods original 'this' pointer.
* Note that, the original 'this' pointer is always local var 0 for non-static method,
* even if we might have created the copy of 'this' pointer in lvaArg0Var.
*/
inline bool Compiler::impIsThis(GenTree* obj)
{
if (compIsForInlining())
{
return impInlineInfo->InlinerCompiler->impIsThis(obj);
}
else
{
return ((obj != nullptr) && (obj->gtOper == GT_LCL_VAR) && lvaIsOriginalThisArg(obj->gtLclVarCommon.gtLclNum));
}
}
/*****************************************************************************
*
* Check to see if the delegate is created using "LDFTN <TOK>" or not.
*/
inline bool Compiler::impIsLDFTN_TOKEN(const BYTE* delegateCreateStart, const BYTE* newobjCodeAddr)
{
assert(newobjCodeAddr[0] == CEE_NEWOBJ);
return (newobjCodeAddr - delegateCreateStart == 6 && // LDFTN <TOK> takes 6 bytes
delegateCreateStart[0] == CEE_PREFIX1 && delegateCreateStart[1] == (CEE_LDFTN & 0xFF));