unsigned modifiedLclNum = BAD_VAR_NUM;
LclVarDsc* destLclVar = nullptr;
FieldSeqNode* destFldSeq = nullptr;
+ unsigned destLclOffset = 0;
bool destDoFldAsg = false;
GenTree* destAddr = nullptr;
GenTree* srcAddr = nullptr;
{
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
destLclNum = lclVarTree->GetLclNum();
modifiedLclNum = destLclNum;
destLclVar = &lvaTable[destLclNum];
+ destLclOffset = lclVarTree->GetLclOffs();
}
}
}
}
}
- 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())
{
if (srcLclNum != BAD_VAR_NUM)
{
- srcLclVar = &lvaTable[srcLclNum];
+ srcLclOffset = srcLclVarTree->GetLclOffs();
+ srcLclVar = &lvaTable[srcLclNum];
if (srcLclVar->lvPromoted && blockWidthIsConst)
{
}
#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)
{
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);
{
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 */);
}
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'
+ }
}
}
}
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'
+ }
}
}
}
}
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;
+ }
}
}
}
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);
}
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);
+ }
}
}
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);
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)
--- /dev/null
+// 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();
+ }
+ }
+}