| 1 | //===------ FlattenAlgo.cpp ------------------------------------*- C++ -*-===// |
| 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 | // Main algorithm of the FlattenSchedulePass. This is a separate file to avoid |
| 10 | // the unittest for this requiring linking against LLVM. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "polly/FlattenAlgo.h" |
| 15 | #include "polly/Support/ISLOStream.h" |
| 16 | #include "polly/Support/ISLTools.h" |
| 17 | #include "polly/Support/PollyDebug.h" |
| 18 | #include "llvm/Support/Debug.h" |
| 19 | #define DEBUG_TYPE "polly-flatten-algo" |
| 20 | |
| 21 | using namespace polly; |
| 22 | using namespace llvm; |
| 23 | |
| 24 | namespace { |
| 25 | |
| 26 | /// Whether a dimension of a set is bounded (lower and upper) by a constant, |
| 27 | /// i.e. there are two constants Min and Max, such that every value x of the |
| 28 | /// chosen dimensions is Min <= x <= Max. |
| 29 | bool isDimBoundedByConstant(isl::set Set, unsigned dim) { |
| 30 | auto ParamDims = unsignedFromIslSize(Size: Set.dim(type: isl::dim::param)); |
| 31 | Set = Set.project_out(type: isl::dim::param, first: 0, n: ParamDims); |
| 32 | Set = Set.project_out(type: isl::dim::set, first: 0, n: dim); |
| 33 | auto SetDims = unsignedFromIslSize(Size: Set.tuple_dim()); |
| 34 | assert(SetDims >= 1); |
| 35 | Set = Set.project_out(type: isl::dim::set, first: 1, n: SetDims - 1); |
| 36 | return bool(Set.is_bounded()); |
| 37 | } |
| 38 | |
| 39 | /// Whether a dimension of a set is (lower and upper) bounded by a constant or |
| 40 | /// parameters, i.e. there are two expressions Min_p and Max_p of the parameters |
| 41 | /// p, such that every value x of the chosen dimensions is |
| 42 | /// Min_p <= x <= Max_p. |
| 43 | bool isDimBoundedByParameter(isl::set Set, unsigned dim) { |
| 44 | Set = Set.project_out(type: isl::dim::set, first: 0, n: dim); |
| 45 | auto SetDims = unsignedFromIslSize(Size: Set.tuple_dim()); |
| 46 | assert(SetDims >= 1); |
| 47 | Set = Set.project_out(type: isl::dim::set, first: 1, n: SetDims - 1); |
| 48 | return bool(Set.is_bounded()); |
| 49 | } |
| 50 | |
| 51 | /// Whether BMap's first out-dimension is not a constant. |
| 52 | bool isVariableDim(const isl::basic_map &BMap) { |
| 53 | auto FixedVal = BMap.plain_get_val_if_fixed(type: isl::dim::out, pos: 0); |
| 54 | return FixedVal.is_null() || FixedVal.is_nan(); |
| 55 | } |
| 56 | |
| 57 | /// Whether Map's first out dimension is no constant nor piecewise constant. |
| 58 | bool isVariableDim(const isl::map &Map) { |
| 59 | for (isl::basic_map BMap : Map.get_basic_map_list()) |
| 60 | if (isVariableDim(BMap)) |
| 61 | return false; |
| 62 | |
| 63 | return true; |
| 64 | } |
| 65 | |
| 66 | /// Whether UMap's first out dimension is no (piecewise) constant. |
| 67 | bool isVariableDim(const isl::union_map &UMap) { |
| 68 | for (isl::map Map : UMap.get_map_list()) |
| 69 | if (isVariableDim(Map)) |
| 70 | return false; |
| 71 | return true; |
| 72 | } |
| 73 | |
| 74 | /// Compute @p UPwAff - @p Val. |
| 75 | isl::union_pw_aff subtract(isl::union_pw_aff UPwAff, isl::val Val) { |
| 76 | if (Val.is_zero()) |
| 77 | return UPwAff; |
| 78 | |
| 79 | auto Result = isl::union_pw_aff::empty(space: UPwAff.get_space()); |
| 80 | isl::stat Stat = |
| 81 | UPwAff.foreach_pw_aff(fn: [=, &Result](isl::pw_aff PwAff) -> isl::stat { |
| 82 | auto ValAff = |
| 83 | isl::pw_aff(isl::set::universe(space: PwAff.get_space().domain()), Val); |
| 84 | auto Subtracted = PwAff.sub(pwaff2: ValAff); |
| 85 | Result = Result.union_add(upa2: isl::union_pw_aff(Subtracted)); |
| 86 | return isl::stat::ok(); |
| 87 | }); |
| 88 | if (Stat.is_error()) |
| 89 | return {}; |
| 90 | return Result; |
| 91 | } |
| 92 | |
| 93 | /// Compute @UPwAff * @p Val. |
| 94 | isl::union_pw_aff multiply(isl::union_pw_aff UPwAff, isl::val Val) { |
| 95 | if (Val.is_one()) |
| 96 | return UPwAff; |
| 97 | |
| 98 | auto Result = isl::union_pw_aff::empty(space: UPwAff.get_space()); |
| 99 | isl::stat Stat = |
| 100 | UPwAff.foreach_pw_aff(fn: [=, &Result](isl::pw_aff PwAff) -> isl::stat { |
| 101 | auto ValAff = |
| 102 | isl::pw_aff(isl::set::universe(space: PwAff.get_space().domain()), Val); |
| 103 | auto Multiplied = PwAff.mul(pwaff2: ValAff); |
| 104 | Result = Result.union_add(upa2: Multiplied); |
| 105 | return isl::stat::ok(); |
| 106 | }); |
| 107 | if (Stat.is_error()) |
| 108 | return {}; |
| 109 | return Result; |
| 110 | } |
| 111 | |
| 112 | /// Remove @p n dimensions from @p UMap's range, starting at @p first. |
| 113 | /// |
| 114 | /// It is assumed that all maps in the maps have at least the necessary number |
| 115 | /// of out dimensions. |
| 116 | isl::union_map scheduleProjectOut(const isl::union_map &UMap, unsigned first, |
| 117 | unsigned n) { |
| 118 | if (n == 0) |
| 119 | return UMap; /* isl_map_project_out would also reset the tuple, which should |
| 120 | have no effect on schedule ranges */ |
| 121 | |
| 122 | auto Result = isl::union_map::empty(ctx: UMap.ctx()); |
| 123 | for (isl::map Map : UMap.get_map_list()) { |
| 124 | auto Outprojected = Map.project_out(type: isl::dim::out, first, n); |
| 125 | Result = Result.unite(umap2: Outprojected); |
| 126 | } |
| 127 | return Result; |
| 128 | } |
| 129 | |
| 130 | /// Return the @p pos' range dimension, converted to an isl_union_pw_aff. |
| 131 | isl::union_pw_aff (isl::union_map UMap, unsigned pos) { |
| 132 | auto SingleUMap = isl::union_map::empty(ctx: UMap.ctx()); |
| 133 | for (isl::map Map : UMap.get_map_list()) { |
| 134 | unsigned MapDims = unsignedFromIslSize(Size: Map.range_tuple_dim()); |
| 135 | assert(MapDims > pos); |
| 136 | isl::map SingleMap = Map.project_out(type: isl::dim::out, first: 0, n: pos); |
| 137 | SingleMap = SingleMap.project_out(type: isl::dim::out, first: 1, n: MapDims - pos - 1); |
| 138 | SingleUMap = SingleUMap.unite(umap2: SingleMap); |
| 139 | }; |
| 140 | |
| 141 | auto UAff = isl::union_pw_multi_aff(SingleUMap); |
| 142 | auto FirstMAff = isl::multi_union_pw_aff(UAff); |
| 143 | return FirstMAff.at(pos: 0); |
| 144 | } |
| 145 | |
| 146 | /// Flatten a sequence-like first dimension. |
| 147 | /// |
| 148 | /// A sequence-like scatter dimension is constant, or at least only small |
| 149 | /// variation, typically the result of ordering a sequence of different |
| 150 | /// statements. An example would be: |
| 151 | /// { Stmt_A[] -> [0, X, ...]; Stmt_B[] -> [1, Y, ...] } |
| 152 | /// to schedule all instances of Stmt_A before any instance of Stmt_B. |
| 153 | /// |
| 154 | /// To flatten, first begin with an offset of zero. Then determine the lowest |
| 155 | /// possible value of the dimension, call it "i" [In the example we start at 0]. |
| 156 | /// Considering only schedules with that value, consider only instances with |
| 157 | /// that value and determine the extent of the next dimension. Let l_X(i) and |
| 158 | /// u_X(i) its minimum (lower bound) and maximum (upper bound) value. Add them |
| 159 | /// as "Offset + X - l_X(i)" to the new schedule, then add "u_X(i) - l_X(i) + 1" |
| 160 | /// to Offset and remove all i-instances from the old schedule. Repeat with the |
| 161 | /// remaining lowest value i' until there are no instances in the old schedule |
| 162 | /// left. |
| 163 | /// The example schedule would be transformed to: |
| 164 | /// { Stmt_X[] -> [X - l_X, ...]; Stmt_B -> [l_X - u_X + 1 + Y - l_Y, ...] } |
| 165 | isl::union_map tryFlattenSequence(isl::union_map Schedule) { |
| 166 | auto IslCtx = Schedule.ctx(); |
| 167 | auto ScatterSet = isl::set(Schedule.range()); |
| 168 | |
| 169 | auto ParamSpace = Schedule.get_space().params(); |
| 170 | auto Dims = unsignedFromIslSize(Size: ScatterSet.tuple_dim()); |
| 171 | assert(Dims >= 2u); |
| 172 | |
| 173 | // Would cause an infinite loop. |
| 174 | if (!isDimBoundedByConstant(Set: ScatterSet, dim: 0)) { |
| 175 | POLLY_DEBUG(dbgs() << "Abort; dimension is not of fixed size\n" ); |
| 176 | return {}; |
| 177 | } |
| 178 | |
| 179 | auto AllDomains = Schedule.domain(); |
| 180 | auto AllDomainsToNull = isl::union_pw_multi_aff(AllDomains); |
| 181 | |
| 182 | auto NewSchedule = isl::union_map::empty(ctx: ParamSpace.ctx()); |
| 183 | auto Counter = isl::pw_aff(isl::local_space(ParamSpace.set_from_params())); |
| 184 | |
| 185 | while (!ScatterSet.is_empty()) { |
| 186 | POLLY_DEBUG(dbgs() << "Next counter:\n " << Counter << "\n" ); |
| 187 | POLLY_DEBUG(dbgs() << "Remaining scatter set:\n " << ScatterSet << "\n" ); |
| 188 | auto ThisSet = ScatterSet.project_out(type: isl::dim::set, first: 1, n: Dims - 1); |
| 189 | auto ThisFirst = ThisSet.lexmin(); |
| 190 | auto ScatterFirst = ThisFirst.add_dims(type: isl::dim::set, n: Dims - 1); |
| 191 | |
| 192 | auto SubSchedule = Schedule.intersect_range(uset: ScatterFirst); |
| 193 | SubSchedule = scheduleProjectOut(UMap: SubSchedule, first: 0, n: 1); |
| 194 | SubSchedule = flattenSchedule(Schedule: SubSchedule); |
| 195 | |
| 196 | unsigned SubDims = getNumScatterDims(Schedule: SubSchedule); |
| 197 | assert(SubDims >= 1); |
| 198 | auto FirstSubSchedule = scheduleProjectOut(UMap: SubSchedule, first: 1, n: SubDims - 1); |
| 199 | auto FirstScheduleAff = scheduleExtractDimAff(UMap: FirstSubSchedule, pos: 0); |
| 200 | auto RemainingSubSchedule = scheduleProjectOut(UMap: SubSchedule, first: 0, n: 1); |
| 201 | |
| 202 | auto FirstSubScatter = isl::set(FirstSubSchedule.range()); |
| 203 | POLLY_DEBUG(dbgs() << "Next step in sequence is:\n " << FirstSubScatter |
| 204 | << "\n" ); |
| 205 | |
| 206 | if (!isDimBoundedByParameter(Set: FirstSubScatter, dim: 0)) { |
| 207 | POLLY_DEBUG(dbgs() << "Abort; sequence step is not bounded\n" ); |
| 208 | return {}; |
| 209 | } |
| 210 | |
| 211 | auto FirstSubScatterMap = isl::map::from_range(set: FirstSubScatter); |
| 212 | |
| 213 | // isl_set_dim_max returns a strange isl_pw_aff with domain tuple_id of |
| 214 | // 'none'. It doesn't match with any space including a 0-dimensional |
| 215 | // anonymous tuple. |
| 216 | // Interesting, one can create such a set using |
| 217 | // isl_set_universe(ParamSpace). Bug? |
| 218 | auto PartMin = FirstSubScatterMap.dim_min(pos: 0); |
| 219 | auto PartMax = FirstSubScatterMap.dim_max(pos: 0); |
| 220 | auto One = isl::pw_aff(isl::set::universe(space: ParamSpace.set_from_params()), |
| 221 | isl::val::one(ctx: IslCtx)); |
| 222 | auto PartLen = PartMax.add(pwaff2: PartMin.neg()).add(pwaff2: One); |
| 223 | |
| 224 | auto AllPartMin = isl::union_pw_aff(PartMin).pullback(upma: AllDomainsToNull); |
| 225 | auto FirstScheduleAffNormalized = FirstScheduleAff.sub(upa2: AllPartMin); |
| 226 | auto AllCounter = isl::union_pw_aff(Counter).pullback(upma: AllDomainsToNull); |
| 227 | auto FirstScheduleAffWithOffset = |
| 228 | FirstScheduleAffNormalized.add(upa2: AllCounter); |
| 229 | |
| 230 | auto ScheduleWithOffset = |
| 231 | isl::union_map::from( |
| 232 | upma: isl::union_pw_multi_aff(FirstScheduleAffWithOffset)) |
| 233 | .flat_range_product(umap2: RemainingSubSchedule); |
| 234 | NewSchedule = NewSchedule.unite(umap2: ScheduleWithOffset); |
| 235 | |
| 236 | ScatterSet = ScatterSet.subtract(set2: ScatterFirst); |
| 237 | Counter = Counter.add(pwaff2: PartLen); |
| 238 | } |
| 239 | |
| 240 | POLLY_DEBUG(dbgs() << "Sequence-flatten result is:\n " << NewSchedule |
| 241 | << "\n" ); |
| 242 | return NewSchedule; |
| 243 | } |
| 244 | |
| 245 | /// Flatten a loop-like first dimension. |
| 246 | /// |
| 247 | /// A loop-like dimension is one that depends on a variable (usually a loop's |
| 248 | /// induction variable). Let the input schedule look like this: |
| 249 | /// { Stmt[i] -> [i, X, ...] } |
| 250 | /// |
| 251 | /// To flatten, we determine the largest extent of X which may not depend on the |
| 252 | /// actual value of i. Let l_X() the smallest possible value of X and u_X() its |
| 253 | /// largest value. Then, construct a new schedule |
| 254 | /// { Stmt[i] -> [i * (u_X() - l_X() + 1), ...] } |
| 255 | isl::union_map tryFlattenLoop(isl::union_map Schedule) { |
| 256 | assert(getNumScatterDims(Schedule) >= 2); |
| 257 | |
| 258 | auto Remaining = scheduleProjectOut(UMap: Schedule, first: 0, n: 1); |
| 259 | auto SubSchedule = flattenSchedule(Schedule: Remaining); |
| 260 | unsigned SubDims = getNumScatterDims(Schedule: SubSchedule); |
| 261 | |
| 262 | assert(SubDims >= 1); |
| 263 | |
| 264 | auto SubExtent = isl::set(SubSchedule.range()); |
| 265 | auto SubExtentDims = unsignedFromIslSize(Size: SubExtent.dim(type: isl::dim::param)); |
| 266 | SubExtent = SubExtent.project_out(type: isl::dim::param, first: 0, n: SubExtentDims); |
| 267 | SubExtent = SubExtent.project_out(type: isl::dim::set, first: 1, n: SubDims - 1); |
| 268 | |
| 269 | if (!isDimBoundedByConstant(Set: SubExtent, dim: 0)) { |
| 270 | POLLY_DEBUG(dbgs() << "Abort; dimension not bounded by constant\n" ); |
| 271 | return {}; |
| 272 | } |
| 273 | |
| 274 | auto Min = SubExtent.dim_min(pos: 0); |
| 275 | POLLY_DEBUG(dbgs() << "Min bound:\n " << Min << "\n" ); |
| 276 | auto MinVal = getConstant(PwAff: Min, Max: false, Min: true); |
| 277 | auto Max = SubExtent.dim_max(pos: 0); |
| 278 | POLLY_DEBUG(dbgs() << "Max bound:\n " << Max << "\n" ); |
| 279 | auto MaxVal = getConstant(PwAff: Max, Max: true, Min: false); |
| 280 | |
| 281 | if (MinVal.is_null() || MaxVal.is_null() || MinVal.is_nan() || |
| 282 | MaxVal.is_nan()) { |
| 283 | POLLY_DEBUG(dbgs() << "Abort; dimension bounds could not be determined\n" ); |
| 284 | return {}; |
| 285 | } |
| 286 | |
| 287 | auto FirstSubScheduleAff = scheduleExtractDimAff(UMap: SubSchedule, pos: 0); |
| 288 | auto RemainingSubSchedule = scheduleProjectOut(UMap: std::move(SubSchedule), first: 0, n: 1); |
| 289 | |
| 290 | auto LenVal = MaxVal.sub(v2: MinVal).add(v2: 1); |
| 291 | auto FirstSubScheduleNormalized = subtract(UPwAff: FirstSubScheduleAff, Val: MinVal); |
| 292 | |
| 293 | // TODO: Normalize FirstAff to zero (convert to isl_map, determine minimum, |
| 294 | // subtract it) |
| 295 | auto FirstAff = scheduleExtractDimAff(UMap: Schedule, pos: 0); |
| 296 | auto Offset = multiply(UPwAff: FirstAff, Val: LenVal); |
| 297 | isl::union_pw_multi_aff Index = FirstSubScheduleNormalized.add(upa2: Offset); |
| 298 | auto IndexMap = isl::union_map::from(upma: Index); |
| 299 | |
| 300 | auto Result = IndexMap.flat_range_product(umap2: RemainingSubSchedule); |
| 301 | POLLY_DEBUG(dbgs() << "Loop-flatten result is:\n " << Result << "\n" ); |
| 302 | return Result; |
| 303 | } |
| 304 | } // anonymous namespace |
| 305 | |
| 306 | isl::union_map polly::flattenSchedule(isl::union_map Schedule) { |
| 307 | unsigned Dims = getNumScatterDims(Schedule); |
| 308 | POLLY_DEBUG(dbgs() << "Recursive schedule to process:\n " << Schedule |
| 309 | << "\n" ); |
| 310 | |
| 311 | // Base case; no dimensions left |
| 312 | if (Dims == 0) { |
| 313 | // TODO: Add one dimension? |
| 314 | return Schedule; |
| 315 | } |
| 316 | |
| 317 | // Base case; already one-dimensional |
| 318 | if (Dims == 1) |
| 319 | return Schedule; |
| 320 | |
| 321 | // Fixed dimension; no need to preserve variabledness. |
| 322 | if (!isVariableDim(UMap: Schedule)) { |
| 323 | POLLY_DEBUG(dbgs() << "Fixed dimension; try sequence flattening\n" ); |
| 324 | auto NewScheduleSequence = tryFlattenSequence(Schedule); |
| 325 | if (!NewScheduleSequence.is_null()) |
| 326 | return NewScheduleSequence; |
| 327 | } |
| 328 | |
| 329 | // Constant stride |
| 330 | POLLY_DEBUG(dbgs() << "Try loop flattening\n" ); |
| 331 | auto NewScheduleLoop = tryFlattenLoop(Schedule); |
| 332 | if (!NewScheduleLoop.is_null()) |
| 333 | return NewScheduleLoop; |
| 334 | |
| 335 | // Try again without loop condition (may blow up the number of pieces!!) |
| 336 | POLLY_DEBUG(dbgs() << "Try sequence flattening again\n" ); |
| 337 | auto NewScheduleSequence = tryFlattenSequence(Schedule); |
| 338 | if (!NewScheduleSequence.is_null()) |
| 339 | return NewScheduleSequence; |
| 340 | |
| 341 | // Cannot flatten |
| 342 | return Schedule; |
| 343 | } |
| 344 | |