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 BOX+UNBOX for "T? -> T" and "T -> T?" #104931

Merged
merged 5 commits into from
Jul 17, 2024
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: 6 additions & 1 deletion src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3455,7 +3455,7 @@ class Compiler

GenTree* gtNewMustThrowException(unsigned helper, var_types type, CORINFO_CLASS_HANDLE clsHnd);

GenTreeLclFld* gtNewLclFldNode(unsigned lnum, var_types type, unsigned offset);
GenTreeLclFld* gtNewLclFldNode(unsigned lnum, var_types type, unsigned offset, ClassLayout* layout = nullptr);
GenTreeRetExpr* gtNewInlineCandidateReturnExpr(GenTreeCall* inlineCandidate, var_types type);

GenTreeFieldAddr* gtNewFieldAddrNode(var_types type,
Expand Down Expand Up @@ -4499,6 +4499,10 @@ class Compiler
MakeInlineObservation = 2,
};

GenTree* impStoreNullableFields(CORINFO_CLASS_HANDLE nullableCls,
GenTree* value);
void impLoadNullableFields(GenTree* nullableObj, CORINFO_CLASS_HANDLE nullableCls, GenTree** hasValueFld, GenTree** valueFld);

int impBoxPatternMatch(CORINFO_RESOLVED_TOKEN* pResolvedToken,
const BYTE* codeAddr,
const BYTE* codeEndp,
Expand Down Expand Up @@ -8229,6 +8233,7 @@ class Compiler

bool eeIsValueClass(CORINFO_CLASS_HANDLE clsHnd);
bool eeIsByrefLike(CORINFO_CLASS_HANDLE clsHnd);
bool eeIsSharedInst(CORINFO_CLASS_HANDLE clsHnd);
bool eeIsIntrinsic(CORINFO_METHOD_HANDLE ftn);
bool eeIsFieldStatic(CORINFO_FIELD_HANDLE fldHnd);

Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/ee_il_dll.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ bool Compiler::eeIsByrefLike(CORINFO_CLASS_HANDLE clsHnd)
return (info.compCompHnd->getClassAttribs(clsHnd) & CORINFO_FLG_BYREF_LIKE) != 0;
}

FORCEINLINE
bool Compiler::eeIsSharedInst(CORINFO_CLASS_HANDLE clsHnd)
{
return (info.compCompHnd->getClassAttribs(clsHnd) & CORINFO_FLG_SHAREDINST) != 0;
}

FORCEINLINE
bool Compiler::eeIsIntrinsic(CORINFO_METHOD_HANDLE ftn)
{
Expand Down
10 changes: 4 additions & 6 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8300,9 +8300,9 @@ GenTreeConditional* Compiler::gtNewConditionalNode(
return node;
}

GenTreeLclFld* Compiler::gtNewLclFldNode(unsigned lnum, var_types type, unsigned offset)
GenTreeLclFld* Compiler::gtNewLclFldNode(unsigned lnum, var_types type, unsigned offset, ClassLayout* layout)
{
GenTreeLclFld* node = new (this, GT_LCL_FLD) GenTreeLclFld(GT_LCL_FLD, type, lnum, offset, nullptr);
GenTreeLclFld* node = new (this, GT_LCL_FLD) GenTreeLclFld(GT_LCL_FLD, type, lnum, offset, layout);
return node;
}

Expand Down Expand Up @@ -15037,8 +15037,7 @@ GenTree* Compiler::gtOptimizeEnumHasFlag(GenTree* thisOp, GenTree* flagOp)

// If we have a shared type instance we can't safely check type
// equality, so bail.
DWORD classAttribs = info.compCompHnd->getClassAttribs(thisHnd);
if (classAttribs & CORINFO_FLG_SHAREDINST)
if (eeIsSharedInst(thisHnd))
{
JITDUMP("bailing, have shared instance type\n");
return nullptr;
Expand Down Expand Up @@ -18840,8 +18839,7 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
// processing the call, but we could/should apply
// similar sharpening to the argument and local types
// of the inlinee.
const unsigned retClassFlags = info.compCompHnd->getClassAttribs(objClass);
if (retClassFlags & CORINFO_FLG_SHAREDINST)
if (eeIsSharedInst(objClass))
{
CORINFO_CONTEXT_HANDLE context = inlInfo->exactContextHnd;

Expand Down
119 changes: 115 additions & 4 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2831,6 +2831,84 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr,
return call;
}

//------------------------------------------------------------------------
// impStoreNullableFields: create a Nullable<T> object and store
// 'hasValue' (always true) and the given value for 'value' field
//
// Arguments:
// nullableCls - class handle for the Nullable<T> class
// value - value to store in 'value' field
//
// Return Value:
// A local node representing the created Nullable<T> object
//
GenTree* Compiler::impStoreNullableFields(CORINFO_CLASS_HANDLE nullableCls, GenTree* value)
{
assert(info.compCompHnd->isNullableType(nullableCls) == TypeCompareState::Must);

CORINFO_FIELD_HANDLE valueFldHnd = info.compCompHnd->getFieldInClass(nullableCls, 1);
CORINFO_CLASS_HANDLE valueStructCls;
var_types valueType = JITtype2varType(info.compCompHnd->getFieldType(valueFldHnd, &valueStructCls));

// We still make some assumptions about the layout of Nullable<T> in JIT
static_assert_no_msg(OFFSETOF__CORINFO_NullableOfT__hasValue == 0);
unsigned hasValOffset = OFFSETOF__CORINFO_NullableOfT__hasValue;
unsigned valueOffset = info.compCompHnd->getFieldOffset(valueFldHnd);

// Define the resulting Nullable<T> local
unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable<T> tmp"));
lvaSetStruct(resultTmp, nullableCls, false);

// Now do two stores:
GenTree* hasValueStore = gtNewStoreLclFldNode(resultTmp, TYP_UBYTE, hasValOffset, gtNewIconNode(1));
ClassLayout* layout = valueType == TYP_STRUCT ? typGetObjLayout(valueStructCls) : nullptr;
GenTree* valueStore = gtNewStoreLclFldNode(resultTmp, valueType, layout, valueOffset, value);

impAppendTree(hasValueStore, CHECK_SPILL_ALL, impCurStmtDI);
impAppendTree(valueStore, CHECK_SPILL_ALL, impCurStmtDI);
return gtNewLclvNode(resultTmp, TYP_STRUCT);
}

//------------------------------------------------------------------------
// impLoadNullableFields: get 'hasValue' and 'value' field loads for Nullable<T> object
//
// Arguments:
// nullableObj - tree representing the Nullable<T> object
// nullableCls - class handle for the Nullable<T> class
// hasValueFld - pointer to store the 'hasValue' field load tree
// valueFld - pointer to store the 'value' field load tree
//
void Compiler::impLoadNullableFields(GenTree* nullableObj,
CORINFO_CLASS_HANDLE nullableCls,
GenTree** hasValueFld,
GenTree** valueFld)
{
assert(info.compCompHnd->isNullableType(nullableCls) == TypeCompareState::Must);

CORINFO_FIELD_HANDLE valueFldHnd = info.compCompHnd->getFieldInClass(nullableCls, 1);
CORINFO_CLASS_HANDLE valueStructCls;
var_types valueType = JITtype2varType(info.compCompHnd->getFieldType(valueFldHnd, &valueStructCls));
ClassLayout* valueLayout = valueType == TYP_STRUCT ? typGetObjLayout(valueStructCls) : nullptr;

static_assert_no_msg(OFFSETOF__CORINFO_NullableOfT__hasValue == 0);
unsigned hasValOffset = OFFSETOF__CORINFO_NullableOfT__hasValue;
unsigned valueOffset = info.compCompHnd->getFieldOffset(valueFldHnd);

unsigned objTmp;
if (!nullableObj->OperIs(GT_LCL_VAR))
{
objTmp = lvaGrabTemp(true DEBUGARG("Nullable<T> tmp"));
impStoreToTemp(objTmp, nullableObj, CHECK_SPILL_ALL);
}
else
{
objTmp = nullableObj->AsLclVarCommon()->GetLclNum();
}

*hasValueFld = gtNewLclFldNode(objTmp, TYP_UBYTE, hasValOffset);
*valueFld = gtNewLclFldNode(objTmp, valueType, valueOffset, valueLayout);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}

//------------------------------------------------------------------------
// impBoxPatternMatch: match and import common box idioms
//
Expand Down Expand Up @@ -2896,6 +2974,40 @@ int Compiler::impBoxPatternMatch(CORINFO_RESOLVED_TOKEN* pResolvedToken,
{
optimize = true;
}
//
// Also, try to optimize (T)(object)nullableT
//
else if (!eeIsSharedInst(unboxResolvedToken.hClass) &&
(info.compCompHnd->isNullableType(pResolvedToken->hClass) == TypeCompareState::Must) &&
(info.compCompHnd->getTypeForBox(pResolvedToken->hClass) == unboxResolvedToken.hClass))
{
GenTree* hasValueFldTree;
GenTree* valueFldTree;
impLoadNullableFields(impPopStack().val, pResolvedToken->hClass, &hasValueFldTree,
&valueFldTree);

// Push "hasValue == 0 ? throw new NullReferenceException() : NOP" qmark
GenTree* fallback = gtNewHelperCallNode(CORINFO_HELP_THROWNULLREF, TYP_VOID);
GenTree* cond = gtNewOperNode(GT_EQ, TYP_INT, hasValueFldTree, gtNewIconNode(0));
GenTreeColon* colon = gtNewColonNode(TYP_VOID, fallback, gtNewNothingNode());
GenTree* qmark = gtNewQmarkNode(TYP_VOID, cond, colon);
impAppendTree(qmark, CHECK_SPILL_ALL, impCurStmtDI);

// Now push the value field
impPushOnStack(valueFldTree, typeInfo(valueFldTree->TypeGet()));
optimize = true;
}
//
// Vice versa, try to optimize (T?)(object)nonNullableT
//
else if (!eeIsSharedInst(pResolvedToken->hClass) &&
(info.compCompHnd->isNullableType(unboxResolvedToken.hClass) == TypeCompareState::Must) &&
(info.compCompHnd->getTypeForBox(unboxResolvedToken.hClass) == pResolvedToken->hClass))
{
GenTree* result = impStoreNullableFields(unboxResolvedToken.hClass, impPopStack().val);
impPushOnStack(result, typeInfo(result->TypeGet()));
optimize = true;
}
}

if (optimize)
Expand Down Expand Up @@ -5554,7 +5666,7 @@ GenTree* Compiler::impCastClassOrIsInstToTree(GenTree* op1,
// We can convert constant-ish tokens of nullable to its underlying type.
// However, when the type is shared generic parameter like Nullable<Struct<__Canon>>, the actual type will require
// runtime lookup. It's too complex to add another level of indirection in op2, fallback to the cast helper instead.
if (isClassExact && !(info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_SHAREDINST))
if (isClassExact && !eeIsSharedInst(pResolvedToken->hClass))
{
CORINFO_CLASS_HANDLE hClass = info.compCompHnd->getTypeForBox(pResolvedToken->hClass);
if (hClass != pResolvedToken->hClass)
Expand Down Expand Up @@ -5600,7 +5712,7 @@ GenTree* Compiler::impCastClassOrIsInstToTree(GenTree* op1,
!compCurBB->isRunRarely())
{
// It doesn't make sense to instrument "x is T" or "(T)x" for shared T
if ((info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_SHAREDINST) == 0)
if (!eeIsSharedInst(pResolvedToken->hClass))
{
HandleHistogramProfileCandidateInfo* pInfo =
new (this, CMK_Inlining) HandleHistogramProfileCandidateInfo;
Expand Down Expand Up @@ -9476,8 +9588,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
CORINFO_FIELD_INFO fi;
eeGetFieldInfo(&fldToken, CORINFO_ACCESS_SET, &fi);
unsigned flagsToCheck = CORINFO_FLG_FIELD_STATIC | CORINFO_FLG_FIELD_FINAL;
if (((fi.fieldFlags & flagsToCheck) == flagsToCheck) &&
((info.compCompHnd->getClassAttribs(info.compClassHnd) & CORINFO_FLG_SHAREDINST) == 0))
if (((fi.fieldFlags & flagsToCheck) == flagsToCheck) && !eeIsSharedInst(info.compClassHnd))
{
#ifdef FEATURE_READYTORUN
if (opts.IsReadyToRun())
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11710,7 +11710,7 @@ void Compiler::fgValueNumberTree(GenTree* tree)
{
JITDUMP("IND(obj) is actually a class handle for %s\n", eeGetClassName(handle));
// Filter out all shared generic instantiations
if ((info.compCompHnd->getClassAttribs(handle) & CORINFO_FLG_SHAREDINST) == 0)
if (!eeIsSharedInst(handle))
{
void* pEmbedClsHnd;
void* embedClsHnd = (void*)info.compCompHnd->embedClassHandle(handle, &pEmbedClsHnd);
Expand Down
Loading