[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.
This commit is contained in:
Roman Lebedev 2020-09-25 00:30:54 +03:00
parent 31177949cb
commit 9bcf7b1c7a
No known key found for this signature in database
GPG Key ID: 083C3EBB4A1689E0
1 changed files with 88 additions and 2 deletions

View File

@ -59,6 +59,12 @@ static void ForeachNumInConstantRange(const ConstantRange &CR, Fn TestFn) {
}
}
unsigned GetNumValuesInConstantRange(const ConstantRange &CR) {
unsigned NumValues = 0;
ForeachNumInConstantRange(CR, [&NumValues](const APInt &) { ++NumValues; });
return NumValues;
}
struct OpRangeGathererBase {
void account(const APInt &N);
ConstantRange getRange();
@ -107,6 +113,79 @@ struct SignedOpRangeGatherer : public OpRangeGathererBase {
}
};
struct AccumulatedPrecisionData {
unsigned NumActualValues;
unsigned NumValuesInActualCR;
unsigned NumValuesInExactCR;
// If NumValuesInActualCR and NumValuesInExactCR are identical, and are not
// equal to the NumActualValues, then the implementation is
// overly conservatively correct, i.e. imprecise.
void reset() {
NumActualValues = 0;
NumValuesInActualCR = 0;
NumValuesInExactCR = 0;
}
};
template <typename OpRangeGathererTy, typename Fn1, typename Fn2>
static void TestUnaryOpExhaustive(Fn1 RangeFn, Fn2 IntFn,
AccumulatedPrecisionData &Total) {
Total.reset();
constexpr unsigned Bits = 4;
EnumerateConstantRanges(Bits, [&](const ConstantRange &CR) {
// We'll want to record each true new value, for precision testing.
SmallDenseSet<APInt, 1 << Bits> ExactValues;
// What constant range does ConstantRange method return?
ConstantRange ActualCR = RangeFn(CR);
// We'll want to sanity-check the ActualCR, so this will build our own CR.
OpRangeGathererTy ExactR(CR.getBitWidth());
// Let's iterate for each value in the original constant range.
ForeachNumInConstantRange(CR, [&](const APInt &N) {
// For this singular value, what is the true new value?
const APInt NewN = IntFn(N);
// Constant range provided by ConstantRange method must be conservatively
// correct, it must contain the true new value.
EXPECT_TRUE(ActualCR.contains(NewN));
// Record this true new value in our own constant range.
ExactR.account(NewN);
// And record the new true value itself.
ExactValues.insert(NewN);
});
// So, what range did we grok by exhaustively looking over each value?
ConstantRange ExactCR = ExactR.getRange();
// So, how many new values are there actually, and as per the ranges?
unsigned NumActualValues = ExactValues.size();
unsigned NumValuesInExactCR = GetNumValuesInConstantRange(ExactCR);
unsigned NumValuesInActualCR = GetNumValuesInConstantRange(ActualCR);
// Ranges should contain at least as much values as there actually was,
// but it is possible they will contain extras.
EXPECT_GE(NumValuesInExactCR, NumActualValues);
EXPECT_GE(NumValuesInActualCR, NumActualValues);
// We expect that OpRangeGathererTy produces the exactly identical range
// to what the ConstantRange method does.
EXPECT_EQ(ExactR.getRange(), ActualCR);
// For precision testing, accumulate the overall numbers.
Total.NumActualValues += NumActualValues;
Total.NumValuesInActualCR += NumValuesInActualCR;
Total.NumValuesInExactCR += NumValuesInExactCR;
});
}
template <typename Fn1, typename Fn2>
static void TestUnsignedUnaryOpExhaustive(Fn1 RangeFn, Fn2 IntFn,
bool SkipSignedIntMin = false) {
@ -2400,9 +2479,16 @@ TEST_F(ConstantRangeTest, binaryXor) {
}
TEST_F(ConstantRangeTest, binaryNot) {
TestUnsignedUnaryOpExhaustive(
AccumulatedPrecisionData Precision;
TestUnaryOpExhaustive<UnsignedOpRangeGatherer>(
[](const ConstantRange &CR) { return CR.binaryNot(); },
[](const APInt &N) { return ~N; });
[](const APInt &N) { return ~N; }, Precision);
// FIXME: the implementation is not precise.
EXPECT_EQ(Precision.NumActualValues, 1936u);
EXPECT_EQ(Precision.NumValuesInActualCR, 2496u);
EXPECT_EQ(Precision.NumValuesInExactCR, 2496u);
TestUnsignedUnaryOpExhaustive(
[](const ConstantRange &CR) {
return CR.binaryXor(