Skip to content

Commit 69b1abd

Browse files
committed
[GS/StackClash] Avoid infinite dataflow loop on Reg2MaxOffset computations.
1 parent 80f7400 commit 69b1abd

File tree

2 files changed

+96
-17
lines changed

2 files changed

+96
-17
lines changed

bolt/lib/Passes/StackClashAnalysis.cpp

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "llvm/MC/MCInst.h"
2020
#include "llvm/Support/Format.h"
2121

22+
#include <limits>
23+
2224
#define DEBUG_TYPE "bolt-stackclash"
2325

2426
namespace llvm {
@@ -68,6 +70,51 @@ bool addToMaxMap(SmallDenseMap<MCPhysReg, uint64_t, 1> &M, MCPhysReg R,
6870
}
6971
}
7072

73+
class MaxOffsetT {
74+
private:
75+
int64_t V;
76+
// FIXME: should I add an "iteration count", and allow
77+
// "max" to be computed a few times. But if it's being
78+
// computed too many times, give up?
79+
80+
public:
81+
MaxOffsetT() : V(Bottom().V) {}
82+
MaxOffsetT(int64_t O) : V(O) {}
83+
static MaxOffsetT Top() { return std::numeric_limits<int64_t>::max(); }
84+
static MaxOffsetT Bottom() { return std::numeric_limits<int64_t>::min(); }
85+
static MaxOffsetT doConfluence(const MaxOffsetT O1, const MaxOffsetT O2) {
86+
if (O1 == O2)
87+
return O1;
88+
if (O1 == Bottom())
89+
return O2;
90+
if (O2 == Bottom())
91+
return O1;
92+
return Top();
93+
}
94+
MaxOffsetT operator+(const int64_t Offset) const {
95+
assert(*this != Bottom());
96+
if (*this == Top())
97+
return Top();
98+
return MaxOffsetT(V + Offset);
99+
}
100+
bool operator==(const MaxOffsetT RHS) const { return V == RHS.V; }
101+
bool operator!=(const MaxOffsetT RHS) const { return !(*this == RHS); }
102+
int64_t getIntValue() const {
103+
assert(*this != Bottom() && *this != Top());
104+
return V;
105+
}
106+
};
107+
108+
raw_ostream &operator<<(raw_ostream &OS, MaxOffsetT O) {
109+
if (O == MaxOffsetT::Top())
110+
OS << "(T)";
111+
else if (O == MaxOffsetT::Bottom())
112+
OS << "(B)";
113+
else
114+
OS << O.getIntValue();
115+
return OS;
116+
}
117+
71118
struct State {
72119
// Store the maximum possible offset to which the stack extends
73120
// beyond the furthest probe seen.
@@ -89,7 +136,7 @@ struct State {
89136
/// This is only tracked in Basic Blocks that are known to be reachable
90137
/// from an entry block. For blocks not (yet) known to be reachable from
91138
/// an entry block, the optional does not contain a value.
92-
std::optional<SmallDenseMap<MCPhysReg, int64_t, 2>> Reg2MaxOffset;
139+
std::optional<SmallDenseMap<MCPhysReg, MaxOffsetT, 2>> Reg2MaxOffset;
93140
// FIXME: It seems that conceptually it does not make sense to
94141
// track wheterh the SP value is currently at a fixed offset from
95142
// the value it was at function entry.
@@ -139,12 +186,18 @@ struct State {
139186
SPFixedOffsetFromOrig.reset();
140187

141188
if (StateIn.Reg2MaxOffset && Reg2MaxOffset) {
142-
SmallDenseMap<MCPhysReg, int64_t, 2> MergedMap;
189+
SmallDenseMap<MCPhysReg, MaxOffsetT, 2> MergedMap;
143190
for (auto R2MaxOff : *Reg2MaxOffset) {
144191
const MCPhysReg R = R2MaxOff.first;
145192
if (auto SIn_R2MaxOff = StateIn.Reg2MaxOffset->find(R);
146-
SIn_R2MaxOff != StateIn.Reg2MaxOffset->end())
147-
MergedMap[R] = std::max(R2MaxOff.second, SIn_R2MaxOff->second);
193+
SIn_R2MaxOff != StateIn.Reg2MaxOffset->end()) {
194+
MaxOffsetT MaxOff1 = R2MaxOff.second;
195+
MaxOffsetT MaxOff2 = SIn_R2MaxOff->second;
196+
MergedMap[R] = MaxOffsetT::doConfluence(MaxOff1, MaxOff2);
197+
#if 0
198+
std::max(R2MaxOff.second, SIn_R2MaxOff->second);
199+
#endif
200+
}
148201
}
149202
Reg2MaxOffset = MergedMap;
150203
} else if (StateIn.Reg2MaxOffset && !Reg2MaxOffset) {
@@ -266,8 +319,7 @@ bool checkNonConstSPOffsetChange(const BinaryContext &BC, BinaryFunction &BF,
266319
<< "; new MaxOffsetSinceLastProbe: "
267320
<< Next->MaxOffsetSinceLastProbe
268321
<< "; new SPFixedOffsetFromOrig: "
269-
<< Next->SPFixedOffsetFromOrig
270-
<< "\n";
322+
<< Next->SPFixedOffsetFromOrig << "\n";
271323
});
272324
}
273325
// assert(!OC.IsPreIndexOffsetChange || IsStackAccess);
@@ -276,10 +328,21 @@ bool checkNonConstSPOffsetChange(const BinaryContext &BC, BinaryFunction &BF,
276328
} else if (Cur.Reg2MaxOffset && Cur.Reg2MaxOffset->contains(OC.FromReg) &&
277329
OC.OffsetChange) {
278330
IsNonConstantSPOffsetChange = false;
279-
const int64_t MaxOffset = Cur.Reg2MaxOffset->find(OC.FromReg)->second;
280-
if (Next) {
281-
Next->MaxOffsetSinceLastProbe = MaxOffset - *OC.OffsetChange;
282-
Next->SPFixedOffsetFromOrig = std::nullopt;
331+
const MaxOffsetT MaxOffset =
332+
Cur.Reg2MaxOffset->find(OC.FromReg)->second;
333+
if (MaxOffset != MaxOffsetT::Top()) {
334+
if (Next) {
335+
Next->MaxOffsetSinceLastProbe =
336+
MaxOffset.getIntValue() - *OC.OffsetChange;
337+
Next->SPFixedOffsetFromOrig = std::nullopt;
338+
}
339+
} else {
340+
// unlimited Max Offset
341+
if (Next) {
342+
Next->MaxOffsetSinceLastProbe = std::numeric_limits<int64_t>::max();//MaxOffsetT::Top();
343+
Next->SPFixedOffsetFromOrig = std::nullopt;
344+
}
345+
IsNonConstantSPOffsetChange = true;
283346
}
284347
}
285348
}
@@ -293,12 +356,15 @@ bool checkNonConstSPOffsetChange(const BinaryContext &BC, BinaryFunction &BF,
293356
// and sp, x9, #0xffffffffffffff80
294357
uint64_t BitsToZeroMask = ~Mask;
295358
int64_t MaxOffsetChange = BitsToZeroMask + 1;
296-
if (Next) {
297-
Next->MaxOffsetSinceLastProbe =
298-
Cur.Reg2MaxOffset->find(FromReg)->second + MaxOffsetChange;
359+
IsNonConstantSPOffsetChange = false;
360+
MaxOffsetT MaxOffset = Cur.Reg2MaxOffset->find(FromReg)->second;
361+
MaxOffsetT NextOffset = MaxOffset + MaxOffsetChange;
362+
if (NextOffset == MaxOffsetT::Top())
363+
IsNonConstantSPOffsetChange = true;
364+
else if (Next) {
365+
Next->MaxOffsetSinceLastProbe = NextOffset.getIntValue();
299366
Next->SPFixedOffsetFromOrig = std::nullopt;
300367
}
301-
IsNonConstantSPOffsetChange = false;
302368
}
303369

304370
return IsNonConstantSPOffsetChange;
@@ -328,7 +394,7 @@ class StackClashDFAnalysis
328394
State getStartingStateAtBB(const BinaryBasicBlock &BB) {
329395
State Next;
330396
if (BB.isEntryPoint())
331-
Next.Reg2MaxOffset = SmallDenseMap<MCPhysReg, int64_t, 2>();
397+
Next.Reg2MaxOffset = SmallDenseMap<MCPhysReg, MaxOffsetT, 2>();
332398
return Next;
333399
}
334400

@@ -440,11 +506,11 @@ class StackClashDFAnalysis
440506
int64_t Offset = *OC.OffsetChange;
441507
if (OC.FromReg == SP) {
442508
(*Next.Reg2MaxOffset)[OC.ToReg] =
443-
*Cur.MaxOffsetSinceLastProbe - Offset;
509+
*Cur.MaxOffsetSinceLastProbe + (-Offset);
444510
FixedOffsetRegJustSet = OC.ToReg;
445511
} else if (auto I = Cur.Reg2MaxOffset->find(OC.FromReg);
446512
I != Cur.Reg2MaxOffset->end()) {
447-
(*Next.Reg2MaxOffset)[OC.ToReg] = (*I).second - Offset;
513+
(*Next.Reg2MaxOffset)[OC.ToReg] = (*I).second + (-Offset);
448514
FixedOffsetRegJustSet = OC.ToReg;
449515
}
450516
}

bolt/test/gadget-scanner/gs-stackclash.s

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
// RUN: llvm-bolt-gadget-scanner --scanners=stack-clash %t.exe 2>&1 | FileCheck -check-prefix=CHECK --allow-empty %s
33

44
.text
5+
.global f_no_inf_loop1
6+
.type f_no_inf_loop1 , %function
7+
f_no_inf_loop1:
8+
mov x21, sp
9+
.Lf_no_inf_loop1:
10+
subs x21, x21, #0x10
11+
b.ne .Lf_no_inf_loop1
12+
ret
13+
.size f_no_inf_loop1 , .-f_no_inf_loop1
14+
15+
// TODO: add a test that in a loop increases the SP by 16 bytes.
16+
// Will that result in a seemingly infinite loop in dataflow, until
17+
// the SP value passes the size of a max page?
518

619
.global f_fixed_large_stack
720
.type f_fixed_large_stack , %function

0 commit comments

Comments
 (0)