Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 252a234

Browse files
committed
Fix lowering's containment analysis.
This fixes a silent bad code generation issue that arose during internal testing. The original repro is a test failure under COMPlus_JitStress=2. Due to explicit null check insertion, we (eventually) end up with the following LIR: t6096 = lclVar ref V86 cse10 <l:$4ad, c:$1b5> /--* t6096 ref * st.lclVar ref V41 tmp29 d:26 t2733 = lclVar ref V41 tmp29 u:26 <l:$4ad, c:$1b5> /--* t2733 ref * nullcheck byte <l:$4b8, c:$58a> t2736 = lclVar ref V41 tmp29 u:26 (last use) <l:$4ad, c:$1b5> t2737 = const long 20 field offset Fseq[y] $107 /--* t2736 ref +--* t2737 long t2735 = * + byref <l:$2ad, c:$2ac> t6081 = lclVar ref V83 cse7 <l:$4bd, c:$1b7> /--* t6081 ref * st.lclVar ref V41 tmp29 d:27 t2762 = lclVar ref V41 tmp29 u:27 <l:$4bd, c:$1b7> /--* t2762 ref * nullcheck byte <l:$583, c:$58f> t2765 = lclVar ref V41 tmp29 u:27 (last use) <l:$4bd, c:$1b7> t2766 = const long 20 field offset Fseq[y] $107 /--* t2765 ref +--* t2766 long t2764 = * + byref <l:$2af, c:$2ae> /--* t2764 byref t2763 = * indir int <l:$54e, c:$1ed> t2767 = lclVar int (AX) V07 loc4 $1ee /--* t2763 int +--* t2767 int t2738 = * + int <l:$554, c:$553> /--* t2735 byref +--* t2738 int * storeIndir int During lowering, we attempt to form an RMW add rooted at the final storeIndir. The pattern matching that attempts to form RMW operations, however, does not consider whether or not it is safe to perform the code motion involved in making the destination and source addresses for the operator contained. In this case, lowering moves the evaluation of the address (i.e. the dataflow tree rooted at the add that produces t2735) into the storeIndir. This moves a use of tmp29 across a def of the same and causes the program to store a value to an incorrect address. There are many variations on this pattern. For example, given the following C#: static int T(C[] a, C c) { return a.Length != c.M() ? 100 : 0; } The evaluation of a.Length (including the necessary null check) should occur before the call to c.M(). The lack of correct checks for safe code motion that caused the original repro, however, cause the JIT to generate bad code in this case as well: the null check for a is folded into the load of a.Length, which is then made contained by the compare. This results in the call to c.M() executing before the null check, which causes the program to behave incorrectly in the case that a is null. In order to fix the code motion analysis, this change introduces a new type, `SideEffectSet`, that can be used to summarize the side effects of a set of nodes and check whether or not they interfere with another set of side effects. This change then uses the new type to ensure that it is safe to perform the code motion necessary to make an operand contained before doing so.
1 parent 7adb700 commit 252a234

15 files changed

+1026
-74
lines changed

src/jit/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ set( JIT_SOURCES
6363
regset.cpp
6464
scopeinfo.cpp
6565
sharedfloat.cpp
66+
sideeffects.cpp
6667
sm.cpp
6768
smdata.cpp
6869
smweights.cpp

src/jit/codegenxarch.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5133,13 +5133,13 @@ regNumber CodeGen::genConsumeReg(GenTree* tree)
51335133
// Do liveness update for an address tree: one of GT_LEA, GT_LCL_VAR, or GT_CNS_INT (for call indirect).
51345134
void CodeGen::genConsumeAddress(GenTree* addr)
51355135
{
5136-
if (addr->OperGet() == GT_LEA)
5136+
if (!addr->isContained())
51375137
{
5138-
genConsumeAddrMode(addr->AsAddrMode());
5138+
genConsumeReg(addr);
51395139
}
5140-
else if (!addr->isContained())
5140+
else if (addr->OperGet() == GT_LEA)
51415141
{
5142-
genConsumeReg(addr);
5142+
genConsumeAddrMode(addr->AsAddrMode());
51435143
}
51445144
}
51455145

src/jit/gentree.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,30 @@ regMaskTP GenTreeCall::GetOtherRegMask() const
16071607
return resultMask;
16081608
}
16091609

1610+
//-------------------------------------------------------------------------
1611+
// IsPure:
1612+
// Returns true if this call is pure. For now, this uses the same
1613+
// definition of "pure" that is that used by HelperCallProperties: a
1614+
// pure call does not read or write any aliased (e.g. heap) memory or
1615+
// have other global side effects (e.g. class constructors, finalizers),
1616+
// but is allowed to throw an exception.
1617+
//
1618+
// NOTE: this call currently only returns true if the call target is a
1619+
// helper method that is known to be pure. No other analysis is
1620+
// performed.
1621+
//
1622+
// Arguments:
1623+
// Copiler - the compiler context.
1624+
//
1625+
// Returns:
1626+
// True if the call is pure; false otherwise.
1627+
//
1628+
bool GenTreeCall::IsPure(Compiler* compiler) const
1629+
{
1630+
return (gtCallType == CT_HELPER) &&
1631+
compiler->s_helperCallProperties.IsPure(compiler->eeGetHelperNum(gtCallMethHnd));
1632+
}
1633+
16101634
#ifndef LEGACY_BACKEND
16111635

16121636
//-------------------------------------------------------------------------

src/jit/gentree.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,7 @@ struct GenTree
13651365
{
13661366
case GT_LOCKADD:
13671367
case GT_XADD:
1368+
case GT_XCHG:
13681369
case GT_CMPXCHG:
13691370
case GT_BLK:
13701371
case GT_OBJ:
@@ -1404,7 +1405,7 @@ struct GenTree
14041405
return (gtOper == GT_XADD || gtOper == GT_XCHG || gtOper == GT_LOCKADD || gtOper == GT_CMPXCHG);
14051406
}
14061407

1407-
bool OperIsAtomicOp()
1408+
bool OperIsAtomicOp() const
14081409
{
14091410
return OperIsAtomicOp(gtOper);
14101411
}
@@ -3306,6 +3307,8 @@ struct GenTreeCall final : public GenTree
33063307
return (gtCallMoreFlags & GTF_CALL_M_DOES_NOT_RETURN) != 0;
33073308
}
33083309

3310+
bool IsPure(Compiler* compiler) const;
3311+
33093312
unsigned short gtCallMoreFlags; // in addition to gtFlags
33103313

33113314
unsigned char gtCallType : 3; // value from the gtCallTypes enumeration

src/jit/hashbv.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,19 @@ elemType hashBvNode::SubtractWithChange(hashBvNode* other)
289289
return result;
290290
}
291291

292+
bool hashBvNode::Intersects(hashBvNode* other)
293+
{
294+
for (int i = 0; i < this->numElements(); i++)
295+
{
296+
if ((this->elements[i] & other->elements[i]) != 0)
297+
{
298+
return true;
299+
}
300+
}
301+
302+
return false;
303+
}
304+
292305
void hashBvNode::AndWith(hashBvNode* other)
293306
{
294307
for (int i = 0; i < this->numElements(); i++)
@@ -1234,6 +1247,46 @@ class CompareAction
12341247
}
12351248
};
12361249

1250+
class IntersectsAction
1251+
{
1252+
public:
1253+
static inline void PreAction(hashBv* lhs, hashBv* rhs)
1254+
{
1255+
}
1256+
static inline void PostAction(hashBv* lhs, hashBv* rhs)
1257+
{
1258+
}
1259+
static inline bool DefaultResult()
1260+
{
1261+
return false;
1262+
}
1263+
1264+
static inline void LeftGap(hashBv* lhs, hashBvNode**& l, hashBvNode*& r, bool& result, bool& terminate)
1265+
{
1266+
// in rhs, not lhs
1267+
// so skip rhs
1268+
r = r->next;
1269+
}
1270+
static inline void RightGap(hashBv* lhs, hashBvNode**& l, hashBvNode*& r, bool& result, bool& terminate)
1271+
{
1272+
// in lhs, not rhs
1273+
// so skip lhs
1274+
l = &((*l)->next);
1275+
}
1276+
static inline void BothPresent(hashBv* lhs, hashBvNode**& l, hashBvNode*& r, bool& result, bool& terminate)
1277+
{
1278+
if ((*l)->Intersects(r))
1279+
{
1280+
terminate = true;
1281+
result = true;
1282+
}
1283+
}
1284+
static inline void LeftEmpty(hashBv* lhs, hashBvNode**& l, hashBvNode*& r, bool& result, bool& terminate)
1285+
{
1286+
r = r->next;
1287+
}
1288+
};
1289+
12371290
template <typename Action>
12381291
bool hashBv::MultiTraverseLHSBigger(hashBv* other)
12391292
{
@@ -1507,6 +1560,11 @@ bool hashBv::MultiTraverse(hashBv* other)
15071560
}
15081561
}
15091562

1563+
bool hashBv::Intersects(hashBv* other)
1564+
{
1565+
return MultiTraverse<IntersectsAction>(other);
1566+
}
1567+
15101568
bool hashBv::AndWithChange(hashBv* other)
15111569
{
15121570
return MultiTraverse<AndAction>(other);

src/jit/hashbv.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class hashBvNode
157157
elemType XorWithChange(hashBvNode* other);
158158
elemType SubtractWithChange(hashBvNode* other);
159159

160+
bool Intersects(hashBvNode* other);
161+
160162
#ifdef DEBUG
161163
void dump();
162164
#endif // DEBUG
@@ -253,6 +255,8 @@ class hashBv
253255
bool XorWithChange(hashBv* other);
254256
bool SubtractWithChange(hashBv* other);
255257

258+
bool Intersects(hashBv* other);
259+
256260
template <class Action>
257261
bool MultiTraverseLHSBigger(hashBv* other);
258262
template <class Action>

src/jit/jit.settings.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
<CppCompile Include="..\jitconfig.cpp" />
8787
<CppCompile Include="..\hostallocator.cpp" />
8888
<CppCompile Include="..\objectalloc.cpp" />
89+
<CppCompile Inlcude="..\sideeffects.cpp" />
8990
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='True'" Include="..\CodeGenLegacy.cpp" />
9091
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='False'" Include="..\Lower.cpp" />
9192
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='False'" Include="..\LSRA.cpp" />

src/jit/lower.cpp

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,34 +78,20 @@ bool Lowering::CheckImmedAndMakeContained(GenTree* parentNode, GenTree* childNod
7878
// and returns 'true' iff memory operand childNode can be contained in parentNode.
7979
//
8080
// Arguments:
81-
// parentNode - a non-leaf binary node
82-
// childNode - a memory op that is a child op of 'parentNode'
81+
// parentNode - any non-leaf node
82+
// childNode - some node that is an input to `parentNode`
8383
//
8484
// Return value:
8585
// true if it is safe to make childNode a contained memory operand.
8686
//
8787
bool Lowering::IsSafeToContainMem(GenTree* parentNode, GenTree* childNode)
8888
{
89-
assert(parentNode->OperIsBinary());
90-
assert(childNode->isMemoryOp());
89+
m_scratchSideEffects.Clear();
90+
m_scratchSideEffects.AddNode(comp, childNode);
9191

92-
unsigned int childFlags = (childNode->gtFlags & GTF_ALL_EFFECT);
93-
94-
GenTree* node;
95-
for (node = childNode; node != parentNode; node = node->gtNext)
92+
for (GenTree* node = childNode->gtNext; node != parentNode; node = node->gtNext)
9693
{
97-
assert(node != nullptr);
98-
99-
if ((childFlags != 0) && node->IsCall())
100-
{
101-
bool isPureHelper = (node->gtCall.gtCallType == CT_HELPER) &&
102-
comp->s_helperCallProperties.IsPure(comp->eeGetHelperNum(node->gtCall.gtCallMethHnd));
103-
if (!isPureHelper && ((node->gtFlags & childFlags & GTF_ALL_EFFECT) != 0))
104-
{
105-
return false;
106-
}
107-
}
108-
else if (node->OperIsStore() && comp->fgNodesMayInterfere(node, childNode))
94+
if (m_scratchSideEffects.InterferesWith(comp, node, false))
10995
{
11096
return false;
11197
}
@@ -2978,17 +2964,57 @@ void Lowering::AddrModeCleanupHelper(GenTreeAddrMode* addrMode, GenTree* node)
29782964
BlockRange().Remove(node);
29792965
}
29802966

2981-
// given two nodes which will be used in an addressing mode (base, index)
2982-
// walk backwards from the use to those nodes to determine if they are
2983-
// potentially modified in that range
2967+
//------------------------------------------------------------------------
2968+
// Lowering::AreSourcesPossibleModifiedLocals:
2969+
// Given two nodes which will be used in an addressing mode (base,
2970+
// index), check to see if they are lclVar reads, and if so, walk
2971+
// backwards from the use until both reads have been visited to
2972+
// determine if they are potentially modified in that range.
2973+
//
2974+
// Arguments:
2975+
// addr - the node that uses the base and index nodes
2976+
// base - the base node
2977+
// index - the index node
2978+
//
2979+
// Returns: true if either the base or index may be modified between the
2980+
// node and addr.
29842981
//
2985-
// returns: true if the sources given may be modified before they are used
2986-
bool Lowering::AreSourcesPossiblyModified(GenTree* addr, GenTree* base, GenTree* index)
2982+
bool Lowering::AreSourcesPossiblyModifiedLocals(GenTree* addr, GenTree* base, GenTree* index)
29872983
{
29882984
assert(addr != nullptr);
29892985

2990-
for (GenTree* cursor = addr; cursor != nullptr; cursor = cursor->gtPrev)
2986+
unsigned markCount = 0;
2987+
2988+
SideEffectSet baseSideEffects;
2989+
if (base != nullptr)
29912990
{
2991+
if (base->OperIsLocalRead())
2992+
{
2993+
baseSideEffects.AddNode(comp, base);
2994+
}
2995+
else
2996+
{
2997+
base = nullptr;
2998+
}
2999+
}
3000+
3001+
SideEffectSet indexSideEffects;
3002+
if (index != nullptr)
3003+
{
3004+
if (index->OperIsLocalRead())
3005+
{
3006+
indexSideEffects.AddNode(comp, index);
3007+
}
3008+
else
3009+
{
3010+
index = nullptr;
3011+
}
3012+
}
3013+
3014+
for (GenTree* cursor = addr;; cursor = cursor->gtPrev)
3015+
{
3016+
assert(cursor != nullptr);
3017+
29923018
if (cursor == base)
29933019
{
29943020
base = nullptr;
@@ -2999,17 +3025,19 @@ bool Lowering::AreSourcesPossiblyModified(GenTree* addr, GenTree* base, GenTree*
29993025
index = nullptr;
30003026
}
30013027

3002-
if (base == nullptr && index == nullptr)
3028+
if ((base == nullptr) && (index == nullptr))
30033029
{
30043030
return false;
30053031
}
30063032

3007-
if (base != nullptr && comp->fgNodesMayInterfere(base, cursor))
3033+
m_scratchSideEffects.Clear();
3034+
m_scratchSideEffects.AddNode(comp, cursor);
3035+
if ((base != nullptr) && m_scratchSideEffects.InterferesWith(baseSideEffects, false))
30083036
{
30093037
return true;
30103038
}
30113039

3012-
if (index != nullptr && comp->fgNodesMayInterfere(index, cursor))
3040+
if ((index != nullptr) && m_scratchSideEffects.InterferesWith(indexSideEffects, false))
30133041
{
30143042
return true;
30153043
}
@@ -3092,7 +3120,7 @@ GenTree* Lowering::TryCreateAddrMode(LIR::Use&& use, bool isIndir)
30923120
}
30933121

30943122
// make sure there are not any side effects between def of leaves and use
3095-
if (!doAddrMode || AreSourcesPossiblyModified(addr, base, index))
3123+
if (!doAddrMode || AreSourcesPossiblyModifiedLocals(addr, base, index))
30963124
{
30973125
JITDUMP(" No addressing mode\n");
30983126
return addr;
@@ -3122,7 +3150,8 @@ GenTree* Lowering::TryCreateAddrMode(LIR::Use&& use, bool isIndir)
31223150
GenTreeAddrMode* addrMode = new (comp, GT_LEA) GenTreeAddrMode(addrModeType, base, index, scale, offset);
31233151

31243152
addrMode->gtRsvdRegs = addr->gtRsvdRegs;
3125-
addrMode->gtFlags |= (addr->gtFlags & (GTF_ALL_EFFECT | GTF_IND_FLAGS));
3153+
addrMode->gtFlags |= (addr->gtFlags & GTF_IND_FLAGS);
3154+
addrMode->gtFlags &= ~GTF_ALL_EFFECT; // LEAs are side-effect-free.
31263155

31273156
JITDUMP("New addressing mode node:\n");
31283157
DISPNODE(addrMode);

src/jit/lower.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1717
#include "compiler.h"
1818
#include "phase.h"
1919
#include "lsra.h"
20+
#include "sideeffects.h"
2021

2122
class Lowering : public Phase
2223
{
@@ -228,6 +229,7 @@ class Lowering : public Phase
228229
void LowerCmp(GenTreePtr tree);
229230

230231
#if !CPU_LOAD_STORE_ARCH
232+
bool IsRMWIndirCandidate(GenTree* operand, GenTree* storeInd);
231233
bool IsBinOpInRMWStoreInd(GenTreePtr tree);
232234
bool IsRMWMemOpRootedAtStoreInd(GenTreePtr storeIndTree, GenTreePtr* indirCandidate, GenTreePtr* indirOpSource);
233235
bool SetStoreIndOpCountsIfRMWMemOp(GenTreePtr storeInd);
@@ -247,7 +249,7 @@ class Lowering : public Phase
247249
private:
248250
static bool NodesAreEquivalentLeaves(GenTreePtr candidate, GenTreePtr storeInd);
249251

250-
bool AreSourcesPossiblyModified(GenTree* addr, GenTree* base, GenTree* index);
252+
bool AreSourcesPossiblyModifiedLocals(GenTree* addr, GenTree* base, GenTree* index);
251253

252254
// return true if 'childNode' is an immediate that can be contained
253255
// by the 'parentNode' (i.e. folded into an instruction)
@@ -269,9 +271,10 @@ class Lowering : public Phase
269271
return LIR::AsRange(m_block);
270272
}
271273

272-
LinearScan* m_lsra;
273-
unsigned vtableCallTemp; // local variable we use as a temp for vtable calls
274-
BasicBlock* m_block;
274+
LinearScan* m_lsra;
275+
unsigned vtableCallTemp; // local variable we use as a temp for vtable calls
276+
SideEffectSet m_scratchSideEffects; // SideEffectSet used for IsSafeToContainMem and isRMWIndirCandidate
277+
BasicBlock* m_block;
275278
};
276279

277280
#endif // _LOWER_H_

src/jit/lowerarm.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2828
#ifdef _TARGET_ARM_
2929

3030
#include "jit.h"
31+
#include "sideeffects.h"
3132
#include "lower.h"
3233
#include "lsra.h"
3334

0 commit comments

Comments
 (0)