@@ -2832,6 +2832,117 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr,
28322832 return call;
28332833}
28342834
2835+ //------------------------------------------------------------------------
2836+ // impInlineUnboxNullable: Generate code for unboxing Nullable<T> from an object (obj)
2837+ // We either inline the unbox operation (if profitable) or call the helper.
2838+ // The inline expansion is as follows:
2839+ //
2840+ // Nullable<T> result;
2841+ // if (obj == null)
2842+ // {
2843+ // result = default;
2844+ // }
2845+ // else if (obj->pMT == <real-boxed-type>)
2846+ // {
2847+ // result._hasValue = true;
2848+ // result._value = *(T*)(obj + sizeof(void*));
2849+ // }
2850+ // else
2851+ // {
2852+ // result = CORINFO_HELP_UNBOX_NULLABLE(&result, nullableCls, obj);
2853+ // }
2854+ //
2855+ // Arguments:
2856+ // nullableCls - class handle representing the Nullable<T> type
2857+ // nullableClsNode - tree node representing the Nullable<T> type (can be a runtime lookup tree)
2858+ // obj - object to unbox
2859+ //
2860+ // Return Value:
2861+ // A local node representing the unboxed value (Nullable<T>)
2862+ //
2863+ GenTree* Compiler::impInlineUnboxNullable(CORINFO_CLASS_HANDLE nullableCls, GenTree* nullableClsNode, GenTree* obj)
2864+ {
2865+ assert(info.compCompHnd->isNullableType(nullableCls) == TypeCompareState::Must);
2866+
2867+ unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable<T> tmp"));
2868+ lvaSetStruct(resultTmp, nullableCls, false);
2869+ lvaGetDesc(resultTmp)->lvHasLdAddrOp = true;
2870+ GenTreeLclFld* resultAddr = gtNewLclAddrNode(resultTmp, 0);
2871+
2872+ // Check profitability of inlining the unbox operation
2873+ bool shouldExpandInline = !compCurBB->isRunRarely() && opts.OptimizationEnabled() && !eeIsSharedInst(nullableCls);
2874+
2875+ // It's less profitable to inline the unbox operation if the underlying type is too large
2876+ CORINFO_CLASS_HANDLE unboxType = NO_CLASS_HANDLE;
2877+ if (shouldExpandInline)
2878+ {
2879+ // The underlying type of the nullable:
2880+ unboxType = info.compCompHnd->getTypeForBox(nullableCls);
2881+ shouldExpandInline = info.compCompHnd->getClassSize(unboxType) <= getUnrollThreshold(Memcpy);
2882+ }
2883+
2884+ if (!shouldExpandInline)
2885+ {
2886+ // No expansion needed, just call the helper
2887+ GenTreeCall* call =
2888+ gtNewHelperCallNode(CORINFO_HELP_UNBOX_NULLABLE, TYP_VOID, resultAddr, nullableClsNode, obj);
2889+ impAppendTree(call, CHECK_SPILL_ALL, impCurStmtDI);
2890+ return gtNewLclvNode(resultTmp, TYP_STRUCT);
2891+ }
2892+
2893+ // Clone the object (and spill side effects)
2894+ GenTree* objClone;
2895+ obj = impCloneExpr(obj, &objClone, CHECK_SPILL_ALL, nullptr DEBUGARG("op1 spilled for Nullable unbox"));
2896+
2897+ // Unbox the object to the result local:
2898+ //
2899+ // result._hasValue = true;
2900+ // result._value = MethodTableLookup(obj);
2901+ //
2902+ CORINFO_FIELD_HANDLE valueFldHnd = info.compCompHnd->getFieldInClass(nullableCls, 1);
2903+ CORINFO_CLASS_HANDLE valueStructCls;
2904+ var_types valueType = JITtype2varType(info.compCompHnd->getFieldType(valueFldHnd, &valueStructCls));
2905+ static_assert_no_msg(OFFSETOF__CORINFO_NullableOfT__hasValue == 0);
2906+ unsigned hasValOffset = OFFSETOF__CORINFO_NullableOfT__hasValue;
2907+ unsigned valueOffset = info.compCompHnd->getFieldOffset(valueFldHnd);
2908+
2909+ GenTree* boxedContentAddr =
2910+ gtNewOperNode(GT_ADD, TYP_BYREF, gtCloneExpr(objClone), gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL));
2911+ // Load the boxed content from the object (op1):
2912+ GenTree* boxedContent = valueType == TYP_STRUCT ? gtNewBlkIndir(typGetObjLayout(valueStructCls), boxedContentAddr)
2913+ : gtNewIndir(valueType, boxedContentAddr);
2914+ // Now do two stores via a comma:
2915+ GenTree* setHasValue = gtNewStoreLclFldNode(resultTmp, TYP_UBYTE, hasValOffset, gtNewIconNode(1));
2916+ GenTree* setValue = gtNewStoreLclFldNode(resultTmp, valueType, valueOffset, boxedContent);
2917+ GenTree* unboxTree = gtNewOperNode(GT_COMMA, TYP_VOID, setHasValue, setValue);
2918+
2919+ // Fallback helper call
2920+ // TODO: Mark as no-return when appropriate
2921+ GenTreeCall* helperCall =
2922+ gtNewHelperCallNode(CORINFO_HELP_UNBOX_NULLABLE, TYP_VOID, resultAddr, nullableClsNode, gtCloneExpr(objClone));
2923+
2924+ // Nested QMARK - "obj->pMT == <boxed-type> ? unboxTree : helperCall"
2925+ assert(unboxType != NO_CLASS_HANDLE);
2926+ GenTree* unboxTypeNode = gtNewIconEmbClsHndNode(unboxType);
2927+ GenTree* objMT = gtNewMethodTableLookup(objClone);
2928+ GenTree* mtLookupCond = gtNewOperNode(GT_NE, TYP_INT, objMT, unboxTypeNode);
2929+ GenTreeColon* mtCheckColon = gtNewColonNode(TYP_VOID, helperCall, unboxTree);
2930+ GenTreeQmark* mtCheckQmark = gtNewQmarkNode(TYP_VOID, mtLookupCond, mtCheckColon);
2931+ mtCheckQmark->SetThenNodeLikelihood(0);
2932+
2933+ // Zero initialize the result in case of "obj == null"
2934+ GenTreeLclVar* zeroInitResultNode = gtNewStoreLclVarNode(resultTmp, gtNewIconNode(0));
2935+
2936+ // Root condition - "obj == null ? zeroInitResultNode : mtCheckQmark"
2937+ GenTree* nullcheck = gtNewOperNode(GT_NE, TYP_INT, obj, gtNewNull());
2938+ GenTreeColon* nullCheckColon = gtNewColonNode(TYP_VOID, mtCheckQmark, zeroInitResultNode);
2939+ GenTreeQmark* nullCheckQmark = gtNewQmarkNode(TYP_VOID, nullcheck, nullCheckColon);
2940+
2941+ // Spill the root QMARK and return the result local
2942+ impAppendTree(nullCheckQmark, CHECK_SPILL_ALL, impCurStmtDI);
2943+ return gtNewLclvNode(resultTmp, TYP_STRUCT);
2944+ }
2945+
28352946//------------------------------------------------------------------------
28362947// impStoreNullableFields: create a Nullable<T> object and store
28372948// 'hasValue' (always true) and the given value for 'value' field
@@ -10098,36 +10209,20 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1009810209 op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL);
1009910210 op1 = gtNewOperNode(GT_ADD, TYP_BYREF, cloneOperand, op2);
1010010211 }
10212+ else if (helper == CORINFO_HELP_UNBOX_NULLABLE)
10213+ {
10214+ // op1 is the object being unboxed
10215+ // op2 is either a class handle node or a runtime lookup node (it's fine to reorder)
10216+ op1 = impInlineUnboxNullable(resolvedToken.hClass, op2, op1);
10217+ }
1010110218 else
1010210219 {
1010310220 // Don't optimize, just call the helper and be done with it
1010410221 JITDUMP("\n Importing %s as helper call because %s\n", opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY",
1010510222 canExpandInline ? "want smaller code or faster jitting" : "inline expansion not legal");
1010610223
10107- if (helper == CORINFO_HELP_UNBOX)
10108- {
10109- op1 = gtNewHelperCallNode(helper, TYP_BYREF, op2, op1);
10110- }
10111- else
10112- {
10113- assert(helper == CORINFO_HELP_UNBOX_NULLABLE);
10114-
10115- // We're going to emit the following sequence of IR:
10116- //
10117- // Nullable<T> result;
10118- // void CORINFO_HELP_UNBOX_NULLABLE(&result, unboxCls, obj);
10119- // push result;
10120- //
10121- unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable<T> tmp"));
10122- lvaSetStruct(resultTmp, resolvedToken.hClass, false);
10123- lvaGetDesc(resultTmp)->lvHasLdAddrOp = true;
10124-
10125- GenTreeLclFld* resultAddr = gtNewLclAddrNode(resultTmp, 0);
10126- // NOTE: it's fine for op2 to be evaluated before op1
10127- GenTreeCall* helperCall = gtNewHelperCallNode(helper, TYP_VOID, resultAddr, op2, op1);
10128- impAppendTree(helperCall, CHECK_SPILL_ALL, impCurStmtDI);
10129- op1 = gtNewLclvNode(resultTmp, TYP_STRUCT);
10130- }
10224+ assert(helper == CORINFO_HELP_UNBOX);
10225+ op1 = gtNewHelperCallNode(helper, TYP_BYREF, op2, op1);
1013110226 }
1013210227
1013310228 assert((helper == CORINFO_HELP_UNBOX && op1->gtType == TYP_BYREF) || // Unbox helper returns a byref.
0 commit comments