| 1 | //===- DeLICMTest.cpp ----------------------------------------------------===// |
| 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 "polly/DeLICM.h" |
| 10 | #include "polly/Support/ISLTools.h" |
| 11 | #include "gtest/gtest.h" |
| 12 | #include <isl/map.h> |
| 13 | #include <isl/set.h> |
| 14 | #include <isl/stream.h> |
| 15 | #include <isl/union_map.h> |
| 16 | #include <isl/union_set.h> |
| 17 | #include <memory> |
| 18 | |
| 19 | using namespace llvm; |
| 20 | using namespace polly; |
| 21 | |
| 22 | namespace { |
| 23 | |
| 24 | /// Get the universes of all spaces in @p USet. |
| 25 | isl::union_set unionSpace(const isl::union_set &USet) { |
| 26 | auto Result = isl::union_set::empty(ctx: USet.ctx()); |
| 27 | for (isl::set Set : USet.get_set_list()) { |
| 28 | isl::space Space = Set.get_space(); |
| 29 | isl::set Universe = isl::set::universe(space: Space); |
| 30 | Result = Result.unite(uset2: Universe); |
| 31 | } |
| 32 | return Result; |
| 33 | } |
| 34 | |
| 35 | void completeLifetime(isl::union_set Universe, isl::union_map OccupiedAndKnown, |
| 36 | isl::union_set &Occupied, isl::union_map &Known, |
| 37 | isl::union_set &Undef) { |
| 38 | auto ParamSpace = Universe.get_space(); |
| 39 | |
| 40 | if (!Undef.is_null() && Occupied.is_null()) { |
| 41 | assert(Occupied.is_null()); |
| 42 | Occupied = Universe.subtract(uset2: Undef); |
| 43 | } |
| 44 | |
| 45 | if (!OccupiedAndKnown.is_null()) { |
| 46 | assert(Known.is_null()); |
| 47 | |
| 48 | Known = isl::union_map::empty(ctx: ParamSpace.ctx()); |
| 49 | |
| 50 | if (Occupied.is_null()) |
| 51 | Occupied = OccupiedAndKnown.domain(); |
| 52 | |
| 53 | for (isl::map Map : OccupiedAndKnown.get_map_list()) { |
| 54 | if (!Map.has_tuple_name(type: isl::dim::out)) |
| 55 | continue; |
| 56 | Known = Known.unite(umap2: Map); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | if (Undef.is_null()) { |
| 61 | assert(!Occupied.is_null()); |
| 62 | Undef = Universe.subtract(uset2: Occupied); |
| 63 | } |
| 64 | |
| 65 | if (Known.is_null()) { // By default, nothing is known. |
| 66 | Known = isl::union_map::empty(ctx: ParamSpace.ctx()); |
| 67 | } |
| 68 | |
| 69 | // Conditions that must hold when returning. |
| 70 | assert(!Occupied.is_null()); |
| 71 | assert(!Undef.is_null()); |
| 72 | assert(!Known.is_null()); |
| 73 | } |
| 74 | |
| 75 | typedef struct { |
| 76 | const char *OccupiedStr; |
| 77 | const char *UndefStr; |
| 78 | const char *WrittenStr; |
| 79 | } KnowledgeStr; |
| 80 | |
| 81 | isl::union_set parseSetOrNull(isl_ctx *Ctx, const char *Str) { |
| 82 | if (!Str) |
| 83 | return {}; |
| 84 | return isl::union_set(Ctx, Str); |
| 85 | } |
| 86 | |
| 87 | isl::union_map parseMapOrNull(isl_ctx *Ctx, const char *Str) { |
| 88 | if (!Str) |
| 89 | return {}; |
| 90 | return isl::union_map(Ctx, Str); |
| 91 | } |
| 92 | |
| 93 | bool checkIsConflictingNonsymmetricCommon( |
| 94 | isl_ctx *Ctx, isl::union_map ExistingOccupiedAndKnown, |
| 95 | isl::union_set ExistingUnused, isl::union_map ExistingWritten, |
| 96 | isl::union_map ProposedOccupiedAndKnown, isl::union_set ProposedUnused, |
| 97 | isl::union_map ProposedWritten) { |
| 98 | // Determine universe (set of all possible domains). |
| 99 | auto Universe = isl::union_set::empty(ctx: Ctx); |
| 100 | if (!ExistingOccupiedAndKnown.is_null()) |
| 101 | Universe = Universe.unite(uset2: ExistingOccupiedAndKnown.domain()); |
| 102 | if (!ExistingUnused.is_null()) |
| 103 | Universe = Universe.unite(uset2: ExistingUnused); |
| 104 | if (!ExistingWritten.is_null()) |
| 105 | Universe = Universe.unite(uset2: ExistingWritten.domain()); |
| 106 | if (!ProposedOccupiedAndKnown.is_null()) |
| 107 | Universe = Universe.unite(uset2: ProposedOccupiedAndKnown.domain()); |
| 108 | if (!ProposedUnused.is_null()) |
| 109 | Universe = Universe.unite(uset2: ProposedUnused); |
| 110 | if (!ProposedWritten.is_null()) |
| 111 | Universe = Universe.unite(uset2: ProposedWritten.domain()); |
| 112 | |
| 113 | Universe = unionSpace(USet: Universe); |
| 114 | |
| 115 | // Add a space the universe that does not occur anywhere else to ensure |
| 116 | // robustness. Use &NewId to ensure that this Id is unique. |
| 117 | isl::id NewId = isl::id::alloc(ctx: Ctx, name: "Unrelated" , user: &NewId); |
| 118 | // The space must contains at least one dimension to allow order |
| 119 | // modifications. |
| 120 | auto NewSpace = isl::space(Ctx, 0, 1); |
| 121 | NewSpace = NewSpace.set_tuple_id(type: isl::dim::set, id: NewId); |
| 122 | auto NewSet = isl::set::universe(space: NewSpace); |
| 123 | Universe = Universe.unite(uset2: NewSet); |
| 124 | |
| 125 | // Using the universe, fill missing data. |
| 126 | isl::union_set ExistingOccupied; |
| 127 | isl::union_map ExistingKnown; |
| 128 | completeLifetime(Universe, OccupiedAndKnown: ExistingOccupiedAndKnown, Occupied&: ExistingOccupied, |
| 129 | Known&: ExistingKnown, Undef&: ExistingUnused); |
| 130 | |
| 131 | isl::union_set ProposedOccupied; |
| 132 | isl::union_map ProposedKnown; |
| 133 | completeLifetime(Universe, OccupiedAndKnown: ProposedOccupiedAndKnown, Occupied&: ProposedOccupied, |
| 134 | Known&: ProposedKnown, Undef&: ProposedUnused); |
| 135 | |
| 136 | auto Result = isConflicting(ExistingOccupied, ExistingUnused, ExistingKnown, |
| 137 | ExistingWrites: ExistingWritten, ProposedOccupied, ProposedUnused, |
| 138 | ProposedKnown, ProposedWrites: ProposedWritten); |
| 139 | |
| 140 | // isConflicting does not require ExistingOccupied nor ProposedUnused and are |
| 141 | // implicitly assumed to be the remainder elements. Test the implicitness as |
| 142 | // well. |
| 143 | EXPECT_EQ(Result, |
| 144 | isConflicting(ExistingOccupied, ExistingUnused, ExistingKnown, |
| 145 | ExistingWritten, ProposedOccupied, {}, ProposedKnown, |
| 146 | ProposedWritten)); |
| 147 | EXPECT_EQ(Result, |
| 148 | isConflicting({}, ExistingUnused, ExistingKnown, ExistingWritten, |
| 149 | ProposedOccupied, ProposedUnused, ProposedKnown, |
| 150 | ProposedWritten)); |
| 151 | EXPECT_EQ(Result, isConflicting({}, ExistingUnused, ExistingKnown, |
| 152 | ExistingWritten, ProposedOccupied, {}, |
| 153 | ProposedKnown, ProposedWritten)); |
| 154 | |
| 155 | return Result; |
| 156 | } |
| 157 | |
| 158 | bool checkIsConflictingNonsymmetricKnown(KnowledgeStr Existing, |
| 159 | KnowledgeStr Proposed) { |
| 160 | std::unique_ptr<isl_ctx, decltype(&isl_ctx_free)> Ctx(isl_ctx_alloc(), |
| 161 | &isl_ctx_free); |
| 162 | |
| 163 | // Parse knowledge. |
| 164 | auto ExistingOccupiedAndKnown = |
| 165 | parseMapOrNull(Ctx: Ctx.get(), Str: Existing.OccupiedStr); |
| 166 | auto ExistingUnused = parseSetOrNull(Ctx: Ctx.get(), Str: Existing.UndefStr); |
| 167 | auto ExistingWritten = parseMapOrNull(Ctx: Ctx.get(), Str: Existing.WrittenStr); |
| 168 | |
| 169 | auto ProposedOccupiedAndKnown = |
| 170 | parseMapOrNull(Ctx: Ctx.get(), Str: Proposed.OccupiedStr); |
| 171 | auto ProposedUnused = parseSetOrNull(Ctx: Ctx.get(), Str: Proposed.UndefStr); |
| 172 | auto ProposedWritten = parseMapOrNull(Ctx: Ctx.get(), Str: Proposed.WrittenStr); |
| 173 | |
| 174 | return checkIsConflictingNonsymmetricCommon( |
| 175 | Ctx: Ctx.get(), ExistingOccupiedAndKnown, ExistingUnused, ExistingWritten, |
| 176 | ProposedOccupiedAndKnown, ProposedUnused, ProposedWritten); |
| 177 | } |
| 178 | |
| 179 | bool checkIsConflictingNonsymmetric(KnowledgeStr Existing, |
| 180 | KnowledgeStr Proposed) { |
| 181 | std::unique_ptr<isl_ctx, decltype(&isl_ctx_free)> Ctx(isl_ctx_alloc(), |
| 182 | &isl_ctx_free); |
| 183 | |
| 184 | // Parse knowledge. |
| 185 | auto ExistingOccupied = parseSetOrNull(Ctx: Ctx.get(), Str: Existing.OccupiedStr); |
| 186 | auto ExistingUnused = parseSetOrNull(Ctx: Ctx.get(), Str: Existing.UndefStr); |
| 187 | auto ExistingWritten = parseSetOrNull(Ctx: Ctx.get(), Str: Existing.WrittenStr); |
| 188 | |
| 189 | auto ProposedOccupied = parseSetOrNull(Ctx: Ctx.get(), Str: Proposed.OccupiedStr); |
| 190 | auto ProposedUnused = parseSetOrNull(Ctx: Ctx.get(), Str: Proposed.UndefStr); |
| 191 | auto ProposedWritten = parseSetOrNull(Ctx: Ctx.get(), Str: Proposed.WrittenStr); |
| 192 | |
| 193 | return checkIsConflictingNonsymmetricCommon( |
| 194 | Ctx: Ctx.get(), ExistingOccupiedAndKnown: isl::union_map::from_domain(uset: ExistingOccupied), ExistingUnused, |
| 195 | ExistingWritten: isl::union_map::from_domain(uset: ExistingWritten), |
| 196 | ProposedOccupiedAndKnown: isl::union_map::from_domain(uset: ProposedOccupied), ProposedUnused, |
| 197 | ProposedWritten: isl::union_map::from_domain(uset: ProposedWritten)); |
| 198 | } |
| 199 | |
| 200 | bool checkIsConflicting(KnowledgeStr Existing, KnowledgeStr Proposed) { |
| 201 | auto Forward = checkIsConflictingNonsymmetric(Existing, Proposed); |
| 202 | auto Backward = checkIsConflictingNonsymmetric(Existing: Proposed, Proposed: Existing); |
| 203 | |
| 204 | // isConflicting should be symmetric. |
| 205 | EXPECT_EQ(Forward, Backward); |
| 206 | |
| 207 | return Forward || Backward; |
| 208 | } |
| 209 | |
| 210 | bool checkIsConflictingKnown(KnowledgeStr Existing, KnowledgeStr Proposed) { |
| 211 | auto Forward = checkIsConflictingNonsymmetricKnown(Existing, Proposed); |
| 212 | auto Backward = checkIsConflictingNonsymmetricKnown(Existing: Proposed, Proposed: Existing); |
| 213 | |
| 214 | // checkIsConflictingKnown should be symmetric. |
| 215 | EXPECT_EQ(Forward, Backward); |
| 216 | |
| 217 | return Forward || Backward; |
| 218 | } |
| 219 | |
| 220 | TEST(DeLICM, isConflicting) { |
| 221 | |
| 222 | // Check occupied vs. occupied. |
| 223 | EXPECT_TRUE( |
| 224 | checkIsConflicting({"{ Dom[i] }" , nullptr, "{}" }, {nullptr, "{}" , "{}" })); |
| 225 | EXPECT_TRUE(checkIsConflicting({"{ Dom[i] }" , nullptr, "{}" }, |
| 226 | {"{ Dom[i] }" , nullptr, "{}" })); |
| 227 | EXPECT_FALSE(checkIsConflicting({"{ Dom[0] }" , nullptr, "{}" }, |
| 228 | {nullptr, "{ Dom[0] }" , "{}" })); |
| 229 | EXPECT_FALSE(checkIsConflicting({"{ Dom[i] : i != 0 }" , nullptr, "{}" }, |
| 230 | {"{ Dom[0] }" , nullptr, "{}" })); |
| 231 | |
| 232 | // Check occupied vs. occupied with known values. |
| 233 | EXPECT_FALSE(checkIsConflictingKnown({"{ Dom[i] -> Val[] }" , nullptr, "{}" }, |
| 234 | {"{ Dom[i] -> Val[] }" , nullptr, "{}" })); |
| 235 | EXPECT_TRUE(checkIsConflictingKnown({"{ Dom[i] -> ValA[] }" , nullptr, "{}" }, |
| 236 | {"{ Dom[i] -> ValB[] }" , nullptr, "{}" })); |
| 237 | EXPECT_TRUE(checkIsConflictingKnown({"{ Dom[i] -> Val[] }" , nullptr, "{}" }, |
| 238 | {"{ Dom[i] -> [] }" , nullptr, "{}" })); |
| 239 | EXPECT_FALSE(checkIsConflictingKnown({"{ Dom[0] -> Val[] }" , nullptr, "{}" }, |
| 240 | {nullptr, "{ Dom[0] }" , "{}" })); |
| 241 | EXPECT_FALSE(checkIsConflictingKnown( |
| 242 | {"{ Dom[i] -> Val[]; Dom[i] -> Phi[] }" , nullptr, "{}" }, |
| 243 | {"{ Dom[i] -> Val[] }" , nullptr, "{}" })); |
| 244 | |
| 245 | // An implementation using subtract would have exponential runtime on patterns |
| 246 | // such as this one. |
| 247 | EXPECT_TRUE(checkIsConflictingKnown( |
| 248 | {"{ Dom[i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14,i15]" |
| 249 | "-> Val[] }" , |
| 250 | nullptr, "{}" }, |
| 251 | {"[p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,q0," |
| 252 | "q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11,q12,q13,q14,q15] -> {" |
| 253 | "Dom[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] -> Val[];" |
| 254 | "Dom[p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15] -> Val[];" |
| 255 | "Dom[q0,q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11,q12,q13,q14,q15] -> Val[] }" , |
| 256 | "{}" , "{}" })); |
| 257 | |
| 258 | // Check occupied vs. written. |
| 259 | EXPECT_TRUE( |
| 260 | checkIsConflicting({nullptr, "{}" , "{}" }, {"{}" , nullptr, "{ Dom[0] }" })); |
| 261 | EXPECT_FALSE( |
| 262 | checkIsConflicting({"{}" , nullptr, "{}" }, {"{}" , nullptr, "{ Dom[0] }" })); |
| 263 | |
| 264 | EXPECT_TRUE(checkIsConflicting({"{ Dom[i] }" , nullptr, "{}" }, |
| 265 | {"{}" , nullptr, "{ Dom[0] }" })); |
| 266 | EXPECT_FALSE(checkIsConflicting({"{ DomA[i] }" , nullptr, "{}" }, |
| 267 | {"{}" , nullptr, "{ DomB[0] }" })); |
| 268 | |
| 269 | // Dom[1] represents the time between 0 and 1. Now Proposed writes at timestep |
| 270 | // 0 such that will have a different value between 0 and 1. Hence it is |
| 271 | // conflicting with Existing. |
| 272 | EXPECT_TRUE(checkIsConflicting({"{ Dom[1] }" , nullptr, "{}" }, |
| 273 | {"{}" , nullptr, "{ Dom[0] }" })); |
| 274 | EXPECT_FALSE(checkIsConflicting({"{ Dom[i] : i != 1 }" , nullptr, "{}" }, |
| 275 | {"{}" , nullptr, "{ Dom[0] }" })); |
| 276 | |
| 277 | // Check occupied vs. written with known values. |
| 278 | EXPECT_FALSE(checkIsConflictingKnown({"{ Dom[i] -> Val[] }" , nullptr, "{}" }, |
| 279 | {"{}" , nullptr, "{ Dom[0] -> Val[] }" })); |
| 280 | EXPECT_TRUE(checkIsConflictingKnown({"{ Dom[i] -> ValA[] }" , nullptr, "{}" }, |
| 281 | {"{}" , nullptr, "{ Dom[0] -> ValB[] }" })); |
| 282 | EXPECT_TRUE(checkIsConflictingKnown({"{ Dom[i] -> Val[] }" , nullptr, "{}" }, |
| 283 | {"{}" , nullptr, "{ Dom[0] -> [] }" })); |
| 284 | EXPECT_TRUE(checkIsConflictingKnown({"{ Dom[i] -> [] }" , nullptr, "{}" }, |
| 285 | {"{}" , nullptr, "{ Dom[0] -> Val[] }" })); |
| 286 | |
| 287 | // The same value can be known under multiple names, for instance a PHINode |
| 288 | // has the same value as one of the incoming values. One matching pair |
| 289 | // suffices. |
| 290 | EXPECT_FALSE(checkIsConflictingKnown( |
| 291 | {"{ Dom[i] -> Val[]; Dom[i] -> Phi[] }" , nullptr, "{}" }, |
| 292 | {"{}" , nullptr, "{ Dom[0] -> Val[] }" })); |
| 293 | EXPECT_FALSE(checkIsConflictingKnown( |
| 294 | {"{ Dom[i] -> Val[] }" , nullptr, "{}" }, |
| 295 | {"{}" , nullptr, "{ Dom[0] -> Val[]; Dom[0] -> Phi[] }" })); |
| 296 | |
| 297 | // Check written vs. written. |
| 298 | EXPECT_TRUE(checkIsConflicting({"{}" , nullptr, "{ Dom[0] }" }, |
| 299 | {"{}" , nullptr, "{ Dom[0] }" })); |
| 300 | EXPECT_FALSE(checkIsConflicting({"{}" , nullptr, "{ Dom[-1] }" }, |
| 301 | {"{}" , nullptr, "{ Dom[0] }" })); |
| 302 | EXPECT_FALSE(checkIsConflicting({"{}" , nullptr, "{ Dom[1] }" }, |
| 303 | {"{}" , nullptr, "{ Dom[0] }" })); |
| 304 | |
| 305 | // Check written vs. written with known values. |
| 306 | EXPECT_FALSE(checkIsConflictingKnown({"{}" , nullptr, "{ Dom[0] -> Val[] }" }, |
| 307 | {"{}" , nullptr, "{ Dom[0] -> Val[] }" })); |
| 308 | EXPECT_TRUE(checkIsConflictingKnown({"{}" , nullptr, "{ Dom[0] -> ValA[] }" }, |
| 309 | {"{}" , nullptr, "{ Dom[0] -> ValB[] }" })); |
| 310 | EXPECT_TRUE(checkIsConflictingKnown({"{}" , nullptr, "{ Dom[0] -> Val[] }" }, |
| 311 | {"{}" , nullptr, "{ Dom[0] -> [] }" })); |
| 312 | EXPECT_FALSE(checkIsConflictingKnown( |
| 313 | {"{}" , nullptr, "{ Dom[0] -> Val[]}" }, |
| 314 | {"{}" , nullptr, "{ Dom[0] -> Val[]; Dom[0] -> Phi[] }" })); |
| 315 | } |
| 316 | } // anonymous namespace |
| 317 | |