blob: 523bc4fbc2aae1311b4db624a4e25e145021bfd7 [file] [edit]
#include <limits>
#include "ir/constraint.h"
#include "ir/abstract.h"
#include "gtest/gtest.h"
using namespace wasm;
using namespace wasm::Abstract;
using namespace wasm::constraint;
TEST(ConstraintTest, TestEq) {
// x == 5 (we use "x" for the name of the thing being compared, in these
// comments).
Constraint c{Eq, {Literal(int32_t(5))}};
// Sets start as proving anything, as representing unreachable code.
AndedConstraintSet s;
EXPECT_TRUE(s.provesEverything());
EXPECT_EQ(s.proves(c), True);
// We can't infer anything if told so.
s.setProvesNothing();
EXPECT_EQ(s.proves(c), Unknown);
// If we add it, then things check out: a thing always proves itself true.
s.approximateAnd(c);
EXPECT_EQ(s.size(), 1);
EXPECT_EQ(s.proves(c), True);
// Ditto using set();
s.set(c);
EXPECT_EQ(s.proves(c), True);
// x == 10, a different number: we can infer false.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), False);
// x != 15: we can infer true.
EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(15))}}), True);
// x != 5: we can infer false.
EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(5))}}), False);
// x > y: we can infer nothing.
EXPECT_EQ(s.proves(Constraint{GtS, {Index(1)}}), Unknown);
}
TEST(ConstraintTest, TestNe) {
AndedConstraintSet s;
// x != 5
Constraint c{Ne, {Literal(int32_t(5))}};
s.set(c);
// Checks out versus itself.
EXPECT_EQ(s.proves(c), True);
// x == 10: we don't know.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), Unknown);
// x != 15: we don't know.
EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(15))}}), Unknown);
// x == 5: we can infer false.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(5))}}), False);
}
TEST(ConstraintTest, TestMulti) {
AndedConstraintSet s;
// x != 5 && x != 10
Constraint c{Ne, {Literal(int32_t(5))}};
Constraint d{Ne, {Literal(int32_t(10))}};
s.set(c);
s.approximateAnd(d);
// Each checks out versus itself.
EXPECT_EQ(s.proves(c), True);
EXPECT_EQ(s.proves(d), True);
// x == 5: false.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(5))}}), False);
// x == 10: false.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), False);
// x == 15: we don't know.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(15))}}), Unknown);
// x != 15: we don't know.
EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(15))}}), Unknown);
}
TEST(ConstraintTest, TestSets) {
// x == 5
Constraint c{Eq, {Literal(int32_t(5))}};
AndedConstraintSet s;
// Any set always proves itself to be true.
EXPECT_EQ(s.proves(s), True);
// Ditto after adding something.
s.set(c);
EXPECT_EQ(s.proves(s), True);
// Another set, empty.
AndedConstraintSet t;
// Make both sets contain the same stuff.
t.set(c);
EXPECT_EQ(s.proves(t), True);
// Now t has *different* stuff, x == 10, which given s is false.
t.set(Constraint{Eq, {Literal(int32_t(10))}});
EXPECT_EQ(s.proves(t), False);
// Same, with x != 10. Now we know it is true.
t.set(Constraint{Ne, {Literal(int32_t(10))}});
EXPECT_EQ(s.proves(t), True);
// In reverse, we can infer nothing: knowing x != 10 does not say if x == 5.
EXPECT_EQ(t.proves(s), Unknown);
}
TEST(ConstraintTest, TestSetsUnknown) {
// x != 5
// x != 10
AndedConstraintSet s;
s.set(Constraint{Ne, {Literal(int32_t(5))}});
s.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}});
// x != 20, which is unknown by s.
AndedConstraintSet t;
t.set(Constraint{Ne, {Literal(int32_t(20))}});
EXPECT_EQ(s.proves(t), Unknown);
// Add x == 10, which is false by s, and so the whole thing is false.
t.set(Constraint{Eq, {Literal(int32_t(10))}});
EXPECT_EQ(s.proves(t), False);
}
TEST(ConstraintTest, TestOrTrivial) {
// { x == 5 }
AndedConstraintSet s;
s.set(Constraint{Eq, {Literal(int32_t(5))}});
// { }
AndedConstraintSet empty;
empty.setProvesNothing();
// Anything ORed with the empty set becomes the empty set: if one side can
// prove nothing, neither can the result.
auto t = s;
t.approximateOr(empty);
EXPECT_EQ(t, empty);
// Flipped.
t = empty;
t.approximateOr(s);
EXPECT_EQ(t, empty);
// ORing with oneself changes nothing
t = s;
t.approximateOr(s);
EXPECT_EQ(t, s);
}
TEST(ConstraintTest, TestOrImplies) {
// { x == 5 }
AndedConstraintSet s;
s.set(Constraint{Eq, {Literal(int32_t(5))}});
// { x != 10 }
AndedConstraintSet t;
t.set(Constraint{Ne, {Literal(int32_t(10))}});
// ORing these leaves us with x != 10.
auto u = s;
u.approximateOr(t);
EXPECT_EQ(u, t);
// Flipped.
u = t;
u.approximateOr(s);
EXPECT_EQ(u, t);
}
TEST(ConstraintTest, TestMaxCapacity) {
EXPECT_EQ(MaxConstraints, 3);
// Max out with x != 10, 20, 30
Constraint not10{Ne, {Literal(int32_t(10))}};
Constraint not20{Ne, {Literal(int32_t(20))}};
Constraint not30{Ne, {Literal(int32_t(30))}};
AndedConstraintSet s;
s.set(not10);
s.approximateAnd(not20);
s.approximateAnd(not30);
// We can prove all those.
EXPECT_EQ(s.proves(not10), True);
EXPECT_EQ(s.proves(not20), True);
EXPECT_EQ(s.proves(not30), True);
// Add another, exceeding the capacity.
Constraint not40{Ne, {Literal(int32_t(40))}};
s.approximateAnd(not40);
// We can prove the old ones but not the new.
EXPECT_EQ(s.proves(not10), True);
EXPECT_EQ(s.proves(not20), True);
EXPECT_EQ(s.proves(not30), True);
EXPECT_EQ(s.proves(not40), Unknown);
}
TEST(ConstraintTest, TestDeduplication) {
Constraint eq10{Eq, {Literal(int32_t(10))}};
AndedConstraintSet s;
EXPECT_EQ(s.size(), 0);
s.set(eq10);
EXPECT_EQ(s.size(), 1);
// The size does not increase when we add eq10 again.
s.approximateAnd(eq10);
EXPECT_EQ(s.size(), 1);
}
TEST(ConstraintTest, TestDeredundancy) {
Constraint eq0{Eq, {Literal(int32_t(0))}};
Constraint ne1{Ne, {Literal(int32_t(1))}};
// If x == 0, then x != 1 is redundant, and does not need to be added, is it
// is implied by x == 0.
AndedConstraintSet s;
s.set(eq0);
s.approximateAnd(ne1);
EXPECT_EQ(s.size(), 1);
EXPECT_EQ(s[0], eq0);
// Reverse order, same result, even though we added x == 0 last: we remove
// x != 1.
AndedConstraintSet t;
t.set(ne1);
t.approximateAnd(eq0);
EXPECT_EQ(t.size(), 1);
EXPECT_EQ(t[0], eq0);
}
static void checkOr(const AndedConstraintSet& a,
const AndedConstraintSet& b,
const AndedConstraintSet& result) {
auto ored = a;
ored.approximateOr(b);
EXPECT_EQ(ored, result);
ored = b;
ored.approximateOr(a);
EXPECT_EQ(ored, result);
}
TEST(ConstraintTest, TestOrInequality) {
// x == 5 || x >= 0 => x >= 0
AndedConstraintSet eq5{{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet ge0{{GeU, {Literal(int32_t(0))}}};
checkOr(eq5, ge0, ge0);
// x == 5 || x > 5 => x >= 5
AndedConstraintSet gts5{{GtS, {Literal(int32_t(5))}}};
AndedConstraintSet ges5{{GeS, {Literal(int32_t(5))}}};
checkOr(eq5, gts5, ges5);
// x == 5 || x >= 5 => x >= 5
checkOr(eq5, ges5, ges5);
// x == 5 || x >= 6 => x >= 5
AndedConstraintSet ges6{{GeS, {Literal(int32_t(6))}}};
checkOr(eq5, ges6, ges5);
// TODO: x == 5 || x >= 7 => x >= 5 TODO
AndedConstraintSet ges7{{GeS, {Literal(int32_t(7))}}};
auto empty = AndedConstraintSet::makeProvesNothing();
checkOr(eq5, ges7, empty);
// x > 5 and x >= 6 are equivalent, so ORing them does not change either.
auto ored1 = gts5;
ored1.approximateOr(ges6);
EXPECT_EQ(ored1, gts5);
auto ored2 = ges6;
ored2.approximateOr(gts5);
EXPECT_EQ(ored2, ges6);
// x > 5 || x >= 5 => x >= 5
checkOr(gts5, ges5, ges5);
// Careful of overflow:
// x == signed_max || x >= (signed_max + 1 === signed_min) != x >= signed_max
AndedConstraintSet eqMax{
{Eq, {Literal(std::numeric_limits<int32_t>::max())}}};
AndedConstraintSet gesMin{
{GeS, {Literal(std::numeric_limits<int32_t>::min())}}};
// TODO: x >= signed_min is always true, so this could be empty
checkOr(eqMax, gesMin, gesMin);
// Careful of overflow:
// x > signed_max || x >= (signed_max + 1 === signed_min) != x > signed_max
AndedConstraintSet gtsMax{
{GtS, {Literal(std::numeric_limits<int32_t>::max())}}};
// x > signed_max is impossible, so it vanishes in the OR.
checkOr(gtsMax, gesMin, gesMin);
}
TEST(ConstraintTest, TestOrLoop) {
// Check common loop patterns at the loop top (merging an initial value with
// an incremented and bounded one):
// { x == A } || { x > A && x <= B } ==> { x >= A && x <= B }
// { x == 5 } || { x > 5 && x <= 42 } ==> { x >= 5 && x <= 42 }
AndedConstraintSet left{{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet right(
{{GtS, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}});
AndedConstraintSet result(
{{GeS, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}});
checkOr(left, right, result);
// Changes to constants:
// Change 5 on the left to 7:
// { x == 7 } || { x > 5 && x <= 42 } ==> { x > 5 && x <= 42}
AndedConstraintSet left7{{Eq, {Literal(int32_t(7))}}};
checkOr(left7, right, right);
// Change 5 on the left to 99:
// { x == 99 } || { x > 5 && x <= 42 } ==> { x > 5 }
// TODO: we could emit a range (5, 99]
AndedConstraintSet left99{{Eq, {Literal(int32_t(99))}}};
AndedConstraintSet rightOnly5{{GtS, {Literal(int32_t(5))}}};
checkOr(left99, right, rightOnly5);
// Change 5 on the left to 4:
// { x == 4 } || { x > 5 && x <= 42 } ==> { x <= 42 }
// TODO: we could emit a range [4, 42]
AndedConstraintSet left4{{Eq, {Literal(int32_t(4))}}};
AndedConstraintSet rightOnly42({{LeS, {Literal(int32_t(42))}}});
checkOr(left4, right, rightOnly42);
// Change 5 on the right to 6:
// { x == 5 } || { x > 6 && x <= 42 } ==> { x <= 42 }
AndedConstraintSet right6(
{{GtS, {Literal(int32_t(6))}}, {LeS, {Literal(int32_t(42))}}});
checkOr(left, right6, rightOnly42);
// Changes to operations:
// Change the Eq on the left to Ne. We fail to find anything for the OR.
// { x != 5 } || { x > 5 && x <= 42 } ==> {}
// TODO: we could emit x != 5
AndedConstraintSet leftNe{{Ne, {Literal(int32_t(5))}}};
auto empty = AndedConstraintSet::makeProvesNothing();
checkOr(leftNe, right, empty);
// Change the GtS on the right to GtU:
// { x == 5 } || { x >U 5 && x <= 42 } ==> { x >=U 5 && x <= 42 }
AndedConstraintSet rightGtU(
{{GtU, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}});
AndedConstraintSet resultMixed(
{{GeU, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}});
checkOr(left, rightGtU, resultMixed);
// Change the LeS on the right to LeU:
// { x == 5 } || { x > 5 && x <=U 42 } ==> { x >= 5 && x <=U 42 }
AndedConstraintSet rightLeU(
{{GtS, {Literal(int32_t(5))}}, {LeU, {Literal(int32_t(42))}}});
AndedConstraintSet rightGesLeU(
{{GeS, {Literal(int32_t(5))}}, {LeU, {Literal(int32_t(42))}}});
checkOr(left, rightLeU, rightGesLeU);
// Add an operation on the right, x != 21:
// { x == 5 } || { x > 5 && x <= 42 && x != 21 } ==>
// { x >= 5 && x <= 42 && x != 21 }
AndedConstraintSet rightAdded({{GtS, {Literal(int32_t(5))}},
{LeS, {Literal(int32_t(42))}},
{Ne, {Literal(int32_t(21))}}});
AndedConstraintSet resultAdded({{GeS, {Literal(int32_t(5))}},
{LeS, {Literal(int32_t(42))}},
{Ne, {Literal(int32_t(21))}}});
checkOr(left, rightAdded, resultAdded);
}
TEST(ConstraintTest, TestOrLoopUnsigned) {
// As above, but unsigned.
// { x == 5 } || { x > 5 && x <= 42 } ==> { x >= 5 && x <= 42 }
AndedConstraintSet left{{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet right(
{{GtU, {Literal(int32_t(5))}}, {LeU, {Literal(int32_t(42))}}});
AndedConstraintSet result(
{{GeU, {Literal(int32_t(5))}}, {LeU, {Literal(int32_t(42))}}});
checkOr(left, right, result);
// Changes to constants:
// Change 5 on the left to 7:
// { x == 7 } || { x > 5 && x <= 42 } ==> { x > 5 && x <= 42}
AndedConstraintSet left7{{Eq, {Literal(int32_t(7))}}};
checkOr(left7, right, right);
// Change 5 on the left to 99:
// { x == 99 } || { x > 5 && x <= 42 } ==> { x > 5 }
// TODO: we could emit a range (5, 99]
AndedConstraintSet left99{{Eq, {Literal(int32_t(99))}}};
AndedConstraintSet rightOnly5{{GtU, {Literal(int32_t(5))}}};
checkOr(left99, right, rightOnly5);
// Change 5 on the left to 4:
// { x == 4 } || { x > 5 && x <= 42 } ==> { x <= 42 }
// TODO: we could emit a range [4, 42]
AndedConstraintSet left4{{Eq, {Literal(int32_t(4))}}};
AndedConstraintSet rightOnly42({{LeU, {Literal(int32_t(42))}}});
checkOr(left4, right, rightOnly42);
// Change 5 on the right to 6:
// { x == 5 } || { x > 6 && x <= 42 } ==> { x <= 42 }
AndedConstraintSet right6(
{{GtU, {Literal(int32_t(6))}}, {LeU, {Literal(int32_t(42))}}});
checkOr(left, right6, rightOnly42);
// Changes to operations:
// Change the Eq on the left to Ne. We fail to find anything for the OR.
// { x != 5 } || { x > 5 && x <= 42 } ==> {}
// TODO: we could emit x != 5
AndedConstraintSet leftNe{{Ne, {Literal(int32_t(5))}}};
auto empty = AndedConstraintSet::makeProvesNothing();
checkOr(leftNe, right, empty);
// Add an operation on the right, x != 21:
// { x == 5 } || { x > 5 && x <= 42 && x != 21 } ==>
// { x >= 5 && x <= 42 && x != 21 }
AndedConstraintSet rightAdded({{GtU, {Literal(int32_t(5))}},
{LeU, {Literal(int32_t(42))}},
{Ne, {Literal(int32_t(21))}}});
AndedConstraintSet resultAdded({{GeU, {Literal(int32_t(5))}},
{LeU, {Literal(int32_t(42))}},
{Ne, {Literal(int32_t(21))}}});
checkOr(left, rightAdded, resultAdded);
}
static void checkAnd(const AndedConstraintSet& a,
const AndedConstraintSet& b,
const AndedConstraintSet& result) {
auto anded = a;
for (auto& bc : b) {
anded.approximateAnd(bc);
}
EXPECT_EQ(anded, result);
anded = b;
for (auto& ac : a) {
anded.approximateAnd(ac);
}
EXPECT_EQ(anded, result);
}
TEST(ConstraintTest, TestAndInequality) {
// x == 5 && x >= 0 => x == 5
AndedConstraintSet eq5{{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet ge0{{GeS, {Literal(int32_t(0))}}};
checkAnd(eq5, ge0, eq5);
// x == 5 && x >= 5 => x == 5
AndedConstraintSet ge5{{GeS, {Literal(int32_t(5))}}};
checkAnd(eq5, ge5, eq5);
// x == 5 && x >= 6 => contradiction
AndedConstraintSet ge6{{GeS, {Literal(int32_t(6))}}};
AndedConstraintSet contradiction;
checkAnd(eq5, ge6, contradiction);
}
TEST(ConstraintTest, TestAndLoop) {
// Check common loop patterns after incrementing and bounds-checking:
// x <= A && x < A => x < A
// x <= 5 && x < 5 => x < 5
AndedConstraintSet le5{{LeS, {Literal(int32_t(5))}}};
AndedConstraintSet lt5{{LtS, {Literal(int32_t(5))}}};
checkAnd(le5, lt5, lt5);
// Ditto, but unsigned.
AndedConstraintSet le5U{{LeU, {Literal(int32_t(5))}}};
AndedConstraintSet lt5U{{LtU, {Literal(int32_t(5))}}};
checkAnd(le5U, lt5U, lt5U);
// Mixing signed and unsigned does not optimize (so we just end up ANDing both
// inputs).
checkAnd(le5, lt5U, AndedConstraintSet{le5[0], lt5U[0]});
// Different constants optimize when one implies the other (x <= 5 and x < 6
// are equivalent).
AndedConstraintSet lt6{{LtS, {Literal(int32_t(6))}}};
auto anded1 = le5;
anded1.approximateAnd(lt6[0]);
EXPECT_EQ(anded1, le5);
auto anded2 = lt6;
anded2.approximateAnd(le5[0]);
EXPECT_EQ(anded2, lt6);
// A non-constant.
// x <= y && x < y => x < y
AndedConstraintSet ley{{LeS, {Index(1)}}};
AndedConstraintSet lty{{LtS, {Index(1)}}};
checkAnd(ley, lty, lty);
// A non-constant with extra info.
// { x <= y && x != 42 } && x < y => x < y && x != 42
Constraint ne42{Ne, {Literal(int32_t(42))}};
checkAnd({ley[0], ne42}, lty, {lty[0], ne42});
// Extra info on the other side, same result.
// x <= y && { x < y && x != 42 } => x < y && x != 42
checkAnd(ley, {lty[0], ne42}, {lty[0], ne42});
}
TEST(ConstraintTest, TestBasicBlockConstraintMap) {
// Maps begin unreachable.
BasicBlockConstraintMap map;
EXPECT_TRUE(map.unreachable);
map.setReachable();
EXPECT_FALSE(map.unreachable);
}
// Check that a set is equal to a constraint.
static void check(const AndedConstraintSet& s, const Constraint& c) {
EXPECT_EQ(s.size(), 1);
EXPECT_EQ(s[0], c);
}
TEST(ConstraintTest, TestBasicBlockConstraintMap_Set) {
Constraint eq0{Eq, {Literal(int32_t(0))}};
Constraint eq1{Eq, {Literal(int32_t(1))}};
Constraint eq2{Eq, {Literal(int32_t(2))}};
BasicBlockConstraintMap map;
map.setReachable();
// Set local 0 to 0. It should read back the same.
map.set(0, eq0);
check(map.get(0), eq0);
// Set another value, replacing the first.
map.set(0, eq1);
check(map.get(0), eq1);
// Set a value using an expression.
Const c;
c.value = Literal(int32_t(2));
c.type = Type::i32;
map.set(0, &c);
check(map.get(0), eq2);
// Set an unfamiliar expression, leading to us knowing nothing.
Nop nop;
map.set(0, &nop);
EXPECT_TRUE(map.get(0).provesNothing());
}
TEST(ConstraintTest, TestIncrement) {
BasicBlockConstraintMap map;
map.setReachable();
// Set up an increment operation, an add which does $0 + 1
LocalGet get;
get.index = 0;
get.type = Type::i32;
Const c;
c.value = Literal(int32_t(1));
c.type = Type::i32;
Binary add;
add.op = AddInt32;
add.type = Type::i32;
add.left = &get;
add.right = &c;
// $0 = 0, $1 = $0 + 1, so $1 = 1 (and $0 is unchanged).
map.set(0, {Eq, {Literal(int32_t(0))}});
map.set(1, &add);
check(map.get(0), {Eq, {Literal(int32_t(0))}});
check(map.get(1), {Eq, {Literal(int32_t(1))}});
// $0 = $0 + 1, where $0 was 0, so it is now 1.
map.set(0, &add);
check(map.get(0), {Eq, {Literal(int32_t(1))}});
// $0 >= 5, $0++ => nothing, since the ++ might overflow into negative
map.set(0, {GeS, {Literal(int32_t(5))}});
map.set(0, &add);
EXPECT_TRUE(map.get(0).empty());
// $0 >= 5, $0 < 100, $0++ => $0 > 5, $0 <= 100 (signed)
Constraint lts100{LtS, {Literal(int32_t(100))}};
Constraint les100{LeS, {Literal(int32_t(100))}};
map.set(0, {{GeS, {Literal(int32_t(5))}}, lts100});
map.set(0, &add);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{{GtS, {Literal(int32_t(5))}}, les100}));
// Ditto, unsigned: without an upper bound we can overflow.
map.set(0, {GeU, {Literal(int32_t(5))}});
map.set(0, &add);
EXPECT_TRUE(map.get(0).empty());
// With an upper bound, we can optimize like before.
Constraint ltu100{LtU, {Literal(int32_t(100))}};
Constraint leu100{LeU, {Literal(int32_t(100))}};
map.set(0, {{GeU, {Literal(int32_t(5))}}, ltu100});
map.set(0, &add);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{{GtU, {Literal(int32_t(5))}}, leu100}));
// $0 < 5, $0++ => $0 <= 5 (signed)
map.set(0, {LtS, {Literal(int32_t(5))}});
map.set(0, &add);
check(map.get(0), {LeS, {Literal(int32_t(5))}});
// Ditto, unsigned. We also add a lower bound here, as after $0++, $0 > 0
// (due to no overflow, proven by the upper bound).
Constraint gtu0{GtU, {Literal(int32_t(0))}};
map.set(0, {LtU, {Literal(int32_t(5))}});
map.set(0, &add);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{{LeU, {Literal(int32_t(5))}}, gtu0}));
// $0 <= 5, $0++ => $0 <= 6 (signed)
map.set(0, {LeS, {Literal(int32_t(5))}});
map.set(0, &add);
check(map.get(0), {LeS, {Literal(int32_t(6))}});
// Ditto, unsigned
map.set(0, {LeU, {Literal(int32_t(5))}});
map.set(0, &add);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{{LeU, {Literal(int32_t(6))}}, gtu0}));
// $0 <= max_signed, $0++ => nothing, because it would overflow
map.set(0, {LeS, {Literal::makeSignedMax(Type::i32)}});
map.set(0, &add);
EXPECT_TRUE(map.get(0).provesNothing());
// $0 <= max_unsigned, $0++ => nothing, because it would overflow
map.set(0, {LeU, {Literal::makeUnsignedMax(Type::i32)}});
map.set(0, &add);
EXPECT_TRUE(map.get(0).provesNothing());
// However, an unsigned operation on the signed max is fine.
map.set(0, {LeU, {Literal::makeSignedMax(Type::i32)}});
map.set(0, &add);
auto one = Literal::makeFromInt32(1, Type::i32);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{
{LeU, {Literal::makeSignedMax(Type::i32).add(one)}}, gtu0}));
// Multiple constraints at once:
// $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20
map.set(0, {GeS, {Literal(int32_t(10))}});
map.approximateAnd(0, {LtS, {Literal(int32_t(20))}});
map.set(0, &add);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{{GtS, {Literal(int32_t(10))}},
{LeS, {Literal(int32_t(20))}}}));
// $0 >= 10 && $0 <= max_signed, $0++ => nothing, as we may overflow.
map.set(0, {GeS, {Literal(int32_t(10))}});
map.approximateAnd(0, {LeS, {Literal::makeSignedMax(Type::i32)}});
map.set(0, &add);
EXPECT_EQ(map.get(0).size(), 0);
// Ditto, unsigned.
map.set(0, {GeU, {Literal(int32_t(10))}});
map.approximateAnd(0, {LeU, {Literal::makeUnsignedMax(Type::i32)}});
map.set(0, &add);
EXPECT_EQ(map.get(0).size(), 0);
// Ditto, 64-bit signed.
map.set(0, {GeS, {Literal(int64_t(10))}});
map.approximateAnd(0, {LeS, {Literal::makeSignedMax(Type::i64)}});
map.set(0, &add);
EXPECT_EQ(map.get(0).size(), 0);
// Ditto, 64-bit unsigned.
map.set(0, {GeU, {Literal(int64_t(10))}});
map.approximateAnd(0, {LeU, {Literal::makeUnsignedMax(Type::i64)}});
map.set(0, &add);
EXPECT_EQ(map.get(0).size(), 0);
// $0 >= 5 && $0 < 100 && $0 == $2, $0++ => we increment and remove the non-
// constant term, leaving $0 > 5 && $0 <= 100.
map.set(0, {{GeS, {Literal(int32_t(5))}}, lts100});
map.approximateAnd(0, {Eq, {Index(2)}});
map.set(0, &add);
EXPECT_EQ(map.get(0),
(AndedConstraintSet{{GtS, {Literal(int32_t(5))}}, les100}));
}
TEST(ConstraintTest, TestEqConstraints) {
BasicBlockConstraintMap map;
map.setReachable();
// $0 == 42
map.set(0, {Eq, {Literal(int32_t(42))}});
// $0 < $1
map.approximateAnd(0, {LtS, {Index(int32_t(1))}});
// $1 has $1 > 42: we constant-propagated the value of $0. This is better than
// having $1 > $0 and needing to look $0 up.
check(map.get(1), {GtS, {Literal(int32_t(42))}});
}
TEST(ConstraintTest, ComplexOrRegression) {
// $0 == 0
BasicBlockConstraintMap left;
left.setReachable();
left.set(0, {Eq, {Literal(int32_t(0))}});
// $0 <= 100, $0 > $1
BasicBlockConstraintMap right;
right.setReachable();
right.set(0, {{LeS, {Literal(int32_t(100))}}, {GtS, {Index(1)}}});
// $0 == 0 || $0 <= 100 => $0 <= 100 (0 is included in <= 100), but the
// other constraint, $0 > $1, was only on one side, and vanishes.
right.approximateOr(left);
check(right.get(0), {LeS, {Literal(int32_t(100))}});
EXPECT_TRUE(right.get(1).empty());
}
TEST(ConstraintTest, GetSpan) {
const IU64 minI32(std::numeric_limits<int32_t>::min());
const IU64 maxI32(std::numeric_limits<int32_t>::max());
const IU64 maxU32(std::numeric_limits<uint32_t>::max());
const IU64 minI64(std::numeric_limits<int64_t>::min());
const IU64 maxI64(std::numeric_limits<int64_t>::max());
const IU64 maxU64(std::numeric_limits<uint64_t>::max());
// Non-literal terms have no constant span.
EXPECT_EQ((Constraint{Eq, {Index(0)}}.getSpan()), std::nullopt);
EXPECT_EQ((Constraint{LtS, {Index(1)}}.getSpan()), std::nullopt);
EXPECT_EQ((Constraint{GeU, {Index(2)}}.getSpan()), std::nullopt);
// Unsupported operations (e.g. Ne) have no constant span.
EXPECT_EQ((Constraint{Ne, {Literal(int32_t(5))}}.getSpan()), std::nullopt);
EXPECT_EQ((Constraint{Ne, {Literal(int32_t(0))}}.getSpan()), std::nullopt);
// Eq (i32): non-negative values up to int32_t max have an unambiguous span.
EXPECT_EQ((Constraint{Eq, {Literal(int32_t(0))}}.getSpan()),
(Span<IU64>{0, 0}));
EXPECT_EQ((Constraint{Eq, {Literal(int32_t(1))}}.getSpan()),
(Span<IU64>{1, 1}));
EXPECT_EQ((Constraint{Eq, {Literal(int32_t(42))}}.getSpan()),
(Span<IU64>{42, 42}));
EXPECT_EQ(
(Constraint{Eq, {Literal(std::numeric_limits<int32_t>::max())}}.getSpan()),
(Span<IU64>{maxI32, maxI32}));
// Eq (i32) with negative or large unsigned values returns nullopt due to
// signed/unsigned ambiguity.
EXPECT_EQ((Constraint{Eq, {Literal(int32_t(-1))}}.getSpan()), std::nullopt);
EXPECT_EQ((Constraint{Eq, {Literal(int32_t(-42))}}.getSpan()), std::nullopt);
EXPECT_EQ(
(Constraint{Eq, {Literal(std::numeric_limits<int32_t>::min())}}.getSpan()),
std::nullopt);
EXPECT_EQ((Constraint{Eq, {Literal(uint32_t(0x80000000u))}}.getSpan()),
std::nullopt);
EXPECT_EQ(
(Constraint{Eq, {Literal(std::numeric_limits<uint32_t>::max())}}.getSpan()),
std::nullopt);
// Eq (i64): non-negative values up to int64_t max have an unambiguous span.
EXPECT_EQ((Constraint{Eq, {Literal(int64_t(0))}}.getSpan()),
(Span<IU64>{0, 0}));
EXPECT_EQ((Constraint{Eq, {Literal(int64_t(42))}}.getSpan()),
(Span<IU64>{42, 42}));
EXPECT_EQ(
(Constraint{Eq, {Literal(int64_t(std::numeric_limits<int32_t>::max()) + 1)}}
.getSpan()),
(Span<IU64>{uint64_t(std::numeric_limits<int32_t>::max()) + 1,
uint64_t(std::numeric_limits<int32_t>::max()) + 1}));
EXPECT_EQ(
(Constraint{Eq, {Literal(std::numeric_limits<int64_t>::max())}}.getSpan()),
(Span<IU64>{maxI64, maxI64}));
// Eq (i64) with negative or large unsigned values returns nullopt.
EXPECT_EQ((Constraint{Eq, {Literal(int64_t(-1))}}.getSpan()), std::nullopt);
EXPECT_EQ(
(Constraint{Eq, {Literal(std::numeric_limits<int64_t>::min())}}.getSpan()),
std::nullopt);
EXPECT_EQ((Constraint{Eq, {Literal(uint64_t(uint64_t(1) << 63))}}.getSpan()),
std::nullopt);
EXPECT_EQ(
(Constraint{Eq, {Literal(std::numeric_limits<uint64_t>::max())}}.getSpan()),
std::nullopt);
// LtS (i32): [minI32, C - 1]
EXPECT_EQ((Constraint{LtS, {Literal(int32_t(10))}}.getSpan()),
(Span<IU64>{minI32, IU64(9)}));
EXPECT_EQ((Constraint{LtS, {Literal(int32_t(0))}}.getSpan()),
(Span<IU64>{minI32, IU64(-1)}));
EXPECT_EQ((Constraint{LtS, {Literal(int32_t(-5))}}.getSpan()),
(Span<IU64>{minI32, IU64(-6)}));
EXPECT_EQ(
(Constraint{LtS, {Literal(std::numeric_limits<int32_t>::max())}}.getSpan()),
(Span<IU64>{minI32, IU64(std::numeric_limits<int32_t>::max() - 1)}));
// LtS min signed (i32): empty span
auto ltsMin32 =
Constraint{LtS, {Literal(std::numeric_limits<int32_t>::min())}}.getSpan();
ASSERT_TRUE(ltsMin32.has_value());
EXPECT_TRUE(ltsMin32->isEmpty());
EXPECT_EQ(ltsMin32, Span<IU64>::empty());
// LtS (i64): [minI64, C - 1]
EXPECT_EQ((Constraint{LtS, {Literal(int64_t(100))}}.getSpan()),
(Span<IU64>{minI64, IU64(99)}));
EXPECT_EQ((Constraint{LtS, {Literal(int64_t(0))}}.getSpan()),
(Span<IU64>{minI64, IU64(-1)}));
EXPECT_EQ(
(Constraint{LtS, {Literal(std::numeric_limits<int64_t>::max())}}.getSpan()),
(Span<IU64>{minI64, IU64(std::numeric_limits<int64_t>::max() - 1)}));
// LtS min signed (i64): empty span
auto ltsMin64 =
Constraint{LtS, {Literal(std::numeric_limits<int64_t>::min())}}.getSpan();
ASSERT_TRUE(ltsMin64.has_value());
EXPECT_TRUE(ltsMin64->isEmpty());
EXPECT_EQ(ltsMin64, Span<IU64>::empty());
// LtU (i32): [0, C - 1]
EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(10))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(9)}));
EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(1))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(0)}));
EXPECT_EQ(
(Constraint{LtU, {Literal(std::numeric_limits<uint32_t>::max())}}
.getSpan()),
(Span<IU64>{IU64(0),
IU64(uint64_t(std::numeric_limits<uint32_t>::max()) - 1)}));
// LtU 0 (i32): empty span
auto ltuZero32 = Constraint{LtU, {Literal(uint32_t(0))}}.getSpan();
ASSERT_TRUE(ltuZero32.has_value());
EXPECT_TRUE(ltuZero32->isEmpty());
EXPECT_EQ(ltuZero32, Span<IU64>::empty());
// LtU (i64): [0, C - 1]
EXPECT_EQ((Constraint{LtU, {Literal(uint64_t(100))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(99)}));
EXPECT_EQ(
(Constraint{LtU, {Literal(std::numeric_limits<uint64_t>::max())}}
.getSpan()),
(Span<IU64>{IU64(0), IU64(std::numeric_limits<uint64_t>::max() - 1)}));
// LtU 0 (i64): empty span
auto ltuZero64 = Constraint{LtU, {Literal(uint64_t(0))}}.getSpan();
ASSERT_TRUE(ltuZero64.has_value());
EXPECT_TRUE(ltuZero64->isEmpty());
EXPECT_EQ(ltuZero64, Span<IU64>::empty());
// LeS (i32): [minI32, C]
EXPECT_EQ((Constraint{LeS, {Literal(int32_t(10))}}.getSpan()),
(Span<IU64>{minI32, IU64(10)}));
EXPECT_EQ((Constraint{LeS, {Literal(int32_t(0))}}.getSpan()),
(Span<IU64>{minI32, IU64(0)}));
EXPECT_EQ((Constraint{LeS, {Literal(int32_t(-5))}}.getSpan()),
(Span<IU64>{minI32, IU64(-5)}));
EXPECT_EQ(
(Constraint{LeS, {Literal(std::numeric_limits<int32_t>::min())}}.getSpan()),
(Span<IU64>{minI32, minI32}));
EXPECT_EQ(
(Constraint{LeS, {Literal(std::numeric_limits<int32_t>::max())}}.getSpan()),
(Span<IU64>{minI32, maxI32}));
// LeS (i64): [minI64, C]
EXPECT_EQ((Constraint{LeS, {Literal(int64_t(10))}}.getSpan()),
(Span<IU64>{minI64, IU64(10)}));
EXPECT_EQ(
(Constraint{LeS, {Literal(std::numeric_limits<int64_t>::min())}}.getSpan()),
(Span<IU64>{minI64, minI64}));
EXPECT_EQ(
(Constraint{LeS, {Literal(std::numeric_limits<int64_t>::max())}}.getSpan()),
(Span<IU64>{minI64, maxI64}));
// LeU (i32): [0, C]
EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(0))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(0)}));
EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(10))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(10)}));
EXPECT_EQ((Constraint{LeU, {Literal(std::numeric_limits<uint32_t>::max())}}
.getSpan()),
(Span<IU64>{IU64(0), maxU32}));
// LeU (i64): [0, C]
EXPECT_EQ((Constraint{LeU, {Literal(uint64_t(0))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(0)}));
EXPECT_EQ((Constraint{LeU, {Literal(uint64_t(10))}}.getSpan()),
(Span<IU64>{IU64(0), IU64(10)}));
EXPECT_EQ((Constraint{LeU, {Literal(std::numeric_limits<uint64_t>::max())}}
.getSpan()),
(Span<IU64>{IU64(0), maxU64}));
// GtS (i32): [C + 1, maxI32]
EXPECT_EQ((Constraint{GtS, {Literal(int32_t(10))}}.getSpan()),
(Span<IU64>{IU64(11), maxI32}));
EXPECT_EQ((Constraint{GtS, {Literal(int32_t(0))}}.getSpan()),
(Span<IU64>{IU64(1), maxI32}));
EXPECT_EQ((Constraint{GtS, {Literal(int32_t(-5))}}.getSpan()),
(Span<IU64>{IU64(-4), maxI32}));
EXPECT_EQ(
(Constraint{GtS, {Literal(std::numeric_limits<int32_t>::min())}}.getSpan()),
(Span<IU64>{IU64(std::numeric_limits<int32_t>::min() + 1), maxI32}));
EXPECT_EQ((Constraint{GtS, {Literal(std::numeric_limits<int32_t>::max() - 1)}}
.getSpan()),
(Span<IU64>{maxI32, maxI32}));
// GtS max signed (i32): empty span
auto gtsMax32 =
Constraint{GtS, {Literal(std::numeric_limits<int32_t>::max())}}.getSpan();
ASSERT_TRUE(gtsMax32.has_value());
EXPECT_TRUE(gtsMax32->isEmpty());
EXPECT_EQ(gtsMax32, Span<IU64>::empty());
// GtS (i64): [C + 1, maxI64]
EXPECT_EQ((Constraint{GtS, {Literal(int64_t(10))}}.getSpan()),
(Span<IU64>{IU64(11), maxI64}));
EXPECT_EQ((Constraint{GtS, {Literal(int64_t(0))}}.getSpan()),
(Span<IU64>{IU64(1), maxI64}));
EXPECT_EQ(
(Constraint{GtS, {Literal(std::numeric_limits<int64_t>::min())}}.getSpan()),
(Span<IU64>{IU64(std::numeric_limits<int64_t>::min() + 1), maxI64}));
EXPECT_EQ((Constraint{GtS, {Literal(std::numeric_limits<int64_t>::max() - 1)}}
.getSpan()),
(Span<IU64>{maxI64, maxI64}));
// GtS max signed (i64): empty span
auto gtsMax64 =
Constraint{GtS, {Literal(std::numeric_limits<int64_t>::max())}}.getSpan();
ASSERT_TRUE(gtsMax64.has_value());
EXPECT_TRUE(gtsMax64->isEmpty());
EXPECT_EQ(gtsMax64, Span<IU64>::empty());
// GtU (i32): [C + 1, maxU32]
EXPECT_EQ((Constraint{GtU, {Literal(uint32_t(0))}}.getSpan()),
(Span<IU64>{IU64(1), maxU32}));
EXPECT_EQ((Constraint{GtU, {Literal(uint32_t(10))}}.getSpan()),
(Span<IU64>{IU64(11), maxU32}));
EXPECT_EQ(
(Constraint{GtU, {Literal(std::numeric_limits<uint32_t>::max() - 1)}}
.getSpan()),
(Span<IU64>{maxU32, maxU32}));
// GtU max unsigned (i32): empty span
auto gtuMax32 =
Constraint{GtU, {Literal(std::numeric_limits<uint32_t>::max())}}.getSpan();
ASSERT_TRUE(gtuMax32.has_value());
EXPECT_TRUE(gtuMax32->isEmpty());
EXPECT_EQ(gtuMax32, Span<IU64>::empty());
// GtU (i64): [C + 1, maxU64]
EXPECT_EQ((Constraint{GtU, {Literal(uint64_t(0))}}.getSpan()),
(Span<IU64>{IU64(1), maxU64}));
EXPECT_EQ((Constraint{GtU, {Literal(uint64_t(10))}}.getSpan()),
(Span<IU64>{IU64(11), maxU64}));
EXPECT_EQ(
(Constraint{GtU, {Literal(std::numeric_limits<uint64_t>::max() - 1)}}
.getSpan()),
(Span<IU64>{maxU64, maxU64}));
// GtU max unsigned (i64): empty span
auto gtuMax64 =
Constraint{GtU, {Literal(std::numeric_limits<uint64_t>::max())}}.getSpan();
ASSERT_TRUE(gtuMax64.has_value());
EXPECT_TRUE(gtuMax64->isEmpty());
EXPECT_EQ(gtuMax64, Span<IU64>::empty());
// GeS (i32): [C, maxI32]
EXPECT_EQ((Constraint{GeS, {Literal(int32_t(10))}}.getSpan()),
(Span<IU64>{IU64(10), maxI32}));
EXPECT_EQ((Constraint{GeS, {Literal(int32_t(0))}}.getSpan()),
(Span<IU64>{IU64(0), maxI32}));
EXPECT_EQ((Constraint{GeS, {Literal(int32_t(-5))}}.getSpan()),
(Span<IU64>{IU64(-5), maxI32}));
EXPECT_EQ(
(Constraint{GeS, {Literal(std::numeric_limits<int32_t>::min())}}.getSpan()),
(Span<IU64>{minI32, maxI32}));
EXPECT_EQ(
(Constraint{GeS, {Literal(std::numeric_limits<int32_t>::max())}}.getSpan()),
(Span<IU64>{maxI32, maxI32}));
// GeS (i64): [C, maxI64]
EXPECT_EQ((Constraint{GeS, {Literal(int64_t(10))}}.getSpan()),
(Span<IU64>{IU64(10), maxI64}));
EXPECT_EQ((Constraint{GeS, {Literal(int64_t(0))}}.getSpan()),
(Span<IU64>{IU64(0), maxI64}));
EXPECT_EQ(
(Constraint{GeS, {Literal(std::numeric_limits<int64_t>::min())}}.getSpan()),
(Span<IU64>{minI64, maxI64}));
EXPECT_EQ(
(Constraint{GeS, {Literal(std::numeric_limits<int64_t>::max())}}.getSpan()),
(Span<IU64>{maxI64, maxI64}));
// GeU (i32): [C, maxU32]
EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(0))}}.getSpan()),
(Span<IU64>{IU64(0), maxU32}));
EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(10))}}.getSpan()),
(Span<IU64>{IU64(10), maxU32}));
EXPECT_EQ((Constraint{GeU, {Literal(std::numeric_limits<uint32_t>::max())}}
.getSpan()),
(Span<IU64>{maxU32, maxU32}));
// GeU (i64): [C, maxU64]
EXPECT_EQ((Constraint{GeU, {Literal(uint64_t(0))}}.getSpan()),
(Span<IU64>{IU64(0), maxU64}));
EXPECT_EQ((Constraint{GeU, {Literal(uint64_t(10))}}.getSpan()),
(Span<IU64>{IU64(10), maxU64}));
EXPECT_EQ((Constraint{GeU, {Literal(std::numeric_limits<uint64_t>::max())}}
.getSpan()),
(Span<IU64>{maxU64, maxU64}));
}
TEST(ConstraintTest, GetSpanType) {
const IU64 minI32(std::numeric_limits<int32_t>::min());
const IU64 minI32Plus1(std::numeric_limits<int32_t>::min() + 1);
const IU64 maxI32(std::numeric_limits<int32_t>::max());
const IU64 maxI32Minus1(std::numeric_limits<int32_t>::max() - 1);
const IU64 maxU32(std::numeric_limits<uint32_t>::max());
const IU64 maxU32Minus1(std::numeric_limits<uint32_t>::max() - 1);
const IU64 minI64(std::numeric_limits<int64_t>::min());
const IU64 minI64Plus1(std::numeric_limits<int64_t>::min() + 1);
const IU64 maxI64(std::numeric_limits<int64_t>::max());
const IU64 maxI64Minus1(std::numeric_limits<int64_t>::max() - 1);
const IU64 maxU64(std::numeric_limits<uint64_t>::max());
const IU64 maxU64Minus1(std::numeric_limits<uint64_t>::max() - 1);
// Providing the type to getSpan() doesn't help with certain things.
EXPECT_EQ((Constraint{Eq, {Index(0)}}.getSpan(Type::i32)), std::nullopt);
EXPECT_EQ((Constraint{Ne, {Index(1)}}.getSpan(Type::i64)), std::nullopt);
EXPECT_EQ((Constraint{GeU, {Index(2)}}.getSpan(Type::i32)), std::nullopt);
EXPECT_EQ((Constraint{GeS, {Index(0)}}.getSpan(Type::i64)), std::nullopt);
EXPECT_EQ((Constraint{LeU, {Index(1)}}.getSpan(Type::i64)), std::nullopt);
EXPECT_EQ((Constraint{LeS, {Index(2)}}.getSpan(Type::i32)), std::nullopt);
// But it does help with others: x < y means x cannot be MAX_INT, so we can
// report a *proven* span, if not an exact one.
EXPECT_EQ((Constraint{LtS, {Index(0)}}.getProvenSpan(Type::i32)),
(Span<IU64>{minI32, maxI32Minus1}));
EXPECT_EQ((Constraint{LtS, {Index(1)}}.getProvenSpan(Type::i64)),
(Span<IU64>{minI64, maxI64Minus1}));
EXPECT_EQ((Constraint{LtU, {Index(2)}}.getProvenSpan(Type::i32)),
(Span<IU64>{0, maxU32Minus1}));
EXPECT_EQ((Constraint{LtU, {Index(0)}}.getProvenSpan(Type::i64)),
(Span<IU64>{0, maxU64Minus1}));
EXPECT_EQ((Constraint{GtS, {Index(1)}}.getProvenSpan(Type::i32)),
(Span<IU64>{minI32Plus1, maxI32}));
EXPECT_EQ((Constraint{GtS, {Index(2)}}.getProvenSpan(Type::i64)),
(Span<IU64>{minI64Plus1, maxI64}));
EXPECT_EQ((Constraint{GtU, {Index(0)}}.getProvenSpan(Type::i32)),
(Span<IU64>{1, maxU32}));
EXPECT_EQ((Constraint{GtU, {Index(1)}}.getProvenSpan(Type::i64)),
(Span<IU64>{1, maxU64}));
// But all the last things are impossible with an exact span.
EXPECT_EQ((Constraint{LtS, {Index(0)}}.getSpan(Type::i32)), std::nullopt);
EXPECT_EQ((Constraint{LtS, {Index(1)}}.getSpan(Type::i64)), std::nullopt);
EXPECT_EQ((Constraint{LtU, {Index(2)}}.getSpan(Type::i32)), std::nullopt);
EXPECT_EQ((Constraint{LtU, {Index(0)}}.getSpan(Type::i64)), std::nullopt);
EXPECT_EQ((Constraint{GtS, {Index(1)}}.getSpan(Type::i32)), std::nullopt);
EXPECT_EQ((Constraint{GtS, {Index(2)}}.getSpan(Type::i64)), std::nullopt);
EXPECT_EQ((Constraint{GtU, {Index(0)}}.getSpan(Type::i32)), std::nullopt);
EXPECT_EQ((Constraint{GtU, {Index(1)}}.getSpan(Type::i64)), std::nullopt);
// Proven spans are otherwise like normal ones.
EXPECT_EQ((Constraint{Eq, {Literal(int32_t(42))}}.getProvenSpan()),
(Span<IU64>{42, 42}));
}
TEST(ConstraintTest, SpanOptimizations) {
// Using spans, we can optimize things like {x < 100} => {x < 200}.
Constraint lts100{LtS, {Literal(int32_t(100))}};
Constraint lts200{LtS, {Literal(int32_t(200))}};
EXPECT_EQ(AndedConstraintSet{lts100}.proves(lts200), True);
// Mixing signed and unsigned works fine: x in [0, 100] (x <= 100 unsigned)
// proves x in [-MIN_INT, 200] (x < 200 signed) is true.
Constraint leu100{LtU, {Literal(int32_t(100))}};
EXPECT_EQ(AndedConstraintSet{leu100}.proves(lts200), True);
// Replacing 100 with 500, we can no longer prove anything.
Constraint leu500{LtU, {Literal(int32_t(500))}};
EXPECT_EQ(AndedConstraintSet{leu500}.proves(lts200), Unknown);
}
TEST(ConstraintTest, EmptySpanContradiction) {
// Impossible constraints produce empty spans.
Constraint gtsMax32{GtS, {Literal(std::numeric_limits<int32_t>::max())}};
Constraint ltsMin32{LtS, {Literal(std::numeric_limits<int32_t>::min())}};
Constraint ltuZero32{LtU, {Literal(uint32_t(0))}};
Constraint gtuMax32{GtU, {Literal(std::numeric_limits<uint32_t>::max())}};
Constraint gtsMax64{GtS, {Literal(std::numeric_limits<int64_t>::max())}};
Constraint ltsMin64{LtS, {Literal(std::numeric_limits<int64_t>::min())}};
Constraint ltuZero64{LtU, {Literal(uint64_t(0))}};
Constraint gtuMax64{GtU, {Literal(std::numeric_limits<uint64_t>::max())}};
Constraint eq5{Eq, {Literal(int32_t(5))}};
Constraint ge0{GeS, {Literal(int32_t(0))}};
Constraint eq100_64{Eq, {Literal(int64_t(100))}};
// An impossible constraint proves anything is True.
EXPECT_EQ(AndedConstraintSet{gtsMax32}.proves(eq5), True);
EXPECT_EQ(AndedConstraintSet{ltsMin32}.proves(ge0), True);
EXPECT_EQ(AndedConstraintSet{ltuZero32}.proves(eq5), True);
EXPECT_EQ(AndedConstraintSet{gtuMax32}.proves(ge0), True);
EXPECT_EQ(AndedConstraintSet{gtsMax64}.proves(eq100_64), True);
EXPECT_EQ(AndedConstraintSet{ltsMin64}.proves(eq100_64), True);
EXPECT_EQ(AndedConstraintSet{ltuZero64}.proves(eq100_64), True);
EXPECT_EQ(AndedConstraintSet{gtuMax64}.proves(eq100_64), True);
// Impossible constraint proves another impossible constraint is True.
EXPECT_EQ(AndedConstraintSet{gtsMax32}.proves(ltsMin32), True);
EXPECT_EQ(AndedConstraintSet{ltuZero32}.proves(gtuMax32), True);
// A normal constraint proves an impossible constraint is False.
EXPECT_EQ(AndedConstraintSet{eq5}.proves(gtsMax32), False);
EXPECT_EQ(AndedConstraintSet{eq5}.proves(ltsMin32), False);
EXPECT_EQ(AndedConstraintSet{eq5}.proves(ltuZero32), False);
EXPECT_EQ(AndedConstraintSet{eq5}.proves(gtuMax32), False);
EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(gtsMax64), False);
EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(ltsMin64), False);
EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(ltuZero64), False);
EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(gtuMax64), False);
// An impossible constraint in a set proves any condition.
AndedConstraintSet s{gtsMax32};
EXPECT_EQ(s.proves(eq5), True);
EXPECT_EQ(s.proves(ge0), True);
// Adding an impossible constraint to a non-empty set proves False and turns
// the set into an explicit contradiction (provesEverything() == true).
AndedConstraintSet s2;
s2.set(eq5);
s2.approximateAnd(ltuZero32);
EXPECT_TRUE(s2.provesEverything());
// ORing an impossible constraint (which has no models) with a valid set
// leaves the valid set.
AndedConstraintSet valid{{Eq, {Literal(int32_t(42))}}};
AndedConstraintSet impossible{gtsMax32};
checkOr(valid, impossible, valid);
}
TEST(ConstraintTest, GetSpanFloat) {
// Non-integer types do not cause errors.
EXPECT_EQ((Constraint{Eq, {Literal(float(3.14159))}}.getSpan()),
std::nullopt);
}
TEST(ConstraintTest, GetSpanGC) {
// Reference types do not cause errors.
EXPECT_EQ((Constraint{Eq, {Literal::makeNull(HeapType::eq)}}.getSpan()),
std::nullopt);
}