1 //===- IntegerPolyhedron.cpp - Tests for IntegerPolyhedron class ----------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "mlir/Analysis/Presburger/IntegerPolyhedron.h" 10 #include "../../Dialect/Affine/Analysis/AffineStructuresParser.h" 11 #include "mlir/IR/MLIRContext.h" 12 13 #include <gmock/gmock.h> 14 #include <gtest/gtest.h> 15 16 #include <numeric> 17 18 namespace mlir { 19 20 using testing::ElementsAre; 21 22 enum class TestFunction { Sample, Empty }; 23 24 /// Construct a IntegerPolyhedron from a set of inequality and 25 /// equality constraints. 26 static IntegerPolyhedron 27 makeSetFromConstraints(unsigned ids, ArrayRef<SmallVector<int64_t, 4>> ineqs, 28 ArrayRef<SmallVector<int64_t, 4>> eqs, 29 unsigned syms = 0) { 30 IntegerPolyhedron set(ineqs.size(), eqs.size(), ids + 1, ids - syms, syms, 31 /*numLocals=*/0); 32 for (const auto &eq : eqs) 33 set.addEquality(eq); 34 for (const auto &ineq : ineqs) 35 set.addInequality(ineq); 36 return set; 37 } 38 39 /// If fn is TestFunction::Sample (default): 40 /// If hasSample is true, check that findIntegerSample returns a valid sample 41 /// for the IntegerPolyhedron poly. 42 /// If hasSample is false, check that findIntegerSample returns None. 43 /// 44 /// If fn is TestFunction::Empty, check that isIntegerEmpty returns the 45 /// opposite of hasSample. 46 static void checkSample(bool hasSample, const IntegerPolyhedron &poly, 47 TestFunction fn = TestFunction::Sample) { 48 Optional<SmallVector<int64_t, 8>> maybeSample; 49 switch (fn) { 50 case TestFunction::Sample: 51 maybeSample = poly.findIntegerSample(); 52 if (!hasSample) { 53 EXPECT_FALSE(maybeSample.hasValue()); 54 if (maybeSample.hasValue()) { 55 for (auto x : *maybeSample) 56 llvm::errs() << x << ' '; 57 llvm::errs() << '\n'; 58 } 59 } else { 60 ASSERT_TRUE(maybeSample.hasValue()); 61 EXPECT_TRUE(poly.containsPoint(*maybeSample)); 62 } 63 break; 64 case TestFunction::Empty: 65 EXPECT_EQ(!hasSample, poly.isIntegerEmpty()); 66 break; 67 } 68 } 69 70 /// Check sampling for all the permutations of the dimensions for the given 71 /// constraint set. Since the GBR algorithm progresses dimension-wise, different 72 /// orderings may cause the algorithm to proceed differently. At least some of 73 ///.these permutations should make it past the heuristics and test the 74 /// implementation of the GBR algorithm itself. 75 /// Use TestFunction fn to test. 76 static void checkPermutationsSample(bool hasSample, unsigned nDim, 77 ArrayRef<SmallVector<int64_t, 4>> ineqs, 78 ArrayRef<SmallVector<int64_t, 4>> eqs, 79 TestFunction fn = TestFunction::Sample) { 80 SmallVector<unsigned, 4> perm(nDim); 81 std::iota(perm.begin(), perm.end(), 0); 82 auto permute = [&perm](ArrayRef<int64_t> coeffs) { 83 SmallVector<int64_t, 4> permuted; 84 for (unsigned id : perm) 85 permuted.push_back(coeffs[id]); 86 permuted.push_back(coeffs.back()); 87 return permuted; 88 }; 89 do { 90 SmallVector<SmallVector<int64_t, 4>, 4> permutedIneqs, permutedEqs; 91 for (const auto &ineq : ineqs) 92 permutedIneqs.push_back(permute(ineq)); 93 for (const auto &eq : eqs) 94 permutedEqs.push_back(permute(eq)); 95 96 checkSample(hasSample, 97 makeSetFromConstraints(nDim, permutedIneqs, permutedEqs), fn); 98 } while (std::next_permutation(perm.begin(), perm.end())); 99 } 100 101 /// Parses a IntegerPolyhedron from a StringRef. It is expected that the 102 /// string represents a valid IntegerSet, otherwise it will violate a gtest 103 /// assertion. 104 static IntegerPolyhedron parsePoly(StringRef str, MLIRContext *context) { 105 FailureOr<IntegerPolyhedron> poly = parseIntegerSetToFAC(str, context); 106 107 EXPECT_TRUE(succeeded(poly)); 108 109 return *poly; 110 } 111 112 TEST(IntegerPolyhedronTest, removeInequality) { 113 IntegerPolyhedron set = 114 makeSetFromConstraints(1, {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}, {}); 115 116 set.removeInequalityRange(0, 0); 117 EXPECT_EQ(set.getNumInequalities(), 5u); 118 119 set.removeInequalityRange(1, 3); 120 EXPECT_EQ(set.getNumInequalities(), 3u); 121 EXPECT_THAT(set.getInequality(0), ElementsAre(0, 0)); 122 EXPECT_THAT(set.getInequality(1), ElementsAre(3, 3)); 123 EXPECT_THAT(set.getInequality(2), ElementsAre(4, 4)); 124 125 set.removeInequality(1); 126 EXPECT_EQ(set.getNumInequalities(), 2u); 127 EXPECT_THAT(set.getInequality(0), ElementsAre(0, 0)); 128 EXPECT_THAT(set.getInequality(1), ElementsAre(4, 4)); 129 } 130 131 TEST(IntegerPolyhedronTest, removeEquality) { 132 IntegerPolyhedron set = 133 makeSetFromConstraints(1, {}, {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}); 134 135 set.removeEqualityRange(0, 0); 136 EXPECT_EQ(set.getNumEqualities(), 5u); 137 138 set.removeEqualityRange(1, 3); 139 EXPECT_EQ(set.getNumEqualities(), 3u); 140 EXPECT_THAT(set.getEquality(0), ElementsAre(0, 0)); 141 EXPECT_THAT(set.getEquality(1), ElementsAre(3, 3)); 142 EXPECT_THAT(set.getEquality(2), ElementsAre(4, 4)); 143 144 set.removeEquality(1); 145 EXPECT_EQ(set.getNumEqualities(), 2u); 146 EXPECT_THAT(set.getEquality(0), ElementsAre(0, 0)); 147 EXPECT_THAT(set.getEquality(1), ElementsAre(4, 4)); 148 } 149 150 TEST(IntegerPolyhedronTest, clearConstraints) { 151 IntegerPolyhedron set = makeSetFromConstraints(1, {}, {}); 152 153 set.addInequality({1, 0}); 154 EXPECT_EQ(set.atIneq(0, 0), 1); 155 EXPECT_EQ(set.atIneq(0, 1), 0); 156 157 set.clearConstraints(); 158 159 set.addInequality({1, 0}); 160 EXPECT_EQ(set.atIneq(0, 0), 1); 161 EXPECT_EQ(set.atIneq(0, 1), 0); 162 } 163 164 TEST(IntegerPolyhedronTest, removeIdRange) { 165 IntegerPolyhedron set(3, 2, 1); 166 167 set.addInequality({10, 11, 12, 20, 21, 30, 40}); 168 set.removeId(IntegerPolyhedron::IdKind::Symbol, 1); 169 EXPECT_THAT(set.getInequality(0), 170 testing::ElementsAre(10, 11, 12, 20, 30, 40)); 171 172 set.removeIdRange(IntegerPolyhedron::IdKind::Dimension, 0, 2); 173 EXPECT_THAT(set.getInequality(0), testing::ElementsAre(12, 20, 30, 40)); 174 175 set.removeIdRange(IntegerPolyhedron::IdKind::Local, 1, 1); 176 EXPECT_THAT(set.getInequality(0), testing::ElementsAre(12, 20, 30, 40)); 177 178 set.removeIdRange(IntegerPolyhedron::IdKind::Local, 0, 1); 179 EXPECT_THAT(set.getInequality(0), testing::ElementsAre(12, 20, 40)); 180 } 181 182 TEST(IntegerPolyhedronTest, FindSampleTest) { 183 // Bounded sets with only inequalities. 184 185 MLIRContext context; 186 187 // 0 <= 7x <= 5 188 checkSample(true, parsePoly("(x) : (7 * x >= 0, -7 * x + 5 >= 0)", &context)); 189 190 // 1 <= 5x and 5x <= 4 (no solution). 191 checkSample(false, 192 parsePoly("(x) : (5 * x - 1 >= 0, -5 * x + 4 >= 0)", &context)); 193 194 // 1 <= 5x and 5x <= 9 (solution: x = 1). 195 checkSample(true, 196 parsePoly("(x) : (5 * x - 1 >= 0, -5 * x + 9 >= 0)", &context)); 197 198 // Bounded sets with equalities. 199 // x >= 8 and 40 >= y and x = y. 200 checkSample(true, parsePoly("(x,y) : (x - 8 >= 0, -y + 40 >= 0, x - y == 0)", 201 &context)); 202 203 // x <= 10 and y <= 10 and 10 <= z and x + 2y = 3z. 204 // solution: x = y = z = 10. 205 checkSample(true, parsePoly("(x,y,z) : (-x + 10 >= 0, -y + 10 >= 0, " 206 "z - 10 >= 0, x + 2 * y - 3 * z == 0)", 207 &context)); 208 209 // x <= 10 and y <= 10 and 11 <= z and x + 2y = 3z. 210 // This implies x + 2y >= 33 and x + 2y <= 30, which has no solution. 211 checkSample(false, parsePoly("(x,y,z) : (-x + 10 >= 0, -y + 10 >= 0, " 212 "z - 11 >= 0, x + 2 * y - 3 * z == 0)", 213 &context)); 214 215 // 0 <= r and r <= 3 and 4q + r = 7. 216 // Solution: q = 1, r = 3. 217 checkSample( 218 true, 219 parsePoly("(q,r) : (r >= 0, -r + 3 >= 0, 4 * q + r - 7 == 0)", &context)); 220 221 // 4q + r = 7 and r = 0. 222 // Solution: q = 1, r = 3. 223 checkSample(false, 224 parsePoly("(q,r) : (4 * q + r - 7 == 0, r == 0)", &context)); 225 226 // The next two sets are large sets that should take a long time to sample 227 // with a naive branch and bound algorithm but can be sampled efficiently with 228 // the GBR algorithm. 229 // 230 // This is a triangle with vertices at (1/3, 0), (2/3, 0) and (10000, 10000). 231 checkSample(true, parsePoly("(x,y) : (y >= 0, " 232 "300000 * x - 299999 * y - 100000 >= 0, " 233 "-300000 * x + 299998 * y + 200000 >= 0)", 234 &context)); 235 236 // This is a tetrahedron with vertices at 237 // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 10000), and (10000, 10000, 10000). 238 // The first three points form a triangular base on the xz plane with the 239 // apex at the fourth point, which is the only integer point. 240 checkPermutationsSample( 241 true, 3, 242 { 243 {0, 1, 0, 0}, // y >= 0 244 {0, -1, 1, 0}, // z >= y 245 {300000, -299998, -1, 246 -100000}, // -300000x + 299998y + 100000 + z <= 0. 247 {-150000, 149999, 0, 100000}, // -150000x + 149999y + 100000 >= 0. 248 }, 249 {}); 250 251 // Same thing with some spurious extra dimensions equated to constants. 252 checkSample( 253 true, 254 parsePoly("(a,b,c,d,e) : (b + d - e >= 0, -b + c - d + e >= 0, " 255 "300000 * a - 299998 * b - c - 9 * d + 21 * e - 112000 >= 0, " 256 "-150000 * a + 149999 * b - 15 * d + 47 * e + 68000 >= 0, " 257 "d - e == 0, d + e - 2000 == 0)", 258 &context)); 259 260 // This is a tetrahedron with vertices at 261 // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 100), (100, 100 - 1/3, 100). 262 checkPermutationsSample(false, 3, 263 { 264 {0, 1, 0, 0}, 265 {0, -300, 299, 0}, 266 {300 * 299, -89400, -299, -100 * 299}, 267 {-897, 894, 0, 598}, 268 }, 269 {}); 270 271 // Two tests involving equalities that are integer empty but not rational 272 // empty. 273 274 // This is a line segment from (0, 1/3) to (100, 100 + 1/3). 275 checkSample( 276 false, 277 parsePoly("(x,y) : (x >= 0, -x + 100 >= 0, 3 * x - 3 * y + 1 == 0)", 278 &context)); 279 280 // A thin parallelogram. 0 <= x <= 100 and x + 1/3 <= y <= x + 2/3. 281 checkSample(false, 282 parsePoly("(x,y) : (x >= 0, -x + 100 >= 0, " 283 "3 * x - 3 * y + 2 >= 0, -3 * x + 3 * y - 1 >= 0)", 284 &context)); 285 286 checkSample(true, parsePoly("(x,y) : (2 * x >= 0, -2 * x + 99 >= 0, " 287 "2 * y >= 0, -2 * y + 99 >= 0)", 288 &context)); 289 290 // 2D cone with apex at (10000, 10000) and 291 // edges passing through (1/3, 0) and (2/3, 0). 292 checkSample(true, parsePoly("(x,y) : (300000 * x - 299999 * y - 100000 >= 0, " 293 "-300000 * x + 299998 * y + 200000 >= 0)", 294 &context)); 295 296 // Cartesian product of a tetrahedron and a 2D cone. 297 // The tetrahedron has vertices at 298 // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 10000), and (10000, 10000, 10000). 299 // The first three points form a triangular base on the xz plane with the 300 // apex at the fourth point, which is the only integer point. 301 // The cone has apex at (10000, 10000) and 302 // edges passing through (1/3, 0) and (2/3, 0). 303 checkPermutationsSample( 304 true /* not empty */, 5, 305 { 306 // Tetrahedron contraints: 307 {0, 1, 0, 0, 0, 0}, // y >= 0 308 {0, -1, 1, 0, 0, 0}, // z >= y 309 // -300000x + 299998y + 100000 + z <= 0. 310 {300000, -299998, -1, 0, 0, -100000}, 311 // -150000x + 149999y + 100000 >= 0. 312 {-150000, 149999, 0, 0, 0, 100000}, 313 314 // Triangle constraints: 315 // 300000p - 299999q >= 100000 316 {0, 0, 0, 300000, -299999, -100000}, 317 // -300000p + 299998q + 200000 >= 0 318 {0, 0, 0, -300000, 299998, 200000}, 319 }, 320 {}); 321 322 // Cartesian product of same tetrahedron as above and {(p, q) : 1/3 <= p <= 323 // 2/3}. Since the second set is empty, the whole set is too. 324 checkPermutationsSample( 325 false /* empty */, 5, 326 { 327 // Tetrahedron contraints: 328 {0, 1, 0, 0, 0, 0}, // y >= 0 329 {0, -1, 1, 0, 0, 0}, // z >= y 330 // -300000x + 299998y + 100000 + z <= 0. 331 {300000, -299998, -1, 0, 0, -100000}, 332 // -150000x + 149999y + 100000 >= 0. 333 {-150000, 149999, 0, 0, 0, 100000}, 334 335 // Second set constraints: 336 // 3p >= 1 337 {0, 0, 0, 3, 0, -1}, 338 // 3p <= 2 339 {0, 0, 0, -3, 0, 2}, 340 }, 341 {}); 342 343 // Cartesian product of same tetrahedron as above and 344 // {(p, q, r) : 1 <= p <= 2 and p = 3q + 3r}. 345 // Since the second set is empty, the whole set is too. 346 checkPermutationsSample( 347 false /* empty */, 5, 348 { 349 // Tetrahedron contraints: 350 {0, 1, 0, 0, 0, 0, 0}, // y >= 0 351 {0, -1, 1, 0, 0, 0, 0}, // z >= y 352 // -300000x + 299998y + 100000 + z <= 0. 353 {300000, -299998, -1, 0, 0, 0, -100000}, 354 // -150000x + 149999y + 100000 >= 0. 355 {-150000, 149999, 0, 0, 0, 0, 100000}, 356 357 // Second set constraints: 358 // p >= 1 359 {0, 0, 0, 1, 0, 0, -1}, 360 // p <= 2 361 {0, 0, 0, -1, 0, 0, 2}, 362 }, 363 { 364 {0, 0, 0, 1, -3, -3, 0}, // p = 3q + 3r 365 }); 366 367 // Cartesian product of a tetrahedron and a 2D cone. 368 // The tetrahedron is empty and has vertices at 369 // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 100), and (100, 100 - 1/3, 100). 370 // The cone has apex at (10000, 10000) and 371 // edges passing through (1/3, 0) and (2/3, 0). 372 // Since the tetrahedron is empty, the Cartesian product is too. 373 checkPermutationsSample(false /* empty */, 5, 374 { 375 // Tetrahedron contraints: 376 {0, 1, 0, 0, 0, 0}, 377 {0, -300, 299, 0, 0, 0}, 378 {300 * 299, -89400, -299, 0, 0, -100 * 299}, 379 {-897, 894, 0, 0, 0, 598}, 380 381 // Triangle constraints: 382 // 300000p - 299999q >= 100000 383 {0, 0, 0, 300000, -299999, -100000}, 384 // -300000p + 299998q + 200000 >= 0 385 {0, 0, 0, -300000, 299998, 200000}, 386 }, 387 {}); 388 389 // Cartesian product of same tetrahedron as above and 390 // {(p, q) : 1/3 <= p <= 2/3}. 391 checkPermutationsSample(false /* empty */, 5, 392 { 393 // Tetrahedron contraints: 394 {0, 1, 0, 0, 0, 0}, 395 {0, -300, 299, 0, 0, 0}, 396 {300 * 299, -89400, -299, 0, 0, -100 * 299}, 397 {-897, 894, 0, 0, 0, 598}, 398 399 // Second set constraints: 400 // 3p >= 1 401 {0, 0, 0, 3, 0, -1}, 402 // 3p <= 2 403 {0, 0, 0, -3, 0, 2}, 404 }, 405 {}); 406 407 checkSample(true, parsePoly("(x, y, z) : (2 * x - 1 >= 0, x - y - 1 == 0, " 408 "y - z == 0)", 409 &context)); 410 411 // Regression tests for the computation of dual coefficients. 412 checkSample(false, parsePoly("(x, y, z) : (" 413 "6*x - 4*y + 9*z + 2 >= 0," 414 "x + 5*y + z + 5 >= 0," 415 "-4*x + y + 2*z - 1 >= 0," 416 "-3*x - 2*y - 7*z - 1 >= 0," 417 "-7*x - 5*y - 9*z - 1 >= 0)", 418 &context)); 419 checkSample(true, parsePoly("(x, y, z) : (" 420 "3*x + 3*y + 3 >= 0," 421 "-4*x - 8*y - z + 4 >= 0," 422 "-7*x - 4*y + z + 1 >= 0," 423 "2*x - 7*y - 8*z - 7 >= 0," 424 "9*x + 8*y - 9*z - 7 >= 0)", 425 &context)); 426 } 427 428 TEST(IntegerPolyhedronTest, IsIntegerEmptyTest) { 429 430 MLIRContext context; 431 432 // 1 <= 5x and 5x <= 4 (no solution). 433 EXPECT_TRUE(parsePoly("(x) : (5 * x - 1 >= 0, -5 * x + 4 >= 0)", &context) 434 .isIntegerEmpty()); 435 // 1 <= 5x and 5x <= 9 (solution: x = 1). 436 EXPECT_FALSE(parsePoly("(x) : (5 * x - 1 >= 0, -5 * x + 9 >= 0)", &context) 437 .isIntegerEmpty()); 438 439 // Unbounded sets. 440 EXPECT_TRUE(parsePoly("(x,y,z) : (2 * y - 1 >= 0, -2 * y + 1 >= 0, " 441 "2 * z - 1 >= 0, 2 * x - 1 == 0)", 442 &context) 443 .isIntegerEmpty()); 444 445 EXPECT_FALSE(parsePoly("(x,y,z) : (2 * x - 1 >= 0, -3 * x + 3 >= 0, " 446 "5 * z - 6 >= 0, -7 * z + 17 >= 0, 3 * y - 2 >= 0)", 447 &context) 448 .isIntegerEmpty()); 449 450 EXPECT_FALSE( 451 parsePoly("(x,y,z) : (2 * x - 1 >= 0, x - y - 1 == 0, y - z == 0)", 452 &context) 453 .isIntegerEmpty()); 454 455 // IntegerPolyhedron::isEmpty() does not detect the following sets to be 456 // empty. 457 458 // 3x + 7y = 1 and 0 <= x, y <= 10. 459 // Since x and y are non-negative, 3x + 7y can never be 1. 460 EXPECT_TRUE(parsePoly("(x,y) : (x >= 0, -x + 10 >= 0, y >= 0, -y + 10 >= 0, " 461 "3 * x + 7 * y - 1 == 0)", 462 &context) 463 .isIntegerEmpty()); 464 465 // 2x = 3y and y = x - 1 and x + y = 6z + 2 and 0 <= x, y <= 100. 466 // Substituting y = x - 1 in 3y = 2x, we obtain x = 3 and hence y = 2. 467 // Since x + y = 5 cannot be equal to 6z + 2 for any z, the set is empty. 468 EXPECT_TRUE( 469 parsePoly("(x,y,z) : (x >= 0, -x + 100 >= 0, y >= 0, -y + 100 >= 0, " 470 "2 * x - 3 * y == 0, x - y - 1 == 0, x + y - 6 * z - 2 == 0)", 471 &context) 472 .isIntegerEmpty()); 473 474 // 2x = 3y and y = x - 1 + 6z and x + y = 6q + 2 and 0 <= x, y <= 100. 475 // 2x = 3y implies x is a multiple of 3 and y is even. 476 // Now y = x - 1 + 6z implies y = 2 mod 3. In fact, since y is even, we have 477 // y = 2 mod 6. Then since x = y + 1 + 6z, we have x = 3 mod 6, implying 478 // x + y = 5 mod 6, which contradicts x + y = 6q + 2, so the set is empty. 479 EXPECT_TRUE( 480 parsePoly( 481 "(x,y,z,q) : (x >= 0, -x + 100 >= 0, y >= 0, -y + 100 >= 0, " 482 "2 * x - 3 * y == 0, x - y + 6 * z - 1 == 0, x + y - 6 * q - 2 == 0)", 483 &context) 484 .isIntegerEmpty()); 485 486 // Set with symbols. 487 EXPECT_FALSE(parsePoly("(x)[s] : (x + s >= 0, x - s == 0)", &context) 488 .isIntegerEmpty()); 489 } 490 491 TEST(IntegerPolyhedronTest, removeRedundantConstraintsTest) { 492 MLIRContext context; 493 494 IntegerPolyhedron poly = 495 parsePoly("(x) : (x - 2 >= 0, -x + 2 >= 0, x - 2 == 0)", &context); 496 poly.removeRedundantConstraints(); 497 498 // Both inequalities are redundant given the equality. Both have been removed. 499 EXPECT_EQ(poly.getNumInequalities(), 0u); 500 EXPECT_EQ(poly.getNumEqualities(), 1u); 501 502 IntegerPolyhedron poly2 = 503 parsePoly("(x,y) : (x - 3 >= 0, y - 2 >= 0, x - y == 0)", &context); 504 poly2.removeRedundantConstraints(); 505 506 // The second inequality is redundant and should have been removed. The 507 // remaining inequality should be the first one. 508 EXPECT_EQ(poly2.getNumInequalities(), 1u); 509 EXPECT_THAT(poly2.getInequality(0), ElementsAre(1, 0, -3)); 510 EXPECT_EQ(poly2.getNumEqualities(), 1u); 511 512 IntegerPolyhedron poly3 = 513 parsePoly("(x,y,z) : (x - y == 0, x - z == 0, y - z == 0)", &context); 514 poly3.removeRedundantConstraints(); 515 516 // One of the three equalities can be removed. 517 EXPECT_EQ(poly3.getNumInequalities(), 0u); 518 EXPECT_EQ(poly3.getNumEqualities(), 2u); 519 520 IntegerPolyhedron poly4 = 521 parsePoly("(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q) : (" 522 "b - 1 >= 0," 523 "-b + 500 >= 0," 524 "-16 * d + f >= 0," 525 "f - 1 >= 0," 526 "-f + 998 >= 0," 527 "16 * d - f + 15 >= 0," 528 "-16 * e + g >= 0," 529 "g - 1 >= 0," 530 "-g + 998 >= 0," 531 "16 * e - g + 15 >= 0," 532 "h >= 0," 533 "-h + 1 >= 0," 534 "j - 1 >= 0," 535 "-j + 500 >= 0," 536 "-f + 16 * l + 15 >= 0," 537 "f - 16 * l >= 0," 538 "-16 * m + o >= 0," 539 "o - 1 >= 0," 540 "-o + 998 >= 0," 541 "16 * m - o + 15 >= 0," 542 "p >= 0," 543 "-p + 1 >= 0," 544 "-g - h + 8 * q + 8 >= 0," 545 "-o - p + 8 * q + 8 >= 0," 546 "o + p - 8 * q - 1 >= 0," 547 "g + h - 8 * q - 1 >= 0," 548 "-f + n >= 0," 549 "f - n >= 0," 550 "k - 10 >= 0," 551 "-k + 10 >= 0," 552 "i - 13 >= 0," 553 "-i + 13 >= 0," 554 "c - 10 >= 0," 555 "-c + 10 >= 0," 556 "a - 13 >= 0," 557 "-a + 13 >= 0" 558 ")", 559 &context); 560 561 // The above is a large set of constraints without any redundant constraints, 562 // as verified by the Fourier-Motzkin based removeRedundantInequalities. 563 unsigned nIneq = poly4.getNumInequalities(); 564 unsigned nEq = poly4.getNumEqualities(); 565 poly4.removeRedundantInequalities(); 566 ASSERT_EQ(poly4.getNumInequalities(), nIneq); 567 ASSERT_EQ(poly4.getNumEqualities(), nEq); 568 // Now we test that removeRedundantConstraints does not find any constraints 569 // to be redundant either. 570 poly4.removeRedundantConstraints(); 571 EXPECT_EQ(poly4.getNumInequalities(), nIneq); 572 EXPECT_EQ(poly4.getNumEqualities(), nEq); 573 574 IntegerPolyhedron poly5 = parsePoly( 575 "(x,y) : (128 * x + 127 >= 0, -x + 7 >= 0, -128 * x + y >= 0, y >= 0)", 576 &context); 577 // 128x + 127 >= 0 implies that 128x >= 0, since x has to be an integer. 578 // (This should be caught by GCDTightenInqualities().) 579 // So -128x + y >= 0 and 128x + 127 >= 0 imply y >= 0 since we have 580 // y >= 128x >= 0. 581 poly5.removeRedundantConstraints(); 582 EXPECT_EQ(poly5.getNumInequalities(), 3u); 583 SmallVector<int64_t, 8> redundantConstraint = {0, 1, 0}; 584 for (unsigned i = 0; i < 3; ++i) { 585 // Ensure that the removed constraint was the redundant constraint [3]. 586 EXPECT_NE(poly5.getInequality(i), ArrayRef<int64_t>(redundantConstraint)); 587 } 588 } 589 590 TEST(IntegerPolyhedronTest, addConstantUpperBound) { 591 IntegerPolyhedron poly(2); 592 poly.addBound(IntegerPolyhedron::UB, 0, 1); 593 EXPECT_EQ(poly.atIneq(0, 0), -1); 594 EXPECT_EQ(poly.atIneq(0, 1), 0); 595 EXPECT_EQ(poly.atIneq(0, 2), 1); 596 597 poly.addBound(IntegerPolyhedron::UB, {1, 2, 3}, 1); 598 EXPECT_EQ(poly.atIneq(1, 0), -1); 599 EXPECT_EQ(poly.atIneq(1, 1), -2); 600 EXPECT_EQ(poly.atIneq(1, 2), -2); 601 } 602 603 TEST(IntegerPolyhedronTest, addConstantLowerBound) { 604 IntegerPolyhedron poly(2); 605 poly.addBound(IntegerPolyhedron::LB, 0, 1); 606 EXPECT_EQ(poly.atIneq(0, 0), 1); 607 EXPECT_EQ(poly.atIneq(0, 1), 0); 608 EXPECT_EQ(poly.atIneq(0, 2), -1); 609 610 poly.addBound(IntegerPolyhedron::LB, {1, 2, 3}, 1); 611 EXPECT_EQ(poly.atIneq(1, 0), 1); 612 EXPECT_EQ(poly.atIneq(1, 1), 2); 613 EXPECT_EQ(poly.atIneq(1, 2), 2); 614 } 615 616 /// Check if the expected division representation of local variables matches the 617 /// computed representation. The expected division representation is given as 618 /// a vector of expressions set in `expectedDividends` and the corressponding 619 /// denominator in `expectedDenominators`. The `denominators` and `dividends` 620 /// obtained through `getLocalRepr` function is verified against the 621 /// `expectedDenominators` and `expectedDividends` respectively. 622 static void checkDivisionRepresentation( 623 IntegerPolyhedron &poly, 624 const std::vector<SmallVector<int64_t, 8>> &expectedDividends, 625 const SmallVectorImpl<unsigned> &expectedDenominators) { 626 627 std::vector<SmallVector<int64_t, 8>> dividends; 628 SmallVector<unsigned, 4> denominators; 629 630 poly.getLocalReprs(dividends, denominators); 631 632 // Check that the `denominators` and `expectedDenominators` match. 633 EXPECT_TRUE(expectedDenominators == denominators); 634 635 // Check that the `dividends` and `expectedDividends` match. If the 636 // denominator for a division is zero, we ignore its dividend. 637 EXPECT_TRUE(dividends.size() == expectedDividends.size()); 638 for (unsigned i = 0, e = dividends.size(); i < e; ++i) 639 if (denominators[i] != 0) 640 EXPECT_TRUE(expectedDividends[i] == dividends[i]); 641 } 642 643 TEST(IntegerPolyhedronTest, computeLocalReprSimple) { 644 IntegerPolyhedron poly(1); 645 646 poly.addLocalFloorDiv({1, 4}, 10); 647 poly.addLocalFloorDiv({1, 0, 100}, 10); 648 649 std::vector<SmallVector<int64_t, 8>> divisions = {{1, 0, 0, 4}, 650 {1, 0, 0, 100}}; 651 SmallVector<unsigned, 8> denoms = {10, 10}; 652 653 // Check if floordivs can be computed when no other inequalities exist 654 // and floor divs do not depend on each other. 655 checkDivisionRepresentation(poly, divisions, denoms); 656 } 657 658 TEST(IntegerPolyhedronTest, computeLocalReprConstantFloorDiv) { 659 IntegerPolyhedron poly(4); 660 661 poly.addInequality({1, 0, 3, 1, 2}); 662 poly.addInequality({1, 2, -8, 1, 10}); 663 poly.addEquality({1, 2, -4, 1, 10}); 664 665 poly.addLocalFloorDiv({0, 0, 0, 0, 100}, 30); 666 poly.addLocalFloorDiv({0, 0, 0, 0, 0, 206}, 101); 667 668 std::vector<SmallVector<int64_t, 8>> divisions = {{0, 0, 0, 0, 0, 0, 3}, 669 {0, 0, 0, 0, 0, 0, 2}}; 670 SmallVector<unsigned, 8> denoms = {1, 1}; 671 672 // Check if floordivs with constant numerator can be computed. 673 checkDivisionRepresentation(poly, divisions, denoms); 674 } 675 676 TEST(IntegerPolyhedronTest, computeLocalReprRecursive) { 677 IntegerPolyhedron poly(4); 678 poly.addInequality({1, 0, 3, 1, 2}); 679 poly.addInequality({1, 2, -8, 1, 10}); 680 poly.addEquality({1, 2, -4, 1, 10}); 681 682 poly.addLocalFloorDiv({0, -2, 7, 2, 10}, 3); 683 poly.addLocalFloorDiv({3, 0, 9, 2, 2, 10}, 5); 684 poly.addLocalFloorDiv({0, 1, -123, 2, 0, -4, 10}, 3); 685 686 poly.addInequality({1, 2, -2, 1, -5, 0, 6, 100}); 687 poly.addInequality({1, 2, -8, 1, 3, 7, 0, -9}); 688 689 std::vector<SmallVector<int64_t, 8>> divisions = { 690 {0, -2, 7, 2, 0, 0, 0, 10}, 691 {3, 0, 9, 2, 2, 0, 0, 10}, 692 {0, 1, -123, 2, 0, -4, 0, 10}}; 693 694 SmallVector<unsigned, 8> denoms = {3, 5, 3}; 695 696 // Check if floordivs which may depend on other floordivs can be computed. 697 checkDivisionRepresentation(poly, divisions, denoms); 698 } 699 700 TEST(IntegerPolyhedronTest, computeLocalReprTightUpperBound) { 701 MLIRContext context; 702 703 { 704 IntegerPolyhedron poly = parsePoly("(i) : (i mod 3 - 1 >= 0)", &context); 705 706 // The set formed by the poly is: 707 // 3q - i + 2 >= 0 <-- Division lower bound 708 // -3q + i - 1 >= 0 709 // -3q + i >= 0 <-- Division upper bound 710 // We remove redundant constraints to get the set: 711 // 3q - i + 2 >= 0 <-- Division lower bound 712 // -3q + i - 1 >= 0 <-- Tighter division upper bound 713 // thus, making the upper bound tighter. 714 poly.removeRedundantConstraints(); 715 716 std::vector<SmallVector<int64_t, 8>> divisions = {{1, 0, 0}}; 717 SmallVector<unsigned, 8> denoms = {3}; 718 719 // Check if the divisions can be computed even with a tighter upper bound. 720 checkDivisionRepresentation(poly, divisions, denoms); 721 } 722 723 { 724 IntegerPolyhedron poly = parsePoly( 725 "(i, j, q) : (4*q - i - j + 2 >= 0, -4*q + i + j >= 0)", &context); 726 // Convert `q` to a local variable. 727 poly.convertDimToLocal(2, 3); 728 729 std::vector<SmallVector<int64_t, 8>> divisions = {{1, 1, 0, 1}}; 730 SmallVector<unsigned, 8> denoms = {4}; 731 732 // Check if the divisions can be computed even with a tighter upper bound. 733 checkDivisionRepresentation(poly, divisions, denoms); 734 } 735 } 736 737 TEST(IntegerPolyhedronTest, computeLocalReprNoRepr) { 738 MLIRContext context; 739 IntegerPolyhedron poly = 740 parsePoly("(x, q) : (x - 3 * q >= 0, -x + 3 * q + 3 >= 0)", &context); 741 // Convert q to a local variable. 742 poly.convertDimToLocal(1, 2); 743 744 std::vector<SmallVector<int64_t, 8>> divisions = {{0, 0, 0}}; 745 SmallVector<unsigned, 8> denoms = {0}; 746 747 // Check that no division is computed. 748 checkDivisionRepresentation(poly, divisions, denoms); 749 } 750 751 TEST(IntegerPolyhedronTest, simplifyLocalsTest) { 752 // (x) : (exists y: 2x + y = 1 and y = 2). 753 IntegerPolyhedron poly(1, 0, 1); 754 poly.addEquality({2, 1, -1}); 755 poly.addEquality({0, 1, -2}); 756 757 EXPECT_TRUE(poly.isEmpty()); 758 759 // (x) : (exists y, z, w: 3x + y = 1 and 2y = z and 3y = w and z = w). 760 IntegerPolyhedron poly2(1, 0, 3); 761 poly2.addEquality({3, 1, 0, 0, -1}); 762 poly2.addEquality({0, 2, -1, 0, 0}); 763 poly2.addEquality({0, 3, 0, -1, 0}); 764 poly2.addEquality({0, 0, 1, -1, 0}); 765 766 EXPECT_TRUE(poly2.isEmpty()); 767 768 // (x) : (exists y: x >= y + 1 and 2x + y = 0 and y >= -1). 769 IntegerPolyhedron poly3(1, 0, 1); 770 poly3.addInequality({1, -1, -1}); 771 poly3.addInequality({0, 1, 1}); 772 poly3.addEquality({2, 1, 0}); 773 774 EXPECT_TRUE(poly3.isEmpty()); 775 } 776 777 TEST(IntegerPolyhedronTest, mergeDivisionsSimple) { 778 { 779 // (x) : (exists z, y = [x / 2] : x = 3y and x + z + 1 >= 0). 780 IntegerPolyhedron poly1(1, 0, 1); 781 poly1.addLocalFloorDiv({1, 0, 0}, 2); // y = [x / 2]. 782 poly1.addEquality({1, 0, -3, 0}); // x = 3y. 783 poly1.addInequality({1, 1, 0, 1}); // x + z + 1 >= 0. 784 785 // (x) : (exists y = [x / 2], z : x = 5y). 786 IntegerPolyhedron poly2(1); 787 poly2.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 788 poly2.addEquality({1, -5, 0}); // x = 5y. 789 poly2.appendLocalId(); // Add local id z. 790 791 poly1.mergeLocalIds(poly2); 792 793 // Local space should be same. 794 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 795 796 // 1 division should be matched + 2 unmatched local ids. 797 EXPECT_EQ(poly1.getNumLocalIds(), 3u); 798 EXPECT_EQ(poly2.getNumLocalIds(), 3u); 799 } 800 801 { 802 // (x) : (exists z = [x / 5], y = [x / 2] : x = 3y). 803 IntegerPolyhedron poly1(1); 804 poly1.addLocalFloorDiv({1, 0}, 5); // z = [x / 5]. 805 poly1.addLocalFloorDiv({1, 0, 0}, 2); // y = [x / 2]. 806 poly1.addEquality({1, 0, -3, 0}); // x = 3y. 807 808 // (x) : (exists y = [x / 2], z = [x / 5]: x = 5z). 809 IntegerPolyhedron poly2(1); 810 poly2.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 811 poly2.addLocalFloorDiv({1, 0, 0}, 5); // z = [x / 5]. 812 poly2.addEquality({1, 0, -5, 0}); // x = 5z. 813 814 poly1.mergeLocalIds(poly2); 815 816 // Local space should be same. 817 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 818 819 // 2 divisions should be matched. 820 EXPECT_EQ(poly1.getNumLocalIds(), 2u); 821 EXPECT_EQ(poly2.getNumLocalIds(), 2u); 822 } 823 824 { 825 // Division Normalization test. 826 // (x) : (exists z, y = [x / 2] : x = 3y and x + z + 1 >= 0). 827 IntegerPolyhedron poly1(1, 0, 1); 828 // This division would be normalized. 829 poly1.addLocalFloorDiv({3, 0, 0}, 6); // y = [3x / 6] -> [x/2]. 830 poly1.addEquality({1, 0, -3, 0}); // x = 3z. 831 poly1.addInequality({1, 1, 0, 1}); // x + y + 1 >= 0. 832 833 // (x) : (exists y = [x / 2], z : x = 5y). 834 IntegerPolyhedron poly2(1); 835 poly2.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 836 poly2.addEquality({1, -5, 0}); // x = 5y. 837 poly2.appendLocalId(); // Add local id z. 838 839 poly1.mergeLocalIds(poly2); 840 841 // Local space should be same. 842 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 843 844 // One division should be matched + 2 unmatched local ids. 845 EXPECT_EQ(poly1.getNumLocalIds(), 3u); 846 EXPECT_EQ(poly2.getNumLocalIds(), 3u); 847 } 848 } 849 850 TEST(IntegerPolyhedronTest, mergeDivisionsNestedDivsions) { 851 { 852 // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z >= x). 853 IntegerPolyhedron poly1(1); 854 poly1.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 855 poly1.addLocalFloorDiv({1, 1, 0}, 3); // z = [x + y / 3]. 856 poly1.addInequality({-1, 1, 1, 0}); // y + z >= x. 857 858 // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z <= x). 859 IntegerPolyhedron poly2(1); 860 poly2.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 861 poly2.addLocalFloorDiv({1, 1, 0}, 3); // z = [x + y / 3]. 862 poly2.addInequality({1, -1, -1, 0}); // y + z <= x. 863 864 poly1.mergeLocalIds(poly2); 865 866 // Local space should be same. 867 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 868 869 // 2 divisions should be matched. 870 EXPECT_EQ(poly1.getNumLocalIds(), 2u); 871 EXPECT_EQ(poly2.getNumLocalIds(), 2u); 872 } 873 874 { 875 // (x) : (exists y = [x / 2], z = [x + y / 3], w = [z + 1 / 5]: y + z >= x). 876 IntegerPolyhedron poly1(1); 877 poly1.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 878 poly1.addLocalFloorDiv({1, 1, 0}, 3); // z = [x + y / 3]. 879 poly1.addLocalFloorDiv({0, 0, 1, 1}, 5); // w = [z + 1 / 5]. 880 poly1.addInequality({-1, 1, 1, 0, 0}); // y + z >= x. 881 882 // (x) : (exists y = [x / 2], z = [x + y / 3], w = [z + 1 / 5]: y + z <= x). 883 IntegerPolyhedron poly2(1); 884 poly2.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 885 poly2.addLocalFloorDiv({1, 1, 0}, 3); // z = [x + y / 3]. 886 poly2.addLocalFloorDiv({0, 0, 1, 1}, 5); // w = [z + 1 / 5]. 887 poly2.addInequality({1, -1, -1, 0, 0}); // y + z <= x. 888 889 poly1.mergeLocalIds(poly2); 890 891 // Local space should be same. 892 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 893 894 // 3 divisions should be matched. 895 EXPECT_EQ(poly1.getNumLocalIds(), 3u); 896 EXPECT_EQ(poly2.getNumLocalIds(), 3u); 897 } 898 { 899 // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z >= x). 900 IntegerPolyhedron poly1(1); 901 poly1.addLocalFloorDiv({2, 0}, 4); // y = [2x / 4] -> [x / 2]. 902 poly1.addLocalFloorDiv({1, 1, 0}, 3); // z = [x + y / 3]. 903 poly1.addInequality({-1, 1, 1, 0}); // y + z >= x. 904 905 // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z <= x). 906 IntegerPolyhedron poly2(1); 907 poly2.addLocalFloorDiv({1, 0}, 2); // y = [x / 2]. 908 // This division would be normalized. 909 poly2.addLocalFloorDiv({3, 3, 0}, 9); // z = [3x + 3y / 9] -> [x + y / 3]. 910 poly2.addInequality({1, -1, -1, 0}); // y + z <= x. 911 912 poly1.mergeLocalIds(poly2); 913 914 // Local space should be same. 915 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 916 917 // 2 divisions should be matched. 918 EXPECT_EQ(poly1.getNumLocalIds(), 2u); 919 EXPECT_EQ(poly2.getNumLocalIds(), 2u); 920 } 921 } 922 923 TEST(IntegerPolyhedronTest, mergeDivisionsConstants) { 924 { 925 // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z >= x). 926 IntegerPolyhedron poly1(1); 927 poly1.addLocalFloorDiv({1, 1}, 2); // y = [x + 1 / 2]. 928 poly1.addLocalFloorDiv({1, 0, 2}, 3); // z = [x + 2 / 3]. 929 poly1.addInequality({-1, 1, 1, 0}); // y + z >= x. 930 931 // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z <= x). 932 IntegerPolyhedron poly2(1); 933 poly2.addLocalFloorDiv({1, 1}, 2); // y = [x + 1 / 2]. 934 poly2.addLocalFloorDiv({1, 0, 2}, 3); // z = [x + 2 / 3]. 935 poly2.addInequality({1, -1, -1, 0}); // y + z <= x. 936 937 poly1.mergeLocalIds(poly2); 938 939 // Local space should be same. 940 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 941 942 // 2 divisions should be matched. 943 EXPECT_EQ(poly1.getNumLocalIds(), 2u); 944 EXPECT_EQ(poly2.getNumLocalIds(), 2u); 945 } 946 { 947 // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z >= x). 948 IntegerPolyhedron poly1(1); 949 poly1.addLocalFloorDiv({1, 1}, 2); // y = [x + 1 / 2]. 950 // Normalization test. 951 poly1.addLocalFloorDiv({3, 0, 6}, 9); // z = [3x + 6 / 9] -> [x + 2 / 3]. 952 poly1.addInequality({-1, 1, 1, 0}); // y + z >= x. 953 954 // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z <= x). 955 IntegerPolyhedron poly2(1); 956 // Normalization test. 957 poly2.addLocalFloorDiv({2, 2}, 4); // y = [2x + 2 / 4] -> [x + 1 / 2]. 958 poly2.addLocalFloorDiv({1, 0, 2}, 3); // z = [x + 2 / 3]. 959 poly2.addInequality({1, -1, -1, 0}); // y + z <= x. 960 961 poly1.mergeLocalIds(poly2); 962 963 // Local space should be same. 964 EXPECT_EQ(poly1.getNumLocalIds(), poly2.getNumLocalIds()); 965 966 // 2 divisions should be matched. 967 EXPECT_EQ(poly1.getNumLocalIds(), 2u); 968 EXPECT_EQ(poly2.getNumLocalIds(), 2u); 969 } 970 } 971 972 } // namespace mlir 973