1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-11 18:20:26 +09:00

Delete GT_INDEX (#69917)

* Do not set NO_CSE on ARR_ADDRs

It is effectively no-CSE already because of how "optIsCSECandidate" works.

* Delete GT_INDEX

Instead:
 1) For "ldelem", import "IND/OBJ(INDEX_ADDR)".
 2) For "ldelema", import "INDEX_ADDR".

This deletes two usages of "ADDR":
 1) "OBJ(ADDR(INDEX))" from "ldelem<struct>".
 2) "ADDR(INDEX)" from "ldelema".

* Add a zero-diff quirk

* Update the first class structs document

Remove references to things that no longer exist.
This commit is contained in:
SingleAccretion 2022-06-07 20:15:33 +03:00 committed by GitHub
parent 7439a889cb
commit 7bccc676c8
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 332 additions and 528 deletions

View file

@ -70,11 +70,6 @@ Current Representation of Struct Values
These struct-typed nodes are created by the importer, but transformed in morph, and so are not
encountered by most phases of the JIT:
* `GT_INDEX`: This is transformed to a `GT_IND`
* Currently, the IND is marked with `GTF_IND_ARR_INDEX` and the node pointer of the `GT_IND` acts as a key
into the array info map.
* Proposed: This should be transformed into a `GT_OBJ` when it represents a struct type, and then the
class handle would no longer need to be obtained from the array info map.
* `GT_FIELD`: This is transformed to a `GT_LCL_VAR` by the `Compiler::fgMarkAddressExposedLocals()` phase
if it's a promoted struct field, or to a `GT_LCL_FLD` or GT_IND` by `fgMorphField()`.
* Proposed: A non-promoted struct typed field should be transformed into a `GT_OBJ`, so that consistently all struct
@ -90,9 +85,7 @@ encountered by most phases of the JIT:
### Struct “objects” as lvalues
* The lhs of a struct assignment is a block or local node:
* `GT_OBJ` nodes represent the “shape” info via a struct handle, along with the GC info
(location and type of GC references within the struct).
* These are currently used only to represent struct values that contain GC references (although see below).
* `GT_OBJ` nodes represent struct types with a handle, and store a pointer to the `ClassLayout` object.
* `GT_BLK` nodes represent struct types with no GC references, or opaque blocks of fixed size.
* These have no struct handle, resulting in some pessimization or even incorrect
code when the appropriate struct handle can't be determined.
@ -101,12 +94,12 @@ encountered by most phases of the JIT:
[#21705](https://github.com/dotnet/coreclr/pull/21705) they are no longer large nodes.
* `GT_STORE_OBJ` and `GT_STORE_BLK` have the same structure as `GT_OBJ` and `GT_BLK`, respectively
* `Data()` is op2
* `GT_DYN_BLK` and `GT_STORE_DYN_BLK` (GenTreeDynBlk extends GenTreeBlk)
* `GT_STORE_DYN_BLK` (GenTreeStoreDynBlk extends GenTreeBlk)
* Additional child `gtDynamicSize`
* Note that these aren't really struct types; they represent dynamically sized blocks
* Note that these aren't really struct stores; they represent dynamically sized blocks
of arbitrary data.
* For `GT_LCL_FLD` nodes, we don't retain shape information, except indirectly via the `FieldSeqNode`.
* For `GT_LCL_VAR` nodes, the`ClassLayout` is obtained from the `LclVarDsc`.
* For `GT_LCL_FLD` nodes, we store a pointer to `ClassLayout` in the node.
* For `GT_LCL_VAR` nodes, the `ClassLayout` is obtained from the `LclVarDsc`.
### Struct “objects” as rvalues
@ -131,10 +124,6 @@ After morph, a struct-typed value on the RHS of assignment is one of:
* `GT_CALL`
* `GT_LCL_VAR`
* `GT_LCL_FLD`
* Note: With `compDoOldStructRetyping()`, a GT_LCL_FLD` with a primitive type of the same size as the struct
is used to represent a reference to the full struct when it is passed in a register.
This forces the struct to live on the stack, and makes it more difficult to optimize these struct values,
which is why this mechanism is being phased out.
* `GT_SIMD`
* `GT_OBJ` nodes can also be used as rvalues when they are call arguments
* Proposed: `GT_OBJ` nodes can be used in any context where a struct rvalue or lvalue might occur,
@ -173,8 +162,7 @@ There are three main phases in the JIT that make changes to the representation o
registers. The necessary transformations for correct code generation would be
made in `Lowering`.
* With `compDoOldStructRetyping()`, if it is passed in a single register, it is morphed into a
`GT_LCL_FLD` node of the appropriate primitive type.
* If it is passed in a single register, it is morphed into a `GT_LCL_FLD` node of the appropriate primitive type.
* This may involve making a copy, if the size cannot be safely loaded.
* Proposed: This would remain a `GT_OBJ` and would be appropriately transformed in `Lowering`,
e.g. using `GT_BITCAST`.
@ -310,22 +298,13 @@ This would be enabled first by [Defer ABI-specific transformations to Lowering](
for block copies. See [\#21711 Improve init/copy block codegen](https://github.com/dotnet/coreclr/pull/21711).
* This also includes cleanup of the block morphing methods such that block nodes needn't be visited multiple
times, such as `fgMorphBlkToInd` (may be simply unneeded), `fgMorphBlkNode` and `fgMorphBlkOperand`.
times, such as `fgMorphBlkNode` and `fgMorphBlkOperand`.
These methods were introduced to preserve old behavior, but should be simplified.
* Somewhat related is the handling of struct-typed array elements. Currently, after the `GT_INDEX` is transformed
into a `GT_IND`, that node must be retained as the key into the `ArrayInfoMap`. For structs, this is then
wrapped in `OBJ(ADDR(...))`. We should be able to change the IND to OBJ and avoid wrapping, and should also be
able to remove the class handle from the array info map and instead used the one provided by the `GT_OBJ`.
### Miscellaneous Cleanup
These are all marked with `TODO-1stClassStructs` or `TODO-Cleanup` in the last case:
* The handling of `DYN_BLK` is unnecessarily complicated to duplicate previous behavior (i.e. to enable previous
refactorings to be zero-diff). These nodes are infrequent so the special handling should just be eliminated
(e.g. see `GenTree::GetChild()`).
* The checking at the end of `gtNewTempAssign()` should be simplified.
* When we create a struct assignment, we use `impAssignStruct()`. This code will, in some cases, create

View file

@ -9373,13 +9373,6 @@ void cTreeFlags(Compiler* comp, GenTree* tree)
}
break;
case GT_INDEX:
if (tree->gtFlags & GTF_INX_STRING_LAYOUT)
{
chars += printf("[INX_STRING_LAYOUT]");
}
FALLTHROUGH;
case GT_INDEX_ADDR:
if (tree->gtFlags & GTF_INX_RNGCHK)
{

View file

@ -2587,7 +2587,19 @@ public:
GenTreeField* gtNewFieldRef(var_types type, CORINFO_FIELD_HANDLE fldHnd, GenTree* obj = nullptr, DWORD offset = 0);
GenTree* gtNewIndexRef(var_types typ, GenTree* arrayOp, GenTree* indexOp);
GenTreeIndexAddr* gtNewIndexAddr(GenTree* arrayOp,
GenTree* indexOp,
var_types elemType,
CORINFO_CLASS_HANDLE elemClassHandle,
unsigned firstElemOffset,
unsigned lengthOffset);
GenTreeIndexAddr* gtNewArrayIndexAddr(GenTree* arrayOp,
GenTree* indexOp,
var_types elemType,
CORINFO_CLASS_HANDLE elemClassHandle);
GenTreeIndir* gtNewIndexIndir(GenTreeIndexAddr* indexAddr);
GenTreeArrLen* gtNewArrLen(var_types typ, GenTree* arrayOp, int lenOffset, BasicBlock* block);
@ -2758,6 +2770,7 @@ public:
GenTree* gtFoldExpr(GenTree* tree);
GenTree* gtFoldExprConst(GenTree* tree);
GenTree* gtFoldIndirConst(GenTreeIndir* indir);
GenTree* gtFoldExprSpecial(GenTree* tree);
GenTree* gtFoldBoxNullable(GenTree* tree);
GenTree* gtFoldExprCompare(GenTree* tree);
@ -5627,7 +5640,7 @@ private:
Statement* fgPreviousCandidateSIMDFieldAsgStmt;
#endif // FEATURE_SIMD
GenTree* fgMorphArrayIndex(GenTree* tree);
GenTree* fgMorphIndexAddr(GenTreeIndexAddr* tree);
GenTree* fgMorphExpandCast(GenTreeCast* tree);
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

View file

@ -1145,16 +1145,48 @@ inline GenTreeField* Compiler::gtNewFieldRef(var_types type, CORINFO_FIELD_HANDL
return fieldNode;
}
/*****************************************************************************
*
* A little helper to create an array index node.
*/
inline GenTree* Compiler::gtNewIndexRef(var_types typ, GenTree* arrayOp, GenTree* indexOp)
inline GenTreeIndexAddr* Compiler::gtNewIndexAddr(GenTree* arrayOp,
GenTree* indexOp,
var_types elemType,
CORINFO_CLASS_HANDLE elemClassHandle,
unsigned firstElemOffset,
unsigned lengthOffset)
{
GenTreeIndex* gtIndx = new (this, GT_INDEX) GenTreeIndex(typ, arrayOp, indexOp, genTypeSize(typ));
unsigned elemSize =
(elemType == TYP_STRUCT) ? info.compCompHnd->getClassSize(elemClassHandle) : genTypeSize(elemType);
return gtIndx;
GenTreeIndexAddr* indexAddr = new (this, GT_INDEX_ADDR)
GenTreeIndexAddr(arrayOp, indexOp, elemType, elemClassHandle, elemSize, lengthOffset, firstElemOffset);
return indexAddr;
}
inline GenTreeIndexAddr* Compiler::gtNewArrayIndexAddr(GenTree* arrayOp,
GenTree* indexOp,
var_types elemType,
CORINFO_CLASS_HANDLE elemClassHandle)
{
unsigned lengthOffset = OFFSETOF__CORINFO_Array__length;
unsigned firstElemOffset = OFFSETOF__CORINFO_Array__data;
return gtNewIndexAddr(arrayOp, indexOp, elemType, elemClassHandle, firstElemOffset, lengthOffset);
}
inline GenTreeIndir* Compiler::gtNewIndexIndir(GenTreeIndexAddr* indexAddr)
{
GenTreeIndir* index;
if (varTypeIsStruct(indexAddr->gtElemType))
{
index = gtNewObjNode(indexAddr->gtStructElemClass, indexAddr);
}
else
{
index = gtNewIndir(indexAddr->gtElemType, indexAddr);
}
index->gtFlags |= GTF_GLOB_REF;
return index;
}
//------------------------------------------------------------------------------

View file

@ -4152,10 +4152,9 @@ void Compiler::fgSetBlockOrder(BasicBlock* block)
}
break;
case GT_INDEX:
case GT_INDEX_ADDR:
// These two call CORINFO_HELP_RNGCHKFAIL for Debug code
if (tree->gtFlags & GTF_INX_RNGCHK)
// This calls CORINFO_HELP_RNGCHKFAIL for Debug code.
if (tree->AsIndexAddr()->IsBoundsChecked())
{
return Compiler::WALK_ABORT;
}

View file

@ -256,7 +256,6 @@ void GenTree::InitNodeSize()
GenTree::s_gtNodeSizes[GT_CAST] = TREE_NODE_SZ_LARGE;
GenTree::s_gtNodeSizes[GT_FTN_ADDR] = TREE_NODE_SZ_LARGE;
GenTree::s_gtNodeSizes[GT_BOX] = TREE_NODE_SZ_LARGE;
GenTree::s_gtNodeSizes[GT_INDEX] = TREE_NODE_SZ_LARGE;
GenTree::s_gtNodeSizes[GT_INDEX_ADDR] = TREE_NODE_SZ_LARGE;
GenTree::s_gtNodeSizes[GT_BOUNDS_CHECK] = TREE_NODE_SZ_SMALL;
GenTree::s_gtNodeSizes[GT_ARR_ELEM] = TREE_NODE_SZ_LARGE;
@ -316,7 +315,6 @@ void GenTree::InitNodeSize()
static_assert_no_msg(sizeof(GenTreeFptrVal) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeQmark) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeIntrinsic) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeIndex) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeIndexAddr) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeArrLen) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeBoundsChk) <= TREE_NODE_SZ_SMALL);
@ -2495,12 +2493,6 @@ AGAIN:
return false;
}
break;
case GT_INDEX:
if (op1->AsIndex()->gtIndElemSize != op2->AsIndex()->gtIndElemSize)
{
return false;
}
break;
case GT_INDEX_ADDR:
if (op1->AsIndexAddr()->gtElemSize != op2->AsIndexAddr()->gtElemSize)
{
@ -2917,9 +2909,6 @@ AGAIN:
case GT_CAST:
hash ^= tree->AsCast()->gtCastType;
break;
case GT_INDEX:
hash += tree->AsIndex()->gtIndElemSize;
break;
case GT_INDEX_ADDR:
hash += tree->AsIndexAddr()->gtElemSize;
break;
@ -2989,7 +2978,6 @@ AGAIN:
// For the ones below no extra argument matters for comparison.
case GT_ARR_INDEX:
case GT_QMARK:
case GT_INDEX:
case GT_INDEX_ADDR:
break;
@ -6397,7 +6385,6 @@ bool GenTree::OperMayThrow(Compiler* comp)
case GT_ARR_OFFSET:
case GT_LCLHEAP:
case GT_CKFINITE:
case GT_INDEX:
case GT_INDEX_ADDR:
return true;
@ -8287,15 +8274,6 @@ GenTree* Compiler::gtCloneExpr(
tree->AsCast()->gtCastType DEBUGARG(/*largeNode*/ TRUE));
break;
case GT_INDEX:
{
GenTreeIndex* asInd = tree->AsIndex();
copy = new (this, GT_INDEX)
GenTreeIndex(asInd->TypeGet(), asInd->Arr(), asInd->Index(), asInd->gtIndElemSize);
copy->AsIndex()->gtStructElemClass = asInd->gtStructElemClass;
}
break;
case GT_INDEX_ADDR:
{
GenTreeIndexAddr* asIndAddr = tree->AsIndexAddr();
@ -10171,8 +10149,6 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, _In_ _In_opt_
}
FALLTHROUGH;
case GT_INDEX:
case GT_INDEX_ADDR:
case GT_FIELD:
if (tree->gtFlags & GTF_IND_VOLATILE)
{
@ -10468,19 +10444,6 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, _In_ _In_opt_
{
layout = tree->AsLclFld()->GetLayout();
}
else if (tree->OperIs(GT_INDEX))
{
GenTreeIndex* asInd = tree->AsIndex();
CORINFO_CLASS_HANDLE clsHnd = asInd->gtStructElemClass;
if (clsHnd != nullptr)
{
// We could create a layout with `typGetObjLayout(asInd->gtStructElemClass)` but we
// don't want to affect the layout table.
const unsigned classSize = info.compCompHnd->getClassSize(clsHnd);
const char16_t* shortClassName = eeGetShortClassName(clsHnd);
printf("<%S, %u>", shortClassName, classSize);
}
}
if (layout != nullptr)
{
@ -10488,15 +10451,21 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, _In_ _In_opt_
}
}
if (tree->OperIs(GT_ARR_ADDR))
if (tree->OperIs(GT_INDEX_ADDR, GT_ARR_ADDR))
{
if (tree->AsArrAddr()->GetElemClassHandle() != NO_CLASS_HANDLE)
var_types elemType =
tree->OperIs(GT_INDEX_ADDR) ? tree->AsIndexAddr()->gtElemType : tree->AsArrAddr()->GetElemType();
CORINFO_CLASS_HANDLE elemClsHnd = tree->OperIs(GT_INDEX_ADDR) ? tree->AsIndexAddr()->gtStructElemClass
: tree->AsArrAddr()->GetElemClassHandle();
if (varTypeIsStruct(elemType) && (elemClsHnd != NO_CLASS_HANDLE))
{
printf("%S[]", eeGetShortClassName(tree->AsArrAddr()->GetElemClassHandle()));
printf("%S[]", eeGetShortClassName(elemClsHnd));
}
else
{
printf("%s[]", varTypeName(tree->AsArrAddr()->GetElemType()));
printf("%s[]", varTypeName(elemType));
}
}
@ -15108,6 +15077,48 @@ INTEGRAL_OVF:
#pragma warning(pop)
#endif
//------------------------------------------------------------------------
// gtFoldIndirConst: Attempt to fold an "IND(addr)" expression to a constant.
//
// Currently handles the case of "addr" being "INDEX_ADDR(CNS_STR, CONST)".
//
// Arguments:
// indir - The IND node to attempt to fold
//
// Return Value:
// The new constant node if the folding was successful, "nullptr" otherwise.
//
GenTree* Compiler::gtFoldIndirConst(GenTreeIndir* indir)
{
assert(opts.OptimizationEnabled() && !optValnumCSE_phase);
assert(indir->OperIs(GT_IND));
GenTree* addr = indir->Addr();
if (indir->TypeIs(TYP_USHORT) && addr->OperIs(GT_INDEX_ADDR) && addr->AsIndexAddr()->Arr()->OperIs(GT_CNS_STR))
{
GenTreeStrCon* stringNode = addr->AsIndexAddr()->Arr()->AsStrCon();
GenTree* indexNode = addr->AsIndexAddr()->Index();
if (!stringNode->IsStringEmptyField() && indexNode->IsCnsIntOrI())
{
int cnsIndex = static_cast<int>(indexNode->AsIntConCommon()->IconValue());
if (cnsIndex >= 0)
{
const int maxStrSize = 1024;
char16_t str[maxStrSize];
int length =
info.compCompHnd->getStringLiteral(stringNode->gtScpHnd, stringNode->gtSconCPX, str, maxStrSize);
if (cnsIndex < length)
{
return gtNewIconNode(str[cnsIndex]);
}
}
}
}
return nullptr;
}
//------------------------------------------------------------------------
// gtNewTempAssign: Create an assignment of the given value to a temp.
//
@ -15711,6 +15722,15 @@ void Compiler::gtExtractSideEffList(GenTree* expr,
PushSideEffects(node);
return Compiler::WALK_SKIP_SUBTREES;
}
// TODO-ADDR: remove this quirk added to avoid diffs.
if (node->OperIs(GT_IND) && node->AsIndir()->Addr()->OperIs(GT_INDEX_ADDR) &&
!m_compiler->fgGlobalMorph)
{
JITDUMP("Keep the GT_INDEX_ADDR and GT_IND together:\n");
PushSideEffects(node);
return Compiler::WALK_SKIP_SUBTREES;
}
}
// Generally all GT_CALL nodes are considered to have side-effects.
@ -17173,9 +17193,6 @@ CORINFO_CLASS_HANDLE Compiler::gtGetStructHandleIfPresent(GenTree* tree)
case GT_RET_EXPR:
structHnd = tree->AsRetExpr()->gtRetClsHnd;
break;
case GT_INDEX:
structHnd = tree->AsIndex()->gtStructElemClass;
break;
case GT_FIELD:
info.compCompHnd->getFieldType(tree->AsField()->gtFldHnd, &structHnd);
break;
@ -17515,13 +17532,19 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
objClass = lvaTable[objLcl].lvClassHnd;
*pIsExact = lvaTable[objLcl].lvClassIsExact;
}
else if (base->OperGet() == GT_ARR_ELEM)
else if (base->OperIs(GT_INDEX_ADDR, GT_ARR_ELEM))
{
// indir(arr_elem(...)) -> array element type
GenTree* array = base->AsArrElem()->gtArrObj;
if (base->OperIs(GT_INDEX_ADDR))
{
objClass = gtGetArrayElementClassHandle(base->AsIndexAddr()->Arr());
}
else
{
objClass = gtGetArrayElementClassHandle(base->AsArrElem()->gtArrObj);
}
objClass = gtGetArrayElementClassHandle(array);
*pIsExact = false;
*pIsNonNull = false;
}
@ -17577,16 +17600,6 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
break;
}
case GT_INDEX:
{
GenTree* array = obj->AsIndex()->Arr();
objClass = gtGetArrayElementClassHandle(array);
*pIsExact = false;
*pIsNonNull = false;
break;
}
default:
{
break;

View file

@ -505,9 +505,8 @@ enum GenTreeFlags : unsigned int
GTF_FLD_INITCLASS = 0x20000000, // GT_FIELD -- field access requires preceding class/static init helper
GTF_FLD_TGT_HEAP = 0x10000000, // GT_FIELD -- same as GTF_IND_TGT_HEAP
GTF_INX_RNGCHK = 0x80000000, // GT_INDEX/GT_INDEX_ADDR -- the array reference should be range-checked.
GTF_INX_STRING_LAYOUT = 0x40000000, // GT_INDEX -- this uses the special string array layout
GTF_INX_NOFAULT = 0x20000000, // GT_INDEX -- the INDEX does not throw an exception (morph to GTF_IND_NONFAULTING)
GTF_INX_RNGCHK = 0x80000000, // GT_INDEX_ADDR -- this array address should be range-checked
GTF_INX_ADDR_NONNULL = 0x40000000, // GT_INDEX_ADDR -- this array address is not null
GTF_IND_TGT_NOT_HEAP = 0x80000000, // GT_IND -- the target is not on the heap
GTF_IND_VOLATILE = 0x40000000, // GT_IND -- the load or store must use volatile sematics (this is a nop on X86)
@ -2078,6 +2077,17 @@ public:
gtFlags |= sourceFlags;
}
void AddAllEffectsFlags(GenTree* firstSource, GenTree* secondSource)
{
AddAllEffectsFlags((firstSource->gtFlags | secondSource->gtFlags) & GTF_ALL_EFFECT);
}
void AddAllEffectsFlags(GenTreeFlags sourceFlags)
{
assert((sourceFlags & ~GTF_ALL_EFFECT) == 0);
gtFlags |= sourceFlags;
}
inline bool IsCnsIntOrI() const;
inline bool IsIntegralConst() const;
@ -6231,50 +6241,9 @@ private:
};
#endif // FEATURE_HW_INTRINSICS
/* gtIndex -- array access */
struct GenTreeIndex : public GenTreeOp
{
GenTree*& Arr()
{
return gtOp1;
}
GenTree*& Index()
{
return gtOp2;
}
unsigned gtIndElemSize; // size of elements in the array
CORINFO_CLASS_HANDLE gtStructElemClass; // If the element type is a struct, this is the struct type.
GenTreeIndex(var_types type, GenTree* arr, GenTree* ind, unsigned indElemSize)
: GenTreeOp(GT_INDEX, type, arr, ind)
, gtIndElemSize(indElemSize)
, gtStructElemClass(nullptr) // We always initialize this after construction.
{
#ifdef DEBUG
if (JitConfig.JitSkipArrayBoundCheck() == 1)
{
// Skip bounds check
}
else
#endif
{
// Do bounds check
gtFlags |= GTF_INX_RNGCHK;
}
gtFlags |= GTF_EXCEPT | GTF_GLOB_REF;
}
#if DEBUGGABLE_GENTREE
GenTreeIndex() : GenTreeOp()
{
}
#endif
};
// gtIndexAddr: given an array object and an index, checks that the index is within the bounds of the array if
// necessary and produces the address of the value at that index of the array.
// GenTreeIndexAddr: Given an array object and an index, checks that the index is within the bounds of the array if
// necessary and produces the address of the value at that index of the array.
//
struct GenTreeIndexAddr : public GenTreeOp
{
GenTree*& Arr()
@ -6310,6 +6279,7 @@ struct GenTreeIndexAddr : public GenTreeOp
, gtLenOffset(lenOffset)
, gtElemOffset(elemOffset)
{
assert(!varTypeIsStruct(elemType) || (structElemClass != NO_CLASS_HANDLE));
#ifdef DEBUG
if (JitConfig.JitSkipArrayBoundCheck() == 1)
{
@ -6338,7 +6308,7 @@ struct GenTreeIndexAddr : public GenTreeOp
bool IsNotNull() const
{
return IsBoundsChecked();
return IsBoundsChecked() || ((gtFlags & GTF_INX_ADDR_NONNULL) != 0);
}
};
@ -6363,10 +6333,6 @@ public:
assert(addr->TypeIs(TYP_BYREF));
assert(((elemType == TYP_STRUCT) && (elemClassHandle != NO_CLASS_HANDLE)) ||
(elemClassHandle == NO_CLASS_HANDLE));
// We will only consider "addr" for CSE. This is more profitable and precise
// because ARR_ADDR can get its VN "polluted" by zero-offset field sequences.
SetDoNotCSE();
}
#if DEBUGGABLE_GENTREE
@ -6449,8 +6415,8 @@ struct GenTreeBoundsChk : public GenTreeOp
SpecialCodeKind gtThrowKind; // Kind of throw block to branch to on failure
// Store some information about the array element type that was in the GT_INDEX node before morphing.
// Note that this information is also stored in the m_arrayInfoMap of the morphed IND node (that
// is marked with GTF_IND_ARR_INDEX), but that can be hard to find.
// Note that this information is also stored in the ARR_ADDR node of the morphed tree, but that can
// be hard to find.
var_types gtInxType; // Save the GT_INDEX type
GenTreeBoundsChk(GenTree* index, GenTree* length, SpecialCodeKind kind)
@ -8487,7 +8453,6 @@ inline GenTree* GenTree::gtGetOp1() const
case GT_RSZ:
case GT_ROL:
case GT_ROR:
case GT_INDEX:
case GT_ASG:
case GT_EQ:
case GT_NE:
@ -8498,6 +8463,7 @@ inline GenTree* GenTree::gtGetOp1() const
case GT_COMMA:
case GT_QMARK:
case GT_COLON:
case GT_INDEX_ADDR:
case GT_MKREFANY:
return true;
default:

View file

@ -148,9 +148,7 @@ GTNODE(COMMA , GenTreeOp ,0,GTK_BINOP|DBK_NOTLIR)
GTNODE(QMARK , GenTreeQmark ,0,GTK_BINOP|GTK_EXOP|DBK_NOTLIR)
GTNODE(COLON , GenTreeColon ,0,GTK_BINOP|DBK_NOTLIR)
GTNODE(INDEX , GenTreeIndex ,0,GTK_BINOP|GTK_EXOP|DBK_NOTLIR) // SZ-array-element.
GTNODE(INDEX_ADDR , GenTreeIndexAddr ,0,GTK_BINOP|GTK_EXOP) // Addr of SZ-array-element; used when aiming to minimize compile times.
GTNODE(INDEX_ADDR , GenTreeIndexAddr ,0,GTK_BINOP|GTK_EXOP) // Address of SZ-array-element.
GTNODE(MKREFANY , GenTreeOp ,0,GTK_BINOP|DBK_NOTLIR)
GTNODE(LEA , GenTreeAddrMode ,0,GTK_BINOP|GTK_EXOP|DBK_NOTHIR)

View file

@ -72,7 +72,6 @@ GTSTRUCT_1(FieldList , GT_FIELD_LIST)
GTSTRUCT_1(Colon , GT_COLON)
GTSTRUCT_1(FptrVal , GT_FTN_ADDR)
GTSTRUCT_1(Intrinsic , GT_INTRINSIC)
GTSTRUCT_1(Index , GT_INDEX)
GTSTRUCT_1(IndexAddr , GT_INDEX_ADDR)
#if defined(FEATURE_HW_INTRINSICS) && defined(FEATURE_SIMD)
GTSTRUCT_N(MultiOp , GT_SIMD, GT_HWINTRINSIC)

View file

@ -1137,7 +1137,7 @@ GenTree* Compiler::impAssignStruct(GenTree* dest,
}
assert(dest->gtOper == GT_LCL_VAR || dest->gtOper == GT_RETURN || dest->gtOper == GT_FIELD ||
dest->gtOper == GT_IND || dest->gtOper == GT_OBJ || dest->gtOper == GT_INDEX);
dest->gtOper == GT_IND || dest->gtOper == GT_OBJ);
// Return a NOP if this is a self-assignment.
if (dest->OperGet() == GT_LCL_VAR && src->OperGet() == GT_LCL_VAR &&
@ -1409,11 +1409,6 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr,
assert(src->AsObj()->GetLayout()->GetClassHandle() == structHnd);
}
}
else if (src->gtOper == GT_INDEX)
{
asgType = impNormStructType(structHnd);
assert(src->AsIndex()->gtStructElemClass == structHnd);
}
else if (src->gtOper == GT_MKREFANY)
{
// Since we are assigning the result of a GT_MKREFANY,
@ -1493,9 +1488,9 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr,
if ((dest == nullptr) && (destAddr->OperGet() == GT_ADDR))
{
GenTree* destNode = destAddr->gtGetOp1();
// If the actual destination is a local, a GT_INDEX or a block node, or is a node that
// will be morphed, don't insert an OBJ(ADDR) if it already has the right type.
if (destNode->OperIs(GT_LCL_VAR, GT_INDEX) || destNode->OperIsBlk())
// If the actual destination is a local, or a block node,
// don't insert an OBJ(ADDR) if it already has the right type.
if (destNode->OperIs(GT_LCL_VAR) || destNode->OperIsBlk())
{
var_types destType = destNode->TypeGet();
// If one or both types are TYP_STRUCT (one may not yet be normalized), they are compatible
@ -1706,7 +1701,7 @@ var_types Compiler::impNormStructType(CORINFO_CLASS_HANDLE structHnd, CorInfoTyp
// it is either:
// - a known struct type (non-TYP_STRUCT, e.g. TYP_SIMD8)
// - an OBJ or a MKREFANY node, or
// - a node (e.g. GT_INDEX) that will be morphed.
// - a node (e.g. GT_FIELD) that will be morphed.
// If the node is a CALL or RET_EXPR, a copy will be made to a new temp.
//
GenTree* Compiler::impNormStructVal(GenTree* structVal,
@ -1745,13 +1740,6 @@ GenTree* Compiler::impNormStructVal(GenTree* structVal,
makeTemp = true;
break;
case GT_INDEX:
// This will be transformed to an OBJ later.
alreadyNormalized = true;
structVal->AsIndex()->gtStructElemClass = structHnd;
structVal->AsIndex()->gtIndElemSize = info.compCompHnd->getClassSize(structHnd);
break;
case GT_FIELD:
// Wrap it in a GT_OBJ, if needed.
structVal->gtType = structType;
@ -3910,10 +3898,11 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
case NI_System_String_get_Chars:
{
GenTree* op2 = impPopStack().val;
GenTree* op1 = impPopStack().val;
retNode = gtNewIndexRef(TYP_USHORT, op1, op2);
retNode->gtFlags |= GTF_INX_STRING_LAYOUT;
GenTree* op2 = impPopStack().val;
GenTree* op1 = impPopStack().val;
GenTree* addr = gtNewIndexAddr(op1, op2, TYP_USHORT, NO_CLASS_HANDLE, OFFSETOF__CORINFO_String__chars,
OFFSETOF__CORINFO_String__stringLen);
retNode = gtNewIndexIndir(addr->AsIndexAddr());
break;
}
@ -12867,7 +12856,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
}
CORINFO_CLASS_HANDLE clsHnd = DUMMY_INIT(NULL);
CORINFO_CLASS_HANDLE ldelemClsHnd = DUMMY_INIT(NULL);
CORINFO_CLASS_HANDLE ldelemClsHnd = NO_CLASS_HANDLE;
CORINFO_CLASS_HANDLE stelemClsHnd = DUMMY_INIT(NULL);
var_types lclTyp, ovflType = TYP_UNKNOWN;
@ -12936,7 +12925,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
CORINFO_SIG_INFO sig;
IL_OFFSET jmpAddr;
bool ovfl, unordered, callNode;
bool ldstruct;
CORINFO_CLASS_HANDLE tokenType;
union {
@ -13634,13 +13622,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
case CEE_LDELEM_I:
lclTyp = TYP_I_IMPL;
goto ARR_LD;
// Should be UINT, but since no platform widens 4->8 bytes it doesn't matter
// and treating it as TYP_INT avoids other asserts.
case CEE_LDELEM_U4:
lclTyp = TYP_INT;
goto ARR_LD;
case CEE_LDELEM_I4:
lclTyp = TYP_INT;
goto ARR_LD;
@ -13666,84 +13650,32 @@ void Compiler::impImportBlockCode(BasicBlock* block)
ARR_LD:
ARR_LD_POST_VERIFY:
/* Pull the index value and array address */
op2 = impPopStack().val;
op1 = impPopStack().val;
assertImp(op1->gtType == TYP_REF);
op2 = impPopStack().val; // index
op1 = impPopStack().val; // array
assertImp(op1->TypeIs(TYP_REF));
/* Check for null pointer - in the inliner case we simply abort */
if (compIsForInlining())
// Check for null pointer - in the inliner case we simply abort.
if (compIsForInlining() && op1->IsCnsIntOrI())
{
if (op1->gtOper == GT_CNS_INT)
{
compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NULL_FOR_LDELEM);
return;
}
compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NULL_FOR_LDELEM);
return;
}
/* Mark the block as containing an index expression */
// Mark the block as containing an index expression.
if (op1->gtOper == GT_LCL_VAR)
if (op1->OperIs(GT_LCL_VAR) && op2->OperIs(GT_LCL_VAR, GT_CNS_INT, GT_ADD))
{
if (op2->gtOper == GT_LCL_VAR || op2->gtOper == GT_CNS_INT || op2->gtOper == GT_ADD)
{
block->bbFlags |= BBF_HAS_IDX_LEN;
optMethodFlags |= OMF_HAS_ARRAYREF;
}
block->bbFlags |= BBF_HAS_IDX_LEN;
optMethodFlags |= OMF_HAS_ARRAYREF;
}
/* Create the index node and push it on the stack */
op1 = gtNewArrayIndexAddr(op1, op2, lclTyp, ldelemClsHnd);
op1 = gtNewIndexRef(lclTyp, op1, op2);
ldstruct = (opcode == CEE_LDELEM && lclTyp == TYP_STRUCT);
if ((opcode == CEE_LDELEMA) || ldstruct ||
(ldelemClsHnd != DUMMY_INIT(NULL) && eeIsValueClass(ldelemClsHnd)))
if (opcode != CEE_LDELEMA)
{
assert(ldelemClsHnd != DUMMY_INIT(NULL));
// remember the element size
if (lclTyp == TYP_REF)
{
op1->AsIndex()->gtIndElemSize = TARGET_POINTER_SIZE;
}
else
{
// If ldElemClass is precisely a primitive type, use that, otherwise, preserve the struct type.
if (info.compCompHnd->getTypeForPrimitiveValueClass(ldelemClsHnd) == CORINFO_TYPE_UNDEF)
{
op1->AsIndex()->gtStructElemClass = ldelemClsHnd;
}
assert(lclTyp != TYP_STRUCT || op1->AsIndex()->gtStructElemClass != nullptr);
if (lclTyp == TYP_STRUCT)
{
size = info.compCompHnd->getClassSize(ldelemClsHnd);
op1->AsIndex()->gtIndElemSize = size;
op1->gtType = lclTyp;
}
}
if ((opcode == CEE_LDELEMA) || ldstruct)
{
// wrap it in a &
lclTyp = TYP_BYREF;
op1 = gtNewOperNode(GT_ADDR, lclTyp, op1);
}
else
{
assert(lclTyp != TYP_STRUCT);
}
op1 = gtNewIndexIndir(op1->AsIndexAddr());
}
if (ldstruct)
{
// Create an OBJ for the result
op1 = gtNewObjNode(ldelemClsHnd, op1);
op1->gtFlags |= GTF_EXCEPT;
}
impPushOnStack(op1, tiRetVal);
break;
@ -13768,7 +13700,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
{
CorInfoType jitTyp = info.compCompHnd->asCorInfoType(stelemClsHnd);
lclTyp = JITtype2varType(jitTyp);
goto ARR_ST_POST_VERIFY;
goto ARR_ST;
}
case CEE_STELEM_REF:
@ -13785,7 +13717,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
if (impCanSkipCovariantStoreCheck(value, array))
{
lclTyp = TYP_REF;
goto ARR_ST_POST_VERIFY;
goto ARR_ST;
}
}
@ -13819,7 +13751,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
goto ARR_ST;
ARR_ST:
ARR_ST_POST_VERIFY:
// TODO-Review: this comment is no longer correct.
/* The strict order of evaluation is LHS-operands, RHS-operands,
range-check, and then assignment. However, codegen currently
does the range-check before evaluation the RHS-operands. So to
@ -13831,45 +13763,29 @@ void Compiler::impImportBlockCode(BasicBlock* block)
"Strict ordering of exceptions for Array store"));
}
/* Pull the new value from the stack */
// Pull the new value from the stack.
op2 = impPopStack().val;
impBashVarAddrsToI(op2);
/* Pull the index value */
// Pull the index value.
op1 = impPopStack().val;
/* Pull the array address */
// Pull the array address.
op3 = impPopStack().val;
assertImp(op3->gtType == TYP_REF);
if (op2->IsLocalAddrExpr() != nullptr)
{
op2->gtType = TYP_I_IMPL;
}
assertImp(op3->TypeIs(TYP_REF));
// Mark the block as containing an index expression
if (op3->gtOper == GT_LCL_VAR)
if (op3->OperIs(GT_LCL_VAR) && op1->OperIs(GT_LCL_VAR, GT_CNS_INT, GT_ADD))
{
if (op1->gtOper == GT_LCL_VAR || op1->gtOper == GT_CNS_INT || op1->gtOper == GT_ADD)
{
block->bbFlags |= BBF_HAS_IDX_LEN;
optMethodFlags |= OMF_HAS_ARRAYREF;
}
block->bbFlags |= BBF_HAS_IDX_LEN;
optMethodFlags |= OMF_HAS_ARRAYREF;
}
/* Create the index node */
// Create the index node.
op1 = gtNewArrayIndexAddr(op3, op1, lclTyp, stelemClsHnd);
op1 = gtNewIndexIndir(op1->AsIndexAddr());
op1 = gtNewIndexRef(lclTyp, op3, op1);
/* Create the assignment node and append it */
if (lclTyp == TYP_STRUCT)
{
assert(stelemClsHnd != DUMMY_INIT(NULL));
op1->AsIndex()->gtStructElemClass = stelemClsHnd;
op1->AsIndex()->gtIndElemSize = info.compCompHnd->getClassSize(stelemClsHnd);
}
// Create the assignment node and append it.
if (varTypeIsStruct(op1))
{
op1 = impAssignStruct(op1, op2, stelemClsHnd, (unsigned)CHECK_SPILL_ALL);
@ -13879,11 +13795,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
op2 = impImplicitR4orR8Cast(op2, op1->TypeGet());
op1 = gtNewAssignNode(op1, op2);
}
/* Mark the expression as containing an assignment */
op1->gtFlags |= GTF_ASG;
goto SPILL_APPEND;
case CEE_ADD:
@ -22640,14 +22551,14 @@ bool Compiler::impCanSkipCovariantStoreCheck(GenTree* value, GenTree* array)
assert(opts.OptimizationEnabled());
// Check for assignment to same array, ie. arrLcl[i] = arrLcl[j]
if (value->OperIs(GT_INDEX) && array->OperIs(GT_LCL_VAR))
if (value->OperIs(GT_IND) && value->AsIndir()->Addr()->OperIs(GT_INDEX_ADDR) && array->OperIs(GT_LCL_VAR))
{
GenTree* valueIndex = value->AsIndex()->Arr();
if (valueIndex->OperIs(GT_LCL_VAR))
GenTree* valueArray = value->AsIndir()->Addr()->AsIndexAddr()->Arr();
if (valueArray->OperIs(GT_LCL_VAR))
{
unsigned valueLcl = valueIndex->AsLclVar()->GetLclNum();
unsigned arrayLcl = array->AsLclVar()->GetLclNum();
if ((valueLcl == arrayLcl) && !lvaGetDesc(arrayLcl)->IsAddressExposed())
unsigned valueArrayLcl = valueArray->AsLclVar()->GetLclNum();
unsigned arrayLcl = array->AsLclVar()->GetLclNum();
if ((valueArrayLcl == arrayLcl) && !lvaGetDesc(arrayLcl)->IsAddressExposed())
{
JITDUMP("\nstelem of ref from same array: skipping covariant store check\n");
return true;

View file

@ -796,17 +796,12 @@ private:
}
// The LHS may be a LCL_VAR/LCL_FLD, these are not indirections so we need to handle them here.
// It can also be a GT_INDEX, this is an indirection but it never applies to lclvar addresses
// so it needs to be handled here as well.
switch (indir->OperGet())
{
case GT_LCL_VAR:
return m_compiler->lvaGetDesc(indir->AsLclVar())->lvExactSize;
case GT_LCL_FLD:
return genTypeSize(indir->TypeGet());
case GT_INDEX:
return indir->AsIndex()->gtIndElemSize;
default:
break;
}

View file

@ -260,7 +260,6 @@ void Compiler::fgPerNodeLocalVarLiveness(GenTree* tree)
// These should have been morphed away to become GT_INDs:
case GT_FIELD:
case GT_INDEX:
unreached();
break;

View file

@ -58,8 +58,7 @@ void ArrIndex::PrintBoundsCheckNodes(unsigned dim /* = -1 */)
// the "type" member
//
// Notes:
// This tree produces GT_INDEX node, the caller is supposed to morph it appropriately
// so it can be codegen'ed.
// This tree produces a GT_IND(GT_INDEX_ADDR) node, the caller is supposed to morph it.
//
GenTree* LC_Array::ToGenTree(Compiler* comp, BasicBlock* bb)
{
@ -71,13 +70,15 @@ GenTree* LC_Array::ToGenTree(Compiler* comp, BasicBlock* bb)
int rank = GetDimRank();
for (int i = 0; i < rank; ++i)
{
arr = comp->gtNewIndexRef(TYP_REF, arr, comp->gtNewLclvNode(arrIndex->indLcls[i],
comp->lvaTable[arrIndex->indLcls[i]].lvType));
GenTree* idx = comp->gtNewLclvNode(arrIndex->indLcls[i], comp->lvaTable[arrIndex->indLcls[i]].lvType);
GenTree* arrAddr = comp->gtNewArrayIndexAddr(arr, idx, TYP_REF, NO_CLASS_HANDLE);
// Clear the range check flag and mark the index as non-faulting: we guarantee that all necessary range
// checking has already been done by the time this array index expression is invoked.
arr->gtFlags &= ~(GTF_INX_RNGCHK | GTF_EXCEPT);
arr->gtFlags |= GTF_INX_NOFAULT;
arrAddr->gtFlags &= ~GTF_INX_RNGCHK;
arrAddr->gtFlags |= GTF_INX_ADDR_NONNULL;
arr = comp->gtNewIndexIndir(arrAddr->AsIndexAddr());
}
// If asked for arrlen invoke arr length operator.
if (oper == ArrLen)
@ -2186,7 +2187,7 @@ bool Compiler::optIsStackLocalInvariant(unsigned loopNum, unsigned lclNum)
//
// Arguments:
// tree the tree to be checked if it is the array [] operation.
// result the extracted GT_INDEX information is updated in result.
// result the extracted GT_INDEX_ADDR information is updated in result.
// lhsNum for the root level (function is recursive) callers should pass BAD_VAR_NUM.
// topLevelIsFinal OUT: set to `true` if see a non-TYP_REF element type array.
//
@ -2196,8 +2197,8 @@ bool Compiler::optIsStackLocalInvariant(unsigned loopNum, unsigned lclNum)
// dimension of [] encountered.
//
// Operation:
// Given a "tree" extract the GT_INDEX node in "result" as ArrIndex. In morph
// we have converted a GT_INDEX tree into a scaled index base offset expression.
// Given a "tree" extract the GT_INDEX_ADDR node in "result" as ArrIndex. In morph
// we have converted a GT_INDEX_ADDR tree into a scaled index base offset expression.
// However, we don't actually bother to parse the morphed tree. All we care about is
// the bounds check node: it contains the array base and element index. The other side
// of the COMMA node can vary between array of primitive type and array of struct. There's
@ -2306,7 +2307,7 @@ bool Compiler::optExtractArrIndex(GenTree* tree, ArrIndex* result, unsigned lhsN
//
// Arguments:
// tree the tree to be checked if it is an array [][][] operation.
// result OUT: the extracted GT_INDEX information.
// result OUT: the extracted GT_INDEX_ADDR information.
// lhsNum var number of array object we're looking for.
// topLevelIsFinal OUT: set to `true` if we reached a non-TYP_REF element type array.
//
@ -2333,7 +2334,7 @@ bool Compiler::optReconstructArrIndexHelp(GenTree* tree, ArrIndex* result, unsig
GenTree* lhs = before->gtGetOp1();
GenTree* rhs = before->gtGetOp2();
// "rhs" should contain an GT_INDEX
// "rhs" should contain an index expression.
if (!lhs->IsLocal() || !optReconstructArrIndexHelp(rhs, result, lhsNum, topLevelIsFinal))
{
return false;

View file

@ -70,7 +70,7 @@ exception occurs.
block, stmt, tree information) to do the optimization later.
a) This involves checking if the loop is well-formed with respect to
the optimization being performed.
b) In array bounds check case, reconstructing the morphed GT_INDEX
b) In array bounds check case, reconstructing the morphed GT_INDEX_ADDR
nodes back to their array representation.
i) The array index is stored in the "context" variable with
additional block, tree, stmt info.
@ -195,7 +195,7 @@ class Compiler;
*
* Represents an array access and associated bounds checks.
* Array access is required to have the array and indices in local variables.
* This struct is constructed using a GT_INDEX node that is broken into
* This struct is constructed using a GT_INDEX_ADDR node that is broken into
* its sub trees.
*
*/

View file

@ -4539,102 +4539,43 @@ BasicBlock* Compiler::fgSetRngChkTargetInner(SpecialCodeKind kind, bool delay)
return nullptr;
}
/*****************************************************************************
*
* Expand a GT_INDEX node and fully morph the child operands
*
* The orginal GT_INDEX node is bashed into the GT_IND node that accesses
* the array element. We expand the GT_INDEX node into a larger tree that
* evaluates the array base and index. The simplest expansion is a GT_COMMA
* with a GT_BOUNDS_CHECK and a GT_IND with a GTF_INX_RNGCHK flag.
* For complex array or index expressions one or more GT_COMMA assignments
* are inserted so that we only evaluate the array or index expressions once.
*
* The fully expanded tree is then morphed. This causes gtFoldExpr to
* perform local constant prop and reorder the constants in the tree and
* fold them.
*
* We then parse the resulting array element expression in order to locate
* and label the constants and variables that occur in the tree.
*/
const int MAX_ARR_COMPLEXITY = 4;
const int MAX_INDEX_COMPLEXITY = 4;
GenTree* Compiler::fgMorphArrayIndex(GenTree* tree)
//------------------------------------------------------------------------
// fgMorphIndexAddr: Expand a GT_INDEX_ADDR node and fully morph the child operands.
//
// We expand the GT_INDEX_ADDR node into a larger tree that evaluates the array
// base and index. The simplest expansion is a GT_COMMA with a GT_BOUNDS_CHECK.
// For complex array or index expressions one or more GT_COMMA assignments
// are inserted so that we only evaluate the array or index expressions once.
//
// The fully expanded tree is then morphed. This causes gtFoldExpr to
// perform local constant prop and reorder the constants in the tree and
// fold them.
//
// Arguments:
// indexAddr - The INDEX_ADRR tree to morph
//
// Return Value:
// The resulting tree.
//
GenTree* Compiler::fgMorphIndexAddr(GenTreeIndexAddr* indexAddr)
{
noway_assert(tree->gtOper == GT_INDEX);
GenTreeIndex* asIndex = tree->AsIndex();
var_types elemTyp = asIndex->TypeGet();
unsigned elemSize = asIndex->gtIndElemSize;
CORINFO_CLASS_HANDLE elemStructType = asIndex->gtStructElemClass;
const int MAX_ARR_COMPLEXITY = 4;
const int MAX_INDEX_COMPLEXITY = 4;
noway_assert(elemTyp != TYP_STRUCT || elemStructType != NO_CLASS_HANDLE);
var_types elemTyp = indexAddr->gtElemType;
unsigned elemSize = indexAddr->gtElemSize;
uint8_t elemOffs = static_cast<uint8_t>(indexAddr->gtElemOffset);
CORINFO_CLASS_HANDLE elemStructType = indexAddr->gtStructElemClass;
// Fold "cns_str"[cns_index] to ushort constant
// NOTE: don't do it for empty string, the operation will fail anyway
if (opts.OptimizationEnabled() && asIndex->Arr()->OperIs(GT_CNS_STR) &&
!asIndex->Arr()->AsStrCon()->IsStringEmptyField() && asIndex->Index()->IsIntCnsFitsInI32())
{
const int cnsIndex = static_cast<int>(asIndex->Index()->AsIntConCommon()->IconValue());
if (cnsIndex >= 0)
{
const int maxStrSize = 1024;
char16_t str[maxStrSize];
int length = info.compCompHnd->getStringLiteral(asIndex->Arr()->AsStrCon()->gtScpHnd,
asIndex->Arr()->AsStrCon()->gtSconCPX, str, maxStrSize);
if ((cnsIndex < length))
{
GenTree* cnsCharNode = gtNewIconNode(str[cnsIndex], TYP_INT);
INDEBUG(cnsCharNode->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
return cnsCharNode;
}
}
}
noway_assert(!varTypeIsStruct(elemTyp) || (elemStructType != NO_CLASS_HANDLE));
#ifdef FEATURE_SIMD
if (varTypeIsStruct(elemTyp) && structSizeMightRepresentSIMDType(elemSize))
{
// If this is a SIMD type, this is the point at which we lose the type information,
// so we need to set the correct type on the GT_IND.
// (We don't care about the base type here, so we only check, but don't retain, the return value).
unsigned simdElemSize = 0;
if (getBaseJitTypeAndSizeOfSIMDType(elemStructType, &simdElemSize) != CORINFO_TYPE_UNDEF)
{
assert(simdElemSize == elemSize);
elemTyp = getSIMDTypeForSize(elemSize);
// This is the new type of the node.
tree->gtType = elemTyp;
// Now set elemStructType to null so that we don't confuse value numbering.
elemStructType = NO_CLASS_HANDLE;
}
}
#endif // FEATURE_SIMD
// Set up the array length's offset into lenOffs
// And the first element's offset into elemOffs
ssize_t lenOffs;
uint8_t elemOffs;
if (tree->gtFlags & GTF_INX_STRING_LAYOUT)
{
lenOffs = OFFSETOF__CORINFO_String__stringLen;
elemOffs = OFFSETOF__CORINFO_String__chars;
tree->gtFlags &= ~GTF_INX_STRING_LAYOUT; // Clear this flag as it is used for GTF_IND_VOLATILE
}
else
{
// We have a standard array
lenOffs = OFFSETOF__CORINFO_Array__length;
elemOffs = OFFSETOF__CORINFO_Array__data;
}
// In minopts, we expand GT_INDEX to GT_IND(GT_INDEX_ADDR) in order to minimize the size of the IR. As minopts
// In minopts, we will not be expanding GT_INDEX_ADDR in order to minimize the size of the IR. As minopts
// compilation time is roughly proportional to the size of the IR, this helps keep compilation times down.
// Furthermore, this representation typically saves on code size in minopts w.r.t. the complete expansion
// performed when optimizing, as it does not require LclVar nodes (which are always stack loads/stores in
// minopts).
//
// When we *are* optimizing, we fully expand GT_INDEX to:
// When we *are* optimizing, we fully expand GT_INDEX_ADDR to:
// 1. Evaluate the array address expression and store the result in a temp if the expression is complex or
// side-effecting.
// 2. Evaluate the array index expression and store the result in a temp if the expression is complex or
@ -4644,63 +4585,46 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree)
// GT_ADD(GT_ADD(array, firstElementOffset), GT_MUL(index, elementSize)) OR
// GT_ADD(GT_ADD(array, GT_ADD(GT_MUL(index, elementSize), firstElementOffset)))
// 5. Wrap the address in a GT_ADD_ADDR (the information saved there will later be used by VN).
// 6. Dereference the address with a GT_IND.
//
// This expansion explicitly exposes the bounds check and the address calculation to the optimizer, which allows
// for more straightforward bounds-check removal, CSE, etc.
if (opts.MinOpts())
{
GenTree* const array = fgMorphTree(asIndex->Arr());
GenTree* const index = fgMorphTree(asIndex->Index());
GenTreeIndexAddr* const indexAddr = new (this, GT_INDEX_ADDR)
GenTreeIndexAddr(array, index, elemTyp, elemStructType, elemSize, static_cast<unsigned>(lenOffs), elemOffs);
indexAddr->gtFlags |= (array->gtFlags | index->gtFlags) & GTF_ALL_EFFECT;
indexAddr->Arr() = fgMorphTree(indexAddr->Arr());
indexAddr->Index() = fgMorphTree(indexAddr->Index());
indexAddr->AddAllEffectsFlags(indexAddr->Arr(), indexAddr->Index());
// Mark the indirection node as needing a range check if necessary.
// Note this will always be true unless JitSkipArrayBoundCheck() is used
if ((indexAddr->gtFlags & GTF_INX_RNGCHK) != 0)
if (indexAddr->IsBoundsChecked())
{
fgSetRngChkTarget(indexAddr);
}
if (!tree->TypeIs(TYP_STRUCT))
{
tree->ChangeOper(GT_IND);
}
else
{
DEBUG_DESTROY_NODE(tree);
tree = gtNewObjNode(elemStructType, indexAddr);
INDEBUG(tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
}
GenTreeIndir* const indir = tree->AsIndir();
indir->Addr() = indexAddr;
bool canCSE = indir->CanCSE();
indir->gtFlags = indexAddr->gtFlags & GTF_ALL_EFFECT;
if (!canCSE)
{
indir->SetDoNotCSE();
}
INDEBUG(indexAddr->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
return indir;
return indexAddr;
}
GenTree* arrRef = asIndex->Arr();
GenTree* index = asIndex->Index();
#ifdef FEATURE_SIMD
if (varTypeIsStruct(elemTyp) && structSizeMightRepresentSIMDType(elemSize))
{
elemTyp = impNormStructType(elemStructType);
}
#endif // FEATURE_SIMD
bool chkd = ((tree->gtFlags & GTF_INX_RNGCHK) != 0); // if false, range checking will be disabled
bool indexNonFaulting = ((tree->gtFlags & GTF_INX_NOFAULT) != 0); // if true, mark GTF_IND_NONFAULTING
bool nCSE = ((tree->gtFlags & GTF_DONT_CSE) != 0);
// TODO-CQ: support precise equivalence classes for SIMD-typed arrays in VN.
if (elemTyp != TYP_STRUCT)
{
elemStructType = NO_CLASS_HANDLE;
}
GenTree* arrRefDefn = nullptr; // non-NULL if we need to allocate a temp for the arrRef expression
GenTree* indexDefn = nullptr; // non-NULL if we need to allocate a temp for the index expression
GenTree* bndsChk = nullptr;
GenTree* arrRef = indexAddr->Arr();
GenTree* index = indexAddr->Index();
GenTree* arrRefDefn = nullptr; // non-NULL if we need to allocate a temp for the arrRef expression
GenTree* indexDefn = nullptr; // non-NULL if we need to allocate a temp for the index expression
GenTreeBoundsChk* boundsCheck = nullptr;
// If we're doing range checking, introduce a GT_BOUNDS_CHECK node for the address.
if (chkd)
if (indexAddr->IsBoundsChecked())
{
GenTree* arrRef2 = nullptr; // The second copy will be used in array address expression
GenTree* index2 = nullptr;
@ -4716,7 +4640,7 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree)
// were mostly ameliorated by adding this condition.
//
// Likewise, allocate a temporary if the expression is a GT_LCL_FLD node. These used to be created
// after fgMorphArrayIndex from GT_FIELD trees so this preserves the existing behavior. This is
// after fgMorphIndexAddr from GT_FIELD trees so this preserves the existing behavior. This is
// perhaps a decision that should be left to CSE but FX diffs show that it is slightly better to
// do this here.
@ -4761,27 +4685,23 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree)
}
#endif // TARGET_64BIT
GenTree* arrLen = gtNewArrLen(TYP_INT, arrRef, (int)lenOffs, compCurBB);
GenTree* arrLen = gtNewArrLen(TYP_INT, arrRef, (int)indexAddr->gtLenOffset, compCurBB);
if (bndsChkType != TYP_INT)
{
arrLen = gtNewCastNode(bndsChkType, arrLen, true, bndsChkType);
}
GenTreeBoundsChk* arrBndsChk = new (this, GT_BOUNDS_CHECK) GenTreeBoundsChk(index, arrLen, SCK_RNGCHK_FAIL);
arrBndsChk->gtInxType = elemTyp;
bndsChk = arrBndsChk;
boundsCheck = new (this, GT_BOUNDS_CHECK) GenTreeBoundsChk(index, arrLen, SCK_RNGCHK_FAIL);
boundsCheck->gtInxType = elemTyp;
// Now we'll switch to using the second copies for arrRef and index
// to compute the address expression
arrRef = arrRef2;
index = index2;
}
// Create the "addr" which is "*(arrRef + ((index * elemSize) + elemOffs))"
GenTree* addr;
#ifdef TARGET_64BIT
@ -4865,74 +4785,50 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree)
addr = gtNewOperNode(GT_ADD, TYP_BYREF, arrRef, addr);
}
// TODO-Throughout: bash the INDEX_ADDR to ARR_ADDR here instead of creating a new node.
addr = new (this, GT_ARR_ADDR) GenTreeArrAddr(addr, elemTyp, elemStructType, elemOffs);
// Change the orginal GT_INDEX node into a GT_IND node
tree->SetOper(GT_IND);
// If the index node is a floating-point type, notify the compiler
// we'll potentially use floating point registers at the time of codegen.
if (varTypeUsesFloatReg(tree->gtType))
if (indexAddr->IsNotNull())
{
this->compFloatingPointUsed = true;
}
// We've now consumed the GTF_INX_RNGCHK and GTF_INX_NOFAULT, and the node
// is no longer a GT_INDEX node.
tree->gtFlags &= ~(GTF_INX_RNGCHK | GTF_INX_NOFAULT);
tree->AsOp()->gtOp1 = addr;
// If there's a bounds check, the indir won't fault.
if (bndsChk || indexNonFaulting)
{
tree->gtFlags |= GTF_IND_NONFAULTING;
addr->gtFlags |= GTF_ARR_ADDR_NONNULL;
}
else
// Transfer the zero-offset annotation from INDEX_ADDR to ARR_ADDR...
FieldSeqNode* zeroOffsetFldSeq;
if (GetZeroOffsetFieldMap()->Lookup(indexAddr, &zeroOffsetFldSeq))
{
tree->gtFlags |= GTF_EXCEPT;
fgAddFieldSeqForZeroOffset(addr, zeroOffsetFldSeq);
}
if (nCSE)
{
tree->gtFlags |= GTF_DONT_CSE;
}
GenTree* tree = addr;
// Did we create a bndsChk tree?
if (bndsChk)
// Prepend the bounds check and the assignment trees that were created (if any).
if (boundsCheck != nullptr)
{
// Use a GT_COMMA node to prepend the array bound check
//
tree = gtNewOperNode(GT_COMMA, elemTyp, bndsChk, tree);
/* Mark the indirection node as needing a range check */
fgSetRngChkTarget(bndsChk);
tree = gtNewOperNode(GT_COMMA, tree->TypeGet(), boundsCheck, tree);
fgSetRngChkTarget(boundsCheck);
}
if (indexDefn != nullptr)
{
// Use a GT_COMMA node to prepend the index assignment
//
tree = gtNewOperNode(GT_COMMA, tree->TypeGet(), indexDefn, tree);
}
if (arrRefDefn != nullptr)
{
// Use a GT_COMMA node to prepend the arRef assignment
//
tree = gtNewOperNode(GT_COMMA, tree->TypeGet(), arrRefDefn, tree);
}
JITDUMP("fgMorphArrayIndex (before remorph):\n")
JITDUMP("fgMorphIndexAddr (before remorph):\n")
DISPTREE(tree)
GenTree* morphedTree = fgMorphTree(tree);
DBEXEC(morphedTree != tree, morphedTree->gtDebugFlags &= ~GTF_DEBUG_NODE_MORPHED);
tree = fgMorphTree(tree);
DBEXEC(tree == indexAddr, tree->gtDebugFlags &= ~GTF_DEBUG_NODE_MORPHED);
JITDUMP("fgMorphArrayIndex (after remorph):\n")
DISPTREE(morphedTree)
DISPTREE(tree)
return morphedTree;
return tree;
}
//------------------------------------------------------------------------
@ -8612,8 +8508,9 @@ GenTree* Compiler::fgMorphCall(GenTreeCall* call)
fgWalkTreePost(&value, resetMorphedFlag);
#endif // DEBUG
GenTree* const arrIndexNode = gtNewIndexRef(TYP_REF, arr, index);
GenTree* const arrStore = gtNewAssignNode(arrIndexNode, value);
GenTree* const arrIndexAddr = gtNewArrayIndexAddr(arr, index, TYP_REF, NO_CLASS_HANDLE);
GenTree* const arrIndex = gtNewIndexIndir(arrIndexAddr->AsIndexAddr());
GenTree* const arrStore = gtNewAssignNode(arrIndex, value);
GenTree* result = fgMorphTree(arrStore);
if (argSetup != nullptr)
@ -9638,7 +9535,6 @@ GenTree* Compiler::fgMorphGetStructAddr(GenTree** pTree, CORINFO_CLASS_HANDLE cl
{
case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_INDEX:
case GT_FIELD:
case GT_ARR_ELEM:
addr = gtNewOperNode(GT_ADDR, TYP_BYREF, tree);
@ -10404,8 +10300,8 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
case GT_FIELD:
return fgMorphField(tree, mac);
case GT_INDEX:
return fgMorphArrayIndex(tree);
case GT_INDEX_ADDR:
return fgMorphIndexAddr(tree->AsIndexAddr());
case GT_CAST:
{
@ -10490,6 +10386,19 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
}
break;
case GT_IND:
if (opts.OptimizationEnabled() && !optValnumCSE_phase)
{
GenTree* constNode = gtFoldIndirConst(tree->AsIndir());
if (constNode != nullptr)
{
assert(constNode->OperIsConst()); // No further morphing required.
INDEBUG(constNode->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
return constNode;
}
}
break;
case GT_DIV:
// Replace "val / dcon" with "val * (1.0 / dcon)" if dcon is a power of two.
// Powers of two within range are always exactly represented,

View file

@ -1639,30 +1639,31 @@ bool Compiler::areLocalFieldsContiguous(GenTreeLclFld* first, GenTreeLclFld* sec
// TODO-CQ:
// Right this can only check array element with const number as index. In future,
// we should consider to allow this function to check the index using expression.
//
bool Compiler::areArrayElementsContiguous(GenTree* op1, GenTree* op2)
{
noway_assert(op1->gtOper == GT_INDEX);
noway_assert(op2->gtOper == GT_INDEX);
GenTreeIndex* op1Index = op1->AsIndex();
GenTreeIndex* op2Index = op2->AsIndex();
assert(op1->OperIs(GT_IND) && op2->OperIs(GT_IND));
assert(!op1->TypeIs(TYP_STRUCT) && (op1->TypeGet() == op2->TypeGet()));
GenTree* op1ArrayRef = op1Index->Arr();
GenTree* op2ArrayRef = op2Index->Arr();
GenTreeIndexAddr* op1IndexAddr = op1->AsIndir()->Addr()->AsIndexAddr();
GenTreeIndexAddr* op2IndexAddr = op2->AsIndir()->Addr()->AsIndexAddr();
GenTree* op1ArrayRef = op1IndexAddr->Arr();
GenTree* op2ArrayRef = op2IndexAddr->Arr();
assert(op1ArrayRef->TypeGet() == TYP_REF);
assert(op2ArrayRef->TypeGet() == TYP_REF);
GenTree* op1IndexNode = op1Index->Index();
GenTree* op2IndexNode = op2Index->Index();
GenTree* op1IndexNode = op1IndexAddr->Index();
GenTree* op2IndexNode = op2IndexAddr->Index();
if ((op1IndexNode->OperGet() == GT_CNS_INT && op2IndexNode->OperGet() == GT_CNS_INT) &&
op1IndexNode->AsIntCon()->gtIconVal + 1 == op2IndexNode->AsIntCon()->gtIconVal)
{
if (op1ArrayRef->OperGet() == GT_FIELD && op2ArrayRef->OperGet() == GT_FIELD &&
if (op1ArrayRef->OperIs(GT_FIELD) && op2ArrayRef->OperIs(GT_FIELD) &&
areFieldsParentsLocatedSame(op1ArrayRef, op2ArrayRef))
{
return true;
}
else if (op1ArrayRef->OperIsLocal() && op2ArrayRef->OperIsLocal() &&
else if (op1ArrayRef->OperIs(GT_LCL_VAR) && op2ArrayRef->OperIs(GT_LCL_VAR) &&
op1ArrayRef->AsLclVarCommon()->GetLclNum() == op2ArrayRef->AsLclVarCommon()->GetLclNum())
{
return true;
@ -1682,14 +1683,13 @@ bool Compiler::areArrayElementsContiguous(GenTree* op1, GenTree* op2)
// TODO-CQ:
// Right now this can only check field and array. In future we should add more cases.
//
bool Compiler::areArgumentsContiguous(GenTree* op1, GenTree* op2)
{
if (op1->OperGet() == GT_INDEX && op2->OperGet() == GT_INDEX)
if (op1->OperIs(GT_IND) && op2->OperIs(GT_IND))
{
return areArrayElementsContiguous(op1, op2);
}
else if (op1->OperGet() == GT_FIELD && op2->OperGet() == GT_FIELD)
else if (op1->OperIs(GT_FIELD) && op2->OperIs(GT_FIELD))
{
return areFieldsContiguous(op1, op2);
}
@ -1713,17 +1713,16 @@ bool Compiler::areArgumentsContiguous(GenTree* op1, GenTree* op2)
// return the address node.
//
// TODO-CQ:
// 1. Currently just support for GT_FIELD and GT_INDEX, because we can only verify the GT_INDEX node or GT_Field
// are located contiguously or not. In future we should support more cases.
// Currently just supports GT_FIELD and GT_IND(GT_INDEX_ADDR), because we can only verify those nodes
// are located contiguously or not. In future we should support more cases.
//
GenTree* Compiler::createAddressNodeForSIMDInit(GenTree* tree, unsigned simdSize)
{
assert(tree->OperGet() == GT_FIELD || tree->OperGet() == GT_INDEX);
GenTree* byrefNode = nullptr;
unsigned offset = 0;
var_types baseType = tree->gtType;
if (tree->OperGet() == GT_FIELD)
if (tree->OperIs(GT_FIELD))
{
GenTree* objRef = tree->AsField()->GetFldObj();
if (objRef != nullptr && objRef->gtOper == GT_ADDR)
@ -1751,23 +1750,25 @@ GenTree* Compiler::createAddressNodeForSIMDInit(GenTree* tree, unsigned simdSize
assert(byrefNode != nullptr);
offset = tree->AsField()->gtFldOffset;
}
else if (tree->OperGet() == GT_INDEX)
else
{
assert(tree->OperIs(GT_IND) && tree->AsIndir()->Addr()->OperIs(GT_INDEX_ADDR));
GenTree* index = tree->AsIndex()->Index();
assert(index->OperGet() == GT_CNS_INT);
GenTreeIndexAddr* indexAddr = tree->AsIndir()->Addr()->AsIndexAddr();
GenTree* arrayRef = indexAddr->Arr();
GenTree* index = indexAddr->Index();
assert(index->IsCnsIntOrI());
GenTree* checkIndexExpr = nullptr;
unsigned indexVal = (unsigned)(index->AsIntCon()->gtIconVal);
unsigned indexVal = (unsigned)index->AsIntCon()->gtIconVal;
offset = indexVal * genTypeSize(tree->TypeGet());
GenTree* arrayRef = tree->AsIndex()->Arr();
// Generate the boundary check exception.
// The length for boundary check should be the maximum index number which should be
// (first argument's index number) + (how many array arguments we have) - 1
// = indexVal + arrayElementsCount - 1
unsigned arrayElementsCount = simdSize / genTypeSize(baseType);
checkIndexExpr = new (this, GT_CNS_INT) GenTreeIntCon(TYP_INT, indexVal + arrayElementsCount - 1);
checkIndexExpr = gtNewIconNode(indexVal + arrayElementsCount - 1);
GenTreeArrLen* arrLen = gtNewArrLen(TYP_INT, arrayRef, (int)OFFSETOF__CORINFO_Array__length, compCurBB);
GenTreeBoundsChk* arrBndsChk =
new (this, GT_BOUNDS_CHECK) GenTreeBoundsChk(checkIndexExpr, arrLen, SCK_ARG_RNG_EXCPN);
@ -1775,10 +1776,6 @@ GenTree* Compiler::createAddressNodeForSIMDInit(GenTree* tree, unsigned simdSize
offset += OFFSETOF__CORINFO_Array__data;
byrefNode = gtNewOperNode(GT_COMMA, arrayRef->TypeGet(), arrBndsChk, gtCloneExpr(arrayRef));
}
else
{
unreached();
}
GenTree* address = gtNewOperNode(GT_ADD, TYP_BYREF, byrefNode, gtNewIconNode(offset, TYP_I_IMPL));
@ -1792,7 +1789,7 @@ GenTree* Compiler::createAddressNodeForSIMDInit(GenTree* tree, unsigned simdSize
//
// Arguments:
// stmt - GenTree*. Input statement node.
//
void Compiler::impMarkContiguousSIMDFieldAssignments(Statement* stmt)
{
if (opts.OptimizationDisabled())