Fix various fgMorphInitBlock issues (dotnet/coreclr#21820)
authormikedn <onemihaid@hotmail.com>
Mon, 28 Jan 2019 19:38:19 +0000 (21:38 +0200)
committerSergey Andreenko <seandree@microsoft.com>
Mon, 28 Jan 2019 19:38:19 +0000 (11:38 -0800)
* Fix various fgMorphInitBlock issues

* Remove unnecessary destAddr variable

* Extend/fix comment headers

Commit migrated from https://github.com/dotnet/coreclr/commit/1a1e4c4d5a8030cb8d82a2e5b06c2ab357b92534

src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/gentree.cpp
src/coreclr/src/jit/gentree.h
src/coreclr/src/jit/morph.cpp
src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_21761/GitHub_21761.il [new file with mode: 0644]
src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_21761/GitHub_21761.ilproj [new file with mode: 0644]

index 4bd410a..c8bae5f 100644 (file)
@@ -2340,7 +2340,7 @@ public:
 
     GenTree* gtNewLconNode(__int64 value);
 
-    GenTree* gtNewDconNode(double value);
+    GenTree* gtNewDconNode(double value, var_types type = TYP_DOUBLE);
 
     GenTree* gtNewSconNode(int CPX, CORINFO_MODULE_HANDLE scpHandle);
 
@@ -5262,6 +5262,7 @@ private:
     void fgAssignSetVarDef(GenTree* tree);
     GenTree* fgMorphOneAsgBlockOp(GenTree* tree);
     GenTree* fgMorphInitBlock(GenTree* tree);
+    GenTree* fgMorphPromoteLocalInitBlock(GenTreeLclVar* destLclNode, GenTree* initVal, unsigned blockSize);
     GenTree* fgMorphBlkToInd(GenTreeBlk* tree, var_types type);
     GenTree* fgMorphGetStructAddr(GenTree** pTree, CORINFO_CLASS_HANDLE clsHnd, bool isRValue = false);
     GenTree* fgMorphBlkNode(GenTree* tree, bool isDest);
index 4610579..d1e1298 100644 (file)
@@ -5638,9 +5638,9 @@ GenTree* Compiler::gtNewLconNode(__int64 value)
     return node;
 }
 
-GenTree* Compiler::gtNewDconNode(double value)
+GenTree* Compiler::gtNewDconNode(double value, var_types type)
 {
-    GenTree* node = new (this, GT_CNS_DBL) GenTreeDblCon(value);
+    GenTree* node = new (this, GT_CNS_DBL) GenTreeDblCon(value, type);
 
     return node;
 }
index 775cfa6..b652552 100644 (file)
@@ -2645,8 +2645,9 @@ struct GenTreeDblCon : public GenTree
         return (bits == otherBits);
     }
 
-    GenTreeDblCon(double val) : GenTree(GT_CNS_DBL, TYP_DOUBLE), gtDconVal(val)
+    GenTreeDblCon(double val, var_types type = TYP_DOUBLE) : GenTree(GT_CNS_DBL, type), gtDconVal(val)
     {
+        assert(varTypeIsFloating(type));
     }
 #if DEBUGGABLE_GENTREE
     GenTreeDblCon() : GenTree()
index ab1bbfa..efe8ec9 100644 (file)
@@ -9282,23 +9282,25 @@ GenTree* Compiler::fgMorphOneAsgBlockOp(GenTree* tree)
 }
 
 //------------------------------------------------------------------------
-// fgMorphInitBlock: Perform the Morphing of a GT_INITBLK node
+// fgMorphInitBlock: Morph a block initialization assignment tree.
 //
 // Arguments:
-//    tree - a tree node with a gtOper of GT_INITBLK
-//           the child nodes for tree have already been Morphed
+//    tree - A GT_ASG tree that performs block initialization
 //
 // Return Value:
-//    We can return the orginal GT_INITBLK unmodified (least desirable, but always correct)
-//    We can return a single assignment, when fgMorphOneAsgBlockOp transforms it (most desirable)
-//    If we have performed struct promotion of the Dest() then we will try to
-//    perform a field by field assignment for each of the promoted struct fields
+//    A single assignment, when fgMorphOneAsgBlockOp transforms it.
+//
+//    If the destination is a promoted struct local variable then we will try to
+//    perform a field by field assignment for each of the promoted struct fields.
+//    This is not always possible (e.g. if the struct has holes and custom layout).
+//
+//    Otherwise the orginal GT_ASG tree is returned unmodified (always correct but
+//    least desirable because it prevents enregistration and/or blocks independent
+//    struct promotion).
+//
+// Assumptions:
+//    GT_ASG's children have already been morphed.
 //
-// Notes:
-//    If we leave it as a GT_INITBLK we will call lvaSetVarDoNotEnregister() with a reason of DNER_BlockOp
-//    if the Dest() is a a struct that has a "CustomLayout" and "ConstainsHoles" then we
-//    can not use a field by field assignment and must the orginal GT_INITBLK unmodified.
-
 GenTree* Compiler::fgMorphInitBlock(GenTree* tree)
 {
     // We must have the GT_ASG form of InitBlkOp.
@@ -9307,7 +9309,6 @@ GenTree* Compiler::fgMorphInitBlock(GenTree* tree)
     bool morphed = false;
 #endif // DEBUG
 
-    GenTree* asg      = tree;
     GenTree* src      = tree->gtGetOp2();
     GenTree* origDest = tree->gtGetOp1();
 
@@ -9333,232 +9334,286 @@ GenTree* Compiler::fgMorphInitBlock(GenTree* tree)
     }
     else
     {
-        GenTree*             destAddr          = nullptr;
-        GenTree*             initVal           = src->OperIsInitVal() ? src->gtGetOp1() : src;
-        GenTree*             blockSize         = nullptr;
-        unsigned             blockWidth        = 0;
-        FieldSeqNode*        destFldSeq        = nullptr;
-        LclVarDsc*           destLclVar        = nullptr;
-        bool                 destDoFldAsg      = false;
-        unsigned             destLclNum        = BAD_VAR_NUM;
-        bool                 blockWidthIsConst = false;
-        GenTreeLclVarCommon* lclVarTree        = nullptr;
+        GenTreeLclVarCommon* destLclNode = nullptr;
+        unsigned             destLclNum  = BAD_VAR_NUM;
+        LclVarDsc*           destLclVar  = nullptr;
+        GenTree*             initVal     = src->OperIsInitVal() ? src->gtGetOp1() : src;
+        unsigned             blockSize   = 0;
+
         if (dest->IsLocal())
         {
-            lclVarTree = dest->AsLclVarCommon();
+            destLclNode = dest->AsLclVarCommon();
+            destLclNum  = destLclNode->GetLclNum();
+            destLclVar  = lvaGetDesc(destLclNum);
+            blockSize   = varTypeIsStruct(destLclVar) ? destLclVar->lvExactSize : genTypeSize(destLclVar->TypeGet());
         }
         else
         {
-            if (dest->OperIsBlk())
+            if (dest->OperIs(GT_IND))
             {
-                destAddr   = dest->AsBlk()->Addr();
-                blockWidth = dest->AsBlk()->gtBlkSize;
+                assert(dest->TypeGet() == TYP_STRUCT);
+                blockSize = genTypeSize(dest->TypeGet());
             }
-            else
+            else if (dest->OperIs(GT_DYN_BLK))
             {
-                assert((dest->gtOper == GT_IND) && (dest->TypeGet() != TYP_STRUCT));
-                destAddr   = dest->gtGetOp1();
-                blockWidth = genTypeSize(dest->TypeGet());
-            }
-        }
-        if (lclVarTree != nullptr)
-        {
-            destLclNum        = lclVarTree->gtLclNum;
-            destLclVar        = &lvaTable[destLclNum];
-            blockWidth        = varTypeIsStruct(destLclVar) ? destLclVar->lvExactSize : genTypeSize(destLclVar);
-            blockWidthIsConst = true;
-        }
-        else
-        {
-            if (dest->gtOper == GT_DYN_BLK)
-            {
-                // The size must be an integer type
-                blockSize = dest->AsBlk()->gtDynBlk.gtDynamicSize;
-                assert(varTypeIsIntegral(blockSize->gtType));
+                blockSize = 0;
             }
             else
             {
-                assert(blockWidth != 0);
-                blockWidthIsConst = true;
+                assert(dest->OperIs(GT_BLK, GT_OBJ));
+                blockSize = dest->AsBlk()->Size();
+                assert(blockSize != 0);
             }
 
-            if ((destAddr != nullptr) && destAddr->IsLocalAddrExpr(this, &lclVarTree, &destFldSeq))
+            FieldSeqNode* destFldSeq = nullptr;
+            if (dest->AsIndir()->Addr()->IsLocalAddrExpr(this, &destLclNode, &destFldSeq))
             {
-                destLclNum = lclVarTree->gtLclNum;
-                destLclVar = &lvaTable[destLclNum];
+                destLclNum = destLclNode->GetLclNum();
+                destLclVar = lvaGetDesc(destLclNum);
             }
         }
+
+        bool destDoFldAsg = false;
+
         if (destLclNum != BAD_VAR_NUM)
         {
 #if LOCAL_ASSERTION_PROP
             // Kill everything about destLclNum (and its field locals)
-            if (optLocalAssertionProp)
+            if (optLocalAssertionProp && (optAssertionCount > 0))
             {
-                if (optAssertionCount > 0)
-                {
-                    fgKillDependentAssertions(destLclNum DEBUGARG(tree));
-                }
+                fgKillDependentAssertions(destLclNum DEBUGARG(tree));
             }
 #endif // LOCAL_ASSERTION_PROP
 
-            if (destLclVar->lvPromoted && blockWidthIsConst)
+            if (destLclVar->lvPromoted)
             {
-                assert(initVal->OperGet() == GT_CNS_INT);
-                noway_assert(varTypeIsStruct(destLclVar));
-                noway_assert(!opts.MinOpts());
-                if (destLclVar->lvAddrExposed & destLclVar->lvContainsHoles)
-                {
-                    JITDUMP(" dest is address exposed");
-                }
-                else
+                GenTree* newTree = fgMorphPromoteLocalInitBlock(destLclNode->AsLclVar(), initVal, blockSize);
+
+                if (newTree != nullptr)
                 {
-                    if (blockWidth == destLclVar->lvExactSize)
-                    {
-                        JITDUMP(" (destDoFldAsg=true)");
-                        // We may decide later that a copyblk is required when this struct has holes
-                        destDoFldAsg = true;
-                    }
-                    else
-                    {
-                        JITDUMP(" with mismatched size");
-                    }
+                    tree         = newTree;
+                    destDoFldAsg = true;
+                    INDEBUG(morphed = true);
                 }
             }
-        }
 
-        // Can we use field by field assignment for the dest?
-        if (destDoFldAsg && destLclVar->lvCustomLayout && destLclVar->lvContainsHoles)
-        {
-            JITDUMP(" dest contains holes");
-            destDoFldAsg = false;
-        }
-
-        JITDUMP(destDoFldAsg ? " using field by field initialization.\n" : " this requires an InitBlock.\n");
-
-        // If we're doing an InitBlock and we've transformed the dest to a non-Blk
-        // we need to change it back.
-        if (!destDoFldAsg && !dest->OperIsBlk())
-        {
-            noway_assert(blockWidth != 0);
-            tree->gtOp.gtOp1 = origDest;
-            tree->gtType     = origDest->gtType;
-        }
-
-        if (!destDoFldAsg && (destLclVar != nullptr))
-        {
             // If destLclVar is not a reg-sized non-field-addressed struct, set it as DoNotEnregister.
-            if (!destLclVar->lvRegStruct)
+            if (!destDoFldAsg && !destLclVar->lvRegStruct)
             {
-                // Mark it as DoNotEnregister.
                 lvaSetVarDoNotEnregister(destLclNum DEBUGARG(DNER_BlockOp));
             }
         }
 
-        // Mark the dest struct as DoNotEnreg
-        // when they are LclVar structs and we are using a CopyBlock
-        // or the struct is not promoted
-        //
         if (!destDoFldAsg)
         {
-            dest             = fgMorphBlockOperand(dest, dest->TypeGet(), blockWidth, true);
+            // If we're doing an InitBlock and we've transformed the dest to a non-Blk
+            // we need to change it back.
+            if (!dest->OperIsBlk())
+            {
+                noway_assert(blockSize != 0);
+                tree->gtOp.gtOp1 = origDest;
+                tree->gtType     = origDest->gtType;
+            }
+
+            dest             = fgMorphBlockOperand(dest, dest->TypeGet(), blockSize, true);
             tree->gtOp.gtOp1 = dest;
             tree->gtFlags |= (dest->gtFlags & GTF_ALL_EFFECT);
         }
-        else
-        {
-            // The initVal must be a constant of TYP_INT
-            noway_assert(initVal->OperGet() == GT_CNS_INT);
-            noway_assert(genActualType(initVal->gtType) == TYP_INT);
+    }
 
-            // The dest must be of a struct type.
-            noway_assert(varTypeIsStruct(destLclVar));
+#ifdef DEBUG
+    if (morphed)
+    {
+        tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED;
 
-            //
-            // Now, convert InitBlock to individual assignments
-            //
+        if (verbose)
+        {
+            printf("fgMorphInitBlock (after):\n");
+            gtDispTree(tree);
+        }
+    }
+#endif
+
+    return tree;
+}
 
-            tree = nullptr;
-            INDEBUG(morphed = true);
+//------------------------------------------------------------------------
+// fgMorphPromoteLocalInitBlock: Attempts to promote a local block init tree
+// to a tree of promoted field initialization assignments.
+//
+// Arguments:
+//    destLclNode - The destination LclVar node
+//    initVal - The initialization value
+//    blockSize - The amount of bytes to initialize
+//
+// Return Value:
+//    A tree that performs field by field initialization of the destination
+//    struct variable if various conditions are met, nullptr otherwise.
+//
+// Notes:
+//    This transforms a single block initialization assignment like:
+//
+//    *  ASG       struct (init)
+//    +--*  BLK(12)   struct
+//    |  \--*  ADDR      long
+//    |     \--*  LCL_VAR   struct(P) V02 loc0
+//    |     \--*    int    V02.a (offs=0x00) -> V06 tmp3
+//    |     \--*    ubyte  V02.c (offs=0x04) -> V07 tmp4
+//    |     \--*    float  V02.d (offs=0x08) -> V08 tmp5
+//    \--*  INIT_VAL  int
+//       \--*  CNS_INT   int    42
+//
+//    into a COMMA tree of assignments that initialize each promoted struct
+//    field:
+//
+//    *  COMMA     void
+//    +--*  COMMA     void
+//    |  +--*  ASG       int
+//    |  |  +--*  LCL_VAR   int    V06 tmp3
+//    |  |  \--*  CNS_INT   int    0x2A2A2A2A
+//    |  \--*  ASG       ubyte
+//    |     +--*  LCL_VAR   ubyte  V07 tmp4
+//    |     \--*  CNS_INT   int    42
+//    \--*  ASG       float
+//       +--*  LCL_VAR   float  V08 tmp5
+//       \--*  CNS_DBL   float  1.5113661732714390e-13
+//
+GenTree* Compiler::fgMorphPromoteLocalInitBlock(GenTreeLclVar* destLclNode, GenTree* initVal, unsigned blockSize)
+{
+    assert(destLclNode->OperIs(GT_LCL_VAR));
 
-            GenTree* dest;
-            GenTree* srcCopy;
-            unsigned fieldLclNum;
-            unsigned fieldCnt = destLclVar->lvFieldCnt;
+    LclVarDsc* destLclVar = lvaGetDesc(destLclNode);
+    assert(varTypeIsStruct(destLclVar->TypeGet()));
+    assert(destLclVar->lvPromoted);
 
-            for (unsigned i = 0; i < fieldCnt; ++i)
-            {
-                fieldLclNum = destLclVar->lvFieldLclStart + i;
-                dest        = gtNewLclvNode(fieldLclNum, lvaTable[fieldLclNum].TypeGet());
+    if (blockSize == 0)
+    {
+        JITDUMP(" size is not known.\n");
+        return nullptr;
+    }
 
-                noway_assert(lclVarTree->gtOper == GT_LCL_VAR);
-                // If it had been labeled a "USEASG", assignments to the the individual promoted fields are not.
-                dest->gtFlags |= (lclVarTree->gtFlags & ~(GTF_NODE_MASK | GTF_VAR_USEASG));
+    if (destLclVar->lvAddrExposed && destLclVar->lvContainsHoles)
+    {
+        JITDUMP(" dest is address exposed and contains holes.\n");
+        return nullptr;
+    }
 
-                srcCopy = gtCloneExpr(initVal);
-                noway_assert(srcCopy != nullptr);
+    if (destLclVar->lvCustomLayout && destLclVar->lvContainsHoles)
+    {
+        JITDUMP(" dest has custom layout and contains holes.\n");
+        return nullptr;
+    }
 
-                // need type of oper to be same as tree
-                if (dest->gtType == TYP_LONG)
-                {
-                    srcCopy->ChangeOperConst(GT_CNS_NATIVELONG);
-                    // copy and extend the value
-                    srcCopy->gtIntConCommon.SetLngValue(initVal->gtIntConCommon.IconValue());
-                    /* Change the types of srcCopy to TYP_LONG */
-                    srcCopy->gtType = TYP_LONG;
-                }
-                else if (varTypeIsFloating(dest->gtType))
-                {
-                    srcCopy->ChangeOperConst(GT_CNS_DBL);
-                    // setup the bit pattern
-                    memset(&srcCopy->gtDblCon.gtDconVal, (int)initVal->gtIntCon.gtIconVal,
-                           sizeof(srcCopy->gtDblCon.gtDconVal));
-                    /* Change the types of srcCopy to TYP_DOUBLE */
-                    srcCopy->gtType = TYP_DOUBLE;
-                }
-                else
-                {
-                    noway_assert(srcCopy->gtOper == GT_CNS_INT);
-                    noway_assert(srcCopy->TypeGet() == TYP_INT);
-                    // setup the bit pattern
-                    memset(&srcCopy->gtIntCon.gtIconVal, (int)initVal->gtIntCon.gtIconVal,
-                           sizeof(srcCopy->gtIntCon.gtIconVal));
-                }
+    if (destLclVar->lvExactSize != blockSize)
+    {
+        JITDUMP(" dest size mismatch.\n");
+        return nullptr;
+    }
 
-                srcCopy->gtType = dest->TypeGet();
+    if (!initVal->OperIs(GT_CNS_INT))
+    {
+        JITDUMP(" source is not constant.\n");
+        return nullptr;
+    }
 
-                asg = gtNewAssignNode(dest, srcCopy);
+    const int64_t initPattern = (initVal->AsIntCon()->IconValue() & 0xFF) * 0x0101010101010101LL;
 
-#if LOCAL_ASSERTION_PROP
-                if (optLocalAssertionProp)
-                {
-                    optAssertionGen(asg);
-                }
-#endif // LOCAL_ASSERTION_PROP
+    if (initPattern != 0)
+    {
+        for (unsigned i = 0; i < destLclVar->lvFieldCnt; ++i)
+        {
+            LclVarDsc* fieldDesc = lvaGetDesc(destLclVar->lvFieldLclStart + i);
 
-                if (tree)
-                {
-                    tree = gtNewOperNode(GT_COMMA, TYP_VOID, tree, asg);
-                }
-                else
-                {
-                    tree = asg;
-                }
+            if (varTypeIsSIMD(fieldDesc->TypeGet()) || varTypeIsGC(fieldDesc->TypeGet()))
+            {
+                // Cannot initialize GC or SIMD types with a non-zero constant.
+                // The former is completly bogus. The later restriction could be
+                // lifted by supporting non-zero SIMD constants or by generating
+                // field initialization code that converts an integer constant to
+                // the appropiate SIMD value. Unlikely to be very useful, though.
+                JITDUMP(" dest contains GC and/or SIMD fields and source constant is not 0.\n");
+                return nullptr;
             }
         }
     }
 
-#ifdef DEBUG
-    if (morphed)
+    JITDUMP(" using field by field initialization.\n");
+
+    GenTree* tree = nullptr;
+
+    for (unsigned i = 0; i < destLclVar->lvFieldCnt; ++i)
     {
-        tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED;
+        unsigned   fieldLclNum = destLclVar->lvFieldLclStart + i;
+        LclVarDsc* fieldDesc   = lvaGetDesc(fieldLclNum);
+        GenTree*   dest        = gtNewLclvNode(fieldLclNum, fieldDesc->TypeGet());
+        // If it had been labeled a "USEASG", assignments to the the individual promoted fields are not.
+        dest->gtFlags |= (destLclNode->gtFlags & ~(GTF_NODE_MASK | GTF_VAR_USEASG));
 
-        if (verbose)
+        GenTree* src;
+
+        switch (dest->TypeGet())
         {
-            printf("fgMorphInitBlock (after):\n");
-            gtDispTree(tree);
+            case TYP_BOOL:
+            case TYP_BYTE:
+            case TYP_UBYTE:
+            case TYP_SHORT:
+            case TYP_USHORT:
+                // Promoted fields are expected to be "normalize on load". If that changes then
+                // we may need to adjust this code to widen the constant correctly.
+                assert(fieldDesc->lvNormalizeOnLoad());
+                __fallthrough;
+            case TYP_INT:
+            {
+                int64_t mask = (int64_t(1) << (genTypeSize(dest->TypeGet()) * 8)) - 1;
+                src          = gtNewIconNode(static_cast<int32_t>(initPattern & mask));
+                break;
+            }
+            case TYP_LONG:
+                src = gtNewLconNode(initPattern);
+                break;
+            case TYP_FLOAT:
+                float floatPattern;
+                memcpy(&floatPattern, &initPattern, sizeof(floatPattern));
+                src = gtNewDconNode(floatPattern, dest->TypeGet());
+                break;
+            case TYP_DOUBLE:
+                double doublePattern;
+                memcpy(&doublePattern, &initPattern, sizeof(doublePattern));
+                src = gtNewDconNode(doublePattern, dest->TypeGet());
+                break;
+            case TYP_REF:
+            case TYP_BYREF:
+#ifdef FEATURE_SIMD
+            case TYP_SIMD8:
+            case TYP_SIMD12:
+            case TYP_SIMD16:
+            case TYP_SIMD32:
+#endif // FEATURE_SIMD
+                assert(initPattern == 0);
+                src = gtNewIconNode(0, dest->TypeGet());
+                break;
+            default:
+                unreached();
+        }
+
+        GenTree* asg = gtNewAssignNode(dest, src);
+
+#if LOCAL_ASSERTION_PROP
+        if (optLocalAssertionProp)
+        {
+            optAssertionGen(asg);
+        }
+#endif // LOCAL_ASSERTION_PROP
+
+        if (tree != nullptr)
+        {
+            tree = gtNewOperNode(GT_COMMA, TYP_VOID, tree, asg);
+        }
+        else
+        {
+            tree = asg;
         }
     }
-#endif
 
     return tree;
 }
diff --git a/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_21761/GitHub_21761.il b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_21761/GitHub_21761.il
new file mode 100644 (file)
index 0000000..386c6df
--- /dev/null
@@ -0,0 +1,318 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+.assembly extern System.Runtime { }
+.assembly GitHub_21761 { }
+
+// Some basic tests to ensure that the JIT handles non-zero
+// initialization correctly.
+
+.class sequential sealed Point extends [System.Runtime]System.ValueType
+{
+    .field public int32 X
+    .field public int32 Y
+}
+
+.method static bool ConstFixedSizeInitBlk() cil managed noinlining
+{
+    .locals init (valuetype Point a)
+
+    ldloca a
+    ldc.i4 42
+    sizeof Point
+    initblk
+
+    ldloca a
+    ldind.i4
+    ldc.i4 0x2a2a2a2a
+    ceq
+    ret
+}
+
+.method static bool NonConstFixedSizeInitBlk() cil managed noinlining
+{
+    .locals init (valuetype Point a, int32 s)
+
+    ldloca a
+    ldloc s
+    sizeof Point
+    initblk
+
+    ldloca a
+    ldfld int32 Point::X
+    ldc.i4 0
+    ceq
+    ret
+}
+
+.class sequential sealed Point64 extends [System.Runtime]System.ValueType
+{
+    .field public int64 X
+    .field public int64 Y
+}
+
+.method static bool ConstFixedSizeInitBlk64() cil managed noinlining
+{
+    .locals init (valuetype Point64 a)
+
+    ldloca a
+    ldc.i4 42
+    sizeof Point64
+    initblk
+
+    ldloca a
+    ldind.i8
+    ldc.i8 0x2a2a2a2a2a2a2a2a
+    ceq
+    ret
+}
+
+.method static bool NonConstFixedSizeInitBlk64() cil managed noinlining
+{
+    .locals init (valuetype Point64 a, int32 s)
+
+    ldloca a
+    ldloc s
+    sizeof Point64
+    initblk
+
+    ldloca a
+    ldfld int64 Point64::X
+    ldc.i8 0
+    ceq
+    ret
+}
+
+// Small int promoted fields are supposed to be "normalize on load" so
+// no special care is needed when initializing them. Still, make sure
+// that field by field initialization handles small ints correctly.
+
+.class sequential sealed SmallInts extends [System.Runtime]System.ValueType
+{
+    .field public int8 I8;
+    .field public uint8 U8;
+    .field public int16 I16;
+}
+
+.method static bool SmallIntsInitBlk() cil managed noinlining
+{
+    .locals init (valuetype SmallInts a)
+
+    ldloca a
+    ldc.i4 42
+    sizeof SmallInts
+    initblk
+
+    ldloca a
+    ldind.i1
+    ldc.i4 0x2a
+    bne.un FAIL
+
+    ldloca a
+    ldind.u1
+    ldc.i4 0x2a
+    bne.un FAIL
+
+    ldloca a
+    ldind.i2
+    ldc.i4 0x2a2a
+    bne.un FAIL
+
+    ldc.i4 1
+    ret
+
+FAIL:
+    ldc.i4 0
+    ret
+}
+
+.method static bool SmallIntsSignedInitBlk() cil managed noinlining
+{
+    .locals init (valuetype SmallInts a)
+
+    ldloca a
+    ldc.i4 0x8a
+    sizeof SmallInts
+    initblk
+
+    ldloca a
+    ldind.i1
+    ldc.i4 0xFFFFFF8a
+    bne.un FAIL
+
+    ldloca a
+    ldind.u1
+    ldc.i4 0x8a
+    bne.un FAIL
+
+    ldloca a
+    ldind.i2
+    ldc.i4 0xFFFF8a8a
+    bne.un FAIL
+
+    ldc.i4 1
+    ret
+
+FAIL:
+    ldc.i4 0
+    ret
+}
+
+// If floating point fields are involved, special care is needed
+// since floating point constants having a required bit pattern
+// have to be generated.
+
+.class sequential sealed F32Vec3 extends [System.Runtime]System.ValueType
+{
+    .field public float32 X
+    .field public float32 Y
+    .field public float32 Z
+}
+
+// Make sure that the JIT produces an appropiate floating point constant.
+// JIT's internal representation uses doubles to store constants, even
+// when constants are float-typed. This means that the JIT should first
+// create a float value having the required bit pattern and then convert
+// it double. Directly creating a double having the required bit pattern
+// is not valid.
+
+.method static bool Float32InitBlk() cil managed noinlining
+{
+    .locals init (valuetype F32Vec3 a)
+
+    ldloca a
+    ldc.i4 42
+    sizeof F32Vec3
+    initblk
+
+    ldloca a
+    ldind.i4
+    ldc.i4 0x2a2a2a2a
+    ceq
+    ret
+}
+
+// Initializing a float value with 255, 255, 255... is a special case
+// because the result is a NaN. And since the JIT uses double to store
+// float constants, this means that the JIT may end up producing a float
+// NaN value, convert it to double and then convert it back to float
+// during codegen. Will the NaN payload be preserved through conversions?
+// This may depend on the host's floating point implementation.
+
+.method static bool Float32NaNInitBlk() cil managed noinlining
+{
+    .locals init (valuetype F32Vec3 a)
+
+    ldloca a
+    ldc.i4 255
+    sizeof F32Vec3
+    initblk
+
+    ldloca a
+    ldind.i4
+    ldc.i4 0xFFFFFFFF
+    ceq
+    ret
+}
+
+// Non-zero initialization of a GC reference is not exactly a valid scenario.
+// Still, the JIT shouldn't end up generating invalid IR (non-zero GC typed
+// constant nodes).
+
+.class sequential sealed Pair extends [System.Runtime]System.ValueType
+{
+    .field public int64 Key
+    .field public class [System.Runtime]System.Object Value
+}
+
+.method static bool ObjRefInitBlk() cil managed noinlining
+{
+    .locals init (valuetype Pair a)
+
+    ldloca a
+    ldc.i4 1
+    sizeof Pair
+    initblk
+
+    ldloca a
+    ldind.i8
+    ldc.i8 0x0101010101010101
+    ceq
+    ret
+}
+
+// Non-zero SIMD constants are not supported so field by field initialization
+// should not be attempted.
+
+.class sequential sealed Wrapper extends [System.Runtime]System.ValueType
+{
+    .field public valuetype [System.Numerics.Vectors]System.Numerics.Vector4 Value
+}
+
+.method static bool SimdInitBlk() cil managed noinlining
+{
+    .locals init (valuetype Wrapper a, valuetype [System.Numerics.Vectors]System.Numerics.Vector4 v, float32 len)
+
+    ldloca a
+    ldc.i4 42
+    ldc.i4 16
+    initblk
+
+    ldloca a
+    ldflda valuetype [System.Numerics.Vectors]System.Numerics.Vector4 Wrapper::Value
+    call instance float32 [System.Numerics.Vectors]System.Numerics.Vector4::Length()
+    stloc len
+
+    ldloca a
+    ldfld valuetype [System.Numerics.Vectors]System.Numerics.Vector4 Wrapper::Value
+    stloc v
+
+    ldloca v
+    ldind.i4
+    ldc.i4 0x2a2a2a2a
+    ceq
+    ret
+}
+
+.method hidebysig static int32 Main() cil managed
+{
+    .entrypoint
+    .locals init (valuetype Point a)
+
+    call bool ConstFixedSizeInitBlk()
+    brfalse FAIL
+
+    call bool NonConstFixedSizeInitBlk()
+    brfalse FAIL
+
+    call bool ConstFixedSizeInitBlk64()
+    brfalse FAIL
+
+    call bool NonConstFixedSizeInitBlk64()
+    brfalse FAIL
+
+    call bool SmallIntsInitBlk()
+    brfalse FAIL
+
+    call bool SmallIntsSignedInitBlk()
+    brfalse FAIL
+
+    call bool Float32InitBlk()
+    brfalse FAIL
+
+    call bool Float32NaNInitBlk()
+    brfalse FAIL
+
+    call bool ObjRefInitBlk()
+    brfalse FAIL
+    
+    call bool SimdInitBlk()
+    brfalse FAIL
+
+    ldc.i4 100
+    ret
+ FAIL:
+    ldc.i4 1
+    ret
+}
diff --git a/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_21761/GitHub_21761.ilproj b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_21761/GitHub_21761.ilproj
new file mode 100644 (file)
index 0000000..2e8a895
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+    <OutputType>Exe</OutputType>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="GitHub_21761.il" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>