Skip to content

Commit 76cf397

Browse files
Fold primitive-typed access to promoted structs in local morph and forbid mismatched struct assignments (#76766)
* GenTree::GetLayout field FIELDs * Do not create invalid IR when replacing promoted fields Instead, create constructions like "OBJ(ADDR(LCL_VAR))" that block morphing can recognize on its own. * Delete lvFieldHnd * GenTree::GetLayout - CALL/COMMA * GenTreeHWIntrinsic::GetLayout * Add an assert that LHS and RHS match * Fix assert violation And simplify the code... * Delete RetypedAsScalarFieldsMap Without handle equality, the assert no longer holds, for example: ```cs private StructWithInt Problem(StructWithInt b, StructWithInt a) { a = ((StructWithStructWithInt*)&b)->StructWithInt; return a; } ``` * Delete a bit of code Dead / unncessary. No diffs. * Fold promoted locals in local morph * Support GT_IND in MorphStructField This is significantly simpler than moving the promotion logic to post-order because we don't need to fiddle with the ref counting process and complexities of intermediate states, e. g. "IND<float>(ADDR(FIELD<int>(ADDR(LCL_VAR<struct>))))" can be naturally turned into "BITCAST<float>(LCL_VAR<int>)" like this. * More code removal * Move "fgMorphStructField" to local morph * TP tuning With this, we have only a very small regression: Base: 1297463478, Diff: 1297750893, +0.0222% LocalAddressVisitor::MorphStructField : 345099 : NA : 31.52% : +0.0266% LocalAddressVisitor::PreOrderVisit : 224684 : +3.53% : 20.52% : +0.0173% memset : 88591 : +0.99% : 8.09% : +0.0068% Compiler::fgMorphSmpOp : -19170 : -0.06% : 1.75% : -0.0015% Compiler::fgMorphStructField : -367474 : -100.00% : 33.56% : -0.0283% (This is with a dummy field on LclVarDsc) Losing the assert does not seem worrysome as we have an identical one in "fgMorphField".
1 parent 41db775 commit 76cf397

8 files changed

Lines changed: 168 additions & 312 deletions

File tree

src/coreclr/jit/compiler.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,8 +1021,6 @@ class LclVarDsc
10211021
return structHnd;
10221022
}
10231023

1024-
CORINFO_FIELD_HANDLE lvFieldHnd; // field handle for promoted struct fields
1025-
10261024
private:
10271025
ClassLayout* m_layout; // layout info for structs
10281026

@@ -3374,10 +3372,6 @@ class Compiler
33743372
structPromotionInfo.typeHnd = NO_CLASS_HANDLE;
33753373
}
33763374

3377-
#ifdef DEBUG
3378-
void CheckRetypedAsScalar(CORINFO_FIELD_HANDLE fieldHnd, var_types requestedType);
3379-
#endif // DEBUG
3380-
33813375
private:
33823376
bool CanPromoteStructVar(unsigned lclNum);
33833377
bool ShouldPromoteStructVar(unsigned lclNum);
@@ -3392,12 +3386,6 @@ class Compiler
33923386
private:
33933387
Compiler* compiler;
33943388
lvaStructPromotionInfo structPromotionInfo;
3395-
3396-
#ifdef DEBUG
3397-
typedef JitHashTable<CORINFO_FIELD_HANDLE, JitPtrKeyFuncs<CORINFO_FIELD_STRUCT_>, var_types>
3398-
RetypedAsScalarFieldsMap;
3399-
RetypedAsScalarFieldsMap retypedFieldsMap;
3400-
#endif // DEBUG
34013389
};
34023390

34033391
StructPromotionHelper* structPromotionHelper;
@@ -5866,7 +5854,6 @@ class Compiler
58665854
#endif
58675855

58685856
PhaseStatus fgPromoteStructs();
5869-
void fgMorphStructField(GenTree* tree, GenTree* parent);
58705857
void fgMorphLocalField(GenTree* tree, GenTree* parent);
58715858

58725859
// Reset the refCount for implicit byrefs.

src/coreclr/jit/gentree.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ ClassLayout* GenTree::GetLayout(Compiler* compiler) const
592592
{
593593
assert(varTypeIsStruct(TypeGet()));
594594

595+
CORINFO_CLASS_HANDLE structHnd = NO_CLASS_HANDLE;
595596
switch (OperGet())
596597
{
597598
case GT_LCL_VAR:
@@ -604,12 +605,31 @@ ClassLayout* GenTree::GetLayout(Compiler* compiler) const
604605
case GT_BLK:
605606
return AsBlk()->GetLayout();
606607

608+
case GT_COMMA:
609+
return AsOp()->gtOp2->GetLayout(compiler);
610+
611+
#ifdef FEATURE_HW_INTRINSICS
612+
case GT_HWINTRINSIC:
613+
return AsHWIntrinsic()->GetLayout(compiler);
614+
#endif // FEATURE_HW_INTRINSICS
615+
607616
case GT_MKREFANY:
608-
return compiler->typGetObjLayout(compiler->impGetRefAnyClass());
617+
structHnd = compiler->impGetRefAnyClass();
618+
break;
619+
620+
case GT_FIELD:
621+
compiler->eeGetFieldType(AsField()->gtFldHnd, &structHnd);
622+
break;
623+
624+
case GT_CALL:
625+
structHnd = AsCall()->gtRetClsHnd;
626+
break;
609627

610628
default:
611629
unreached();
612630
}
631+
632+
return compiler->typGetObjLayout(structHnd);
613633
}
614634

615635
//-----------------------------------------------------------
@@ -22853,6 +22873,43 @@ bool GenTreeHWIntrinsic::OperIsMemoryLoadOrStore() const
2285322873
return OperIsMemoryLoad() || OperIsMemoryStore();
2285422874
}
2285522875

22876+
//------------------------------------------------------------------------
22877+
// GetLayout: Get the layout for this TYP_STRUCT HWI node.
22878+
//
22879+
// Arguments:
22880+
// compiler - The Compiler instance
22881+
//
22882+
// Return Value:
22883+
// The struct layout for the node.
22884+
//
22885+
// Notes:
22886+
// Currently, this method synthesizes block layouts, since there is not
22887+
// enough space in the GenTreeHWIntrinsic node to store a proper layout
22888+
// pointer or number.
22889+
//
22890+
ClassLayout* GenTreeHWIntrinsic::GetLayout(Compiler* compiler) const
22891+
{
22892+
assert(TypeIs(TYP_STRUCT));
22893+
22894+
switch (GetHWIntrinsicId())
22895+
{
22896+
#ifdef TARGET_ARM64
22897+
case NI_AdvSimd_Arm64_LoadPairScalarVector64:
22898+
case NI_AdvSimd_Arm64_LoadPairScalarVector64NonTemporal:
22899+
case NI_AdvSimd_Arm64_LoadPairVector64:
22900+
case NI_AdvSimd_Arm64_LoadPairVector64NonTemporal:
22901+
return compiler->typGetBlkLayout(16);
22902+
22903+
case NI_AdvSimd_Arm64_LoadPairVector128:
22904+
case NI_AdvSimd_Arm64_LoadPairVector128NonTemporal:
22905+
return compiler->typGetBlkLayout(32);
22906+
#endif // TARGET_ARM64
22907+
22908+
default:
22909+
unreached();
22910+
}
22911+
}
22912+
2285622913
NamedIntrinsic GenTreeHWIntrinsic::GetHWIntrinsicId() const
2285722914
{
2285822915
NamedIntrinsic id = gtHWIntrinsicId;

src/coreclr/jit/gentree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6363,6 +6363,8 @@ struct GenTreeHWIntrinsic : public GenTreeJitIntrinsic
63636363

63646364
unsigned GetResultOpNumForFMA(GenTree* use, GenTree* op1, GenTree* op2, GenTree* op3);
63656365

6366+
ClassLayout* GetLayout(Compiler* compiler) const;
6367+
63666368
NamedIntrinsic GetHWIntrinsicId() const;
63676369

63686370
//---------------------------------------------------------------------------------------

src/coreclr/jit/lclmorph.cpp

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
381381
{
382382
GenTree* const node = *use;
383383

384-
if (node->OperIs(GT_FIELD))
384+
if (node->OperIs(GT_IND, GT_FIELD))
385385
{
386386
MorphStructField(node, user);
387387
}
@@ -933,8 +933,6 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
933933
indir->AsLclFld()->SetLayout(indirLayout);
934934
lclNode = indir->AsLclVarCommon();
935935

936-
// Promoted locals aren't currently handled here so partial access can't be
937-
// later be transformed into a LCL_VAR and the variable cannot be enregistered.
938936
m_compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::LocalField));
939937
break;
940938

@@ -1000,13 +998,6 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
1000998

1001999
if (indir->TypeGet() != TYP_STRUCT)
10021000
{
1003-
if (varDsc->lvPromoted)
1004-
{
1005-
// TODO-ADDR: support promoted locals here by moving the promotion morphing
1006-
// from pre-order to post-order.
1007-
return IndirTransform::None;
1008-
}
1009-
10101001
if (indir->TypeGet() == varDsc->TypeGet())
10111002
{
10121003
return IndirTransform::LclVar;
@@ -1082,23 +1073,105 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
10821073
}
10831074

10841075
//------------------------------------------------------------------------
1085-
// MorphStructField: Replaces a GT_FIELD based promoted/normed struct field access
1086-
// (e.g. FIELD(ADDR(LCL_VAR))) with a GT_LCL_VAR that references the struct field.
1076+
// MorphStructField: Reduces indirect access to a promoted local (e.g.
1077+
// FIELD(ADDR(LCL_VAR))) to a GT_LCL_VAR that references the struct field.
10871078
//
10881079
// Arguments:
1089-
// node - the GT_FIELD node
1090-
// user - the node that uses the field
1080+
// indir - the GT_IND/GT_FIELD node
1081+
// user - the node that uses the field
10911082
//
10921083
// Notes:
1093-
// This does not do anything if the field access does not denote
1094-
// a promoted/normed struct field.
1084+
// This does not do anything if the access does not denote a promoted
1085+
// struct field.
10951086
//
1096-
void MorphStructField(GenTree* node, GenTree* user)
1087+
void MorphStructField(GenTree* indir, GenTree* user)
10971088
{
1098-
assert(node->OperIs(GT_FIELD));
1099-
// TODO-Cleanup: Move fgMorphStructField implementation here, it's not used anywhere else.
1100-
m_compiler->fgMorphStructField(node, user);
1101-
m_stmtModified |= node->OperIs(GT_LCL_VAR);
1089+
assert(indir->OperIs(GT_IND, GT_FIELD));
1090+
1091+
GenTree* objRef = indir->AsUnOp()->gtOp1;
1092+
GenTree* obj = ((objRef != nullptr) && objRef->OperIs(GT_ADDR)) ? objRef->AsOp()->gtOp1 : nullptr;
1093+
1094+
// TODO-Bug: this code does not pay attention to "GTF_IND_VOLATILE".
1095+
if ((obj != nullptr) && obj->OperIs(GT_LCL_VAR) && varTypeIsStruct(obj))
1096+
{
1097+
const LclVarDsc* varDsc = m_compiler->lvaGetDesc(obj->AsLclVarCommon());
1098+
1099+
if (varDsc->lvPromoted)
1100+
{
1101+
unsigned fieldOffset = indir->OperIs(GT_FIELD) ? indir->AsField()->gtFldOffset : 0;
1102+
unsigned fieldLclNum = m_compiler->lvaGetFieldLocal(varDsc, fieldOffset);
1103+
1104+
if (fieldLclNum == BAD_VAR_NUM)
1105+
{
1106+
// Access a promoted struct's field with an offset that doesn't correspond to any field.
1107+
// It can happen if the struct was cast to another struct with different offsets.
1108+
return;
1109+
}
1110+
1111+
const LclVarDsc* fieldDsc = m_compiler->lvaGetDesc(fieldLclNum);
1112+
var_types fieldType = fieldDsc->TypeGet();
1113+
GenTree* lclVarNode = nullptr;
1114+
GenTreeFlags lclVarFlags = indir->gtFlags & (GTF_NODE_MASK | GTF_DONT_CSE);
1115+
1116+
assert(fieldType != TYP_STRUCT); // promoted LCL_VAR can't have a struct type.
1117+
if ((indir->TypeGet() == fieldType) || ((user != nullptr) && user->OperIs(GT_ADDR)))
1118+
{
1119+
lclVarNode = indir;
1120+
1121+
if (user != nullptr)
1122+
{
1123+
if (user->OperIs(GT_ASG) && (user->AsOp()->gtOp1 == indir))
1124+
{
1125+
lclVarFlags |= GTF_VAR_DEF;
1126+
}
1127+
else if (user->OperIs(GT_ADDR))
1128+
{
1129+
// TODO-ADDR: delete this quirk.
1130+
lclVarFlags &= ~GTF_DONT_CSE;
1131+
}
1132+
}
1133+
}
1134+
else // Here we will turn "FIELD/IND(ADDR(LCL_VAR<parent>))" into "OBJ/IND(ADDR(LCL_VAR<field>))".
1135+
{
1136+
// This type mismatch is somewhat common due to how we retype fields of struct type that
1137+
// recursively simplify down to a primitive. E. g. for "struct { struct { int a } A, B }",
1138+
// the promoted local would look like "{ int a, B }", while the IR would contain "FIELD"
1139+
// nodes for the outer struct "A".
1140+
//
1141+
if (indir->TypeIs(TYP_STRUCT))
1142+
{
1143+
// TODO-1stClassStructs: delete this once "IND<struct>" nodes are no more.
1144+
if (indir->OperIs(GT_IND))
1145+
{
1146+
// We do not have a layout for this node.
1147+
return;
1148+
}
1149+
1150+
ClassLayout* layout = indir->GetLayout(m_compiler);
1151+
indir->SetOper(GT_OBJ);
1152+
indir->AsBlk()->SetLayout(layout);
1153+
indir->AsBlk()->gtBlkOpKind = GenTreeBlk::BlkOpKindInvalid;
1154+
#ifndef JIT32_GCENCODER
1155+
indir->AsBlk()->gtBlkOpGcUnsafe = false;
1156+
#endif // !JIT32_GCENCODER
1157+
}
1158+
else
1159+
{
1160+
indir->SetOper(GT_IND);
1161+
}
1162+
1163+
lclVarNode = obj;
1164+
}
1165+
1166+
lclVarNode->SetOper(GT_LCL_VAR);
1167+
lclVarNode->AsLclVarCommon()->SetLclNum(fieldLclNum);
1168+
lclVarNode->gtType = fieldType;
1169+
lclVarNode->gtFlags = lclVarFlags;
1170+
1171+
JITDUMP("Replacing the field in promoted struct with local var V%02u\n", fieldLclNum);
1172+
m_stmtModified = true;
1173+
}
1174+
}
11021175
}
11031176

11041177
//------------------------------------------------------------------------

src/coreclr/jit/lclvars.cpp

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,12 +1668,7 @@ bool Compiler::lvaFieldOffsetCmp::operator()(const lvaStructFieldInfo& field1, c
16681668
// Arguments:
16691669
// compiler - pointer to a compiler to get access to an allocator, compHandle etc.
16701670
//
1671-
Compiler::StructPromotionHelper::StructPromotionHelper(Compiler* compiler)
1672-
: compiler(compiler)
1673-
, structPromotionInfo()
1674-
#ifdef DEBUG
1675-
, retypedFieldsMap(compiler->getAllocator(CMK_DebugOnly))
1676-
#endif // DEBUG
1671+
Compiler::StructPromotionHelper::StructPromotionHelper(Compiler* compiler) : compiler(compiler), structPromotionInfo()
16771672
{
16781673
}
16791674

@@ -1706,27 +1701,6 @@ bool Compiler::StructPromotionHelper::TryPromoteStructVar(unsigned lclNum)
17061701
return false;
17071702
}
17081703

1709-
#ifdef DEBUG
1710-
//--------------------------------------------------------------------------------------------
1711-
// CheckRetypedAsScalar - check that the fldType for this fieldHnd was retyped as requested type.
1712-
//
1713-
// Arguments:
1714-
// fieldHnd - the field handle;
1715-
// requestedType - as which type the field was accessed;
1716-
//
1717-
// Notes:
1718-
// For example it can happen when such struct A { struct B { long c } } is compiled and we access A.B.c,
1719-
// it could look like "GT_FIELD struct B.c -> ADDR -> GT_FIELD struct A.B -> ADDR -> LCL_VAR A" , but
1720-
// "GT_FIELD struct A.B -> ADDR -> LCL_VAR A" can be promoted to "LCL_VAR long A.B" and then
1721-
// there is type mistmatch between "GT_FIELD struct B.c" and "LCL_VAR long A.B".
1722-
//
1723-
void Compiler::StructPromotionHelper::CheckRetypedAsScalar(CORINFO_FIELD_HANDLE fieldHnd, var_types requestedType)
1724-
{
1725-
assert(retypedFieldsMap.Lookup(fieldHnd));
1726-
assert(retypedFieldsMap[fieldHnd] == requestedType);
1727-
}
1728-
#endif // DEBUG
1729-
17301704
//--------------------------------------------------------------------------------------------
17311705
// CanPromoteStructType - checks if the struct type can be promoted.
17321706
//
@@ -2285,9 +2259,6 @@ Compiler::lvaStructFieldInfo Compiler::StructPromotionHelper::GetFieldInfo(CORIN
22852259
{
22862260
fieldInfo.fldType = compiler->getSIMDTypeForSize(simdSize);
22872261
fieldInfo.fldSize = simdSize;
2288-
#ifdef DEBUG
2289-
retypedFieldsMap.Set(fieldInfo.fldHnd, fieldInfo.fldType, RetypedAsScalarFieldsMap::Overwrite);
2290-
#endif // DEBUG
22912262
}
22922263
}
22932264
}
@@ -2388,9 +2359,7 @@ bool Compiler::StructPromotionHelper::TryPromoteStructField(lvaStructFieldInfo&
23882359
// (tracked by #10019).
23892360
fieldInfo.fldType = fieldVarType;
23902361
fieldInfo.fldSize = fieldSize;
2391-
#ifdef DEBUG
2392-
retypedFieldsMap.Set(fieldInfo.fldHnd, fieldInfo.fldType, RetypedAsScalarFieldsMap::Overwrite);
2393-
#endif // DEBUG
2362+
23942363
return true;
23952364
}
23962365

@@ -2472,7 +2441,6 @@ void Compiler::StructPromotionHelper::PromoteStructVar(unsigned lclNum)
24722441
fieldVarDsc->lvType = pFieldInfo->fldType;
24732442
fieldVarDsc->lvExactSize = pFieldInfo->fldSize;
24742443
fieldVarDsc->lvIsStructField = true;
2475-
fieldVarDsc->lvFieldHnd = pFieldInfo->fldHnd;
24762444
fieldVarDsc->lvFldOffset = pFieldInfo->fldOffset;
24772445
fieldVarDsc->lvFldOrdinal = pFieldInfo->fldOrdinal;
24782446
fieldVarDsc->lvParentLcl = lclNum;

src/coreclr/jit/lower.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3414,8 +3414,7 @@ void Lowering::LowerRet(GenTreeUnOp* ret)
34143414
bool constStructInit = retVal->IsConstInitVal();
34153415
bool implicitCastFromSameOrBiggerSize = (genTypeSize(retActualType) <= genTypeSize(retValActualType));
34163416

3417-
// This could happen if we have retyped op1 as a primitive type during struct promotion,
3418-
// check `retypedFieldsMap` for details.
3417+
// This could happen if we have retyped op1 as a primitive type during struct promotion.
34193418
bool actualTypesMatch = (retActualType == retValActualType);
34203419

34213420
assert(actualTypesMatch || constStructInit || implicitCastFromSameOrBiggerSize);

0 commit comments

Comments
 (0)