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

JIT: Skip more covariance checks #107116

Merged
merged 19 commits into from
Sep 4, 2024
8 changes: 5 additions & 3 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3692,6 +3692,8 @@ class Compiler
return callMethodHandle == info.compMethodHnd;
}

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

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

GenTree* gtFoldExpr(GenTree* tree);
Expand Down Expand Up @@ -5150,8 +5152,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 @@ -8295,7 +8295,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 memberParent)
{
return JITtype2varType(info.compCompHnd->getFieldType(fldHnd, pStructHnd));
return JITtype2varType(info.compCompHnd->getFieldType(fldHnd, pStructHnd, memberParent));
}

FORCEINLINE
Expand Down
125 changes: 123 additions & 2 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19168,10 +19168,18 @@ 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);
}

// TODO: implement for NAOT in this PR..
if (!opts.IsReadyToRun() && eeGetFieldType(fieldHnd, &fieldClass, fieldOwner) == TYP_REF)
{
objClass = fieldClass;
}
Expand Down Expand Up @@ -31840,3 +31848,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.
//
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No changes here, it's just moved from importer.cpp and renamed.

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 @@ -7079,7 +7079,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 @@ -7218,7 +7218,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 @@ -13908,116 +13908,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
13 changes: 4 additions & 9 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2902,18 +2902,13 @@ private bool isMoreSpecificType(CORINFO_CLASS_STRUCT_* cls1, CORINFO_CLASS_STRUC
TypeDesc type1 = HandleToObject(cls1);
TypeDesc type2 = HandleToObject(cls2);

// If we have a mixture of shared and unshared types,
// consider the unshared type as more specific.
bool isType1CanonSubtype = type1.IsCanonicalSubtype(CanonicalFormKind.Any);
bool isType2CanonSubtype = type2.IsCanonicalSubtype(CanonicalFormKind.Any);
if (isType1CanonSubtype != isType2CanonSubtype)
// If both types have the same type definition while
// hnd1 is shared and hnd2 is not - consider hnd2 more specific.
if (type1.HasSameTypeDefinition(type2))
{
// Only one of type1 and type2 is shared.
// type2 is more specific if type1 is the shared type.
return isType1CanonSubtype;
return type1.IsCanonicalSubtype(CanonicalFormKind.Any) && !type2.IsCanonicalSubtype(CanonicalFormKind.Any);
}

// Otherwise both types are either shared or not shared.
// Look for a common parent type.
TypeDesc merged = TypeExtensions.MergeTypesToCommonParent(type1, type2);

Expand Down
Loading
Loading