Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
37e3f98
comparisons
kripken Jul 21, 2026
aa3dc24
fmt
kripken Jul 21, 2026
6c7e2b0
work
kripken Jul 21, 2026
8854733
work
kripken Jul 21, 2026
f9ae5f1
work
kripken Jul 21, 2026
f708a61
work
kripken Jul 21, 2026
d00bed6
work
kripken Jul 21, 2026
e58adf7
work
kripken Jul 21, 2026
d9f1f87
test
kripken Jul 22, 2026
1617154
work
kripken Jul 22, 2026
0da0740
work
kripken Jul 22, 2026
e793be3
work
kripken Jul 22, 2026
e508ca6
work
kripken Jul 22, 2026
48ee24c
work
kripken Jul 22, 2026
c4b9d36
Merge remote-tracking branch 'origin/main' into compar.b
kripken Jul 23, 2026
b31d7f9
work
kripken Jul 23, 2026
f0dd6c8
work
kripken Jul 23, 2026
cf8f32e
work
kripken Jul 23, 2026
f6b58b4
work
kripken Jul 23, 2026
60e9620
work
kripken Jul 23, 2026
a4fc9e9
work
kripken Jul 23, 2026
7d337dc
work
kripken Jul 23, 2026
fb07869
work
kripken Jul 23, 2026
023cb40
work
kripken Jul 23, 2026
6268c82
work
kripken Jul 23, 2026
f15411c
work
kripken Jul 23, 2026
fd61010
work
kripken Jul 23, 2026
9a7e163
work
kripken Jul 23, 2026
9458a8a
work
kripken Jul 23, 2026
0628af6
work
kripken Jul 23, 2026
7e267ba
work
kripken Jul 23, 2026
71736fd
work
kripken Jul 23, 2026
859faa7
work
kripken Jul 23, 2026
e72fa61
work
kripken Jul 23, 2026
ebcfa80
work
kripken Jul 23, 2026
8c76ee7
work
kripken Jul 23, 2026
65f7508
work
kripken Jul 23, 2026
f28a35a
work
kripken Jul 23, 2026
2b6e73b
work
kripken Jul 23, 2026
753490d
work
kripken Jul 23, 2026
fa1144a
work
kripken Jul 23, 2026
dc858e8
work
kripken Jul 23, 2026
ed9d41f
work
kripken Jul 23, 2026
00b33c5
work
kripken Jul 23, 2026
e470ae1
work
kripken Jul 23, 2026
65f85dd
work
kripken Jul 23, 2026
2ac1514
work
kripken Jul 23, 2026
e713c6b
work
kripken Jul 23, 2026
f478cd0
work
kripken Jul 23, 2026
70085e5
work
kripken Jul 23, 2026
d153b16
work
kripken Jul 23, 2026
e16c4c6
work
kripken Jul 23, 2026
5ad4c40
work
kripken Jul 23, 2026
abcfee8
work
kripken Jul 23, 2026
a3d9a54
work
kripken Jul 23, 2026
4b16180
work
kripken Jul 23, 2026
14e5098
work
kripken Jul 23, 2026
3c59516
work
kripken Jul 23, 2026
ea598cb
work
kripken Jul 23, 2026
f31ae72
work
kripken Jul 23, 2026
6a3d5bc
work
kripken Jul 23, 2026
437174c
work
kripken Jul 23, 2026
ba4f0b5
work
kripken Jul 23, 2026
8a4f04a
work
kripken Jul 23, 2026
1b9efc0
work
kripken Jul 23, 2026
0789050
work
kripken Jul 23, 2026
5f0ad62
work
kripken Jul 23, 2026
242dd6e
work
kripken Jul 23, 2026
cda9027
work
kripken Jul 23, 2026
b141979
work
kripken Jul 23, 2026
32fa2b3
UNDO
kripken Jul 23, 2026
51cf381
go
kripken Jul 23, 2026
60d3fdf
go
kripken Jul 24, 2026
8362896
form
kripken Jul 24, 2026
d350b97
work
kripken Jul 24, 2026
b56f466
work
kripken Jul 24, 2026
c536d88
work
kripken Jul 24, 2026
0f48d73
work
kripken Jul 24, 2026
d8747d7
work
kripken Jul 24, 2026
09ebda2
work
kripken Jul 24, 2026
5d4a89f
work
kripken Jul 24, 2026
7cd6884
work
kripken Jul 24, 2026
9b7cac8
work
kripken Jul 24, 2026
8678398
work
kripken Jul 24, 2026
b11e984
work
kripken Jul 24, 2026
0069b7a
work
kripken Jul 24, 2026
2ce896f
work
kripken Jul 24, 2026
1acec6e
Merge remote-tracking branch 'origin/main' into compar.b.replace
kripken Jul 24, 2026
d6599aa
Update test/gtest/constraint.cpp
kripken Jul 24, 2026
128e9f4
Update test/gtest/constraint.cpp
kripken Jul 24, 2026
e11289e
work
kripken Jul 24, 2026
a8d6827
work
kripken Jul 24, 2026
da360bb
work
kripken Jul 24, 2026
822064c
work
kripken Jul 24, 2026
2415ce9
work
kripken Jul 24, 2026
0ae2a66
go
kripken Jul 24, 2026
5ecdcd2
go
kripken Jul 24, 2026
5740704
Merge remote-tracking branch 'origin/main' into compar.b.replace.2
kripken Jul 24, 2026
a54ebaa
clean
kripken Jul 24, 2026
c3ddbf3
work
kripken Jul 24, 2026
edb27b2
work
kripken Jul 24, 2026
c08d4d7
work
kripken Jul 24, 2026
2449fdb
work
kripken Jul 24, 2026
755c5b3
work
kripken Jul 24, 2026
cbc81d2
work
kripken Jul 24, 2026
771274c
work
kripken Jul 24, 2026
0cfccab
work
kripken Jul 24, 2026
cd7d416
work
kripken Jul 24, 2026
f156636
work
kripken Jul 24, 2026
8401726
work
kripken Jul 24, 2026
8ee2866
work
kripken Jul 24, 2026
b0607f8
Apply suggestion from @tlively
kripken Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 68 additions & 21 deletions src/ir/constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,45 @@ Result provesConstantPair(Abstract::Op aOp,
Abstract::Op bOp,
const Literal& bConstant,
bool recursing = false) {
using namespace Abstract;

// a == A =?=> a op B. Simply apply A to the operation against B.
if (aOp == Abstract::Eq) {
if (aOp == Eq) {
switch (bOp) {
case Abstract::Eq:
case Eq:
return TrueFalse(aConstant == bConstant);
case Abstract::Ne:
case Ne:
return TrueFalse(aConstant != bConstant);
case Abstract::LtS:
case LtS:
return TrueFalse(aConstant.ltS(bConstant));
case Abstract::LeS:
case LeS:
return TrueFalse(aConstant.leS(bConstant));
case Abstract::GtS:
case GtS:
return TrueFalse(aConstant.gtS(bConstant));
case Abstract::GeS:
case GeS:
return TrueFalse(aConstant.geS(bConstant));
case Abstract::LtU:
case LtU:
return TrueFalse(aConstant.ltU(bConstant));
case Abstract::LeU:
case LeU:
return TrueFalse(aConstant.leU(bConstant));
case Abstract::GtU:
case GtU:
return TrueFalse(aConstant.gtU(bConstant));
case Abstract::GeU:
case GeU:
return TrueFalse(aConstant.geU(bConstant));
default: {
}
}
}

// a != A =?=> a == B. False if A = B, else unknown.
if (aOp == Abstract::Ne && bOp == Abstract::Eq) {
if (aOp == Ne && bOp == Eq) {
if (aConstant == bConstant) {
return False;
}
}

// a != A =?=> a != B. True if A = B, else unknown.
if (aOp == Abstract::Ne && bOp == Abstract::Ne) {
if (aOp == Ne && bOp == Ne) {
if (aConstant == bConstant) {
return True;
}
Expand Down Expand Up @@ -160,6 +162,55 @@ Result AndedConstraintSet::proves(const AndedConstraintSet& other) const {
return hasUnknown ? Unknown : True;
}

namespace {

// Do an AND on a pair of constraints, looking for a way to fuse them together
// into a single constraint that represents them both, while assuming the
// constraints have an equal term. If we fail, return nullopt.
std::optional<Constraint> fusedApproximateAndTermEqualPair(
const Abstract::Op aOp, const Abstract::Op bOp, const Term& term) {
using namespace Abstract;

// x < C && x <= C === x < C
if (aOp == LtS && bOp == LeS) {
return Constraint{LtS, term};
}
if (aOp == LtU && bOp == LeU) {
return Constraint{LtU, term};
}

// TODO: all the rest

return {};
}

// Do an AND on a pair of constraints, looking for a way to fuse them together
// into a single constraint that represents them both. If we fail, return
// nullopt.
std::optional<Constraint> fusedApproximateAndPair(const Constraint& a,
const Constraint& b,
bool recursing = false) {
// If a proves b is true, all we need is a (e.g. { x == 5 && x > 0 } => x == 5
if (provesPair(a, b) == True) {
return a;
}

if (a.term == b.term) {
if (auto result = fusedApproximateAndTermEqualPair(a.op, b.op, a.term)) {
return result;
}
}

if (!recursing) {
// The flipped form may be recognized.
return fusedApproximateAndPair(b, a, true);
}

return {};
}

} // anonymous namespace

void AndedConstraintSet::approximateAnd(const Constraint& c) {
if (provesEverything()) {
// Nothing to add.
Expand All @@ -172,24 +223,20 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) {
return;
} else if (result == False) {
// We are now a contradiction.
isContradiction = true;
setProvesEverything();
return;
}

// If c proves something already present to be true, it can just replace it.
for (auto& existing : *this) {
auto result = provesPair(c, existing);
if (result == True) {
existing = c;
// Some ANDed constraints fuse together into a new constraint.
if (auto fused = fusedApproximateAndPair(existing, c)) {
existing = *fused;

// Sort to ensure we are in the right place.
std::sort(begin(), end());

return;
}

// There cannot be a contradiction here, because we checked for that above.
assert(result != False);
}

if (size() < MaxConstraints) {
Expand Down
93 changes: 82 additions & 11 deletions test/gtest/constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,25 +254,26 @@ static void checkOr(const AndedConstraintSet& a,

TEST(ConstraintTest, TestOrInequality) {
// x == 5 || x >= 0 => x >= 0
AndedConstraintSet eq5{Constraint{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet ge0{Constraint{GeU, {Literal(int32_t(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{Constraint{GtS, {Literal(int32_t(5))}}};
AndedConstraintSet ges5{Constraint{GeS, {Literal(int32_t(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);
}

TEST(ConstraintTest, TestOrLoop) {
// Check common loop patterns:
// 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{Constraint{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet left{{Eq, {Literal(int32_t(5))}}};
AndedConstraintSet right(
{{GtS, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}});
AndedConstraintSet result(
Expand All @@ -283,20 +284,20 @@ TEST(ConstraintTest, TestOrLoop) {

// Change 5 on the left to 7:
// { x == 7 } || { x > 5 && x <= 42 } ==> { x > 5 && x <= 42}
AndedConstraintSet left7{Constraint{Eq, {Literal(int32_t(7))}}};
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{Constraint{Eq, {Literal(int32_t(99))}}};
AndedConstraintSet rightOnly5{Constraint{GtS, {Literal(int32_t(5))}}};
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{Constraint{Eq, {Literal(int32_t(4))}}};
AndedConstraintSet left4{{Eq, {Literal(int32_t(4))}}};
AndedConstraintSet rightOnly42({{LeS, {Literal(int32_t(42))}}});
checkOr(left4, right, rightOnly42);

Expand All @@ -311,7 +312,7 @@ TEST(ConstraintTest, TestOrLoop) {
// 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{Constraint{Ne, {Literal(int32_t(5))}}};
AndedConstraintSet leftNe{{Ne, {Literal(int32_t(5))}}};
auto empty = AndedConstraintSet::makeProvesNothing();
checkOr(leftNe, right, empty);

Expand Down Expand Up @@ -340,3 +341,73 @@ TEST(ConstraintTest, TestOrLoop) {
{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});
}
Loading