diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e7822dc591597b..6115bd18795d68 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4514,6 +4514,7 @@ class Compiler GenTree* impStoreNullableFields(CORINFO_CLASS_HANDLE nullableCls, GenTree* value); + GenTree* impInlineUnboxNullable(CORINFO_CLASS_HANDLE nullableCls, GenTree* nullableClsNode, GenTree* obj); void impLoadNullableFields(GenTree* nullableObj, CORINFO_CLASS_HANDLE nullableCls, GenTree** hasValueFld, GenTree** valueFld); int impBoxPatternMatch(CORINFO_RESOLVED_TOKEN* pResolvedToken, diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 6b4323de0c5491..ad0c4d2b902813 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -2832,6 +2832,117 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr, return call; } +//------------------------------------------------------------------------ +// impInlineUnboxNullable: Generate code for unboxing Nullable from an object (obj) +// We either inline the unbox operation (if profitable) or call the helper. +// The inline expansion is as follows: +// +// Nullable result; +// if (obj == null) +// { +// result = default; +// } +// else if (obj->pMT == ) +// { +// result._hasValue = true; +// result._value = *(T*)(obj + sizeof(void*)); +// } +// else +// { +// result = CORINFO_HELP_UNBOX_NULLABLE(&result, nullableCls, obj); +// } +// +// Arguments: +// nullableCls - class handle representing the Nullable type +// nullableClsNode - tree node representing the Nullable type (can be a runtime lookup tree) +// obj - object to unbox +// +// Return Value: +// A local node representing the unboxed value (Nullable) +// +GenTree* Compiler::impInlineUnboxNullable(CORINFO_CLASS_HANDLE nullableCls, GenTree* nullableClsNode, GenTree* obj) +{ + assert(info.compCompHnd->isNullableType(nullableCls) == TypeCompareState::Must); + + unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable tmp")); + lvaSetStruct(resultTmp, nullableCls, false); + lvaGetDesc(resultTmp)->lvHasLdAddrOp = true; + GenTreeLclFld* resultAddr = gtNewLclAddrNode(resultTmp, 0); + + // Check profitability of inlining the unbox operation + bool shouldExpandInline = !compCurBB->isRunRarely() && opts.OptimizationEnabled() && !eeIsSharedInst(nullableCls); + + // It's less profitable to inline the unbox operation if the underlying type is too large + CORINFO_CLASS_HANDLE unboxType = NO_CLASS_HANDLE; + if (shouldExpandInline) + { + // The underlying type of the nullable: + unboxType = info.compCompHnd->getTypeForBox(nullableCls); + shouldExpandInline = info.compCompHnd->getClassSize(unboxType) <= getUnrollThreshold(Memcpy); + } + + if (!shouldExpandInline) + { + // No expansion needed, just call the helper + GenTreeCall* call = + gtNewHelperCallNode(CORINFO_HELP_UNBOX_NULLABLE, TYP_VOID, resultAddr, nullableClsNode, obj); + impAppendTree(call, CHECK_SPILL_ALL, impCurStmtDI); + return gtNewLclvNode(resultTmp, TYP_STRUCT); + } + + // Clone the object (and spill side effects) + GenTree* objClone; + obj = impCloneExpr(obj, &objClone, CHECK_SPILL_ALL, nullptr DEBUGARG("op1 spilled for Nullable unbox")); + + // Unbox the object to the result local: + // + // result._hasValue = true; + // result._value = MethodTableLookup(obj); + // + CORINFO_FIELD_HANDLE valueFldHnd = info.compCompHnd->getFieldInClass(nullableCls, 1); + CORINFO_CLASS_HANDLE valueStructCls; + var_types valueType = JITtype2varType(info.compCompHnd->getFieldType(valueFldHnd, &valueStructCls)); + static_assert_no_msg(OFFSETOF__CORINFO_NullableOfT__hasValue == 0); + unsigned hasValOffset = OFFSETOF__CORINFO_NullableOfT__hasValue; + unsigned valueOffset = info.compCompHnd->getFieldOffset(valueFldHnd); + + GenTree* boxedContentAddr = + gtNewOperNode(GT_ADD, TYP_BYREF, gtCloneExpr(objClone), gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL)); + // Load the boxed content from the object (op1): + GenTree* boxedContent = valueType == TYP_STRUCT ? gtNewBlkIndir(typGetObjLayout(valueStructCls), boxedContentAddr) + : gtNewIndir(valueType, boxedContentAddr); + // Now do two stores via a comma: + GenTree* setHasValue = gtNewStoreLclFldNode(resultTmp, TYP_UBYTE, hasValOffset, gtNewIconNode(1)); + GenTree* setValue = gtNewStoreLclFldNode(resultTmp, valueType, valueOffset, boxedContent); + GenTree* unboxTree = gtNewOperNode(GT_COMMA, TYP_VOID, setHasValue, setValue); + + // Fallback helper call + // TODO: Mark as no-return when appropriate + GenTreeCall* helperCall = + gtNewHelperCallNode(CORINFO_HELP_UNBOX_NULLABLE, TYP_VOID, resultAddr, nullableClsNode, gtCloneExpr(objClone)); + + // Nested QMARK - "obj->pMT == ? unboxTree : helperCall" + assert(unboxType != NO_CLASS_HANDLE); + GenTree* unboxTypeNode = gtNewIconEmbClsHndNode(unboxType); + GenTree* objMT = gtNewMethodTableLookup(objClone); + GenTree* mtLookupCond = gtNewOperNode(GT_NE, TYP_INT, objMT, unboxTypeNode); + GenTreeColon* mtCheckColon = gtNewColonNode(TYP_VOID, helperCall, unboxTree); + GenTreeQmark* mtCheckQmark = gtNewQmarkNode(TYP_VOID, mtLookupCond, mtCheckColon); + mtCheckQmark->SetThenNodeLikelihood(0); + + // Zero initialize the result in case of "obj == null" + GenTreeLclVar* zeroInitResultNode = gtNewStoreLclVarNode(resultTmp, gtNewIconNode(0)); + + // Root condition - "obj == null ? zeroInitResultNode : mtCheckQmark" + GenTree* nullcheck = gtNewOperNode(GT_NE, TYP_INT, obj, gtNewNull()); + GenTreeColon* nullCheckColon = gtNewColonNode(TYP_VOID, mtCheckQmark, zeroInitResultNode); + GenTreeQmark* nullCheckQmark = gtNewQmarkNode(TYP_VOID, nullcheck, nullCheckColon); + + // Spill the root QMARK and return the result local + impAppendTree(nullCheckQmark, CHECK_SPILL_ALL, impCurStmtDI); + return gtNewLclvNode(resultTmp, TYP_STRUCT); +} + //------------------------------------------------------------------------ // impStoreNullableFields: create a Nullable object and store // 'hasValue' (always true) and the given value for 'value' field @@ -10098,36 +10209,20 @@ void Compiler::impImportBlockCode(BasicBlock* block) op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); op1 = gtNewOperNode(GT_ADD, TYP_BYREF, cloneOperand, op2); } + else if (helper == CORINFO_HELP_UNBOX_NULLABLE) + { + // op1 is the object being unboxed + // op2 is either a class handle node or a runtime lookup node (it's fine to reorder) + op1 = impInlineUnboxNullable(resolvedToken.hClass, op2, op1); + } else { // Don't optimize, just call the helper and be done with it JITDUMP("\n Importing %s as helper call because %s\n", opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY", canExpandInline ? "want smaller code or faster jitting" : "inline expansion not legal"); - if (helper == CORINFO_HELP_UNBOX) - { - op1 = gtNewHelperCallNode(helper, TYP_BYREF, op2, op1); - } - else - { - assert(helper == CORINFO_HELP_UNBOX_NULLABLE); - - // We're going to emit the following sequence of IR: - // - // Nullable result; - // void CORINFO_HELP_UNBOX_NULLABLE(&result, unboxCls, obj); - // push result; - // - unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable tmp")); - lvaSetStruct(resultTmp, resolvedToken.hClass, false); - lvaGetDesc(resultTmp)->lvHasLdAddrOp = true; - - GenTreeLclFld* resultAddr = gtNewLclAddrNode(resultTmp, 0); - // NOTE: it's fine for op2 to be evaluated before op1 - GenTreeCall* helperCall = gtNewHelperCallNode(helper, TYP_VOID, resultAddr, op2, op1); - impAppendTree(helperCall, CHECK_SPILL_ALL, impCurStmtDI); - op1 = gtNewLclvNode(resultTmp, TYP_STRUCT); - } + assert(helper == CORINFO_HELP_UNBOX); + op1 = gtNewHelperCallNode(helper, TYP_BYREF, op2, op1); } assert((helper == CORINFO_HELP_UNBOX && op1->gtType == TYP_BYREF) || // Unbox helper returns a byref.