struct improvement, part1: create more LCL_FLD (#48377)
authorSergey Andreenko <seandree@microsoft.com>
Fri, 19 Mar 2021 15:37:51 +0000 (08:37 -0700)
committerGitHub <noreply@github.com>
Fri, 19 Mar 2021 15:37:51 +0000 (08:37 -0700)
* Add additional tests.

* Use LclFld when copy to/from promoted from/to an unpromoted struct/block.

* Response review.

src/coreclr/jit/compiler.h
src/coreclr/jit/morph.cpp
src/tests/JIT/opt/Structs/structcopies.cs [new file with mode: 0644]
src/tests/JIT/opt/Structs/structcopies.csproj [new file with mode: 0644]

index a1ac034..78c592a 100644 (file)
@@ -5890,6 +5890,8 @@ private:
     GenTree* fgMorphSmpOpOptional(GenTreeOp* tree);
     GenTree* fgMorphConst(GenTree* tree);
 
+    bool fgMorphCanUseLclFldForCopy(unsigned lclNum1, unsigned lclNum2);
+
     GenTreeLclVar* fgMorphTryFoldObjAsLclVar(GenTreeObj* obj);
     GenTree* fgMorphCommutative(GenTreeOp* tree);
 
index 975f81c..de5f6d0 100644 (file)
@@ -10987,6 +10987,7 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
         unsigned             modifiedLclNum    = BAD_VAR_NUM;
         LclVarDsc*           destLclVar        = nullptr;
         FieldSeqNode*        destFldSeq        = nullptr;
+        unsigned             destLclOffset     = 0;
         bool                 destDoFldAsg      = false;
         GenTree*             destAddr          = nullptr;
         GenTree*             srcAddr           = nullptr;
@@ -11024,9 +11025,11 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
             {
                 assert(dest->TypeGet() != TYP_STRUCT);
                 assert(dest->gtOper == GT_LCL_FLD);
-                blockWidth = genTypeSize(dest->TypeGet());
-                destAddr   = gtNewOperNode(GT_ADDR, TYP_BYREF, dest);
-                destFldSeq = dest->AsLclFld()->GetFieldSeq();
+                GenTreeLclFld* destFld = dest->AsLclFld();
+                blockWidth             = genTypeSize(destFld->TypeGet());
+                destAddr               = gtNewOperNode(GT_ADDR, TYP_BYREF, destFld);
+                destFldSeq             = destFld->GetFieldSeq();
+                destLclOffset          = destFld->GetLclOffs();
             }
         }
         else
@@ -11063,6 +11066,7 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                     destLclNum     = lclVarTree->GetLclNum();
                     modifiedLclNum = destLclNum;
                     destLclVar     = &lvaTable[destLclNum];
+                    destLclOffset  = lclVarTree->GetLclOffs();
                 }
             }
         }
@@ -11098,10 +11102,14 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
             }
         }
 
-        FieldSeqNode* srcFldSeq   = nullptr;
-        unsigned      srcLclNum   = BAD_VAR_NUM;
-        LclVarDsc*    srcLclVar   = nullptr;
-        bool          srcDoFldAsg = false;
+        FieldSeqNode* srcFldSeq    = nullptr;
+        unsigned      srcLclNum    = BAD_VAR_NUM;
+        LclVarDsc*    srcLclVar    = nullptr;
+        unsigned      srcLclOffset = 0;
+        bool          srcDoFldAsg  = false;
+
+        bool srcUseLclFld  = false;
+        bool destUseLclFld = false;
 
         if (src->IsLocal())
         {
@@ -11126,7 +11134,8 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
 
         if (srcLclNum != BAD_VAR_NUM)
         {
-            srcLclVar = &lvaTable[srcLclNum];
+            srcLclOffset = srcLclVarTree->GetLclOffs();
+            srcLclVar    = &lvaTable[srcLclNum];
 
             if (srcLclVar->lvPromoted && blockWidthIsConst)
             {
@@ -11200,11 +11209,9 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
         }
 #endif // TARGET_ARM
 
-        // Don't use field by field assignment if the src is a call
-        //
-        // TODO: Document why do we have this restriction?
-        //       Maybe it isn't needed, or maybe it is only needed
-        //       for calls that return multiple registers
+        // Don't use field by field assignment if the src is a call,
+        // lowering will handle it without spilling the call result into memory
+        // to access the individual fields.
         //
         if (src->OperGet() == GT_CALL)
         {
@@ -11403,8 +11410,9 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
 
         if (destDoFldAsg && srcDoFldAsg)
         {
-            // To do fieldwise assignments for both sides, they'd better be the same struct type!
-            // All of these conditions were checked above...
+            // To do fieldwise assignments for both sides.
+            // The structs do not have to be the same exact types but have to have same field types
+            // at the same offsets.
             assert(destLclNum != BAD_VAR_NUM && srcLclNum != BAD_VAR_NUM);
             assert(destLclVar != nullptr && srcLclVar != nullptr && destLclVar->lvFieldCnt == srcLclVar->lvFieldCnt);
 
@@ -11416,7 +11424,10 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
         {
             fieldCnt = destLclVar->lvFieldCnt;
             src      = fgMorphBlockOperand(src, asgType, blockWidth, false /*isBlkReqd*/);
-            if (srcAddr == nullptr)
+
+            srcUseLclFld = fgMorphCanUseLclFldForCopy(destLclNum, srcLclNum);
+
+            if (!srcUseLclFld && srcAddr == nullptr)
             {
                 srcAddr = fgMorphGetStructAddr(&src, destLclVar->GetStructHnd(), true /* rValue */);
             }
@@ -11431,28 +11442,35 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                 dest->SetOper(GT_IND);
                 dest->gtType = TYP_STRUCT;
             }
-            destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest);
+            destUseLclFld = fgMorphCanUseLclFldForCopy(srcLclNum, destLclNum);
+            if (!destUseLclFld)
+            {
+                destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest);
+            }
         }
 
         if (destDoFldAsg)
         {
             noway_assert(!srcDoFldAsg);
-            if (gtClone(srcAddr))
+            if (!srcUseLclFld)
             {
-                // srcAddr is simple expression. No need to spill.
-                noway_assert((srcAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
-            }
-            else
-            {
-                // srcAddr is complex expression. Clone and spill it (unless the destination is
-                // a struct local that only has one field, in which case we'd only use the
-                // address value once...)
-                if (destLclVar->lvFieldCnt > 1)
+                if (gtClone(srcAddr))
                 {
-                    // We will spill srcAddr (i.e. assign to a temp "BlockOp address local")
-                    // no need to clone a new copy as it is only used once
-                    //
-                    addrSpill = srcAddr; // addrSpill represents the 'srcAddr'
+                    // srcAddr is simple expression. No need to spill.
+                    noway_assert((srcAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
+                }
+                else
+                {
+                    // srcAddr is complex expression. Clone and spill it (unless the destination is
+                    // a struct local that only has one field, in which case we'd only use the
+                    // address value once...)
+                    if (destLclVar->lvFieldCnt > 1)
+                    {
+                        // We will spill srcAddr (i.e. assign to a temp "BlockOp address local")
+                        // no need to clone a new copy as it is only used once
+                        //
+                        addrSpill = srcAddr; // addrSpill represents the 'srcAddr'
+                    }
                 }
             }
         }
@@ -11472,22 +11490,25 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                 lclVarTree->gtFlags &= ~(GTF_VAR_DEF | GTF_VAR_USEASG);
             }
 
-            if (gtClone(destAddr))
+            if (!destUseLclFld)
             {
-                // destAddr is simple expression. No need to spill
-                noway_assert((destAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
-            }
-            else
-            {
-                // destAddr is complex expression. Clone and spill it (unless
-                // the source is a struct local that only has one field, in which case we'd only
-                // use the address value once...)
-                if (srcLclVar->lvFieldCnt > 1)
+                if (gtClone(destAddr))
                 {
-                    // We will spill destAddr (i.e. assign to a temp "BlockOp address local")
-                    // no need to clone a new copy as it is only used once
-                    //
-                    addrSpill = destAddr; // addrSpill represents the 'destAddr'
+                    // destAddr is simple expression. No need to spill
+                    noway_assert((destAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
+                }
+                else
+                {
+                    // destAddr is complex expression. Clone and spill it (unless
+                    // the source is a struct local that only has one field, in which case we'd only
+                    // use the address value once...)
+                    if (srcLclVar->lvFieldCnt > 1)
+                    {
+                        // We will spill destAddr (i.e. assign to a temp "BlockOp address local")
+                        // no need to clone a new copy as it is only used once
+                        //
+                        addrSpill = destAddr; // addrSpill represents the 'destAddr'
+                    }
                 }
             }
         }
@@ -11585,43 +11606,48 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                 }
                 else
                 {
-                    if (addrSpill)
+                    GenTree* dstAddrClone = nullptr;
+                    if (!destUseLclFld)
                     {
-                        assert(addrSpillTemp != BAD_VAR_NUM);
-                        dstFld = gtNewLclvNode(addrSpillTemp, TYP_BYREF);
-                    }
-                    else
-                    {
-                        if (i == 0)
+                        // Need address of the destination.
+                        if (addrSpill)
                         {
-                            // Use the orginal destAddr tree when i == 0
-                            dstFld = destAddr;
+                            assert(addrSpillTemp != BAD_VAR_NUM);
+                            dstAddrClone = gtNewLclvNode(addrSpillTemp, TYP_BYREF);
                         }
                         else
                         {
-                            // We can't clone multiple copies of a tree with persistent side effects
-                            noway_assert((destAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
+                            if (i == 0)
+                            {
+                                // Use the orginal destAddr tree when i == 0
+                                dstAddrClone = destAddr;
+                            }
+                            else
+                            {
+                                // We can't clone multiple copies of a tree with persistent side effects
+                                noway_assert((destAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
 
-                            dstFld = gtCloneExpr(destAddr);
-                            noway_assert(dstFld != nullptr);
+                                dstAddrClone = gtCloneExpr(destAddr);
+                                noway_assert(dstAddrClone != nullptr);
 
-                            JITDUMP("dstFld - Multiple Fields Clone created:\n");
-                            DISPTREE(dstFld);
+                                JITDUMP("dstAddr - Multiple Fields Clone created:\n");
+                                DISPTREE(dstAddrClone);
 
-                            // Morph the newly created tree
-                            dstFld = fgMorphTree(dstFld);
-                        }
+                                // Morph the newly created tree
+                                dstAddrClone = fgMorphTree(dstAddrClone);
+                            }
 
-                        // Is the address of a local?
-                        GenTreeLclVarCommon* lclVarTree = nullptr;
-                        bool                 isEntire   = false;
-                        bool*                pIsEntire  = (blockWidthIsConst ? &isEntire : nullptr);
-                        if (dstFld->DefinesLocalAddr(this, blockWidth, &lclVarTree, pIsEntire))
-                        {
-                            lclVarTree->gtFlags |= GTF_VAR_DEF;
-                            if (!isEntire)
+                            // Is the address of a local?
+                            GenTreeLclVarCommon* lclVarTree = nullptr;
+                            bool                 isEntire   = false;
+                            bool*                pIsEntire  = (blockWidthIsConst ? &isEntire : nullptr);
+                            if (dstAddrClone->DefinesLocalAddr(this, blockWidth, &lclVarTree, pIsEntire))
                             {
-                                lclVarTree->gtFlags |= GTF_VAR_USEASG;
+                                lclVarTree->gtFlags |= GTF_VAR_DEF;
+                                if (!isEntire)
+                                {
+                                    lclVarTree->gtFlags |= GTF_VAR_USEASG;
+                                }
                             }
                         }
                     }
@@ -11636,27 +11662,46 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                         info.compCompHnd->getFieldInClass(classHnd, srcFieldVarDsc->lvFldOrdinal);
                     FieldSeqNode* curFieldSeq = GetFieldSeqStore()->CreateSingleton(fieldHnd);
 
-                    unsigned srcFieldOffset = lvaGetDesc(srcFieldLclNum)->lvFldOffset;
+                    unsigned  srcFieldOffset = lvaGetDesc(srcFieldLclNum)->lvFldOffset;
+                    var_types srcType        = srcFieldVarDsc->TypeGet();
 
-                    if (srcFieldOffset == 0)
+                    if (!destUseLclFld)
                     {
-                        fgAddFieldSeqForZeroOffset(dstFld, curFieldSeq);
+
+                        if (srcFieldOffset == 0)
+                        {
+                            fgAddFieldSeqForZeroOffset(dstAddrClone, curFieldSeq);
+                        }
+                        else
+                        {
+                            GenTree* fieldOffsetNode = gtNewIconNode(srcFieldVarDsc->lvFldOffset, curFieldSeq);
+                            dstAddrClone             = gtNewOperNode(GT_ADD, TYP_BYREF, dstAddrClone, fieldOffsetNode);
+                        }
+
+                        dstFld = gtNewIndir(srcType, dstAddrClone);
                     }
                     else
                     {
-                        GenTree* fieldOffsetNode = gtNewIconNode(srcFieldVarDsc->lvFldOffset, curFieldSeq);
-                        dstFld                   = gtNewOperNode(GT_ADD, TYP_BYREF, dstFld, fieldOffsetNode);
+                        assert(dstAddrClone == nullptr);
+                        assert((destLclOffset == 0) || (destFldSeq != nullptr));
+                        // If the dst was a struct type field "B" in a struct "A" then we add
+                        // add offset of ("B" in "A") + current offset in "B".
+                        unsigned summOffset        = destLclOffset + srcFieldOffset;
+                        dstFld                     = gtNewLclFldNode(destLclNum, srcType, summOffset);
+                        FieldSeqNode* dstFldFldSeq = GetFieldSeqStore()->Append(destFldSeq, curFieldSeq);
+                        dstFld->AsLclFld()->SetFieldSeq(dstFldFldSeq);
+
+                        // TODO-1stClassStructs: remove this and implement storing to a field in a struct in a reg.
+                        lvaSetVarDoNotEnregister(destLclNum DEBUGARG(DNER_LocalField));
                     }
 
-                    dstFld = gtNewIndir(srcFieldVarDsc->TypeGet(), dstFld);
-
                     // !!! The destination could be on stack. !!!
                     // This flag will let us choose the correct write barrier.
                     dstFld->gtFlags |= GTF_IND_TGTANYWHERE;
                 }
             }
 
-            GenTree* srcFld;
+            GenTree* srcFld = nullptr;
             if (srcDoFldAsg)
             {
                 noway_assert(srcLclNum != BAD_VAR_NUM);
@@ -11682,31 +11727,36 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                 }
                 else
                 {
-                    if (addrSpill)
+                    GenTree* srcAddrClone = nullptr;
+                    if (!srcUseLclFld)
                     {
-                        assert(addrSpillTemp != BAD_VAR_NUM);
-                        srcFld = gtNewLclvNode(addrSpillTemp, TYP_BYREF);
-                    }
-                    else
-                    {
-                        if (i == 0)
+                        // Need address of the source.
+                        if (addrSpill)
                         {
-                            // Use the orginal srcAddr tree when i == 0
-                            srcFld = srcAddr;
+                            assert(addrSpillTemp != BAD_VAR_NUM);
+                            srcAddrClone = gtNewLclvNode(addrSpillTemp, TYP_BYREF);
                         }
                         else
                         {
-                            // We can't clone multiple copies of a tree with persistent side effects
-                            noway_assert((srcAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
+                            if (i == 0)
+                            {
+                                // Use the orginal srcAddr tree when i == 0
+                                srcAddrClone = srcAddr;
+                            }
+                            else
+                            {
+                                // We can't clone multiple copies of a tree with persistent side effects
+                                noway_assert((srcAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0);
 
-                            srcFld = gtCloneExpr(srcAddr);
-                            noway_assert(srcFld != nullptr);
+                                srcAddrClone = gtCloneExpr(srcAddr);
+                                noway_assert(srcAddrClone != nullptr);
 
-                            JITDUMP("srcFld - Multiple Fields Clone created:\n");
-                            DISPTREE(srcFld);
+                                JITDUMP("srcAddr - Multiple Fields Clone created:\n");
+                                DISPTREE(srcAddrClone);
 
-                            // Morph the newly created tree
-                            srcFld = fgMorphTree(srcFld);
+                                // Morph the newly created tree
+                                srcAddrClone = fgMorphTree(srcAddrClone);
+                            }
                         }
                     }
 
@@ -11743,20 +11793,36 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree)
                     if (!done)
                     {
                         unsigned fldOffset = lvaGetDesc(dstFieldLclNum)->lvFldOffset;
-                        if (fldOffset == 0)
+                        if (!srcUseLclFld)
                         {
-                            fgAddFieldSeqForZeroOffset(srcFld, curFieldSeq);
+                            assert(srcAddrClone != nullptr);
+                            if (fldOffset == 0)
+                            {
+                                fgAddFieldSeqForZeroOffset(srcAddrClone, curFieldSeq);
+                            }
+                            else
+                            {
+                                GenTreeIntCon* fldOffsetNode = gtNewIconNode(fldOffset, curFieldSeq);
+                                srcAddrClone = gtNewOperNode(GT_ADD, TYP_BYREF, srcAddrClone, fldOffsetNode);
+                            }
+                            srcFld = gtNewIndir(destType, srcAddrClone);
                         }
                         else
                         {
-                            GenTreeIntCon* fldOffsetNode = gtNewIconNode(fldOffset, curFieldSeq);
-                            srcFld                       = gtNewOperNode(GT_ADD, TYP_BYREF, srcFld, fldOffsetNode);
+                            assert((srcLclOffset == 0) || (srcFldSeq != 0));
+                            // If the src was a struct type field "B" in a struct "A" then we add
+                            // add offset of ("B" in "A") + current offset in "B".
+                            unsigned summOffset        = srcLclOffset + fldOffset;
+                            srcFld                     = gtNewLclFldNode(srcLclNum, destType, summOffset);
+                            FieldSeqNode* srcFldFldSeq = GetFieldSeqStore()->Append(srcFldSeq, curFieldSeq);
+                            srcFld->AsLclFld()->SetFieldSeq(srcFldFldSeq);
+                            // TODO-1stClassStructs: remove this and implement reading a field from a struct in a reg.
+                            lvaSetVarDoNotEnregister(srcLclNum DEBUGARG(DNER_LocalField));
                         }
-                        srcFld = gtNewIndir(destType, srcFld);
                     }
                 }
             }
-
+            assert(srcFld != nullptr);
             noway_assert(dstFld->TypeGet() == srcFld->TypeGet());
 
             asg = gtNewAssignNode(dstFld, srcFld);
@@ -11807,6 +11873,46 @@ _Done:
     return tree;
 }
 
+//------------------------------------------------------------------------
+// fgMorphCanUseLclFldForCopy: check if we can access LclVar2 using LclVar1's fields.
+//
+// Arguments:
+//    lclNum1 - a promoted lclVar that is used in fieldwise assignment;
+//    lclNum2 - the local variable on the other side of ASG, can be BAD_VAR_NUM.
+//
+// Return Value:
+//    True if the second local is valid and has the same struct handle as the first,
+//    false otherwise.
+//
+// Notes:
+//   This check is needed to avoid accesing LCL_VARs with incorrect
+//   CORINFO_FIELD_HANDLE that would confuse VN optimizations.
+//
+bool Compiler::fgMorphCanUseLclFldForCopy(unsigned lclNum1, unsigned lclNum2)
+{
+    assert(lclNum1 != BAD_VAR_NUM);
+    if (lclNum2 == BAD_VAR_NUM)
+    {
+        return false;
+    }
+    const LclVarDsc* varDsc1 = lvaGetDesc(lclNum1);
+    const LclVarDsc* varDsc2 = lvaGetDesc(lclNum2);
+    assert(varTypeIsStruct(varDsc1));
+    if (!varTypeIsStruct(varDsc2))
+    {
+        return false;
+    }
+    CORINFO_CLASS_HANDLE struct1 = varDsc1->GetStructHnd();
+    CORINFO_CLASS_HANDLE struct2 = varDsc2->GetStructHnd();
+    assert(struct1 != NO_CLASS_HANDLE);
+    assert(struct2 != NO_CLASS_HANDLE);
+    if (struct1 != struct2)
+    {
+        return false;
+    }
+    return true;
+}
+
 // insert conversions and normalize to make tree amenable to register
 // FP architectures
 GenTree* Compiler::fgMorphForRegisterFP(GenTree* tree)
diff --git a/src/tests/JIT/opt/Structs/structcopies.cs b/src/tests/JIT/opt/Structs/structcopies.cs
new file mode 100644 (file)
index 0000000..e1331e3
--- /dev/null
@@ -0,0 +1,1721 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// The program tests different cases that could cause issues with aggresive 
+// struct optimizations with existing retyping or missing field sequences.
+
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+using System.Numerics;
+
+
+namespace TestStructFields
+{
+    class Program
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static void blockPromotion<T>(ref T s)
+        {
+
+        }
+
+        #region S4 tests
+
+        struct S4
+        {
+            public int i;
+        }
+
+        struct S4W
+        {
+            public S4 s4;
+        }
+
+        struct S4WW
+        {
+            public S4W s4;
+        }
+
+        struct S4Copy
+        {
+            public int i;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        struct S4Corrupted1
+        {
+            [FieldOffset(0)] public int i;
+            [FieldOffset(0)] public bool b0;
+            [FieldOffset(1)] public bool b1;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        struct S4Corrupted2
+        {
+            [FieldOffset(0)] public int i;
+            [FieldOffset(0)] public bool b0;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        struct S4Corrupted3
+        {
+            [FieldOffset(0)] public byte b0;
+            [FieldOffset(3)] public byte b1;
+        }
+
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Simple()
+        {
+            S4 s1 = new S4();
+            S4 s2 = new S4();
+            s2.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            blockPromotion(ref s2);
+            s1 = s2;
+            s2.i = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_W1()
+        {
+            S4 s1 = new S4();
+            S4W s2 = new S4W();
+            s2.s4.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = s2.s4;
+            s2.s4.i = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.s4.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_W2()
+        {
+            S4W s1 = new S4W();
+            S4 s2 = new S4();
+            s2.i = 1;
+            if (s1.s4.i != 0)
+            {
+                return 101;
+            }
+            s1.s4 = s2;
+            s2.i = 2;
+            if (s1.s4.i != 1)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_WW1()
+        {
+            S4 s1 = new S4();
+            S4WW s2 = new S4WW();
+            s2.s4.s4.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = s2.s4.s4;
+            s2.s4.s4.i = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.s4.s4.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_WW2()
+        {
+            S4WW s1 = new S4WW();
+            S4 s2 = new S4();
+            s2.i = 1;
+            if (s1.s4.s4.i != 0)
+            {
+                return 101;
+            }
+            s1.s4.s4 = s2;
+            s2.i = 2;
+            if (s1.s4.s4.i != 1)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Copy1()
+        {
+            S4 s1 = new S4();
+            S4Copy s2 = new S4Copy();
+            s2.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S4Copy, S4>(ref s2);
+            s2.i = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Copy2()
+        {
+            S4Copy s1 = new S4Copy();
+            S4 s2 = new S4();
+            s2.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S4, S4Copy>(ref s2);
+            s2.i = 2;
+            if (s1.i == 0)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Corrupted1()
+        {
+            S4 s1 = new S4();
+            S4Corrupted1 s2 = new S4Corrupted1();
+            s2.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S4Corrupted1, S4>(ref s2);
+            s2.i = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+
+            s2.b0 = false;
+            s1 = Unsafe.As<S4Corrupted1, S4>(ref s2);
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            if (s2.i != 0)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Corrupted2()
+        {
+            S4 s1 = new S4();
+            S4Corrupted2 s2 = new S4Corrupted2();
+            s2.i = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S4Corrupted2, S4>(ref s2);
+            s2.i = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.i != 2)
+            {
+                return 101;
+            }
+
+            s2.b0 = false;
+            s1 = Unsafe.As<S4Corrupted2, S4>(ref s2);
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            if (s2.i != 0)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Corrupted3()
+        {
+            S4 s1 = new S4();
+            S4Corrupted3 s2 = new S4Corrupted3();
+            s2.b0 = 1;
+            if (s1.i != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S4Corrupted3, S4>(ref s2);
+            s2.b0 = 2;
+            if (s1.i != 1)
+            {
+                return 101;
+            }
+            if (s2.b0 != 2)
+            {
+                return 101;
+            }
+
+            s2.b1 = 1;
+            s1 = Unsafe.As<S4Corrupted3, S4>(ref s2);
+            if (s1.i != 16777218)
+            {
+                return 101;
+            }
+            if (s2.b0 != 2)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS4_Corrupted4()
+        {
+            S4 s1 = new S4();
+            s1.i = 0x1010101;
+            S4Corrupted3 s2 = new S4Corrupted3();
+            s2 = Unsafe.As<S4, S4Corrupted3>(ref s1);
+            S4Corrupted3 s3 = s2;
+            s2.b0 = 0;
+            s3.b1 = 1;
+
+            s1 = Unsafe.As<S4Corrupted3, S4>(ref s3);
+            if (s1.i != 0x01010101)
+            {
+                return 101;
+            }
+            if (s2.b0 != 0)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        static int TestS4()
+        {
+            int res = 100;
+            bool failed = false;
+            res = TestS4_Simple();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Simple failed");
+                failed = true;
+            }
+
+            res = TestS4_W1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_W1 failed");
+                failed = true;
+            }
+
+            res = TestS4_W2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_W2 failed");
+                failed = true;
+            }
+
+            res = TestS4_WW1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_WW1 failed");
+                failed = true;
+            }
+
+            res = TestS4_WW2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_WW2 failed");
+                failed = true;
+            }
+
+            res = TestS4_Copy1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Copy1 failed");
+                failed = true;
+            }
+
+            res = TestS4_Copy2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Copy2 failed");
+                failed = true;
+            }
+
+            res = TestS4_Corrupted1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Corrupted1 failed");
+                failed = true;
+            }
+
+            res = TestS4_Corrupted2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Corrupted2 failed");
+                failed = true;
+            }
+
+            res = TestS4_Corrupted3();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Corrupted3 failed");
+                failed = true;
+            }
+
+            res = TestS4_Corrupted4();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4_Corrupted4 failed");
+                failed = true;
+            }
+
+            if (failed)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        #endregion  // S4 tests
+
+        #region S8 tests
+        struct S8
+        {
+            public int i1;
+            public int i2;
+        }
+
+        struct S8W
+        {
+            public S8 s8;
+        }
+
+        struct S8WW
+        {
+            public S8W s8;
+        }
+
+        struct S8Copy
+        {
+            public int i1;
+            public int i2;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        struct S8Corrupted1
+        {
+            [FieldOffset(0)] public int i1;
+            [FieldOffset(4)] public int i2;
+            [FieldOffset(7)] public bool b0;
+            [FieldOffset(5)] public bool b1;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        struct S8Corrupted2
+        {
+            [FieldOffset(0)] public int i1;
+            [FieldOffset(7)] public byte b1;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        struct S8Corrupted3
+        {
+            [FieldOffset(0)] public object o1;
+            [FieldOffset(0)] public long i1;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_Simple()
+        {
+            S8 s1 = new S8();
+            S8 s2 = new S8();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            blockPromotion(ref s2);
+            s1 = s2;
+            s2.i1 = 3;
+            s2.i2 = 4;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.i2 != 4)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_W1()
+        {
+            S8 s1 = new S8();
+            S8W s2 = new S8W();
+            s2.s8.i1 = 1;
+            s2.s8.i2 = 2;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            s1 = s2.s8;
+            s2.s8.i1 = 3;
+            s2.s8.i2 = 4;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s2.s8.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.s8.i2 != 4)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_W2()
+        {
+            S8W s1 = new S8W();
+            S8 s2 = new S8();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            if (s1.s8.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.s8.i2 != 0)
+            {
+                return 101;
+            }
+            s1.s8 = s2;
+            s2.i1 = 3;
+            s2.i2 = 4;
+            if (s1.s8.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.s8.i2 != 2)
+            {
+                return 101;
+            }
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.i2 != 4)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_WW1()
+        {
+            S8 s1 = new S8();
+            S8WW s2 = new S8WW();
+            s2.s8.s8.i1 = 1;
+            s2.s8.s8.i2 = 2;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            s1 = s2.s8.s8;
+            s2.s8.s8.i1 = 3;
+            s2.s8.s8.i2 = 4;
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s2.s8.s8.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.s8.s8.i2 != 4)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_WW2()
+        {
+            S8WW s1 = new S8WW();
+            S8 s2 = new S8();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            if (s1.s8.s8.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.s8.s8.i2 != 0)
+            {
+                return 101;
+            }
+            s1.s8.s8 = s2;
+            s2.i1 = 3;
+            s2.i2 = 4;
+            if (s1.s8.s8.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.s8.s8.i2 != 2)
+            {
+                return 101;
+            }
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.i2 != 4)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_Copy1()
+        {
+            S8 s1 = new S8();
+            S8Copy s2 = new S8Copy();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S8Copy, S8>(ref s2);
+            s2.i1 = 3;
+            s2.i2 = 4;
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.i2 != 4)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_Copy2()
+        {
+            S8Copy s1 = new S8Copy();
+            S8 s2 = new S8();
+            s2.i1 = 132;
+            s2.i2 = 567;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S8, S8Copy>(ref s2);
+            s2.i1 = 32;
+            s2.i2 = 33;
+            if (s1.i1 != 132)
+            {
+                return 101;
+            }
+            if (s1.i2 != 567)
+            {
+                return 101;
+            }
+            if (s2.i1 == 132)
+            {
+                return 101;
+            }
+            if (s2.i2 != 33)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_Corrupted1()
+        {
+            S8 s1 = new S8();
+            S8Corrupted1 s2 = new S8Corrupted1();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S8Corrupted1, S8>(ref s2);
+            s2.i1 = 3;
+            s2.i2 = 4;
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.i2 != 4)
+            {
+                return 101;
+            }
+
+            s2.b0 = true;
+            s1 = Unsafe.As<S8Corrupted1, S8>(ref s2);
+            if (s1.i1 != 3)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0x01000004)
+            {
+                return 101;
+            }
+            s2.b1 = true;
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.i2 != 0x01000104)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_Corrupted2()
+        {
+            S8 s1 = new S8();
+            S8Corrupted2 s2 = new S8Corrupted2();
+            s2.i1 = 1;
+            s2.b1 = 2;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S8Corrupted2, S8>(ref s2);
+            s2.i1 = 3;
+            s2.b1 = 4;
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0x02000000)
+            {
+                return 101;
+            }
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.b1 != 4)
+            {
+                return 101;
+            }
+
+            s2.b1 = 5;
+            s1 = Unsafe.As<S8Corrupted2, S8>(ref s2);
+            if (s1.i1 != 3)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0x05000000)
+            {
+                return 101;
+            }
+            if (s2.i1 != 3)
+            {
+                return 101;
+            }
+            if (s2.b1 != 5)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS8_Corrupted3()
+        {
+            S8 s1 = new S8();
+            S8Corrupted3 s2 = new S8Corrupted3();
+            s2.o1 = new string("Hello world!");
+            s1 = Unsafe.As<S8Corrupted3, S8>(ref s2);
+            S8Corrupted3 s3 = Unsafe.As<S8, S8Corrupted3>(ref s1);
+            s2.i1 = 0;
+            GC.Collect();
+            s3.i1 = 0;
+            GC.Collect();
+
+            return 100;
+        }
+
+        static int TestS8()
+        {
+            int res = 100;
+            bool failed = false;
+            res = TestS8_Simple();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_Simple failed");
+                failed = true;
+            }
+
+            res = TestS8_W1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_W1 failed");
+                failed = true;
+            }
+
+            res = TestS8_W2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_W2 failed");
+                failed = true;
+            }
+
+            res = TestS8_WW1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_WW1 failed");
+                failed = true;
+            }
+
+            res = TestS8_WW2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_WW2 failed");
+                failed = true;
+            }
+
+            res = TestS8_Copy1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_Copy1 failed");
+                failed = true;
+            }
+
+            res = TestS8_Copy2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_Copy2 failed");
+                failed = true;
+            }
+
+            res = TestS8_Corrupted1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_Corrupted1 failed");
+                failed = true;
+            }
+
+            res = TestS8_Corrupted2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8_Corrupted2 failed");
+                failed = true;
+            }
+
+            try
+            {
+                res = TestS8_Corrupted3();
+                if (res != 100)
+                {
+                    Console.WriteLine("TestS8_Corrupted3 failed");
+                    failed = true;
+                }
+                failed = true;
+            }
+            catch
+            {
+
+            }
+
+            if (failed)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        #endregion // S8 tests
+
+
+
+        #region S16 tests
+        struct S16
+        {
+            public int i1;
+            public int i2;
+            public int i3;
+            public int i4;
+        }
+
+        struct S16W
+        {
+            public S16 s16;
+        }
+
+        struct S16WW
+        {
+            public S16W s16;
+        }
+
+        struct S16Copy
+        {
+            public int i1;
+            public int i2;
+            public int i3;
+            public int i4;
+        }
+
+
+        struct S16WithS4
+        {
+            public S4 s1;
+            public S4 s2;
+            public S8 s3;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_Simple()
+        {
+            S16 s1 = new S16();
+            S16 s2 = new S16();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            s2.i3 = 3;
+            s2.i4 = 4;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.i4 != 0)
+            {
+                return 101;
+            }
+            blockPromotion(ref s2);
+            s1 = s2;
+            s2.i1 = 5;
+            s2.i2 = 6;
+            s2.i3 = 7;
+            s2.i4 = 8;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_W1()
+        {
+            S16 s1 = new S16();
+            S16W s2 = new S16W();
+            s2.s16.i1 = 1;
+            s2.s16.i2 = 2;
+            s2.s16.i3 = 3;
+            s2.s16.i4 = 4;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.i4 != 0)
+            {
+                return 101;
+            }
+            s1 = s2.s16;
+            s2.s16.i1 = 5;
+            s2.s16.i2 = 6;
+            s2.s16.i3 = 7;
+            s2.s16.i4 = 8;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.s16.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.s16.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.s16.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.s16.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_W2()
+        {
+            S16W s1 = new S16W();
+            S16 s2 = new S16();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            s2.i3 = 3;
+            s2.i4 = 4;
+            if (s1.s16.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.s16.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.s16.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.s16.i4 != 0)
+            {
+                return 101;
+            }
+            s1.s16 = s2;
+            s2.i1 = 5;
+            s2.i2 = 6;
+            s2.i3 = 7;
+            s2.i4 = 8;
+
+            if (s1.s16.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.s16.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.s16.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.s16.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_WW1()
+        {
+            S16 s1 = new S16();
+            S16WW s2 = new S16WW();
+            s2.s16.s16.i1 = 1;
+            s2.s16.s16.i2 = 2;
+            s2.s16.s16.i3 = 3;
+            s2.s16.s16.i4 = 4;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.i4 != 0)
+            {
+                return 101;
+            }
+            s1 = s2.s16.s16;
+            s2.s16.s16.i1 = 5;
+            s2.s16.s16.i2 = 6;
+            s2.s16.s16.i3 = 7;
+            s2.s16.s16.i4 = 8;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.s16.s16.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.s16.s16.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.s16.s16.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.s16.s16.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_WW2()
+        {
+            S16WW s1 = new S16WW();
+            S16 s2 = new S16();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            s2.i3 = 3;
+            s2.i4 = 4;
+            if (s1.s16.s16.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.s16.s16.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.s16.s16.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.s16.s16.i4 != 0)
+            {
+                return 101;
+            }
+            s1.s16.s16 = s2;
+            s2.i1 = 5;
+            s2.i2 = 6;
+            s2.i3 = 7;
+            s2.i4 = 8;
+
+            if (s1.s16.s16.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.s16.s16.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.s16.s16.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.s16.s16.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_Copy1()
+        {
+            S16 s1 = new S16();
+            S16Copy s2 = new S16Copy();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            s2.i3 = 3;
+            s2.i4 = 4;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.i4 != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S16Copy, S16>(ref s2);
+            s2.i1 = 5;
+            s2.i2 = 6;
+            s2.i3 = 7;
+            s2.i4 = 8;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_Copy2()
+        {
+            S16Copy s1 = new S16Copy();
+            S16 s2 = new S16();
+            s2.i1 = 1;
+            s2.i2 = 2;
+            s2.i3 = 3;
+            s2.i4 = 4;
+            if (s1.i1 != 0)
+            {
+                return 101;
+            }
+            if (s1.i2 != 0)
+            {
+                return 101;
+            }
+            if (s1.i3 != 0)
+            {
+                return 101;
+            }
+            if (s1.i4 != 0)
+            {
+                return 101;
+            }
+            s1 = Unsafe.As<S16, S16Copy>(ref s2);
+            s2.i1 = 5;
+            s2.i2 = 6;
+            s2.i3 = 7;
+            s2.i4 = 8;
+
+            if (s1.i1 != 1)
+            {
+                return 101;
+            }
+            if (s1.i2 != 2)
+            {
+                return 101;
+            }
+            if (s1.i3 != 3)
+            {
+                return 101;
+            }
+            if (s1.i4 != 4)
+            {
+                return 101;
+            }
+
+            if (s2.i1 != 5)
+            {
+                return 101;
+            }
+            if (s2.i2 != 6)
+            {
+                return 101;
+            }
+            if (s2.i3 != 7)
+            {
+                return 101;
+            }
+            if (s2.i4 != 8)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_RetypedFields1()
+        {
+            S16WithS4 s16 = new S16WithS4();
+            S4 s4 = new S4();
+            s4.i = 1;
+            if (s4.i != 1)
+            {
+                return 101;
+            }
+            if (s16.s1.i != 0)
+            {
+                return 101;
+            }
+
+            s16.s1 = s4;
+            s4.i = 2;
+            s16.s2 = s4;
+            s4.i = 3;
+            if (s16.s1.i != 1)
+            {
+                return 101;
+            }
+            if (s16.s2.i != 2)
+            {
+                return 101;
+            }
+            if (s4.i != 3)
+            {
+                return 101;
+            }
+            if (s4.i + s16.s1.i + s16.s2.i != 6)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_RetypedFields2()
+        {
+            S16WithS4 s16 = new S16WithS4();
+            S4 s4 = new S4();
+            s4.i = 1;
+            if (s4.i != 1)
+            {
+                return 101;
+            }
+            if (s16.s1.i != 0)
+            {
+                return 101;
+            }
+
+            s16.s1 = Unsafe.As<int, S4>(ref s4.i);
+            s4.i = 2;
+            s16.s2 = Unsafe.As<int, S4>(ref s4.i);
+            s4.i = 3;
+            if (s16.s1.i != 1)
+            {
+                return 101;
+            }
+            if (s16.s2.i != 2)
+            {
+                return 101;
+            }
+            if (s4.i != 3)
+            {
+                return 101;
+            }
+            if (s4.i + s16.s1.i + s16.s2.i != 6)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int TestS16_RetypedFields3()
+        {
+            S16WithS4 s16 = new S16WithS4();
+            S4 s4 = new S4();
+            s4.i = 1;
+            if (s4.i != 1)
+            {
+                return 101;
+            }
+            if (s16.s1.i != 0)
+            {
+                return 101;
+            }
+
+            s16.s1.i = Unsafe.As<S4, int>(ref s4);
+            s4.i = 2;
+            s16.s2.i = Unsafe.As<S4, int>(ref s4);
+            s4.i = 3;
+            if (s16.s1.i != 1)
+            {
+                return 101;
+            }
+            if (s16.s2.i != 2)
+            {
+                return 101;
+            }
+            if (s4.i != 3)
+            {
+                return 101;
+            }
+            if (s4.i + s16.s1.i + s16.s2.i != 6)
+            {
+                return 101;
+            }
+
+            return 100;
+        }
+
+        static int TestS16()
+        {
+            int res = 100;
+            bool failed = false;
+
+            res = TestS16_Simple();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_Simple failed");
+                failed = true;
+            }
+
+            res = TestS16_W1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_W1 failed");
+                failed = true;
+            }
+
+            res = TestS16_W2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_W2 failed");
+                failed = true;
+            }
+
+            res = TestS16_WW1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_WW1 failed");
+                failed = true;
+            }
+
+            res = TestS16_WW2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_WW2 failed");
+                failed = true;
+            }
+
+            res = TestS16_Copy1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_Copy1 failed");
+                failed = true;
+            }
+
+            res = TestS16_Copy2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_Copy2 failed");
+                failed = true;
+            }
+
+            res = TestS16_RetypedFields1();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_RetypedFields1 failed");
+                failed = true;
+            }
+
+            res = TestS16_RetypedFields2();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_RetypedFields2 failed");
+                failed = true;
+            }
+
+            res = TestS16_RetypedFields3();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16_RetypedFields3 failed");
+                failed = true;
+            }
+
+            if (failed)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        #endregion // S16 tests
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int Test()
+        {
+            int res = 100;
+            bool failed = false;
+
+            res = TestS4();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS4 failed");
+                failed = true;
+            }
+
+            res = TestS8();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS8 failed");
+                failed = true;
+            }
+
+            res = TestS16();
+            if (res != 100)
+            {
+                Console.WriteLine("TestS16 failed");
+                failed = true;
+            }
+
+            if (failed)
+            {
+                return 101;
+            }
+            return 100;
+        }
+
+        static int Main(string[] args)
+        {
+            return Test();
+        }
+    }
+}
diff --git a/src/tests/JIT/opt/Structs/structcopies.csproj b/src/tests/JIT/opt/Structs/structcopies.csproj
new file mode 100644 (file)
index 0000000..f3e1cbd
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>