1 //===- ScheduleOrderedAssignments.cpp -- Ordered Assignment Scheduling ----===// 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 "ScheduleOrderedAssignments.h" 10 #include "flang/Optimizer/Analysis/AliasAnalysis.h" 11 #include "flang/Optimizer/Builder/FIRBuilder.h" 12 #include "flang/Optimizer/Builder/Todo.h" 13 #include "flang/Optimizer/Dialect/Support/FIRContext.h" 14 #include "llvm/ADT/SmallSet.h" 15 #include "llvm/Support/Debug.h" 16 17 #define DEBUG_TYPE "flang-ordered-assignment" 18 19 //===----------------------------------------------------------------------===// 20 // Scheduling logging utilities for debug and test 21 //===----------------------------------------------------------------------===// 22 23 /// Log RAW or WAW conflict. 24 static void LLVM_ATTRIBUTE_UNUSED logConflict(llvm::raw_ostream &os, 25 mlir::Value writtenOrReadVarA, 26 mlir::Value writtenVarB); 27 /// Log when an expression evaluation must be saved. 28 static void LLVM_ATTRIBUTE_UNUSED logSaveEvaluation(llvm::raw_ostream &os, 29 unsigned runid, 30 mlir::Region &yieldRegion, 31 bool anyWrite); 32 /// Log when an assignment is scheduled. 33 static void LLVM_ATTRIBUTE_UNUSED logAssignmentEvaluation( 34 llvm::raw_ostream &os, unsigned runid, hlfir::RegionAssignOp assign); 35 /// Log when starting to schedule an order assignment tree. 36 static void LLVM_ATTRIBUTE_UNUSED logStartScheduling( 37 llvm::raw_ostream &os, hlfir::OrderedAssignmentTreeOpInterface root); 38 /// Log op if effect value is not known. 39 static void LLVM_ATTRIBUTE_UNUSED logIfUnkownEffectValue( 40 llvm::raw_ostream &os, mlir::MemoryEffects::EffectInstance effect, 41 mlir::Operation &op); 42 43 //===----------------------------------------------------------------------===// 44 // Scheduling Implementation 45 //===----------------------------------------------------------------------===// 46 47 namespace { 48 /// Structure that is in charge of building the schedule. For each 49 /// hlfir.region_assign inside an ordered assignment tree, it is walked through 50 /// the parent operations and their "leaf" regions (that contain expression 51 /// evaluations). The Scheduler analyze the memory effects of these regions 52 /// against the effect of the current assignment, and if any conflict is found, 53 /// it will create an action to save the value computed by the region before the 54 /// assignment evaluation. 55 class Scheduler { 56 public: 57 Scheduler(bool tryFusingAssignments) 58 : tryFusingAssignments{tryFusingAssignments} {} 59 60 /// Start scheduling an assignment. Gather the write side effect from the 61 /// assignment. 62 void startSchedulingAssignment(hlfir::RegionAssignOp assign, 63 bool leafRegionsMayOnlyRead); 64 65 /// Start analysing a set of evaluation regions that can be evaluated in 66 /// any order between themselves according to Fortran rules (like the controls 67 /// of forall). The point of this is to avoid adding the side effects of 68 /// independent evaluations to a run that would save only one of the control. 69 void startIndependentEvaluationGroup() { 70 assert(independentEvaluationEffects.empty() && 71 "previous group was not finished"); 72 }; 73 74 /// Analyze the memory effects of a region containing an expression 75 /// evaluation. If any conflict is found with the current assignment, or if 76 /// the expression has write effects (which is possible outside of forall), 77 /// create an action in the schedule to save the value in the schedule before 78 /// evaluating the current assignment. For expression with write effect, 79 /// saving them ensures they are evaluated only once. A region whose value 80 /// was saved in a previous run is considered to have no side effects with the 81 /// current assignment: the saved value will be used. 82 void saveEvaluationIfConflict(mlir::Region &yieldRegion, 83 bool leafRegionsMayOnlyRead, 84 bool yieldIsImplicitRead = true); 85 86 /// Finish evaluating a group of independent regions. The current independent 87 /// regions effects are added to the "parent" effect list since evaluating the 88 /// next analyzed region would require evaluating the current independent 89 /// regions. 90 void finishIndependentEvaluationGroup() { 91 parentEvaluationEffects.append(independentEvaluationEffects.begin(), 92 independentEvaluationEffects.end()); 93 independentEvaluationEffects.clear(); 94 } 95 96 /// After all the dependent evaluation regions have been analyzed, create the 97 /// action to evaluate the assignment that was being analyzed. 98 void finishSchedulingAssignment(hlfir::RegionAssignOp assign); 99 100 /// Once all the assignments have been analyzed and scheduled, return the 101 /// schedule. The scheduler object should not be used after this call. 102 hlfir::Schedule moveSchedule() { return std::move(schedule); } 103 104 private: 105 /// Save a conflicting region that is evaluating an expression that is 106 /// controlling or masking the current assignment, or is evaluating the 107 /// RHS/LHS. 108 void 109 saveEvaluation(mlir::Region &yieldRegion, 110 llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effects, 111 bool anyWrite); 112 113 /// Can the current assignment be schedule with the previous run. This is 114 /// only possible if the assignment and all of its dependencies have no side 115 /// effects conflicting with the previous run. 116 bool canFuseAssignmentWithPreviousRun(); 117 118 /// Memory effects of the assignments being lowered. 119 llvm::SmallVector<mlir::MemoryEffects::EffectInstance> assignEffects; 120 /// Memory effects of the unsaved evaluation region that are controlling or 121 /// masking the current assignments. 122 llvm::SmallVector<mlir::MemoryEffects::EffectInstance> 123 parentEvaluationEffects; 124 /// Same as parentEvaluationEffects, but for the current "leaf group" being 125 /// analyzed scheduled. 126 llvm::SmallVector<mlir::MemoryEffects::EffectInstance> 127 independentEvaluationEffects; 128 129 /// Were any region saved for the current assignment? 130 bool savedAnyRegionForCurrentAssignment = false; 131 132 // Schedule being built. 133 hlfir::Schedule schedule; 134 /// Leaf regions that have been saved so far. 135 llvm::SmallSet<mlir::Region *, 16> savedRegions; 136 /// Is schedule.back() a schedule that is only saving region with read 137 /// effects? 138 bool currentRunIsReadOnly = false; 139 140 /// Option to tell if the scheduler should try fusing to assignments in the 141 /// same loops. 142 const bool tryFusingAssignments; 143 }; 144 } // namespace 145 146 //===----------------------------------------------------------------------===// 147 // Scheduling Implementation : gathering memory effects of nodes. 148 //===----------------------------------------------------------------------===// 149 150 /// Is \p var the result of a ForallIndexOp? 151 /// Read effects to forall index can be ignored since forall 152 /// indices cannot be assigned to. 153 static bool isForallIndex(mlir::Value var) { 154 return var && 155 mlir::isa_and_nonnull<hlfir::ForallIndexOp>(var.getDefiningOp()); 156 } 157 158 /// Gather the memory effects of the operations contained in a region. 159 /// \p mayOnlyRead can be given to exclude some potential write effects that 160 /// cannot affect the current scheduling problem because it is known that the 161 /// regions are evaluating pure expressions from a Fortran point of view. It is 162 /// useful because low level IR in the region may contain operation that lacks 163 /// side effect interface, or that are writing temporary variables that may be 164 /// hard to identify as such (one would have to prove the write is "local" to 165 /// the region even when the alloca may be outside of the region). 166 static void gatherMemoryEffects( 167 mlir::Region ®ion, bool mayOnlyRead, 168 llvm::SmallVectorImpl<mlir::MemoryEffects::EffectInstance> &effects) { 169 /// This analysis is a simple walk of all the operations of the region that is 170 /// evaluating and yielding a value. This is a lot simpler and safer than 171 /// trying to walk back the SSA DAG from the yielded value. But if desired, 172 /// this could be changed. 173 for (mlir::Operation &op : region.getOps()) { 174 if (op.hasTrait<mlir::OpTrait::HasRecursiveMemoryEffects>()) { 175 for (mlir::Region &subRegion : op.getRegions()) 176 gatherMemoryEffects(subRegion, mayOnlyRead, effects); 177 // In MLIR, RecursiveMemoryEffects can be combined with 178 // MemoryEffectOpInterface to describe extra effects on top of the 179 // effects of the nested operations. However, the presence of 180 // RecursiveMemoryEffects and the absence of MemoryEffectOpInterface 181 // implies the operation has no other memory effects than the one of its 182 // nested operations. 183 if (!mlir::isa<mlir::MemoryEffectOpInterface>(op)) 184 continue; 185 } 186 mlir::MemoryEffectOpInterface interface = 187 mlir::dyn_cast<mlir::MemoryEffectOpInterface>(op); 188 if (!interface) { 189 LLVM_DEBUG(llvm::dbgs() << "unknown effect: " << op << "\n";); 190 // There is no generic way to know what this operation is reading/writing 191 // to. Assume the worst. No need to continue analyzing the code any 192 // further. 193 effects.emplace_back(mlir::MemoryEffects::Read::get()); 194 if (!mayOnlyRead) 195 effects.emplace_back(mlir::MemoryEffects::Write::get()); 196 return; 197 } 198 // Collect read/write effects. Alloc/Free effects do not matter, they 199 // are either local to the evaluation region and can be repeated, or, if 200 // they are allocatable/pointer allocation/deallocation, they are conveyed 201 // via the write that is updating the descriptor/allocatable (and there 202 // cannot be any indirect allocatable/pointer allocation/deallocation if 203 // mayOnlyRead is set). When mayOnlyRead is set, local write effects are 204 // also ignored. 205 llvm::SmallVector<mlir::MemoryEffects::EffectInstance> opEffects; 206 interface.getEffects(opEffects); 207 for (auto &effect : opEffects) 208 if (!isForallIndex(effect.getValue())) { 209 if (mlir::isa<mlir::MemoryEffects::Read>(effect.getEffect())) { 210 LLVM_DEBUG(logIfUnkownEffectValue(llvm::dbgs(), effect, op);); 211 effects.push_back(effect); 212 } else if (!mayOnlyRead && 213 mlir::isa<mlir::MemoryEffects::Write>(effect.getEffect())) { 214 LLVM_DEBUG(logIfUnkownEffectValue(llvm::dbgs(), effect, op);); 215 effects.push_back(effect); 216 } 217 } 218 } 219 } 220 221 /// Return the entity yielded by a region, or a null value if the region 222 /// is not terminated by a yield. 223 static mlir::Value getYieldedEntity(mlir::Region ®ion) { 224 if (region.empty() || region.back().empty()) 225 return nullptr; 226 if (auto yield = mlir::dyn_cast<hlfir::YieldOp>(region.back().back())) 227 return yield.getEntity(); 228 if (auto elementalAddr = 229 mlir::dyn_cast<hlfir::ElementalAddrOp>(region.back().back())) 230 return elementalAddr.getYieldOp().getEntity(); 231 return nullptr; 232 } 233 234 /// Gather the effect of an assignment. This is the implicit write to the LHS 235 /// of an assignment. This also includes the effects of the user defined 236 /// assignment, if any, but this does not include the effects of evaluating the 237 /// RHS and LHS, which occur before the assignment effects in Fortran. 238 static void gatherAssignEffects( 239 hlfir::RegionAssignOp regionAssign, 240 bool userDefAssignmentMayOnlyWriteToAssignedVariable, 241 llvm::SmallVectorImpl<mlir::MemoryEffects::EffectInstance> &assignEffects) { 242 mlir::Value assignedVar = getYieldedEntity(regionAssign.getLhsRegion()); 243 assert(assignedVar && "lhs cannot be an empty region"); 244 assignEffects.emplace_back(mlir::MemoryEffects::Write::get(), assignedVar); 245 246 if (!regionAssign.getUserDefinedAssignment().empty()) { 247 // The write effect on the INTENT(OUT) LHS argument is already taken 248 // into account above. 249 // This side effects are "defensive" and could be improved. 250 // On top of the passed RHS argument, user defined assignments (even when 251 // pure) may also read host/used/common variable. Impure user defined 252 // assignments may write to host/used/common variables not passed via 253 // arguments. For now, simply assume the worst. Once fir.call side effects 254 // analysis is improved, it would best to let the call side effects be used 255 // directly. 256 if (userDefAssignmentMayOnlyWriteToAssignedVariable) 257 assignEffects.emplace_back(mlir::MemoryEffects::Read::get()); 258 else 259 assignEffects.emplace_back(mlir::MemoryEffects::Write::get()); 260 } 261 } 262 263 //===----------------------------------------------------------------------===// 264 // Scheduling Implementation : finding conflicting memory effects. 265 //===----------------------------------------------------------------------===// 266 267 /// Follow addressing and declare like operation to the storage source. 268 /// This allows using FIR alias analysis that otherwise does not know 269 /// about those operations. This is correct, but ignoring the designate 270 /// and declare info may yield false positive regarding aliasing (e.g, 271 /// if it could be proved that the variable are different sub-part of 272 /// an array). 273 static mlir::Value getStorageSource(mlir::Value var) { 274 // TODO: define some kind of View interface for Fortran in FIR, 275 // and use it in the FIR alias analysis. 276 mlir::Value source = var; 277 while (auto *op = source.getDefiningOp()) { 278 if (auto designate = mlir::dyn_cast<hlfir::DesignateOp>(op)) { 279 source = designate.getMemref(); 280 } else if (auto declare = mlir::dyn_cast<hlfir::DeclareOp>(op)) { 281 source = declare.getMemref(); 282 } else { 283 break; 284 } 285 } 286 return source; 287 } 288 289 /// Could there be any read or write in effectsA on a variable written to in 290 /// effectsB? 291 static bool 292 anyRAWorWAW(llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effectsA, 293 llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effectsB, 294 fir::AliasAnalysis &aliasAnalysis) { 295 for (const auto &effectB : effectsB) 296 if (mlir::isa<mlir::MemoryEffects::Write>(effectB.getEffect())) { 297 mlir::Value writtenVarB = effectB.getValue(); 298 if (writtenVarB) 299 writtenVarB = getStorageSource(writtenVarB); 300 for (const auto &effectA : effectsA) 301 if (mlir::isa<mlir::MemoryEffects::Write, mlir::MemoryEffects::Read>( 302 effectA.getEffect())) { 303 mlir::Value writtenOrReadVarA = effectA.getValue(); 304 if (!writtenVarB || !writtenOrReadVarA) { 305 LLVM_DEBUG( 306 logConflict(llvm::dbgs(), writtenOrReadVarA, writtenVarB);); 307 return true; // unknown conflict. 308 } 309 writtenOrReadVarA = getStorageSource(writtenOrReadVarA); 310 if (!aliasAnalysis.alias(writtenOrReadVarA, writtenVarB).isNo()) { 311 LLVM_DEBUG( 312 logConflict(llvm::dbgs(), writtenOrReadVarA, writtenVarB);); 313 return true; 314 } 315 } 316 } 317 return false; 318 } 319 320 /// Could there be any read or write in effectsA on a variable written to in 321 /// effectsB, or any read in effectsB on a variable written to in effectsA? 322 static bool 323 conflict(llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effectsA, 324 llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effectsB) { 325 fir::AliasAnalysis aliasAnalysis; 326 // (RAW || WAW) || (WAR || WAW). 327 return anyRAWorWAW(effectsA, effectsB, aliasAnalysis) || 328 anyRAWorWAW(effectsB, effectsA, aliasAnalysis); 329 } 330 331 /// Could there be any write effects in "effects"? 332 static bool 333 anyWrite(llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effects) { 334 return llvm::any_of( 335 effects, [](const mlir::MemoryEffects::EffectInstance &effect) { 336 return mlir::isa<mlir::MemoryEffects::Write>(effect.getEffect()); 337 }); 338 } 339 340 //===----------------------------------------------------------------------===// 341 // Scheduling Implementation : Scheduler class implementation 342 //===----------------------------------------------------------------------===// 343 344 void Scheduler::startSchedulingAssignment(hlfir::RegionAssignOp assign, 345 bool leafRegionsMayOnlyRead) { 346 gatherAssignEffects(assign, leafRegionsMayOnlyRead, assignEffects); 347 } 348 349 void Scheduler::saveEvaluationIfConflict(mlir::Region &yieldRegion, 350 bool leafRegionsMayOnlyRead, 351 bool yieldIsImplicitRead) { 352 // If the region evaluation was previously executed and saved, the saved 353 // value will be used when evaluating the current assignment and this has 354 // no effects in the current assignment evaluation. 355 if (savedRegions.contains(&yieldRegion)) 356 return; 357 llvm::SmallVector<mlir::MemoryEffects::EffectInstance> effects; 358 gatherMemoryEffects(yieldRegion, leafRegionsMayOnlyRead, effects); 359 // Yield has no effect as such, but in the context of order assignments. 360 // The order assignments will usually read the yielded entity (except for 361 // the yielded assignments LHS that is only read if this is an assignment 362 // with a finalizer, or a user defined assignment where the LHS is 363 // intent(inout)). 364 if (yieldIsImplicitRead) { 365 mlir::Value entity = getYieldedEntity(yieldRegion); 366 if (entity && hlfir::isFortranVariableType(entity.getType())) 367 effects.emplace_back(mlir::MemoryEffects::Read::get(), entity); 368 } 369 if (!leafRegionsMayOnlyRead && anyWrite(effects)) { 370 // Region with write effect must be executed only once: save it the first 371 // time it is encountered. 372 saveEvaluation(yieldRegion, effects, /*anyWrite=*/true); 373 } else if (conflict(effects, assignEffects)) { 374 // Region that conflicts with the current assignments must be fully 375 // evaluated and saved before doing the assignment (Note that it may 376 // have already have been evaluated without saving it before, but this 377 // implies that it never conflicted with a prior assignment, so its value 378 // should be the same.) 379 saveEvaluation(yieldRegion, effects, /*anyWrite=*/false); 380 } else { 381 // Can be executed while doing the assignment. 382 independentEvaluationEffects.append(effects.begin(), effects.end()); 383 } 384 } 385 386 void Scheduler::saveEvaluation( 387 mlir::Region &yieldRegion, 388 llvm::ArrayRef<mlir::MemoryEffects::EffectInstance> effects, 389 bool anyWrite) { 390 savedAnyRegionForCurrentAssignment = true; 391 if (anyWrite) { 392 // Create a new run just for regions with side effect. Further analysis 393 // could try to prove the effects do not conflict with the previous 394 // schedule. 395 schedule.emplace_back(hlfir::Run{}); 396 currentRunIsReadOnly = false; 397 } else if (!currentRunIsReadOnly) { 398 // For now, do not try to fuse an evaluation with a previous 399 // run that contains any write effects. One could try to prove 400 // that "effects" do not conflict with the current run assignments. 401 schedule.emplace_back(hlfir::Run{}); 402 currentRunIsReadOnly = true; 403 } 404 // Otherwise, save the yielded entity in the current run, that already 405 // saving other read only entities. 406 schedule.back().actions.emplace_back(hlfir::SaveEntity{&yieldRegion}); 407 // The run to save the yielded entity will need to evaluate all the unsaved 408 // parent control or masks. Note that these effects may already be in the 409 // current run memoryEffects, but it is just easier always add them, even if 410 // this may add them again. 411 schedule.back().memoryEffects.append(parentEvaluationEffects.begin(), 412 parentEvaluationEffects.end()); 413 schedule.back().memoryEffects.append(effects.begin(), effects.end()); 414 savedRegions.insert(&yieldRegion); 415 LLVM_DEBUG( 416 logSaveEvaluation(llvm::dbgs(), schedule.size(), yieldRegion, anyWrite);); 417 } 418 419 bool Scheduler::canFuseAssignmentWithPreviousRun() { 420 // If a region was saved for the current assignment, the previous 421 // run is already known to conflict. Skip the analysis. 422 if (savedAnyRegionForCurrentAssignment || schedule.empty()) 423 return false; 424 auto &previousRunEffects = schedule.back().memoryEffects; 425 return !conflict(previousRunEffects, assignEffects) && 426 !conflict(previousRunEffects, parentEvaluationEffects) && 427 !conflict(previousRunEffects, independentEvaluationEffects); 428 } 429 430 void Scheduler::finishSchedulingAssignment(hlfir::RegionAssignOp assign) { 431 // For now, always schedule each assignment in its own run. They could 432 // be done as part of previous assignment runs if it is proven they have 433 // no conflicting effects. 434 currentRunIsReadOnly = false; 435 if (!tryFusingAssignments || !canFuseAssignmentWithPreviousRun()) 436 schedule.emplace_back(hlfir::Run{}); 437 schedule.back().actions.emplace_back(assign); 438 // TODO: when fusing, it would probably be best to filter the 439 // parentEvaluationEffects that already in the previous run effects (since 440 // assignments may share the same parents), otherwise, this can make the 441 // conflict() calls more and more expensive. 442 schedule.back().memoryEffects.append(parentEvaluationEffects.begin(), 443 parentEvaluationEffects.end()); 444 schedule.back().memoryEffects.append(assignEffects.begin(), 445 assignEffects.end()); 446 assignEffects.clear(); 447 parentEvaluationEffects.clear(); 448 independentEvaluationEffects.clear(); 449 savedAnyRegionForCurrentAssignment = false; 450 LLVM_DEBUG(logAssignmentEvaluation(llvm::dbgs(), schedule.size(), assign)); 451 } 452 453 //===----------------------------------------------------------------------===// 454 // Scheduling Implementation : driving the Scheduler in the assignment tree. 455 //===----------------------------------------------------------------------===// 456 457 /// Gather the hlfir.region_assign nested directly and indirectly inside root in 458 /// execution order. 459 static void 460 gatherAssignments(hlfir::OrderedAssignmentTreeOpInterface root, 461 llvm::SmallVector<hlfir::RegionAssignOp> &assignments) { 462 llvm::SmallVector<mlir::Operation *> nodeStack{root.getOperation()}; 463 while (!nodeStack.empty()) { 464 mlir::Operation *node = nodeStack.pop_back_val(); 465 if (auto regionAssign = mlir::dyn_cast<hlfir::RegionAssignOp>(node)) { 466 assignments.push_back(regionAssign); 467 continue; 468 } 469 auto nodeIface = 470 mlir::dyn_cast<hlfir::OrderedAssignmentTreeOpInterface>(node); 471 if (nodeIface) 472 if (mlir::Block *block = nodeIface.getSubTreeBlock()) 473 for (mlir::Operation &op : llvm::reverse(block->getOperations())) 474 nodeStack.push_back(&op); 475 } 476 } 477 478 /// Gather the parents of (not included) \p node in reverse execution order. 479 static void gatherParents( 480 hlfir::OrderedAssignmentTreeOpInterface node, 481 llvm::SmallVectorImpl<hlfir::OrderedAssignmentTreeOpInterface> &parents) { 482 while (node) { 483 auto parent = 484 mlir::dyn_cast_or_null<hlfir::OrderedAssignmentTreeOpInterface>( 485 node->getParentOp()); 486 if (parent && parent.getSubTreeRegion() == node->getParentRegion()) { 487 parents.push_back(parent); 488 node = parent; 489 } else { 490 break; 491 } 492 } 493 } 494 495 // Build the list of the parent nodes for this assignment. The list is built 496 // from the closest parent until the ordered assignment tree root (this is the 497 // revere of their execution order). 498 static void gatherAssignmentParents( 499 hlfir::RegionAssignOp assign, 500 llvm::SmallVectorImpl<hlfir::OrderedAssignmentTreeOpInterface> &parents) { 501 gatherParents(mlir::cast<hlfir::OrderedAssignmentTreeOpInterface>( 502 assign.getOperation()), 503 parents); 504 } 505 506 hlfir::Schedule 507 hlfir::buildEvaluationSchedule(hlfir::OrderedAssignmentTreeOpInterface root, 508 bool tryFusingAssignments) { 509 LLVM_DEBUG(logStartScheduling(llvm::dbgs(), root);); 510 // The expressions inside an hlfir.forall must be pure (with the Fortran 511 // definition of pure). This is not a commitment that there are no operation 512 // with write effect in the regions: entities local to the region may still 513 // be written to (e.g., a temporary accumulator implementing SUM). This is 514 // a commitment that no write effect will affect the scheduling problem, and 515 // that all write effect caught by MLIR analysis can be ignored for the 516 // current problem. 517 const bool leafRegionsMayOnlyRead = 518 mlir::isa<hlfir::ForallOp>(root.getOperation()); 519 520 // Loop through the assignments and schedule them. 521 Scheduler scheduler(tryFusingAssignments); 522 llvm::SmallVector<hlfir::RegionAssignOp> assignments; 523 gatherAssignments(root, assignments); 524 for (hlfir::RegionAssignOp assign : assignments) { 525 scheduler.startSchedulingAssignment(assign, leafRegionsMayOnlyRead); 526 // Go through the list of parents (not including the current 527 // hlfir.region_assign) in Fortran execution order so that any parent leaf 528 // region that must be saved is saved in order. 529 llvm::SmallVector<hlfir::OrderedAssignmentTreeOpInterface> parents; 530 gatherAssignmentParents(assign, parents); 531 for (hlfir::OrderedAssignmentTreeOpInterface parent : 532 llvm::reverse(parents)) { 533 scheduler.startIndependentEvaluationGroup(); 534 llvm::SmallVector<mlir::Region *, 4> yieldRegions; 535 parent.getLeafRegions(yieldRegions); 536 for (mlir::Region *yieldRegion : yieldRegions) 537 scheduler.saveEvaluationIfConflict(*yieldRegion, 538 leafRegionsMayOnlyRead); 539 scheduler.finishIndependentEvaluationGroup(); 540 } 541 // Look for conflicts between the RHS/LHS evaluation and the assignments. 542 // The LHS yield has no implicit read effect on the produced variable (the 543 // variable is not read before the assignment). 544 scheduler.startIndependentEvaluationGroup(); 545 scheduler.saveEvaluationIfConflict(assign.getRhsRegion(), 546 leafRegionsMayOnlyRead); 547 // There is no point to save the LHS outside of Forall and assignment to a 548 // vector subscripted LHS because the LHS is already fully evaluated and 549 // saved in the resulting SSA address value (that may be a descriptor or 550 // descriptor address). 551 if (mlir::isa<hlfir::ForallOp>(root.getOperation()) || 552 mlir::isa<hlfir::ElementalAddrOp>(assign.getLhsRegion().back().back())) 553 scheduler.saveEvaluationIfConflict(assign.getLhsRegion(), 554 leafRegionsMayOnlyRead, 555 /*yieldIsImplicitRead=*/false); 556 scheduler.finishIndependentEvaluationGroup(); 557 scheduler.finishSchedulingAssignment(assign); 558 } 559 return scheduler.moveSchedule(); 560 } 561 562 mlir::Value hlfir::SaveEntity::getSavedValue() { 563 mlir::Value saved = getYieldedEntity(*yieldRegion); 564 assert(saved && "SaveEntity must contain region terminated by YieldOp"); 565 return saved; 566 } 567 568 //===----------------------------------------------------------------------===// 569 // Debug and test logging implementation 570 //===----------------------------------------------------------------------===// 571 572 static llvm::raw_ostream &printRegionId(llvm::raw_ostream &os, 573 mlir::Region &yieldRegion) { 574 mlir::Operation *parent = yieldRegion.getParentOp(); 575 if (auto forall = mlir::dyn_cast<hlfir::ForallOp>(parent)) { 576 if (&forall.getLbRegion() == &yieldRegion) 577 os << "lb"; 578 else if (&forall.getUbRegion() == &yieldRegion) 579 os << "ub"; 580 else if (&forall.getStepRegion() == &yieldRegion) 581 os << "step"; 582 } else if (auto assign = mlir::dyn_cast<hlfir::ForallMaskOp>(parent)) { 583 if (&assign.getMaskRegion() == &yieldRegion) 584 os << "mask"; 585 } else if (auto assign = mlir::dyn_cast<hlfir::RegionAssignOp>(parent)) { 586 if (&assign.getRhsRegion() == &yieldRegion) 587 os << "rhs"; 588 else if (&assign.getLhsRegion() == &yieldRegion) 589 os << "lhs"; 590 } else if (auto where = mlir::dyn_cast<hlfir::WhereOp>(parent)) { 591 if (&where.getMaskRegion() == &yieldRegion) 592 os << "mask"; 593 } else if (auto elseWhereOp = mlir::dyn_cast<hlfir::ElseWhereOp>(parent)) { 594 if (&elseWhereOp.getMaskRegion() == &yieldRegion) 595 os << "mask"; 596 } else { 597 os << "unknown"; 598 } 599 return os; 600 } 601 602 static llvm::raw_ostream & 603 printNodeIndexInBody(llvm::raw_ostream &os, 604 hlfir::OrderedAssignmentTreeOpInterface node, 605 hlfir::OrderedAssignmentTreeOpInterface parent) { 606 if (!parent || !parent.getSubTreeRegion()) 607 return os; 608 mlir::Operation *nodeOp = node.getOperation(); 609 unsigned index = 1; 610 for (mlir::Operation &op : parent.getSubTreeRegion()->getOps()) 611 if (nodeOp == &op) { 612 return os << index; 613 } else if (nodeOp->getName() == op.getName()) { 614 ++index; 615 } 616 return os; 617 } 618 619 static llvm::raw_ostream &printNodePath(llvm::raw_ostream &os, 620 mlir::Operation *op) { 621 auto node = 622 mlir::dyn_cast_or_null<hlfir::OrderedAssignmentTreeOpInterface>(op); 623 if (!node) { 624 os << "unknown node"; 625 return os; 626 } 627 llvm::SmallVector<hlfir::OrderedAssignmentTreeOpInterface> parents; 628 gatherParents(node, parents); 629 hlfir::OrderedAssignmentTreeOpInterface previousParent; 630 for (auto parent : llvm::reverse(parents)) { 631 os << parent->getName().stripDialect(); 632 printNodeIndexInBody(os, parent, previousParent) << "/"; 633 previousParent = parent; 634 } 635 os << node->getName().stripDialect(); 636 return printNodeIndexInBody(os, node, previousParent); 637 } 638 639 static llvm::raw_ostream &printRegionPath(llvm::raw_ostream &os, 640 mlir::Region &yieldRegion) { 641 printNodePath(os, yieldRegion.getParentOp()) << "/"; 642 return printRegionId(os, yieldRegion); 643 } 644 645 static void LLVM_ATTRIBUTE_UNUSED logSaveEvaluation(llvm::raw_ostream &os, 646 unsigned runid, 647 mlir::Region &yieldRegion, 648 bool anyWrite) { 649 os << "run " << runid << " save " << (anyWrite ? "(w)" : " ") << ": "; 650 printRegionPath(os, yieldRegion) << "\n"; 651 } 652 653 static void LLVM_ATTRIBUTE_UNUSED logAssignmentEvaluation( 654 llvm::raw_ostream &os, unsigned runid, hlfir::RegionAssignOp assign) { 655 os << "run " << runid << " evaluate: "; 656 printNodePath(os, assign.getOperation()) << "\n"; 657 } 658 659 static void LLVM_ATTRIBUTE_UNUSED logConflict(llvm::raw_ostream &os, 660 mlir::Value writtenOrReadVarA, 661 mlir::Value writtenVarB) { 662 auto printIfValue = [&](mlir::Value var) -> llvm::raw_ostream & { 663 if (!var) 664 return os << "<unknown>"; 665 return os << var; 666 }; 667 os << "conflict: R/W: "; 668 printIfValue(writtenOrReadVarA) << " W:"; 669 printIfValue(writtenVarB) << "\n"; 670 } 671 672 static void LLVM_ATTRIBUTE_UNUSED logStartScheduling( 673 llvm::raw_ostream &os, hlfir::OrderedAssignmentTreeOpInterface root) { 674 os << "------------ scheduling "; 675 printNodePath(os, root.getOperation()); 676 if (auto funcOp = root->getParentOfType<mlir::func::FuncOp>()) 677 os << " in " << funcOp.getSymName() << " "; 678 os << "------------\n"; 679 } 680 681 static void LLVM_ATTRIBUTE_UNUSED logIfUnkownEffectValue( 682 llvm::raw_ostream &os, mlir::MemoryEffects::EffectInstance effect, 683 mlir::Operation &op) { 684 if (effect.getValue() != nullptr) 685 return; 686 os << "unknown effected value ("; 687 os << (mlir::isa<mlir::MemoryEffects::Read>(effect.getEffect()) ? "R" : "W"); 688 os << "): " << op << "\n"; 689 } 690