Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize multi-dimensional array access #70271

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1495,10 +1495,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 @@ -1549,10 +1548,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 @@ -6750,19 +6818,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 @@ -9232,6 +9306,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 @@ -10724,6 +10802,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