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