diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 743cd5c4095fda..74f40eb1525523 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -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, @@ -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, @@ -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); diff --git a/src/coreclr/jit/ee_il_dll.hpp b/src/coreclr/jit/ee_il_dll.hpp index 237d43ef762e5c..5ee1c7510d2755 100644 --- a/src/coreclr/jit/ee_il_dll.hpp +++ b/src/coreclr/jit/ee_il_dll.hpp @@ -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) { diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 1022d6a5d4b656..3582870397a990 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -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; } @@ -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; @@ -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; diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index a84f3db3ff473d..3e92e78163cca0 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -2831,6 +2831,84 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr, return call; } +//------------------------------------------------------------------------ +// impStoreNullableFields: create a Nullable object and store +// 'hasValue' (always true) and the given value for 'value' field +// +// Arguments: +// nullableCls - class handle for the Nullable class +// value - value to store in 'value' field +// +// Return Value: +// A local node representing the created Nullable 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 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 local + unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable 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 object +// +// Arguments: +// nullableObj - tree representing the Nullable object +// nullableCls - class handle for the Nullable 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 tmp")); + impStoreToTemp(objTmp, nullableObj, CHECK_SPILL_ALL); + } + else + { + objTmp = nullableObj->AsLclVarCommon()->GetLclNum(); + } + + *hasValueFld = gtNewLclFldNode(objTmp, TYP_UBYTE, hasValOffset); + *valueFld = gtNewLclFldNode(objTmp, valueType, valueOffset, valueLayout); +} + //------------------------------------------------------------------------ // impBoxPatternMatch: match and import common box idioms // @@ -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) @@ -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>, 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) @@ -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; @@ -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()) diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 1b795678e7b000..fab5e97310ac5d 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -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);