Skip to content

Commit

Permalink
JIT: Optimize unused array allocations (#67205)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
  • Loading branch information
3 people authored Apr 4, 2022
1 parent 7281406 commit c830e33
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 53 deletions.
2 changes: 2 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1919,6 +1919,8 @@ struct CORINFO_VarArgInfo

#define SIZEOF__CORINFO_Object TARGET_POINTER_SIZE /* methTable */

#define CORINFO_Array_MaxLength 0x7FFFFFC7

#define OFFSETOF__CORINFO_Array__length SIZEOF__CORINFO_Object
#ifdef TARGET_64BIT
#define OFFSETOF__CORINFO_Array__data (OFFSETOF__CORINFO_Array__length + sizeof(uint32_t) /* length */ + sizeof(uint32_t) /* alignpad */)
Expand Down
52 changes: 0 additions & 52 deletions src/coreclr/jit/earlyprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,58 +29,6 @@ bool Compiler::optDoEarlyPropForBlock(BasicBlock* block)
return bbHasArrayRef || bbHasNullCheck;
}

//------------------------------------------------------------------------------
// getArrayLengthFromAllocation: Return the array length for an array allocation
// helper call.
//
// Arguments:
// tree - The array allocation helper call.
// block - tree's basic block.
//
// Return Value:
// Return the array length node.

GenTree* Compiler::getArrayLengthFromAllocation(GenTree* tree DEBUGARG(BasicBlock* block))
{
assert(tree != nullptr);

GenTree* arrayLength = nullptr;

if (tree->OperGet() == GT_CALL)
{
GenTreeCall* call = tree->AsCall();

if (call->gtCallType == CT_HELPER)
{
if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_DIRECT) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_OBJ) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_VC) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_ALIGN8))
{
// This is an array allocation site. Grab the array length node.
arrayLength = gtArgEntryByArgNum(call, 1)->GetNode();
}
else if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_READYTORUN_NEWARR_1))
{
// On arm when compiling on certain platforms for ready to run, a handle will be
// inserted before the length. To handle this case, we will grab the last argument
// as that's always the length. See fgInitArgInfo for where the handle is inserted.
int arrLenArgNum = call->fgArgInfo->ArgCount() - 1;
arrayLength = gtArgEntryByArgNum(call, arrLenArgNum)->GetNode();
}
#ifdef DEBUG
if (arrayLength != nullptr)
{
optCheckFlagsAreSet(OMF_HAS_NEWARRAY, "OMF_HAS_NEWARRAY", BBF_HAS_NEWARRAY, "BBF_HAS_NEWARRAY", tree,
block);
}
#endif
}
}

return arrayLength;
}

#ifdef DEBUG
//-----------------------------------------------------------------------------
// optCheckFlagsAreSet: Check that the method flag and the basic block flag are set.
Expand Down
87 changes: 87 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,78 @@ bool GenTreeCall::IsPure(Compiler* compiler) const
compiler->s_helperCallProperties.IsPure(compiler->eeGetHelperNum(gtCallMethHnd));
}

//------------------------------------------------------------------------------
// getArrayLengthFromAllocation: Return the array length for an array allocation
// helper call.
//
// Arguments:
// tree - The array allocation helper call.
// block - tree's basic block.
//
// Return Value:
// Return the array length node.

GenTree* Compiler::getArrayLengthFromAllocation(GenTree* tree DEBUGARG(BasicBlock* block))
{
assert(tree != nullptr);

GenTree* arrayLength = nullptr;

if (tree->OperGet() == GT_CALL)
{
GenTreeCall* call = tree->AsCall();
if (call->fgArgInfo == nullptr)
{
// Currently this function is only profitable during the late stages
// so we avoid complicating below code to access the early args.
return nullptr;
}

if (call->gtCallType == CT_HELPER)
{
switch (eeGetHelperNum(call->gtCallMethHnd))
{
case CORINFO_HELP_NEWARR_1_DIRECT:
case CORINFO_HELP_NEWARR_1_OBJ:
case CORINFO_HELP_NEWARR_1_VC:
case CORINFO_HELP_NEWARR_1_ALIGN8:
{
// This is an array allocation site. Grab the array length node.
arrayLength = gtArgEntryByArgNum(call, 1)->GetNode();
break;
}

case CORINFO_HELP_READYTORUN_NEWARR_1:
{
// On arm when compiling on certain platforms for ready to run, a handle will be
// inserted before the length. To handle this case, we will grab the last argument
// as that's always the length. See fgInitArgInfo for where the handle is inserted.
int arrLenArgNum = call->fgArgInfo->ArgCount() - 1;
arrayLength = gtArgEntryByArgNum(call, arrLenArgNum)->GetNode();
break;
}

default:
break;
}
#ifdef DEBUG
if ((arrayLength != nullptr) && (block != nullptr))
{
optCheckFlagsAreSet(OMF_HAS_NEWARRAY, "OMF_HAS_NEWARRAY", BBF_HAS_NEWARRAY, "BBF_HAS_NEWARRAY", tree,
block);
}
#endif
}
}

if (arrayLength != nullptr)
{
arrayLength = arrayLength->OperIsPutArg() ? arrayLength->gtGetOp1() : arrayLength;
}

return arrayLength;
}

//-------------------------------------------------------------------------
// HasSideEffects:
// Returns true if this call has any side effects. All non-helpers are considered to have side-effects. Only helpers
Expand Down Expand Up @@ -1052,6 +1124,21 @@ bool GenTreeCall::HasSideEffects(Compiler* compiler, bool ignoreExceptions, bool
return true;
}

// Consider array allocators side-effect free for constant length (if it's not negative and fits into i32)
if (helperProperties.IsAllocator(helper))
{
GenTree* arrLen = compiler->getArrayLengthFromAllocation((GenTree*)this DEBUGARG(nullptr));
// if arrLen is nullptr it means it wasn't an array allocator
if ((arrLen != nullptr) && arrLen->IsIntCnsFitsInI32())
{
ssize_t cns = arrLen->AsIntConCommon()->IconValue();
if ((cns >= 0) && (cns <= CORINFO_Array_MaxLength))
{
return false;
}
}
}

// If we also care about exceptions then check if the helper can throw
if (!ignoreExceptions && !helperProperties.NoThrow(helper))
{
Expand Down
2 changes: 1 addition & 1 deletion src/tests/GC/API/GC/GetGCMemoryInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static void MakeTemporarySOHAllocations()
int byteArraySize = 1000;
for (int i = 0; i < (totalTempAllocBytes / byteArraySize); i++)
{
byte[] byteArray = new byte[byteArraySize];
GC.KeepAlive(new byte[byteArraySize]);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/tests/profiler/gc/gcallocate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public static int RunTest(String[] args)
{
int[] large = new int[100000];
int[] pinned = GC.AllocateArray<int>(32, true);

// don't let the jit to optimize these allocations
GC.KeepAlive(large);
GC.KeepAlive(pinned);

Console.WriteLine("Test Passed");
return 100;
}
Expand Down

0 comments on commit c830e33

Please sign in to comment.