Skip to content

Commit

Permalink
Optimize multi-dimensional array access (#70271)
Browse files Browse the repository at this point in the history
Currently, multi-dimensional (MD) array access operations are treated as opaque to most of
the JIT; they pass through the optimization pipeline untouched. Lowering expands the `GT_ARR_ELEM`
node (representing a `a[i,j]` operation, for example) to `GT_ARR_OFFSET` and `GT_ARR_INDEX` trees,
to expand the register requirements of the operation. These are then directly used to generate code.

This change moves the expansion of `GT_ARR_ELEM` to a new pass that follows loop optimization but precedes
Value Numbering, CSE, and the rest of the optimizer. This placement allows for future improvement to
loop cloning to support cloning loops with MD references, but allows the optimizer to kick in on the new
expansion. One nice feature of this change: there is no machine-dependent code required; all the nodes
get lowered to machine-independent nodes before code generation.

The MDBenchI and MDBenchF micro-benchmarks (very targeted to this work) improve about 10% to 60%, but there is
one significant CQ regression in MDMulMatrix of over 20%. Future loop cloning, CSE, and/or LSRA work will be needed to get that back.

In this change, `GT_ARR_ELEM` nodes are morphed to appropriate trees. Note that an MD array `Get`, `Set`, or `Address`
operation is imported as a call, and, if all required conditions are satisfied, is treated as an intrinsic
and replaced by IR nodes, especially `GT_ARR_ELEM` nodes, in `impArrayAccessIntrinsic()`.

For example, a simple 2-dimensional array access like `a[i,j]` looks like:

```
\--*  ARR_ELEM[,] byref
   +--*  LCL_VAR   ref    V00 arg0
   +--*  LCL_VAR   int    V01 arg1
   \--*  LCL_VAR   int    V02 arg2
```

This is replaced by:

```
&a + offset + elemSize * ((i - a.GetLowerBound(0)) * a.GetLength(1) + (j - a.GetLowerBound(1)))
```

plus the appropriate `i` and `j` bounds checks.

In IR, this is:

```
*  ADD       byref
+--*  ADD       long
|  +--*  MUL       long
|  |  +--*  CAST      long <- uint
|  |  |  \--*  ADD       int
|  |  |     +--*  MUL       int
|  |  |     |  +--*  COMMA     int
|  |  |     |  |  +--*  ASG       int
|  |  |     |  |  |  +--*  LCL_VAR   int    V04 tmp1
|  |  |     |  |  |  \--*  SUB       int
|  |  |     |  |  |     +--*  LCL_VAR   int    V01 arg1
|  |  |     |  |  |     \--*  MDARR_LOWER_BOUND int    (0)
|  |  |     |  |  |        \--*  LCL_VAR   ref    V00 arg0
|  |  |     |  |  \--*  COMMA     int
|  |  |     |  |     +--*  BOUNDS_CHECK_Rng void
|  |  |     |  |     |  +--*  LCL_VAR   int    V04 tmp1
|  |  |     |  |     |  \--*  MDARR_LENGTH int    (0)
|  |  |     |  |     |     \--*  LCL_VAR   ref    V00 arg0
|  |  |     |  |     \--*  LCL_VAR   int    V04 tmp1
|  |  |     |  \--*  MDARR_LENGTH int    (1)
|  |  |     |     \--*  LCL_VAR   ref    V00 arg0
|  |  |     \--*  COMMA     int
|  |  |        +--*  ASG       int
|  |  |        |  +--*  LCL_VAR   int    V05 tmp2
|  |  |        |  \--*  SUB       int
|  |  |        |     +--*  LCL_VAR   int    V02 arg2
|  |  |        |     \--*  MDARR_LOWER_BOUND int    (1)
|  |  |        |        \--*  LCL_VAR   ref    V00 arg0
|  |  |        \--*  COMMA     int
|  |  |           +--*  BOUNDS_CHECK_Rng void
|  |  |           |  +--*  LCL_VAR   int    V05 tmp2
|  |  |           |  \--*  MDARR_LENGTH int    (1)
|  |  |           |     \--*  LCL_VAR   ref    V00 arg0
|  |  |           \--*  LCL_VAR   int    V05 tmp2
|  |  \--*  CNS_INT   long   4
|  \--*  CNS_INT   long   32
\--*  LCL_VAR   ref    V00 arg0
```

before being morphed by the usual morph transformations.

Some things to consider:
1. MD arrays have both a lower bound and length for each dimension (even if very few MD arrays actually have a
   non-zero lower bound)
2. The new `GT_MDARR_LOWER_BOUND(dim)` node represents the lower-bound value for a particular array dimension. The "effective index" for a dimension is the index minus the lower bound.
3. The new `GT_MDARR_LENGTH(dim)` node represents the length value (number of elements in a dimension) for a particular array dimension.
4. The effective index is bounds checked against the dimension length.
5. The lower bound and length values are 32-bit signed integers (`TYP_INT`).
6. After constructing a "linearized index", the index is scaled by the array element size, and the offset from
   the array object to the beginning of the array data is added.
7. Much of the complexity above is simply to assign temps to the various values that are used subsequently.
8. The index expressions are used exactly once. However, if have side effects, they need to be copied, early,
   to preserve exception ordering.
9. Only the top-level operation adds the array object to the scaled, linearized index, to create the final
   address `byref`. As usual, we need to be careful to not create an illegal byref by adding any partial index.
   calculation.
10. To avoid doing unnecessary work, the importer sets the global `OMF_HAS_MDARRAYREF` flag if there are any
   MD array expressions to expand. Also, the block flag `BBF_HAS_MDARRAYREF` is set on blocks where these exist,
   so only those blocks are processed.

Remaining work:
1. Implement `optEarlyProp` support for MD arrays.
2. Implement loop cloning support for MD arrays.
3. (optionally) Remove old `GT_ARR_OFFSET` and `GT_ARR_INDEX` nodes and related code, as well as `GT_ARR_ELEM`
code used after the new expansion.
4. Implement improvements in CSE and LSRA to improve codegen for the MDMulMatrix benchmark.

The new early expansion is enabled by default. It can be disabled (even in Release, currently), by setting
`COMPlus_JitEarlyExpandMDArrays=0`. If disabled, it can be selectively enabled using
`COMPlus_JitEarlyExpandMDArraysFilter=<method_set>` (e.g., as specified for `JitDump`).

Fixes #60785.
  • Loading branch information
BruceForstall committed Jul 5, 2022
1 parent dfbc648 commit cc0ccbe
Show file tree
Hide file tree
Showing 28 changed files with 1,188 additions and 316 deletions.
7 changes: 5 additions & 2 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ bool IntegralRange::Contains(int64_t value) const
return {SymbolicIntegerValue::Zero, SymbolicIntegerValue::One};

case GT_ARR_LENGTH:
case GT_MDARR_LENGTH:
return {SymbolicIntegerValue::Zero, SymbolicIntegerValue::ArrayLenMax};

case GT_CALL:
Expand Down Expand Up @@ -2690,8 +2691,10 @@ void Compiler::optAssertionGen(GenTree* tree)
break;

case GT_ARR_LENGTH:
// An array length is an (always R-value) indirection (but doesn't derive from GenTreeIndir).
assertionInfo = optCreateAssertion(tree->AsArrLen()->ArrRef(), nullptr, OAK_NOT_EQUAL);
case GT_MDARR_LENGTH:
case GT_MDARR_LOWER_BOUND:
// An array meta-data access is an (always R-value) indirection (but doesn't derive from GenTreeIndir).
assertionInfo = optCreateAssertion(tree->AsArrCommon()->ArrRef(), nullptr, OAK_NOT_EQUAL);
break;

case GT_NULLCHECK:
Expand Down
8 changes: 6 additions & 2 deletions src/coreclr/jit/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,9 @@ void BasicBlock::dspFlags()
{
printf("idxlen ");
}
if (bbFlags & BBF_HAS_NEWARRAY)
if (bbFlags & BBF_HAS_MD_IDX_LEN)
{
printf("new[] ");
printf("mdidxlen ");
}
if (bbFlags & BBF_HAS_NEWOBJ)
{
Expand Down Expand Up @@ -512,6 +512,10 @@ void BasicBlock::dspFlags()
{
printf("align ");
}
if (bbFlags & BBF_HAS_MDARRAYREF)
{
printf("mdarr ");
}
}

/*****************************************************************************
Expand Down
16 changes: 8 additions & 8 deletions src/coreclr/jit/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,8 @@ enum BasicBlockFlags : unsigned __int64
// cases, because the BB occurs in a loop, and we've determined that all
// paths in the loop body leading to BB include a call.

BBF_HAS_IDX_LEN = MAKE_BBFLAG(20), // BB contains simple index or length expressions on an array local var.
BBF_HAS_NEWARRAY = MAKE_BBFLAG(21), // BB contains 'new' of an array
BBF_HAS_IDX_LEN = MAKE_BBFLAG(20), // BB contains simple index or length expressions on an SD array local var.
BBF_HAS_MD_IDX_LEN = MAKE_BBFLAG(21), // BB contains simple index, length, or lower bound expressions on an MD array local var.
BBF_HAS_NEWOBJ = MAKE_BBFLAG(22), // BB contains 'new' of an object type.

#if defined(FEATURE_EH_FUNCLETS) && defined(TARGET_ARM)
Expand Down Expand Up @@ -552,6 +552,7 @@ enum BasicBlockFlags : unsigned __int64
BBF_TAILCALL_SUCCESSOR = MAKE_BBFLAG(40), // BB has pred that has potential tail call

BBF_BACKWARD_JUMP_SOURCE = MAKE_BBFLAG(41), // Block is a source of a backward jump
BBF_HAS_MDARRAYREF = MAKE_BBFLAG(42), // Block has a multi-dimensional array reference

// The following are sets of flags.

Expand All @@ -561,8 +562,8 @@ enum BasicBlockFlags : unsigned __int64

// Flags to update when two blocks are compacted

BBF_COMPACT_UPD = BBF_CHANGED | BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_BACKWARD_JUMP | BBF_HAS_NEWARRAY | \
BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK,
BBF_COMPACT_UPD = BBF_CHANGED | BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_BACKWARD_JUMP | \
BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF,

// Flags a block should not have had before it is split.

Expand All @@ -577,12 +578,11 @@ enum BasicBlockFlags : unsigned __int64

// Flags gained by the bottom block when a block is split.
// Note, this is a conservative guess.
// For example, the bottom block might or might not have BBF_HAS_NEWARRAY or BBF_HAS_NULLCHECK,
// but we assume it has BBF_HAS_NEWARRAY and BBF_HAS_NULLCHECK.
// For example, the bottom block might or might not have BBF_HAS_NULLCHECK, but we assume it has BBF_HAS_NULLCHECK.
// TODO: Should BBF_RUN_RARELY be added to BBF_SPLIT_GAINED ?

BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_NEWARRAY | BBF_PROF_WEIGHT | \
BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE,
BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | \
BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_MDARRAYREF,
};

inline constexpr BasicBlockFlags operator ~(BasicBlockFlags a)
Expand Down
14 changes: 6 additions & 8 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1507,10 +1507,9 @@ void CodeGen::genCodeForArrIndex(GenTreeArrIndex* arrIndex)
regNumber tmpReg = arrIndex->GetSingleTempReg();
assert(tgtReg != tmpReg);

unsigned dim = arrIndex->gtCurrDim;
unsigned rank = arrIndex->gtArrRank;
var_types elemType = arrIndex->gtArrElemType;
unsigned offset;
unsigned dim = arrIndex->gtCurrDim;
unsigned rank = arrIndex->gtArrRank;
unsigned offset;

offset = compiler->eeGetMDArrayLowerBoundOffset(rank, dim);
emit->emitIns_R_R_I(INS_ldr, EA_4BYTE, tmpReg, arrReg, offset);
Expand Down Expand Up @@ -1561,10 +1560,9 @@ void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset)

regNumber tmpReg = arrOffset->GetSingleTempReg();

unsigned dim = arrOffset->gtCurrDim;
unsigned rank = arrOffset->gtArrRank;
var_types elemType = arrOffset->gtArrElemType;
unsigned offset = compiler->eeGetMDArrayLengthOffset(rank, dim);
unsigned dim = arrOffset->gtCurrDim;
unsigned rank = arrOffset->gtArrRank;
unsigned offset = compiler->eeGetMDArrayLengthOffset(rank, dim);

// Load tmpReg with the dimension size and evaluate
// tgtReg = offsetReg*tmpReg + indexReg.
Expand Down
14 changes: 8 additions & 6 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4236,16 +4236,17 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree)

void CodeGen::genCodeForArrIndex(GenTreeArrIndex* arrIndex)
{
assert(!compiler->opts.compJitEarlyExpandMDArrays);

GenTree* arrObj = arrIndex->ArrObj();
GenTree* indexNode = arrIndex->IndexExpr();

regNumber arrReg = genConsumeReg(arrObj);
regNumber indexReg = genConsumeReg(indexNode);
regNumber tgtReg = arrIndex->GetRegNum();

unsigned dim = arrIndex->gtCurrDim;
unsigned rank = arrIndex->gtArrRank;
var_types elemType = arrIndex->gtArrElemType;
unsigned dim = arrIndex->gtCurrDim;
unsigned rank = arrIndex->gtArrRank;

noway_assert(tgtReg != REG_NA);

Expand Down Expand Up @@ -4279,16 +4280,17 @@ void CodeGen::genCodeForArrIndex(GenTreeArrIndex* arrIndex)

void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset)
{
assert(!compiler->opts.compJitEarlyExpandMDArrays);

GenTree* offsetNode = arrOffset->gtOffset;
GenTree* indexNode = arrOffset->gtIndex;
GenTree* arrObj = arrOffset->gtArrObj;

regNumber tgtReg = arrOffset->GetRegNum();
assert(tgtReg != REG_NA);

unsigned dim = arrOffset->gtCurrDim;
unsigned rank = arrOffset->gtArrRank;
var_types elemType = arrOffset->gtArrElemType;
unsigned dim = arrOffset->gtCurrDim;
unsigned rank = arrOffset->gtArrRank;

// First, consume the operands in the correct order.
regNumber offsetReg = REG_NA;
Expand Down
22 changes: 21 additions & 1 deletion src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2820,6 +2820,8 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
opts.compJitSaveFpLrWithCalleeSavedRegisters = 0;
#endif // defined(TARGET_ARM64)

opts.compJitEarlyExpandMDArrays = (JitConfig.JitEarlyExpandMDArrays() != 0);

#ifdef DEBUG
opts.dspInstrs = false;
opts.dspLines = false;
Expand Down Expand Up @@ -2992,6 +2994,18 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
{
opts.optRepeat = true;
}

// If JitEarlyExpandMDArrays is non-zero, then early MD expansion is enabled.
// If JitEarlyExpandMDArrays is zero, then conditionally enable it for functions specfied by
// JitEarlyExpandMDArraysFilter.
if (JitConfig.JitEarlyExpandMDArrays() == 0)
{
if (JitConfig.JitEarlyExpandMDArraysFilter().contains(info.compMethodName, info.compClassName,
&info.compMethodInfo->args))
{
opts.compJitEarlyExpandMDArrays = true;
}
}
}

if (verboseDump)
Expand Down Expand Up @@ -4839,6 +4853,12 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
fgDebugCheckLinks();
#endif

// Morph multi-dimensional array operations.
// (Consider deferring all array operation morphing, including single-dimensional array ops,
// from global morph to here, so cloning doesn't have to deal with morphed forms.)
//
DoPhase(this, PHASE_MORPH_MDARR, &Compiler::fgMorphArrayOps);

// Create the variable table (and compute variable ref counts)
//
DoPhase(this, PHASE_MARK_LOCAL_VARS, &Compiler::lvaMarkLocalVars);
Expand Down Expand Up @@ -9834,7 +9854,7 @@ void cTreeFlags(Compiler* comp, GenTree* tree)
#endif
if (tree->gtFlags & GTF_IND_NONFAULTING)
{
if (tree->OperIsIndirOrArrLength())
if (tree->OperIsIndirOrArrMetaData())
{
chars += printf("[IND_NONFAULTING]");
}
Expand Down
104 changes: 92 additions & 12 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2606,8 +2606,14 @@ class Compiler

GenTreeIndir* gtNewIndexIndir(GenTreeIndexAddr* indexAddr);

void gtAnnotateNewArrLen(GenTree* arrLen, BasicBlock* block);

GenTreeArrLen* gtNewArrLen(var_types typ, GenTree* arrayOp, int lenOffset, BasicBlock* block);

GenTreeMDArr* gtNewMDArrLen(GenTree* arrayOp, unsigned dim, unsigned rank, BasicBlock* block);

GenTreeMDArr* gtNewMDArrLowerBound(GenTree* arrayOp, unsigned dim, unsigned rank, BasicBlock* block);

GenTreeIndir* gtNewIndir(var_types typ, GenTree* addr);

GenTree* gtNewNullCheck(GenTree* addr, BasicBlock* basicBlock);
Expand Down Expand Up @@ -4540,6 +4546,68 @@ class Compiler

bool fgMorphBlockStmt(BasicBlock* block, Statement* stmt DEBUGARG(const char* msg));

//------------------------------------------------------------------------------------------------------------
// MorphMDArrayTempCache: a simple cache of compiler temporaries in the local variable table, used to minimize
// the number of locals allocated when doing early multi-dimensional array operation expansion. Two types of
// temps are created and cached (due to the two types of temps needed by the MD array expansion): TYP_INT and
// TYP_REF. `GrabTemp` either returns an available temp from the cache or allocates a new temp and returns it
// after adding it to the cache. `Reset` makes all the temps in the cache available for subsequent re-use.
//
class MorphMDArrayTempCache
{
private:
class TempList
{
public:
TempList(Compiler* compiler)
: m_compiler(compiler), m_first(nullptr), m_insertPtr(&m_first), m_nextAvail(nullptr)
{
}

unsigned GetTemp();

void Reset()
{
m_nextAvail = m_first;
}

private:
struct Node
{
Node(unsigned tmp) : next(nullptr), tmp(tmp)
{
}

Node* next;
unsigned tmp;
};

Compiler* m_compiler;
Node* m_first;
Node** m_insertPtr;
Node* m_nextAvail;
};

TempList intTemps; // Temps for genActualType() == TYP_INT
TempList refTemps; // Temps for TYP_REF

public:
MorphMDArrayTempCache(Compiler* compiler) : intTemps(compiler), refTemps(compiler)
{
}

unsigned GrabTemp(var_types type);

void Reset()
{
intTemps.Reset();
refTemps.Reset();
}
};

bool fgMorphArrayOpsStmt(MorphMDArrayTempCache* pTempCache, BasicBlock* block, Statement* stmt);
PhaseStatus fgMorphArrayOps();

void fgSetOptions();

#ifdef DEBUG
Expand Down Expand Up @@ -6752,19 +6820,25 @@ class Compiler
}
};

#define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an array
#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type.
#define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores.
#define OMF_HAS_NULLCHECK 0x00000008 // Method contains null check.
#define OMF_HAS_FATPOINTER 0x00000010 // Method contains call, that needs fat pointer transformation.
#define OMF_HAS_OBJSTACKALLOC 0x00000020 // Method contains an object allocated on the stack.
#define OMF_HAS_GUARDEDDEVIRT 0x00000040 // Method contains guarded devirtualization candidate
#define OMF_HAS_EXPRUNTIMELOOKUP 0x00000080 // Method contains a runtime lookup to an expandable dictionary.
#define OMF_HAS_PATCHPOINT 0x00000100 // Method contains patchpoints
#define OMF_NEEDS_GCPOLLS 0x00000200 // Method needs GC polls
#define OMF_HAS_FROZEN_STRING 0x00000400 // Method has a frozen string (REF constant int), currently only on NativeAOT.
// clang-format off

#define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an SD array
#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type.
#define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores.
#define OMF_HAS_NULLCHECK 0x00000008 // Method contains null check.
#define OMF_HAS_FATPOINTER 0x00000010 // Method contains call, that needs fat pointer transformation.
#define OMF_HAS_OBJSTACKALLOC 0x00000020 // Method contains an object allocated on the stack.
#define OMF_HAS_GUARDEDDEVIRT 0x00000040 // Method contains guarded devirtualization candidate
#define OMF_HAS_EXPRUNTIMELOOKUP 0x00000080 // Method contains a runtime lookup to an expandable dictionary.
#define OMF_HAS_PATCHPOINT 0x00000100 // Method contains patchpoints
#define OMF_NEEDS_GCPOLLS 0x00000200 // Method needs GC polls
#define OMF_HAS_FROZEN_STRING 0x00000400 // Method has a frozen string (REF constant int), currently only on NativeAOT.
#define OMF_HAS_PARTIAL_COMPILATION_PATCHPOINT 0x00000800 // Method contains partial compilation patchpoints
#define OMF_HAS_TAILCALL_SUCCESSOR 0x00001000 // Method has potential tail call in a non BBJ_RETURN block
#define OMF_HAS_TAILCALL_SUCCESSOR 0x00001000 // Method has potential tail call in a non BBJ_RETURN block
#define OMF_HAS_MDNEWARRAY 0x00002000 // Method contains 'new' of an MD array
#define OMF_HAS_MDARRAYREF 0x00004000 // Method contains multi-dimensional instrinsic array element loads or stores.

// clang-format on

bool doesMethodHaveFatPointer()
{
Expand Down Expand Up @@ -9234,6 +9308,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
static const bool compUseSoftFP = false;
#endif // ARM_SOFTFP
#endif // CONFIGURABLE_ARM_ABI

// Use early multi-dimensional array operator expansion (expand after loop optimizations; before lowering).
bool compJitEarlyExpandMDArrays;

} opts;

static bool s_pAltJitExcludeAssembliesListInitialized;
Expand Down Expand Up @@ -10726,6 +10804,8 @@ class GenTreeVisitor
case GT_COPY:
case GT_RELOAD:
case GT_ARR_LENGTH:
case GT_MDARR_LENGTH:
case GT_MDARR_LOWER_BOUND:
case GT_CAST:
case GT_BITCAST:
case GT_CKFINITE:
Expand Down
Loading

0 comments on commit cc0ccbe

Please sign in to comment.