diff --git a/src/jit/optimizer.cpp b/src/jit/optimizer.cpp
index 30a2f1e64417..1f57e053d08d 100644
--- a/src/jit/optimizer.cpp
+++ b/src/jit/optimizer.cpp
@@ -7757,14 +7757,16 @@ void Compiler::optComputeLoopSideEffectsOfBlock(BasicBlock* blk)
ArrayInfo arrInfo;
bool b = GetArrayInfoMap()->Lookup(addrArg, &arrInfo);
assert(b);
- CORINFO_CLASS_HANDLE elemType =
+ CORINFO_CLASS_HANDLE elemTypeEq =
EncodeElemType(arrInfo.m_elemType, arrInfo.m_elemStructType);
- tree->gtVNPair.SetBoth(
- vnStore->VNForFunc(TYP_BYREF, VNF_PtrToArrElem,
- vnStore->VNForHandle(ssize_t(elemType), GTF_ICON_CLASS_HDL),
+ ValueNum elemTypeEqVN =
+ vnStore->VNForHandle(ssize_t(elemTypeEq), GTF_ICON_CLASS_HDL);
+ ValueNum ptrToArrElemVN =
+ vnStore->VNForFunc(TYP_BYREF, VNF_PtrToArrElem, elemTypeEqVN,
// The rest are dummy arguments.
vnStore->VNForNull(), vnStore->VNForNull(),
- vnStore->VNForNull()));
+ vnStore->VNForNull());
+ tree->gtVNPair.SetBoth(ptrToArrElemVN);
}
}
}
diff --git a/src/jit/valuenum.cpp b/src/jit/valuenum.cpp
index b090b80fc7e6..6238ba9f20e1 100644
--- a/src/jit/valuenum.cpp
+++ b/src/jit/valuenum.cpp
@@ -3520,8 +3520,7 @@ ValueNum ValueNumStore::VNApplySelectors(ValueNumKind vnk,
printf(" VNApplySelectors:\n");
const char* modName;
const char* fldName = m_pComp->eeGetFieldName(fldHnd, &modName);
- printf(" VNForHandle(Fseq[%s]) is " FMT_VN ", fieldType is %s", fldName, fldHndVN,
- varTypeName(fieldType));
+ printf(" VNForHandle(%s) is " FMT_VN ", fieldType is %s", fldName, fldHndVN, varTypeName(fieldType));
if (varTypeIsStruct(fieldType))
{
printf(", size = %d", structSize);
@@ -3662,24 +3661,46 @@ ValueNum ValueNumStore::VNApplySelectorsAssign(
}
// Otherwise, fldHnd is a real field handle.
- CORINFO_FIELD_HANDLE fldHnd = fieldSeq->m_fieldHnd;
+ CORINFO_FIELD_HANDLE fldHnd = fieldSeq->m_fieldHnd;
+ ValueNum fldHndVN = VNForHandle(ssize_t(fldHnd), GTF_ICON_FIELD_HDL);
noway_assert(fldHnd != nullptr);
CorInfoType fieldCit = m_pComp->info.compCompHnd->getFieldType(fldHnd);
var_types fieldType = JITtype2varType(fieldCit);
- ValueNum fieldHndVN = VNForHandle(ssize_t(fldHnd), GTF_ICON_FIELD_HDL);
ValueNum elemAfter;
if (fieldSeq->m_next)
{
- ValueNum fseqMap = VNForMapSelect(vnk, fieldType, map, fieldHndVN);
+#ifdef DEBUG
+ if (m_pComp->verbose)
+ {
+ const char* modName;
+ const char* fldName = m_pComp->eeGetFieldName(fldHnd, &modName);
+ printf(" VNForHandle(%s) is " FMT_VN ", fieldType is %s\n", fldName, fldHndVN,
+ varTypeName(fieldType));
+ }
+#endif
+ ValueNum fseqMap = VNForMapSelect(vnk, fieldType, map, fldHndVN);
elemAfter = VNApplySelectorsAssign(vnk, fseqMap, fieldSeq->m_next, elem, indType, block);
}
else
{
+#ifdef DEBUG
+ if (m_pComp->verbose)
+ {
+ if (fieldSeq->m_next == nullptr)
+ {
+ printf(" VNApplySelectorsAssign:\n");
+ }
+ const char* modName;
+ const char* fldName = m_pComp->eeGetFieldName(fldHnd, &modName);
+ printf(" VNForHandle(%s) is " FMT_VN ", fieldType is %s\n", fldName, fldHndVN,
+ varTypeName(fieldType));
+ }
+#endif
elemAfter = VNApplySelectorsAssignTypeCoerce(elem, indType, block);
}
- ValueNum newMap = VNForMapStore(fieldType, map, fieldHndVN, elemAfter);
+ ValueNum newMap = VNForMapStore(fieldType, map, fldHndVN, elemAfter);
return newMap;
}
}
@@ -3726,13 +3747,9 @@ ValueNum ValueNumStore::VNForFieldSeq(FieldSeqNode* fieldSeq)
#ifdef DEBUG
if (m_pComp->verbose)
{
- printf(" fieldHnd " FMT_VN " is ", fieldHndVN);
- vnDump(m_pComp, fieldHndVN);
- printf("\n");
-
- printf(" fieldSeq " FMT_VN " is ", fieldSeqVN);
+ printf(" FieldSeq");
vnDump(m_pComp, fieldSeqVN);
- printf("\n");
+ printf(" is " FMT_VN "\n", fieldSeqVN);
}
#endif
@@ -3804,7 +3821,7 @@ ValueNum ValueNumStore::ExtendPtrVN(GenTree* opA, GenTree* opB)
FieldSeqNode* fldSeq = opB->gtIntCon.gtFieldSeq;
if (fldSeq != nullptr)
{
- return ExtendPtrVN(opA, opB->gtIntCon.gtFieldSeq);
+ return ExtendPtrVN(opA, fldSeq);
}
}
return NoVN;
@@ -6109,7 +6126,7 @@ ValueNum Compiler::fgMemoryVNForLoopSideEffects(MemoryKind memoryKind,
{
const char* modName;
const char* fldName = eeGetFieldName(fldHnd, &modName);
- printf(" VNForHandle(Fseq[%s]) is " FMT_VN "\n", fldName, fldHndVN);
+ printf(" VNForHandle(%s) is " FMT_VN "\n", fldName, fldHndVN);
}
#endif // DEBUG
@@ -6701,20 +6718,28 @@ void Compiler::fgValueNumberTree(GenTree* tree)
{
GenTreeLclVarCommon* lcl = tree->AsLclVarCommon();
unsigned lclNum = lcl->gtLclNum;
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+ // Do we have a Use (read) of the LclVar?
+ //
if ((lcl->gtFlags & GTF_VAR_DEF) == 0 ||
(lcl->gtFlags & GTF_VAR_USEASG)) // If it is a "pure" def, will handled as part of the assignment.
{
- LclVarDsc* varDsc = &lvaTable[lcl->gtLclNum];
+ bool generateUniqueVN = false;
+ FieldSeqNode* zeroOffsetFldSeq = nullptr;
+
+ // When we have a TYP_BYREF LclVar it can have a zero offset field sequence that needs to be added
+ if (typ == TYP_BYREF)
+ {
+ GetZeroOffsetFieldMap()->Lookup(tree, &zeroOffsetFldSeq);
+ }
+
if (varDsc->lvPromoted && varDsc->lvFieldCnt == 1)
{
// If the promoted var has only one field var, treat like a use of the field var.
lclNum = varDsc->lvFieldLclStart;
}
- // Initialize to the undefined value, so we know whether we hit any of the cases here.
- lcl->gtVNPair = ValueNumPair();
-
if (lcl->gtSsaNum == SsaConfig::RESERVED_SSA_NUM)
{
// Not an SSA variable.
@@ -6731,18 +6756,17 @@ void Compiler::fgValueNumberTree(GenTree* tree)
else
{
// Assign odd cases a new, unique, VN.
- lcl->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, lcl->TypeGet()));
+ generateUniqueVN = true;
}
}
else
{
- var_types varType = varDsc->TypeGet();
ValueNumPair wholeLclVarVNP = varDsc->GetPerSsaData(lcl->gtSsaNum)->m_vnPair;
// Check for mismatched LclVar size
//
unsigned typSize = genTypeSize(genActualType(typ));
- unsigned varSize = genTypeSize(genActualType(varType));
+ unsigned varSize = genTypeSize(genActualType(varDsc->TypeGet()));
if (typSize == varSize)
{
@@ -6755,54 +6779,76 @@ void Compiler::fgValueNumberTree(GenTree* tree)
// the indirection is reading less that the whole LclVar
// create a new VN that represent the partial value
//
- ValueNumPair partialLclVarVNP = vnStore->VNPairForCast(wholeLclVarVNP, typ, varType);
- lcl->gtVNPair = partialLclVarVNP;
+ ValueNumPair partialLclVarVNP =
+ vnStore->VNPairForCast(wholeLclVarVNP, typ, varDsc->TypeGet());
+ lcl->gtVNPair = partialLclVarVNP;
}
else
{
assert(typSize > varSize);
// the indirection is reading beyond the end of the field
//
- lcl->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, typ)); // return a new unique value
- // number
+ generateUniqueVN = true;
}
}
}
- // Temporary, to make progress.
- // TODO-CQ: This should become an assert again...
- if (lcl->gtVNPair.GetLiberal() == ValueNumStore::NoVN)
+
+ if (!generateUniqueVN)
{
- assert(lcl->gtVNPair.GetConservative() == ValueNumStore::NoVN);
-
- // We don't want to fabricate arbitrary value numbers to things we can't reason about.
- // So far, we know about two of these cases:
- // Case 1) We have a local var who has never been defined but it's seen as a use.
- // This is the case of storeIndir(addr(lclvar)) = expr. In this case since we only
- // take the address of the variable, this doesn't mean it's a use nor we have to
- // initialize it, so in this very rare case, we fabricate a value number.
- // Case 2) Local variables that represent structs which are assigned using CpBlk.
- GenTree* nextNode = lcl->gtNext;
- assert((nextNode->gtOper == GT_ADDR && nextNode->gtOp.gtOp1 == lcl) ||
- varTypeIsStruct(lcl->TypeGet()));
- lcl->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, lcl->TypeGet()));
+ // There are a couple of cases where we haven't assigned a valid value number to 'lcl'
+ //
+ if (lcl->gtVNPair.GetLiberal() == ValueNumStore::NoVN)
+ {
+ // So far, we know about two of these cases:
+ // Case 1) We have a local var who has never been defined but it's seen as a use.
+ // This is the case of storeIndir(addr(lclvar)) = expr. In this case since we only
+ // take the address of the variable, this doesn't mean it's a use nor we have to
+ // initialize it, so in this very rare case, we fabricate a value number.
+ // Case 2) Local variables that represent structs which are assigned using CpBlk.
+ //
+ // Make sure we have either case 1 or case 2
+ //
+ GenTree* nextNode = lcl->gtNext;
+ assert((nextNode->gtOper == GT_ADDR && nextNode->gtOp.gtOp1 == lcl) ||
+ varTypeIsStruct(lcl->TypeGet()));
+
+ // We will assign a unique value number for these
+ //
+ generateUniqueVN = true;
+ }
}
- assert(lcl->gtVNPair.BothDefined());
- }
- // TODO-Review: For the short term, we have a workaround for copyblk/initblk. Those that use
- // addrSpillTemp will have a statement like "addrSpillTemp = addr(local)." If we previously decided
- // that this block operation defines the local, we will have labeled the "local" node as a DEF
- // This flag propagates to the "local" on the RHS. So we'll assume that this is correct,
- // and treat it as a def (to a new, unique VN).
+ if (!generateUniqueVN && (zeroOffsetFldSeq != nullptr))
+ {
+ ValueNum addrExtended = vnStore->ExtendPtrVN(lcl, zeroOffsetFldSeq);
+ if (addrExtended != ValueNumStore::NoVN)
+ {
+ lcl->gtVNPair.SetBoth(addrExtended);
+ }
+ }
+
+ if (generateUniqueVN)
+ {
+ ValueNum uniqVN = vnStore->VNForExpr(compCurBB, lcl->TypeGet());
+ lcl->gtVNPair.SetBoth(uniqVN);
+ }
+ }
else if ((lcl->gtFlags & GTF_VAR_DEF) != 0)
{
- LclVarDsc* varDsc = &lvaTable[lcl->gtLclNum];
+ // We have a Def (write) of the LclVar
+
+ // TODO-Review: For the short term, we have a workaround for copyblk/initblk. Those that use
+ // addrSpillTemp will have a statement like "addrSpillTemp = addr(local)." If we previously decided
+ // that this block operation defines the local, we will have labeled the "local" node as a DEF
+ // This flag propagates to the "local" on the RHS. So we'll assume that this is correct,
+ // and treat it as a def (to a new, unique VN).
+ //
if (lcl->gtSsaNum != SsaConfig::RESERVED_SSA_NUM)
{
- lvaTable[lclNum]
- .GetPerSsaData(lcl->gtSsaNum)
- ->m_vnPair.SetBoth(vnStore->VNForExpr(compCurBB, lcl->TypeGet()));
+ ValueNum uniqVN = vnStore->VNForExpr(compCurBB, lcl->TypeGet());
+ varDsc->GetPerSsaData(lcl->gtSsaNum)->m_vnPair.SetBoth(uniqVN);
}
+
lcl->gtVNPair = ValueNumPair(); // Avoid confusion -- we don't set the VN of a lcl being defined.
}
}
@@ -7298,11 +7344,14 @@ void Compiler::fgValueNumberTree(GenTree* tree)
ValueNum inxVN = funcApp.m_args[2];
FieldSeqNode* fldSeq = vnStore->FieldSeqVNToFieldSeq(funcApp.m_args[3]);
- // Does the child of the GT_IND 'arg' have an associated zero-offset field sequence?
- FieldSeqNode* addrFieldSeq = nullptr;
- if (GetZeroOffsetFieldMap()->Lookup(arg, &addrFieldSeq))
+ if (arg->gtOper != GT_LCL_VAR)
{
- fldSeq = GetFieldSeqStore()->Append(addrFieldSeq, fldSeq);
+ // Does the child of the GT_IND 'arg' have an associated zero-offset field sequence?
+ FieldSeqNode* addrFieldSeq = nullptr;
+ if (GetZeroOffsetFieldMap()->Lookup(arg, &addrFieldSeq))
+ {
+ fldSeq = GetFieldSeqStore()->Append(addrFieldSeq, fldSeq);
+ }
}
#ifdef DEBUG
@@ -7374,6 +7423,7 @@ void Compiler::fgValueNumberTree(GenTree* tree)
assert(staticOffset == nullptr);
}
#endif // DEBUG
+
// Get the first (instance or static) field from field seq. GcHeap[field] will yield
// the "field map".
if (fldSeq->IsFirstElemFieldSeq())
@@ -7681,6 +7731,10 @@ void Compiler::fgValueNumberTree(GenTree* tree)
// Get the array element type equivalence class rep.
CORINFO_CLASS_HANDLE elemTypeEq = EncodeElemType(arrInfo.m_elemType, arrInfo.m_elemStructType);
ValueNum elemTypeEqVN = vnStore->VNForHandle(ssize_t(elemTypeEq), GTF_ICON_CLASS_HDL);
+ JITDUMP(" VNForHandle(arrElemType: %s) is " FMT_VN "\n",
+ (arrInfo.m_elemType == TYP_STRUCT) ? eeGetClassName(arrInfo.m_elemStructType)
+ : varTypeName(arrInfo.m_elemType),
+ elemTypeEqVN)
// We take the "VNNormalValue"s here, because if either has exceptional outcomes, they will be captured
// as part of the value of the composite "addr" operation...
@@ -7711,6 +7765,7 @@ void Compiler::fgValueNumberTree(GenTree* tree)
}
}
#endif // DEBUG
+
// We now need to retrieve the value number for the array element value
// and give this value number to the GT_IND node 'tree'
// We do this whenever we have an rvalue, but we don't do it for a
@@ -7800,6 +7855,7 @@ void Compiler::fgValueNumberTree(GenTree* tree)
assert(staticOffset == nullptr);
}
#endif // DEBUG
+
// Get a field sequence for just the first field in the sequence
//
FieldSeqNode* firstFieldOnly = GetFieldSeqStore()->CreateSingleton(fldSeq2->m_fieldHnd);
@@ -8361,6 +8417,7 @@ void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueN
GenTree* indirectCellAddress = call->fgArgInfo->GetArgNode(0);
assert(indirectCellAddress->IsCnsIntOrI() && indirectCellAddress->gtRegNum == REG_R2R_INDIRECT_PARAM);
#endif // DEBUG
+
// For ARM indirectCellAddress is consumed by the call itself, so it should have added as an implicit argument
// in morph. So we do not need to use EntryPointAddrAsArg0, because arg0 is already an entry point addr.
useEntryPointAddrAsArg0 = false;
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_18259/GitHub_18259.cs b/tests/src/JIT/Regression/JitBlue/GitHub_18259/GitHub_18259.cs
new file mode 100644
index 000000000000..86aa93811758
--- /dev/null
+++ b/tests/src/JIT/Regression/JitBlue/GitHub_18259/GitHub_18259.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+// Test case for issue 18259
+//
+// We were a missing check for ZeroOffsetFldSeq values on LclVar reads
+//
+// Debug: Outputs 0
+// Release: Outputs 1234
+
+struct S1
+{
+ public uint F0;
+ public S1(uint f0): this() { F0 = f0; }
+}
+
+struct S2
+{
+ public S1 F1;
+ public int F2;
+ public S2(S1 f1): this() { F1 = f1; F2 = 1; }
+}
+
+public class Program
+{
+ static S2[] s_11 = new S2[]{new S2(new S1(1234u))}; // Assigns 1234 to F1.F0
+ public static int Main()
+ {
+ ref S1 vr7 = ref s_11[0].F1;
+ vr7.F0 = vr7.F0;
+
+ vr7.F0 = 0; // Bug: We fail to update the Map with the proper ValueNum here.
+
+ if (vr7.F0 != 0) // Bug: We continue to return the old value for vr7.F0
+ {
+ System.Console.WriteLine(vr7.F0);
+ System.Console.WriteLine("Failed");
+ return 101;
+ }
+ else
+ {
+ System.Console.WriteLine("Passed");
+ return 100;
+ }
+ }
+}
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_18259/GitHub_18259.csproj b/tests/src/JIT/Regression/JitBlue/GitHub_18259/GitHub_18259.csproj
new file mode 100644
index 000000000000..d86ed9f3d765
--- /dev/null
+++ b/tests/src/JIT/Regression/JitBlue/GitHub_18259/GitHub_18259.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+ Release
+ AnyCPU
+ $(MSBuildProjectName)
+ Exe
+
+ True
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_20838/GitHub_20838.cs b/tests/src/JIT/Regression/JitBlue/GitHub_20838/GitHub_20838.cs
new file mode 100644
index 000000000000..2b0f4cb66f6b
--- /dev/null
+++ b/tests/src/JIT/Regression/JitBlue/GitHub_20838/GitHub_20838.cs
@@ -0,0 +1,628 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+// Test case for fix 20838
+// We are a missing check for ZeroOffsetFldSeq values on LclVar reads
+// without the fix several cases in this test fail because value numbering
+// gives us the old value instead of the modified value.
+//
+// CoreRun.exe GitHub_18259.exe
+// Failed - Test_e0_S2_F3_F1() - 640 -- 512 + 128
+// Failed - Test_e0_S2_F4_F1() - 960 -- 512 + 256 + 128 + 64
+// Failed - Test_e1_S2_F3_F1() - 640
+// Failed - Test_e1_S2_F4_F1() - 960
+
+struct S1
+{
+ public int F1;
+ public int F2;
+
+ public S1(int a1, int a2) { F1 = a1; F2 = a2; }
+}
+
+struct S2
+{
+ public S1 F3;
+ public S1 F4;
+
+ public S2(S1 a1, S1 a2) { F3 = a1; F4 = a2; }
+}
+
+public class Program
+{
+
+ static S1 ss_S1a = new S1(101, 102);
+ static S1 ss_S1b = new S1(103, 104);
+ static S1 ss_S1c = new S1(105, 106);
+ static S1 ss_S1d = new S1(107, 108);
+ static S2 ss_S2a = new S2(ss_S1a, ss_S1b);
+ static S2 ss_S2b = new S2(ss_S1c, ss_S1d);
+ static S2[] sa_S2 = new S2[] { ss_S2a, ss_S2b };
+
+ static bool Test_e0_S2_F3_F1()
+ {
+ ref S2 ref_e0_S2 = ref sa_S2[0];
+ ref S1 ref_e0_S2_F3 = ref sa_S2[0].F3;
+ ref int ref_e0_S2_F3_F1 = ref sa_S2[0].F3.F1;
+
+ int result = 0;
+
+ if (sa_S2[0].F3.F1 != 101)
+ {
+ result |= 1;
+ }
+
+ if (ref_e0_S2_F3_F1 != 101)
+ {
+ result |= 2;
+ }
+
+ if (ref_e0_S2_F3.F1 != 101)
+ {
+ result |= 4;
+ }
+
+ if (ref_e0_S2.F3.F1 != 101)
+ {
+ result |= 8;
+ }
+
+ if (ref_e0_S2_F3.F1 != 101)
+ {
+ result |= 16;
+ }
+
+ ref_e0_S2_F3.F1 = 99;
+
+ if (sa_S2[0].F3.F1 != 99)
+ {
+ result |= 32;
+ }
+
+ if (ref_e0_S2_F3_F1 != 99)
+ {
+ result |= 64;
+ }
+
+ if (ref_e0_S2_F3.F1 != 99)
+ {
+ result |= 128;
+ }
+
+ if (ref_e0_S2.F3.F1 != 99)
+ {
+ result |= 256;
+ }
+
+ if (ref_e0_S2_F3.F1 != 99)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e0_S2_F3_F1() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e0_S2_F3_F2()
+ {
+ ref S2 ref_e0_S2 = ref sa_S2[0];
+ ref S1 ref_e0_S2_F3 = ref sa_S2[0].F3;
+ ref int ref_e0_S2_F3_F2 = ref sa_S2[0].F3.F2;
+
+ int result = 0;
+
+ if (sa_S2[0].F3.F2 != 102)
+ {
+ result |= 1;
+ }
+
+ if (ref_e0_S2_F3_F2 != 102)
+ {
+ result |= 2;
+ }
+
+ if (ref_e0_S2_F3.F2 != 102)
+ {
+ result |= 4;
+ }
+
+ if (ref_e0_S2.F3.F2 != 102)
+ {
+ result |= 8;
+ }
+
+ if (ref_e0_S2_F3.F2 != 102)
+ {
+ result |= 16;
+ }
+
+ ref_e0_S2_F3.F2 = 98;
+
+ if (sa_S2[0].F3.F2 != 98)
+ {
+ result |= 32;
+ }
+
+ if (ref_e0_S2_F3_F2 != 98)
+ {
+ result |= 64;
+ }
+
+ if (ref_e0_S2_F3.F2 != 98)
+ {
+ result |= 128;
+ }
+
+ if (ref_e0_S2.F3.F2 != 98)
+ {
+ result |= 256;
+ }
+
+ if (ref_e0_S2_F3.F2 != 98)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e0_S2_F3_F2() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e0_S2_F4_F1()
+ {
+ ref S2 ref_e0_S2 = ref sa_S2[0];
+ ref S1 ref_e0_S2_F4 = ref sa_S2[0].F4;
+ ref int ref_e0_S2_F4_F1 = ref sa_S2[0].F4.F1;
+
+ int result = 0;
+
+ if (sa_S2[0].F4.F1 != 103)
+ {
+ result |= 1;
+ }
+
+ if (ref_e0_S2_F4_F1 != 103)
+ {
+ result |= 2;
+ }
+
+ if (ref_e0_S2_F4.F1 != 103)
+ {
+ result |= 4;
+ }
+
+ if (ref_e0_S2.F4.F1 != 103)
+ {
+ result |= 8;
+ }
+
+ if (ref_e0_S2_F4.F1 != 103)
+ {
+ result |= 16;
+ }
+
+ ref_e0_S2_F4.F1 = 97;
+
+ if (sa_S2[0].F4.F1 != 97)
+ {
+ result |= 32;
+ }
+
+ if (ref_e0_S2_F4_F1 != 97)
+ {
+ result |= 64;
+ }
+
+ if (ref_e0_S2_F4.F1 != 97)
+ {
+ result |= 128;
+ }
+
+ if (ref_e0_S2.F4.F1 != 97)
+ {
+ result |= 256;
+ }
+
+ if (ref_e0_S2_F4.F1 != 97)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e0_S2_F4_F1() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e0_S2_F4_F2()
+ {
+ ref S2 ref_e0_S2 = ref sa_S2[0];
+ ref S1 ref_e0_S2_F4 = ref sa_S2[0].F4;
+ ref int ref_e0_S2_F4_F2 = ref sa_S2[0].F4.F2;
+
+ int result = 0;
+
+ if (sa_S2[0].F4.F2 != 104)
+ {
+ result |= 1;
+ }
+
+ if (ref_e0_S2_F4_F2 != 104)
+ {
+ result |= 2;
+ }
+
+ if (ref_e0_S2_F4.F2 != 104)
+ {
+ result |= 4;
+ }
+
+ if (ref_e0_S2.F4.F2 != 104)
+ {
+ result |= 8;
+ }
+
+ if (ref_e0_S2_F4.F2 != 104)
+ {
+ result |= 16;
+ }
+
+ ref_e0_S2_F4.F2 = 96;
+
+ if (sa_S2[0].F4.F2 != 96)
+ {
+ result |= 32;
+ }
+
+ if (ref_e0_S2_F4_F2 != 96)
+ {
+ result |= 64;
+ }
+
+ if (ref_e0_S2_F4.F2 != 96)
+ {
+ result |= 128;
+ }
+
+ if (ref_e0_S2.F4.F2 != 96)
+ {
+ result |= 256;
+ }
+
+ if (ref_e0_S2_F4.F2 != 96)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e0_S2_F4_F2() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e1_S2_F3_F1()
+ {
+ ref S2 ref_e1_S2 = ref sa_S2[1];
+ ref S1 ref_e1_S2_F3 = ref sa_S2[1].F3;
+ ref int ref_e1_S2_F3_F1 = ref sa_S2[1].F3.F1;
+
+ int result = 0;
+
+ if (sa_S2[1].F3.F1 != 105)
+ {
+ result |= 1;
+ }
+
+ if (ref_e1_S2_F3_F1 != 105)
+ {
+ result |= 2;
+ }
+
+ if (ref_e1_S2_F3.F1 != 105)
+ {
+ result |= 4;
+ }
+
+ if (ref_e1_S2.F3.F1 != 105)
+ {
+ result |= 8;
+ }
+
+ if (ref_e1_S2_F3.F1 != 105)
+ {
+ result |= 16;
+ }
+
+ ref_e1_S2_F3.F1 = 95;
+
+ if (sa_S2[1].F3.F1 != 95)
+ {
+ result |= 32;
+ }
+
+ if (ref_e1_S2_F3_F1 != 95)
+ {
+ result |= 64;
+ }
+
+ if (ref_e1_S2_F3.F1 != 95)
+ {
+ result |= 128;
+ }
+
+ if (ref_e1_S2.F3.F1 != 95)
+ {
+ result |= 256;
+ }
+
+ if (ref_e1_S2_F3.F1 != 95)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e1_S2_F3_F1() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e1_S2_F3_F2()
+ {
+ ref S2 ref_e1_S2 = ref sa_S2[1];
+ ref S1 ref_e1_S2_F3 = ref sa_S2[1].F3;
+ ref int ref_e1_S2_F3_F2 = ref sa_S2[1].F3.F2;
+
+ int result = 0;
+
+ if (sa_S2[1].F3.F2 != 106)
+ {
+ result |= 1;
+ }
+
+ if (ref_e1_S2_F3_F2 != 106)
+ {
+ result |= 2;
+ }
+
+ if (ref_e1_S2_F3.F2 != 106)
+ {
+ result |= 4;
+ }
+
+ if (ref_e1_S2.F3.F2 != 106)
+ {
+ result |= 8;
+ }
+
+ if (ref_e1_S2_F3.F2 != 106)
+ {
+ result |= 16;
+ }
+
+ ref_e1_S2_F3.F2 = 94;
+
+ if (sa_S2[1].F3.F2 != 94)
+ {
+ result |= 32;
+ }
+
+ if (ref_e1_S2_F3_F2 != 94)
+ {
+ result |= 64;
+ }
+
+ if (ref_e1_S2_F3.F2 != 94)
+ {
+ result |= 128;
+ }
+
+ if (ref_e1_S2.F3.F2 != 94)
+ {
+ result |= 256;
+ }
+
+ if (ref_e1_S2_F3.F2 != 94)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e1_S2_F3_F2() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e1_S2_F4_F1()
+ {
+ ref S2 ref_e1_S2 = ref sa_S2[1];
+ ref S1 ref_e1_S2_F4 = ref sa_S2[1].F4;
+ ref int ref_e1_S2_F4_F1 = ref sa_S2[1].F4.F1;
+
+ int result = 0;
+
+ if (sa_S2[1].F4.F1 != 107)
+ {
+ result |= 1;
+ }
+
+ if (ref_e1_S2_F4_F1 != 107)
+ {
+ result |= 2;
+ }
+
+ if (ref_e1_S2_F4.F1 != 107)
+ {
+ result |= 4;
+ }
+
+ if (ref_e1_S2.F4.F1 != 107)
+ {
+ result |= 8;
+ }
+
+ if (ref_e1_S2_F4.F1 != 107)
+ {
+ result |= 16;
+ }
+
+ ref_e1_S2_F4.F1 = 93;
+
+ if (sa_S2[1].F4.F1 != 93)
+ {
+ result |= 32;
+ }
+
+ if (ref_e1_S2_F4_F1 != 93)
+ {
+ result |= 64;
+ }
+
+ if (ref_e1_S2_F4.F1 != 93)
+ {
+ result |= 128;
+ }
+
+ if (ref_e1_S2.F4.F1 != 93)
+ {
+ result |= 256;
+ }
+
+ if (ref_e1_S2_F4.F1 != 93)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e1_S2_F4_F1() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool Test_e1_S2_F4_F2()
+ {
+ ref S2 ref_e1_S2 = ref sa_S2[1];
+ ref S1 ref_e1_S2_F4 = ref sa_S2[1].F4;
+ ref int ref_e1_S2_F4_F2 = ref sa_S2[1].F4.F2;
+
+ int result = 0;
+
+ if (sa_S2[1].F4.F2 != 108)
+ {
+ result |= 1;
+ }
+
+ if (ref_e1_S2_F4_F2 != 108)
+ {
+ result |= 2;
+ }
+
+ if (ref_e1_S2_F4.F2 != 108)
+ {
+ result |= 4;
+ }
+
+ if (ref_e1_S2.F4.F2 != 108)
+ {
+ result |= 8;
+ }
+
+ if (ref_e1_S2_F4.F2 != 108)
+ {
+ result |= 16;
+ }
+
+ ref_e1_S2_F4.F2 = 92;
+
+ if (sa_S2[1].F4.F2 != 92)
+ {
+ result |= 32;
+ }
+
+ if (ref_e1_S2_F4_F2 != 92)
+ {
+ result |= 64;
+ }
+
+ if (ref_e1_S2_F4.F2 != 92)
+ {
+ result |= 128;
+ }
+
+ if (ref_e1_S2.F4.F2 != 92)
+ {
+ result |= 256;
+ }
+
+ if (ref_e1_S2_F4.F2 != 92)
+ {
+ result |= 512;
+ }
+
+ if (result != 0)
+ {
+ Console.WriteLine("Failed - Test_e1_S2_F4_F2() - " + result);
+ return false;
+ }
+
+ return true;
+ }
+
+ public static int Main()
+ {
+ bool isPassing = true;
+
+ isPassing &= Test_e0_S2_F3_F1();
+
+ isPassing &= Test_e0_S2_F3_F2();
+
+ isPassing &= Test_e0_S2_F4_F1();
+
+ isPassing &= Test_e0_S2_F4_F2();
+
+ isPassing &= Test_e1_S2_F3_F1();
+
+ isPassing &= Test_e1_S2_F3_F2();
+
+ isPassing &= Test_e1_S2_F4_F1();
+
+ isPassing &= Test_e1_S2_F4_F2();
+
+ if (isPassing)
+ {
+ Console.WriteLine("Passed");
+ return 100;
+ }
+ else
+ {
+ Console.WriteLine("Failed");
+ return 101;
+ }
+ }
+}
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_20838/GitHub_20838.csproj b/tests/src/JIT/Regression/JitBlue/GitHub_20838/GitHub_20838.csproj
new file mode 100644
index 000000000000..d86ed9f3d765
--- /dev/null
+++ b/tests/src/JIT/Regression/JitBlue/GitHub_20838/GitHub_20838.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+ Release
+ AnyCPU
+ $(MSBuildProjectName)
+ Exe
+
+ True
+
+
+
+
+
+
+
\ No newline at end of file