Skip to content

Commit 9bcf7b1

Browse files
committed
[NFCI][IR] ConstantRangeTest: add basic scaffolding for next-gen precision/correctness testing
I have long complained that while we have exhaustive tests for ConstantRange, they are, uh, not good. The approach of groking our own constant range via exhaustive enumeration is, mysterious. It neither tells us without doubt that the result is conservatively correct, nor the precise match to the ConstantRange result tells us that the result is precise. But yeah, it's fast, i give it that. In short, there are three things that we need to check: 1. That ConstantRange result is conservatively correct 2. That ConstantRange range is reasonable 3. That ConstantRange result is reasonably precise So let's not just check the middle one, but all three. This provides precision test coverage for D88178.
1 parent 3117794 commit 9bcf7b1

File tree

1 file changed

+88
-2
lines changed

1 file changed

+88
-2
lines changed

llvm/unittests/IR/ConstantRangeTest.cpp

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ static void ForeachNumInConstantRange(const ConstantRange &CR, Fn TestFn) {
5959
}
6060
}
6161

62+
unsigned GetNumValuesInConstantRange(const ConstantRange &CR) {
63+
unsigned NumValues = 0;
64+
ForeachNumInConstantRange(CR, [&NumValues](const APInt &) { ++NumValues; });
65+
return NumValues;
66+
}
67+
6268
struct OpRangeGathererBase {
6369
void account(const APInt &N);
6470
ConstantRange getRange();
@@ -107,6 +113,79 @@ struct SignedOpRangeGatherer : public OpRangeGathererBase {
107113
}
108114
};
109115

116+
struct AccumulatedPrecisionData {
117+
unsigned NumActualValues;
118+
unsigned NumValuesInActualCR;
119+
unsigned NumValuesInExactCR;
120+
121+
// If NumValuesInActualCR and NumValuesInExactCR are identical, and are not
122+
// equal to the NumActualValues, then the implementation is
123+
// overly conservatively correct, i.e. imprecise.
124+
125+
void reset() {
126+
NumActualValues = 0;
127+
NumValuesInActualCR = 0;
128+
NumValuesInExactCR = 0;
129+
}
130+
};
131+
132+
template <typename OpRangeGathererTy, typename Fn1, typename Fn2>
133+
static void TestUnaryOpExhaustive(Fn1 RangeFn, Fn2 IntFn,
134+
AccumulatedPrecisionData &Total) {
135+
Total.reset();
136+
137+
constexpr unsigned Bits = 4;
138+
139+
EnumerateConstantRanges(Bits, [&](const ConstantRange &CR) {
140+
// We'll want to record each true new value, for precision testing.
141+
SmallDenseSet<APInt, 1 << Bits> ExactValues;
142+
143+
// What constant range does ConstantRange method return?
144+
ConstantRange ActualCR = RangeFn(CR);
145+
146+
// We'll want to sanity-check the ActualCR, so this will build our own CR.
147+
OpRangeGathererTy ExactR(CR.getBitWidth());
148+
149+
// Let's iterate for each value in the original constant range.
150+
ForeachNumInConstantRange(CR, [&](const APInt &N) {
151+
// For this singular value, what is the true new value?
152+
const APInt NewN = IntFn(N);
153+
154+
// Constant range provided by ConstantRange method must be conservatively
155+
// correct, it must contain the true new value.
156+
EXPECT_TRUE(ActualCR.contains(NewN));
157+
158+
// Record this true new value in our own constant range.
159+
ExactR.account(NewN);
160+
161+
// And record the new true value itself.
162+
ExactValues.insert(NewN);
163+
});
164+
165+
// So, what range did we grok by exhaustively looking over each value?
166+
ConstantRange ExactCR = ExactR.getRange();
167+
168+
// So, how many new values are there actually, and as per the ranges?
169+
unsigned NumActualValues = ExactValues.size();
170+
unsigned NumValuesInExactCR = GetNumValuesInConstantRange(ExactCR);
171+
unsigned NumValuesInActualCR = GetNumValuesInConstantRange(ActualCR);
172+
173+
// Ranges should contain at least as much values as there actually was,
174+
// but it is possible they will contain extras.
175+
EXPECT_GE(NumValuesInExactCR, NumActualValues);
176+
EXPECT_GE(NumValuesInActualCR, NumActualValues);
177+
178+
// We expect that OpRangeGathererTy produces the exactly identical range
179+
// to what the ConstantRange method does.
180+
EXPECT_EQ(ExactR.getRange(), ActualCR);
181+
182+
// For precision testing, accumulate the overall numbers.
183+
Total.NumActualValues += NumActualValues;
184+
Total.NumValuesInActualCR += NumValuesInActualCR;
185+
Total.NumValuesInExactCR += NumValuesInExactCR;
186+
});
187+
}
188+
110189
template <typename Fn1, typename Fn2>
111190
static void TestUnsignedUnaryOpExhaustive(Fn1 RangeFn, Fn2 IntFn,
112191
bool SkipSignedIntMin = false) {
@@ -2400,9 +2479,16 @@ TEST_F(ConstantRangeTest, binaryXor) {
24002479
}
24012480

24022481
TEST_F(ConstantRangeTest, binaryNot) {
2403-
TestUnsignedUnaryOpExhaustive(
2482+
AccumulatedPrecisionData Precision;
2483+
2484+
TestUnaryOpExhaustive<UnsignedOpRangeGatherer>(
24042485
[](const ConstantRange &CR) { return CR.binaryNot(); },
2405-
[](const APInt &N) { return ~N; });
2486+
[](const APInt &N) { return ~N; }, Precision);
2487+
// FIXME: the implementation is not precise.
2488+
EXPECT_EQ(Precision.NumActualValues, 1936u);
2489+
EXPECT_EQ(Precision.NumValuesInActualCR, 2496u);
2490+
EXPECT_EQ(Precision.NumValuesInExactCR, 2496u);
2491+
24062492
TestUnsignedUnaryOpExhaustive(
24072493
[](const ConstantRange &CR) {
24082494
return CR.binaryXor(

0 commit comments

Comments
 (0)