Skip to content

Commit cc0ccbe

Browse files
Optimize multi-dimensional array access (#70271)
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.
1 parent dfbc648 commit cc0ccbe

28 files changed

+1188
-316
lines changed

src/coreclr/jit/assertionprop.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ bool IntegralRange::Contains(int64_t value) const
158158
return {SymbolicIntegerValue::Zero, SymbolicIntegerValue::One};
159159

160160
case GT_ARR_LENGTH:
161+
case GT_MDARR_LENGTH:
161162
return {SymbolicIntegerValue::Zero, SymbolicIntegerValue::ArrayLenMax};
162163

163164
case GT_CALL:
@@ -2690,8 +2691,10 @@ void Compiler::optAssertionGen(GenTree* tree)
26902691
break;
26912692

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

26972700
case GT_NULLCHECK:

src/coreclr/jit/block.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,9 @@ void BasicBlock::dspFlags()
438438
{
439439
printf("idxlen ");
440440
}
441-
if (bbFlags & BBF_HAS_NEWARRAY)
441+
if (bbFlags & BBF_HAS_MD_IDX_LEN)
442442
{
443-
printf("new[] ");
443+
printf("mdidxlen ");
444444
}
445445
if (bbFlags & BBF_HAS_NEWOBJ)
446446
{
@@ -512,6 +512,10 @@ void BasicBlock::dspFlags()
512512
{
513513
printf("align ");
514514
}
515+
if (bbFlags & BBF_HAS_MDARRAYREF)
516+
{
517+
printf("mdarr ");
518+
}
515519
}
516520

517521
/*****************************************************************************

src/coreclr/jit/block.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,8 @@ enum BasicBlockFlags : unsigned __int64
511511
// cases, because the BB occurs in a loop, and we've determined that all
512512
// paths in the loop body leading to BB include a call.
513513

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

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

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

556557
// The following are sets of flags.
557558

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

562563
// Flags to update when two blocks are compacted
563564

564-
BBF_COMPACT_UPD = BBF_CHANGED | BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_BACKWARD_JUMP | BBF_HAS_NEWARRAY | \
565-
BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK,
565+
BBF_COMPACT_UPD = BBF_CHANGED | BBF_GC_SAFE_POINT | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_BACKWARD_JUMP | \
566+
BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF,
566567

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

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

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

584-
BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_NEWARRAY | BBF_PROF_WEIGHT | \
585-
BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE,
584+
BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | \
585+
BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_MDARRAYREF,
586586
};
587587

588588
inline constexpr BasicBlockFlags operator ~(BasicBlockFlags a)

src/coreclr/jit/codegenarmarch.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,10 +1507,9 @@ void CodeGen::genCodeForArrIndex(GenTreeArrIndex* arrIndex)
15071507
regNumber tmpReg = arrIndex->GetSingleTempReg();
15081508
assert(tgtReg != tmpReg);
15091509

1510-
unsigned dim = arrIndex->gtCurrDim;
1511-
unsigned rank = arrIndex->gtArrRank;
1512-
var_types elemType = arrIndex->gtArrElemType;
1513-
unsigned offset;
1510+
unsigned dim = arrIndex->gtCurrDim;
1511+
unsigned rank = arrIndex->gtArrRank;
1512+
unsigned offset;
15141513

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

15621561
regNumber tmpReg = arrOffset->GetSingleTempReg();
15631562

1564-
unsigned dim = arrOffset->gtCurrDim;
1565-
unsigned rank = arrOffset->gtArrRank;
1566-
var_types elemType = arrOffset->gtArrElemType;
1567-
unsigned offset = compiler->eeGetMDArrayLengthOffset(rank, dim);
1563+
unsigned dim = arrOffset->gtCurrDim;
1564+
unsigned rank = arrOffset->gtArrRank;
1565+
unsigned offset = compiler->eeGetMDArrayLengthOffset(rank, dim);
15681566

15691567
// Load tmpReg with the dimension size and evaluate
15701568
// tgtReg = offsetReg*tmpReg + indexReg.

src/coreclr/jit/codegenxarch.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4236,16 +4236,17 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree)
42364236

42374237
void CodeGen::genCodeForArrIndex(GenTreeArrIndex* arrIndex)
42384238
{
4239+
assert(!compiler->opts.compJitEarlyExpandMDArrays);
4240+
42394241
GenTree* arrObj = arrIndex->ArrObj();
42404242
GenTree* indexNode = arrIndex->IndexExpr();
42414243

42424244
regNumber arrReg = genConsumeReg(arrObj);
42434245
regNumber indexReg = genConsumeReg(indexNode);
42444246
regNumber tgtReg = arrIndex->GetRegNum();
42454247

4246-
unsigned dim = arrIndex->gtCurrDim;
4247-
unsigned rank = arrIndex->gtArrRank;
4248-
var_types elemType = arrIndex->gtArrElemType;
4248+
unsigned dim = arrIndex->gtCurrDim;
4249+
unsigned rank = arrIndex->gtArrRank;
42494250

42504251
noway_assert(tgtReg != REG_NA);
42514252

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

42804281
void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset)
42814282
{
4283+
assert(!compiler->opts.compJitEarlyExpandMDArrays);
4284+
42824285
GenTree* offsetNode = arrOffset->gtOffset;
42834286
GenTree* indexNode = arrOffset->gtIndex;
42844287
GenTree* arrObj = arrOffset->gtArrObj;
42854288

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

4289-
unsigned dim = arrOffset->gtCurrDim;
4290-
unsigned rank = arrOffset->gtArrRank;
4291-
var_types elemType = arrOffset->gtArrElemType;
4292+
unsigned dim = arrOffset->gtCurrDim;
4293+
unsigned rank = arrOffset->gtArrRank;
42924294

42934295
// First, consume the operands in the correct order.
42944296
regNumber offsetReg = REG_NA;

src/coreclr/jit/compiler.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,8 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
28202820
opts.compJitSaveFpLrWithCalleeSavedRegisters = 0;
28212821
#endif // defined(TARGET_ARM64)
28222822

2823+
opts.compJitEarlyExpandMDArrays = (JitConfig.JitEarlyExpandMDArrays() != 0);
2824+
28232825
#ifdef DEBUG
28242826
opts.dspInstrs = false;
28252827
opts.dspLines = false;
@@ -2992,6 +2994,18 @@ void Compiler::compInitOptions(JitFlags* jitFlags)
29922994
{
29932995
opts.optRepeat = true;
29942996
}
2997+
2998+
// If JitEarlyExpandMDArrays is non-zero, then early MD expansion is enabled.
2999+
// If JitEarlyExpandMDArrays is zero, then conditionally enable it for functions specfied by
3000+
// JitEarlyExpandMDArraysFilter.
3001+
if (JitConfig.JitEarlyExpandMDArrays() == 0)
3002+
{
3003+
if (JitConfig.JitEarlyExpandMDArraysFilter().contains(info.compMethodName, info.compClassName,
3004+
&info.compMethodInfo->args))
3005+
{
3006+
opts.compJitEarlyExpandMDArrays = true;
3007+
}
3008+
}
29953009
}
29963010

29973011
if (verboseDump)
@@ -4839,6 +4853,12 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
48394853
fgDebugCheckLinks();
48404854
#endif
48414855

4856+
// Morph multi-dimensional array operations.
4857+
// (Consider deferring all array operation morphing, including single-dimensional array ops,
4858+
// from global morph to here, so cloning doesn't have to deal with morphed forms.)
4859+
//
4860+
DoPhase(this, PHASE_MORPH_MDARR, &Compiler::fgMorphArrayOps);
4861+
48424862
// Create the variable table (and compute variable ref counts)
48434863
//
48444864
DoPhase(this, PHASE_MARK_LOCAL_VARS, &Compiler::lvaMarkLocalVars);
@@ -9834,7 +9854,7 @@ void cTreeFlags(Compiler* comp, GenTree* tree)
98349854
#endif
98359855
if (tree->gtFlags & GTF_IND_NONFAULTING)
98369856
{
9837-
if (tree->OperIsIndirOrArrLength())
9857+
if (tree->OperIsIndirOrArrMetaData())
98389858
{
98399859
chars += printf("[IND_NONFAULTING]");
98409860
}

src/coreclr/jit/compiler.h

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,8 +2606,14 @@ class Compiler
26062606

26072607
GenTreeIndir* gtNewIndexIndir(GenTreeIndexAddr* indexAddr);
26082608

2609+
void gtAnnotateNewArrLen(GenTree* arrLen, BasicBlock* block);
2610+
26092611
GenTreeArrLen* gtNewArrLen(var_types typ, GenTree* arrayOp, int lenOffset, BasicBlock* block);
26102612

2613+
GenTreeMDArr* gtNewMDArrLen(GenTree* arrayOp, unsigned dim, unsigned rank, BasicBlock* block);
2614+
2615+
GenTreeMDArr* gtNewMDArrLowerBound(GenTree* arrayOp, unsigned dim, unsigned rank, BasicBlock* block);
2616+
26112617
GenTreeIndir* gtNewIndir(var_types typ, GenTree* addr);
26122618

26132619
GenTree* gtNewNullCheck(GenTree* addr, BasicBlock* basicBlock);
@@ -4540,6 +4546,68 @@ class Compiler
45404546

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

4549+
//------------------------------------------------------------------------------------------------------------
4550+
// MorphMDArrayTempCache: a simple cache of compiler temporaries in the local variable table, used to minimize
4551+
// the number of locals allocated when doing early multi-dimensional array operation expansion. Two types of
4552+
// temps are created and cached (due to the two types of temps needed by the MD array expansion): TYP_INT and
4553+
// TYP_REF. `GrabTemp` either returns an available temp from the cache or allocates a new temp and returns it
4554+
// after adding it to the cache. `Reset` makes all the temps in the cache available for subsequent re-use.
4555+
//
4556+
class MorphMDArrayTempCache
4557+
{
4558+
private:
4559+
class TempList
4560+
{
4561+
public:
4562+
TempList(Compiler* compiler)
4563+
: m_compiler(compiler), m_first(nullptr), m_insertPtr(&m_first), m_nextAvail(nullptr)
4564+
{
4565+
}
4566+
4567+
unsigned GetTemp();
4568+
4569+
void Reset()
4570+
{
4571+
m_nextAvail = m_first;
4572+
}
4573+
4574+
private:
4575+
struct Node
4576+
{
4577+
Node(unsigned tmp) : next(nullptr), tmp(tmp)
4578+
{
4579+
}
4580+
4581+
Node* next;
4582+
unsigned tmp;
4583+
};
4584+
4585+
Compiler* m_compiler;
4586+
Node* m_first;
4587+
Node** m_insertPtr;
4588+
Node* m_nextAvail;
4589+
};
4590+
4591+
TempList intTemps; // Temps for genActualType() == TYP_INT
4592+
TempList refTemps; // Temps for TYP_REF
4593+
4594+
public:
4595+
MorphMDArrayTempCache(Compiler* compiler) : intTemps(compiler), refTemps(compiler)
4596+
{
4597+
}
4598+
4599+
unsigned GrabTemp(var_types type);
4600+
4601+
void Reset()
4602+
{
4603+
intTemps.Reset();
4604+
refTemps.Reset();
4605+
}
4606+
};
4607+
4608+
bool fgMorphArrayOpsStmt(MorphMDArrayTempCache* pTempCache, BasicBlock* block, Statement* stmt);
4609+
PhaseStatus fgMorphArrayOps();
4610+
45434611
void fgSetOptions();
45444612

45454613
#ifdef DEBUG
@@ -6752,19 +6820,25 @@ class Compiler
67526820
}
67536821
};
67546822

6755-
#define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an array
6756-
#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type.
6757-
#define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores.
6758-
#define OMF_HAS_NULLCHECK 0x00000008 // Method contains null check.
6759-
#define OMF_HAS_FATPOINTER 0x00000010 // Method contains call, that needs fat pointer transformation.
6760-
#define OMF_HAS_OBJSTACKALLOC 0x00000020 // Method contains an object allocated on the stack.
6761-
#define OMF_HAS_GUARDEDDEVIRT 0x00000040 // Method contains guarded devirtualization candidate
6762-
#define OMF_HAS_EXPRUNTIMELOOKUP 0x00000080 // Method contains a runtime lookup to an expandable dictionary.
6763-
#define OMF_HAS_PATCHPOINT 0x00000100 // Method contains patchpoints
6764-
#define OMF_NEEDS_GCPOLLS 0x00000200 // Method needs GC polls
6765-
#define OMF_HAS_FROZEN_STRING 0x00000400 // Method has a frozen string (REF constant int), currently only on NativeAOT.
6823+
// clang-format off
6824+
6825+
#define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an SD array
6826+
#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type.
6827+
#define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores.
6828+
#define OMF_HAS_NULLCHECK 0x00000008 // Method contains null check.
6829+
#define OMF_HAS_FATPOINTER 0x00000010 // Method contains call, that needs fat pointer transformation.
6830+
#define OMF_HAS_OBJSTACKALLOC 0x00000020 // Method contains an object allocated on the stack.
6831+
#define OMF_HAS_GUARDEDDEVIRT 0x00000040 // Method contains guarded devirtualization candidate
6832+
#define OMF_HAS_EXPRUNTIMELOOKUP 0x00000080 // Method contains a runtime lookup to an expandable dictionary.
6833+
#define OMF_HAS_PATCHPOINT 0x00000100 // Method contains patchpoints
6834+
#define OMF_NEEDS_GCPOLLS 0x00000200 // Method needs GC polls
6835+
#define OMF_HAS_FROZEN_STRING 0x00000400 // Method has a frozen string (REF constant int), currently only on NativeAOT.
67666836
#define OMF_HAS_PARTIAL_COMPILATION_PATCHPOINT 0x00000800 // Method contains partial compilation patchpoints
6767-
#define OMF_HAS_TAILCALL_SUCCESSOR 0x00001000 // Method has potential tail call in a non BBJ_RETURN block
6837+
#define OMF_HAS_TAILCALL_SUCCESSOR 0x00001000 // Method has potential tail call in a non BBJ_RETURN block
6838+
#define OMF_HAS_MDNEWARRAY 0x00002000 // Method contains 'new' of an MD array
6839+
#define OMF_HAS_MDARRAYREF 0x00004000 // Method contains multi-dimensional instrinsic array element loads or stores.
6840+
6841+
// clang-format on
67686842

67696843
bool doesMethodHaveFatPointer()
67706844
{
@@ -9234,6 +9308,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
92349308
static const bool compUseSoftFP = false;
92359309
#endif // ARM_SOFTFP
92369310
#endif // CONFIGURABLE_ARM_ABI
9311+
9312+
// Use early multi-dimensional array operator expansion (expand after loop optimizations; before lowering).
9313+
bool compJitEarlyExpandMDArrays;
9314+
92379315
} opts;
92389316

92399317
static bool s_pAltJitExcludeAssembliesListInitialized;
@@ -10726,6 +10804,8 @@ class GenTreeVisitor
1072610804
case GT_COPY:
1072710805
case GT_RELOAD:
1072810806
case GT_ARR_LENGTH:
10807+
case GT_MDARR_LENGTH:
10808+
case GT_MDARR_LOWER_BOUND:
1072910809
case GT_CAST:
1073010810
case GT_BITCAST:
1073110811
case GT_CKFINITE:

0 commit comments

Comments
 (0)