xref: /llvm-project/flang/lib/Optimizer/HLFIR/Transforms/ScheduleOrderedAssignments.cpp (revision e52a6d7784adbcb98b17af7943c69c854aa7b10f)
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 &region, 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 &region) {
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