| #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); | |
| } | |
| 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 || x >= 6 => x > 5 | |
| checkOr(gts5, ges6, gts5); | |
| // 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())}}}; | |
| // TODO: x > signed_max is always false, so this could return a contradiction | |
| checkOr(gtsMax, gesMin, empty); | |
| } | |
| 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 do not optimize, but could TODO | |
| AndedConstraintSet lt6{{LtS, {Literal(int32_t(6))}}}; | |
| checkAnd(le5, lt6, AndedConstraintSet{le5[0], lt6[0]}); | |
| // 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++ => $0 > 5 (signed) | |
| map.set(0, {GeS, {Literal(int32_t(5))}}); | |
| map.set(0, &add); | |
| check(map.get(0), {GtS, {Literal(int32_t(5))}}); | |
| // Ditto, unsigned | |
| map.set(0, {GeU, {Literal(int32_t(5))}}); | |
| map.set(0, &add); | |
| check(map.get(0), {GtU, {Literal(int32_t(5))}}); | |
| // $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 | |
| map.set(0, {LtU, {Literal(int32_t(5))}}); | |
| map.set(0, &add); | |
| check(map.get(0), {LeU, {Literal(int32_t(5))}}); | |
| // $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); | |
| check(map.get(0), {LeU, {Literal(int32_t(6))}}); | |
| // $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); | |
| check(map.get(0), {LeU, {Literal::makeSignedMax(Type::i32).add(one)}}); | |
| // 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++ => $0 > 10 (overflowing constraint | |
| // removed) | |
| 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), (AndedConstraintSet{{GtS, {Literal(int32_t(10))}}})); | |
| // $0 >= 10 && $0 == $2, $0++ => $0 > 10 (non-constant term removed) | |
| map.set(0, {GeS, {Literal(int32_t(10))}}); | |
| map.approximateAnd(0, {Eq, {Index(2)}}); | |
| map.set(0, &add); | |
| EXPECT_EQ(map.get(0), (AndedConstraintSet{{GtS, {Literal(int32_t(10))}}})); | |
| } | |
| 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))}}); | |
| } |