Skip to content

Commit

Permalink
JIT: Skip more covariance checks (#107116)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
EgorBo and jkotas committed Sep 4, 2024
1 parent eec40f1 commit f8e9dd1
Show file tree
Hide file tree
Showing 20 changed files with 210 additions and 191 deletions.
6 changes: 3 additions & 3 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2750,12 +2750,12 @@ class ICorStaticInfo
// the field's value class (if 'structType' == 0, then don't bother
// the structure info).
//
// 'memberParent' is typically only set when verifying. It should be the
// result of calling getMemberParent.
// 'fieldOwnerHint' is, potentially, a more exact owner of the field.
// it's fine for it to be non-precise, it's just a hint.
virtual CorInfoType getFieldType(
CORINFO_FIELD_HANDLE field,
CORINFO_CLASS_HANDLE * structType = NULL,
CORINFO_CLASS_HANDLE memberParent = NULL /* IN */
CORINFO_CLASS_HANDLE fieldOwnerHint = NULL /* IN */
) = 0;

// return the data member's instance offset
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ CORINFO_CLASS_HANDLE getFieldClass(
CorInfoType getFieldType(
CORINFO_FIELD_HANDLE field,
CORINFO_CLASS_HANDLE* structType,
CORINFO_CLASS_HANDLE memberParent) override;
CORINFO_CLASS_HANDLE fieldOwnerHint) override;

unsigned getFieldOffset(
CORINFO_FIELD_HANDLE field) override;
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -939,10 +939,10 @@ CORINFO_CLASS_HANDLE WrapICorJitInfo::getFieldClass(
CorInfoType WrapICorJitInfo::getFieldType(
CORINFO_FIELD_HANDLE field,
CORINFO_CLASS_HANDLE* structType,
CORINFO_CLASS_HANDLE memberParent)
CORINFO_CLASS_HANDLE fieldOwnerHint)
{
API_ENTER(getFieldType);
CorInfoType temp = wrapHnd->getFieldType(field, structType, memberParent);
CorInfoType temp = wrapHnd->getFieldType(field, structType, fieldOwnerHint);
API_LEAVE(getFieldType);
return temp;
}
Expand Down
8 changes: 5 additions & 3 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3695,6 +3695,8 @@ class Compiler
return callMethodHandle == info.compMethodHnd;
}

bool gtCanSkipCovariantStoreCheck(GenTree* value, GenTree* array);

//-------------------------------------------------------------------------

GenTree* gtFoldExpr(GenTree* tree);
Expand Down Expand Up @@ -5153,8 +5155,6 @@ class Compiler
bool impIsImplicitTailCallCandidate(
OPCODE curOpcode, const BYTE* codeAddrOfNextOpcode, const BYTE* codeEnd, int prefixFlags, bool isRecursive);

bool impCanSkipCovariantStoreCheck(GenTree* value, GenTree* array);

methodPointerInfo* impAllocateMethodPointerInfo(const CORINFO_RESOLVED_TOKEN& token, mdToken tokenConstrained);

/*
Expand Down Expand Up @@ -8294,7 +8294,9 @@ class Compiler
bool eeIsIntrinsic(CORINFO_METHOD_HANDLE ftn);
bool eeIsFieldStatic(CORINFO_FIELD_HANDLE fldHnd);

var_types eeGetFieldType(CORINFO_FIELD_HANDLE fldHnd, CORINFO_CLASS_HANDLE* pStructHnd = nullptr);
var_types eeGetFieldType(CORINFO_FIELD_HANDLE fldHnd,
CORINFO_CLASS_HANDLE* pStructHnd = nullptr,
CORINFO_CLASS_HANDLE memberParent = NO_CLASS_HANDLE);

template <typename TPrint>
void eeAppendPrint(class StringPrinter* printer, TPrint print);
Expand Down
6 changes: 4 additions & 2 deletions src/coreclr/jit/ee_il_dll.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,11 @@ bool Compiler::eeIsFieldStatic(CORINFO_FIELD_HANDLE fldHnd)
}

FORCEINLINE
var_types Compiler::eeGetFieldType(CORINFO_FIELD_HANDLE fldHnd, CORINFO_CLASS_HANDLE* pStructHnd)
var_types Compiler::eeGetFieldType(CORINFO_FIELD_HANDLE fldHnd,
CORINFO_CLASS_HANDLE* pStructHnd,
CORINFO_CLASS_HANDLE fieldOwnerHint)
{
return JITtype2varType(info.compCompHnd->getFieldType(fldHnd, pStructHnd));
return JITtype2varType(info.compCompHnd->getFieldType(fldHnd, pStructHnd, fieldOwnerHint));
}

FORCEINLINE
Expand Down
124 changes: 122 additions & 2 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19168,10 +19168,17 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
// No benefit to calling gtGetFieldClassHandle here, as
// the exact field being accessed can vary.
CORINFO_FIELD_HANDLE fieldHnd = fieldSeq->GetFieldHandle();
CORINFO_CLASS_HANDLE fieldOwner = NO_CLASS_HANDLE;
CORINFO_CLASS_HANDLE fieldClass = NO_CLASS_HANDLE;
var_types fieldType = eeGetFieldType(fieldHnd, &fieldClass);

if (fieldType == TYP_REF)
// fieldOwner helps us to get a more exact field class for instance fields
if (!fieldSeq->IsStaticField())
{
bool objIsExact, objIsNonNull;
fieldOwner = gtGetClassHandle(op1, &objIsExact, &objIsNonNull);
}

if (eeGetFieldType(fieldHnd, &fieldClass, fieldOwner) == TYP_REF)
{
objClass = fieldClass;
}
Expand Down Expand Up @@ -31840,3 +31847,116 @@ GenTree* Compiler::gtFoldExprHWIntrinsic(GenTreeHWIntrinsic* tree)
return resultNode;
}
#endif // FEATURE_HW_INTRINSICS

//------------------------------------------------------------------------
// gtCanSkipCovariantStoreCheck: see if storing a ref type value to an array
// can skip the array store covariance check.
//
// Arguments:
// value -- tree producing the value to store
// array -- tree representing the array to store to
//
// Returns:
// true if the store does not require a covariance check.
//
bool Compiler::gtCanSkipCovariantStoreCheck(GenTree* value, GenTree* array)
{
// We should only call this when optimizing.
assert(opts.OptimizationEnabled());

// Check for store to same array, ie. arrLcl[i] = arrLcl[j]
if (value->OperIs(GT_IND) && value->AsIndir()->Addr()->OperIs(GT_INDEX_ADDR) && array->OperIs(GT_LCL_VAR))
{
GenTree* valueArray = value->AsIndir()->Addr()->AsIndexAddr()->Arr();
if (valueArray->OperIs(GT_LCL_VAR))
{
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;
}
}
}

// Check for store of NULL.
if (value->OperIs(GT_CNS_INT))
{
assert(value->gtType == TYP_REF);
if (value->AsIntCon()->gtIconVal == 0)
{
JITDUMP("\nstelem of null: skipping covariant store check\n");
return true;
}
// Non-0 const refs can only occur with frozen objects
assert(value->IsIconHandle(GTF_ICON_OBJ_HDL));
assert(doesMethodHaveFrozenObjects() ||
(compIsForInlining() && impInlineInfo->InlinerCompiler->doesMethodHaveFrozenObjects()));
}

// Try and get a class handle for the array
if (!value->TypeIs(TYP_REF))
{
return false;
}

bool arrayIsExact = false;
bool arrayIsNonNull = false;
CORINFO_CLASS_HANDLE arrayHandle = gtGetClassHandle(array, &arrayIsExact, &arrayIsNonNull);

if (arrayHandle == NO_CLASS_HANDLE)
{
return false;
}

// There are some methods in corelib where we're storing to an array but the IL
// doesn't reflect this (see SZArrayHelper). Avoid.
DWORD attribs = info.compCompHnd->getClassAttribs(arrayHandle);
if ((attribs & CORINFO_FLG_ARRAY) == 0)
{
return false;
}

CORINFO_CLASS_HANDLE arrayElementHandle = nullptr;
CorInfoType arrayElemType = info.compCompHnd->getChildType(arrayHandle, &arrayElementHandle);

// Verify array type handle is really an array of ref type
assert(arrayElemType == CORINFO_TYPE_CLASS);

// Check for exactly object[]
if (arrayIsExact && (arrayElementHandle == impGetObjectClass()))
{
JITDUMP("\nstelem to (exact) object[]: skipping covariant store check\n");
return true;
}

const bool arrayTypeIsSealed = info.compCompHnd->isExactType(arrayElementHandle);

if ((!arrayIsExact && !arrayTypeIsSealed) || (arrayElementHandle == NO_CLASS_HANDLE))
{
// Bail out if we don't know array's exact type
return false;
}

bool valueIsExact = false;
bool valueIsNonNull = false;
CORINFO_CLASS_HANDLE valueHandle = gtGetClassHandle(value, &valueIsExact, &valueIsNonNull);

// Array's type is sealed and equals to value's type
if (arrayTypeIsSealed && (valueHandle == arrayElementHandle))
{
JITDUMP("\nstelem to T[] with T exact: skipping covariant store check\n");
return true;
}

// Array's type is not sealed but we know its exact type
if (arrayIsExact && (valueHandle != NO_CLASS_HANDLE) &&
(info.compCompHnd->compareTypesForCast(valueHandle, arrayElementHandle) == TypeCompareState::Must))
{
JITDUMP("\nstelem to T[] with T exact: skipping covariant store check\n");
return true;
}

return false;
}
117 changes: 2 additions & 115 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7069,7 +7069,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
lclTyp = JITtype2varType(info.compCompHnd->asCorInfoType(ldelemClsHnd));

// If it's a value class / pointer array, or a readonly access, we don't need a type check.
// TODO-CQ: adapt "impCanSkipCovariantStoreCheck" to handle "ldelema"s and call it here to
// TODO-CQ: adapt "gtCanSkipCovariantStoreCheck" to handle "ldelema"s and call it here to
// skip using the helper in more cases.
if ((lclTyp != TYP_REF) || ((prefixFlags & PREFIX_READONLY) != 0))
{
Expand Down Expand Up @@ -7208,7 +7208,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
if (opts.OptimizationEnabled())
{
// Is this a case where we can skip the covariant store check?
if (impCanSkipCovariantStoreCheck(value, array))
if (gtCanSkipCovariantStoreCheck(value, array))
{
lclTyp = TYP_REF;
goto ARR_ST;
Expand Down Expand Up @@ -13898,116 +13898,3 @@ methodPointerInfo* Compiler::impAllocateMethodPointerInfo(const CORINFO_RESOLVED
memory->m_tokenConstraint = tokenConstrained;
return memory;
}

//------------------------------------------------------------------------
// impCanSkipCovariantStoreCheck: see if storing a ref type value to an array
// can skip the array store covariance check.
//
// Arguments:
// value -- tree producing the value to store
// array -- tree representing the array to store to
//
// Returns:
// true if the store does not require a covariance check.
//
bool Compiler::impCanSkipCovariantStoreCheck(GenTree* value, GenTree* array)
{
// We should only call this when optimizing.
assert(opts.OptimizationEnabled());

// Check for store to same array, ie. arrLcl[i] = arrLcl[j]
if (value->OperIs(GT_IND) && value->AsIndir()->Addr()->OperIs(GT_INDEX_ADDR) && array->OperIs(GT_LCL_VAR))
{
GenTree* valueArray = value->AsIndir()->Addr()->AsIndexAddr()->Arr();
if (valueArray->OperIs(GT_LCL_VAR))
{
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;
}
}
}

// Check for store of NULL.
if (value->OperIs(GT_CNS_INT))
{
assert(value->gtType == TYP_REF);
if (value->AsIntCon()->gtIconVal == 0)
{
JITDUMP("\nstelem of null: skipping covariant store check\n");
return true;
}
// Non-0 const refs can only occur with frozen objects
assert(value->IsIconHandle(GTF_ICON_OBJ_HDL));
assert(doesMethodHaveFrozenObjects() ||
(compIsForInlining() && impInlineInfo->InlinerCompiler->doesMethodHaveFrozenObjects()));
}

// Try and get a class handle for the array
if (value->gtType != TYP_REF)
{
return false;
}

bool arrayIsExact = false;
bool arrayIsNonNull = false;
CORINFO_CLASS_HANDLE arrayHandle = gtGetClassHandle(array, &arrayIsExact, &arrayIsNonNull);

if (arrayHandle == NO_CLASS_HANDLE)
{
return false;
}

// There are some methods in corelib where we're storing to an array but the IL
// doesn't reflect this (see SZArrayHelper). Avoid.
DWORD attribs = info.compCompHnd->getClassAttribs(arrayHandle);
if ((attribs & CORINFO_FLG_ARRAY) == 0)
{
return false;
}

CORINFO_CLASS_HANDLE arrayElementHandle = nullptr;
CorInfoType arrayElemType = info.compCompHnd->getChildType(arrayHandle, &arrayElementHandle);

// Verify array type handle is really an array of ref type
assert(arrayElemType == CORINFO_TYPE_CLASS);

// Check for exactly object[]
if (arrayIsExact && (arrayElementHandle == impGetObjectClass()))
{
JITDUMP("\nstelem to (exact) object[]: skipping covariant store check\n");
return true;
}

const bool arrayTypeIsSealed = info.compCompHnd->isExactType(arrayElementHandle);

if ((!arrayIsExact && !arrayTypeIsSealed) || (arrayElementHandle == NO_CLASS_HANDLE))
{
// Bail out if we don't know array's exact type
return false;
}

bool valueIsExact = false;
bool valueIsNonNull = false;
CORINFO_CLASS_HANDLE valueHandle = gtGetClassHandle(value, &valueIsExact, &valueIsNonNull);

// Array's type is sealed and equals to value's type
if (arrayTypeIsSealed && (valueHandle == arrayElementHandle))
{
JITDUMP("\nstelem to T[] with T exact: skipping covariant store check\n");
return true;
}

// Array's type is not sealed but we know its exact type
if (arrayIsExact && (valueHandle != NO_CLASS_HANDLE) &&
(info.compCompHnd->compareTypesForCast(valueHandle, arrayElementHandle) == TypeCompareState::Must))
{
JITDUMP("\nstelem to T[] with T exact: skipping covariant store check\n");
return true;
}

return false;
}
7 changes: 2 additions & 5 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2908,12 +2908,9 @@ GenTree* Compiler::impCreateSpanIntrinsic(CORINFO_SIG_INFO* sig)
return nullptr;
}

CORINFO_CLASS_HANDLE fieldOwnerHnd = info.compCompHnd->getFieldClass(fieldToken);

CORINFO_CLASS_HANDLE fieldClsHnd;
var_types fieldElementType =
JITtype2varType(info.compCompHnd->getFieldType(fieldToken, &fieldClsHnd, fieldOwnerHnd));
unsigned totalFieldSize;
var_types fieldElementType = JITtype2varType(info.compCompHnd->getFieldType(fieldToken, &fieldClsHnd));
unsigned totalFieldSize;

// Most static initialization data fields are of some structure, but it is possible for them to be of various
// primitive types as well
Expand Down
13 changes: 5 additions & 8 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7088,18 +7088,15 @@ GenTree* Compiler::fgMorphCall(GenTreeCall* call)

// Morph stelem.ref helper call to store a null value, into a store into an array without the helper.
// This needs to be done after the arguments are morphed to ensure constant propagation has already taken place.
if (opts.OptimizationEnabled() && call->IsHelperCall() &&
(call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ARRADDR_ST)))
if (opts.OptimizationEnabled() && call->IsHelperCall(this, CORINFO_HELP_ARRADDR_ST))
{
assert(call->gtArgs.CountArgs() == 3);
GenTree* arr = call->gtArgs.GetArgByIndex(0)->GetNode();
GenTree* index = call->gtArgs.GetArgByIndex(1)->GetNode();
GenTree* value = call->gtArgs.GetArgByIndex(2)->GetNode();
if (value->IsIntegralConst(0))
{
assert(value->OperGet() == GT_CNS_INT);

GenTree* arr = call->gtArgs.GetArgByIndex(0)->GetNode();
GenTree* index = call->gtArgs.GetArgByIndex(1)->GetNode();

if (gtCanSkipCovariantStoreCheck(value, arr))
{
// Either or both of the array and index arguments may have been spilled to temps by `fgMorphArgs`. Copy
// the spill trees as well if necessary.
GenTree* argSetup = nullptr;
Expand Down
Loading

0 comments on commit f8e9dd1

Please sign in to comment.