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