xref: /llvm-project/flang/lib/Semantics/canonicalize-acc.cpp (revision a527248a3c2d638b0c92a06992f3f1c1f80842ad)
1 //===-- lib/Semantics/canonicalize-acc.cpp --------------------------------===//
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 "canonicalize-acc.h"
10 #include "flang/Parser/parse-tree-visitor.h"
11 #include "flang/Semantics/tools.h"
12 
13 // After Loop Canonicalization, rewrite OpenACC parse tree to make OpenACC
14 // Constructs more structured which provide explicit scopes for later
15 // structural checks and semantic analysis.
16 //   1. move structured DoConstruct into
17 //      OpenACCLoopConstruct. Compilation will not proceed in case of errors
18 //      after this pass.
19 //   2. move structured DoConstruct into OpenACCCombinedConstruct. Move
20 //      AccEndCombinedConstruct into OpenACCCombinedConstruct if present.
21 //      Compilation will not proceed in case of errors after this pass.
22 namespace Fortran::semantics {
23 
24 using namespace parser::literals;
25 
26 class CanonicalizationOfAcc {
27 public:
28   template <typename T> bool Pre(T &) { return true; }
29   template <typename T> void Post(T &) {}
30   CanonicalizationOfAcc(parser::Messages &messages) : messages_{messages} {}
31 
32   void Post(parser::Block &block) {
33     for (auto it{block.begin()}; it != block.end(); ++it) {
34       if (auto *accLoop{parser::Unwrap<parser::OpenACCLoopConstruct>(*it)}) {
35         RewriteOpenACCLoopConstruct(*accLoop, block, it);
36       } else if (auto *accCombined{
37                      parser::Unwrap<parser::OpenACCCombinedConstruct>(*it)}) {
38         RewriteOpenACCCombinedConstruct(*accCombined, block, it);
39       } else if (auto *endDir{
40                      parser::Unwrap<parser::AccEndCombinedDirective>(*it)}) {
41         // Unmatched AccEndCombinedDirective
42         messages_.Say(endDir->v.source,
43             "The %s directive must follow the DO loop associated with the "
44             "loop construct"_err_en_US,
45             parser::ToUpperCaseLetters(endDir->v.source.ToString()));
46       }
47     } // Block list
48   }
49 
50 private:
51   // Check constraint in 2.9.7
52   // If there are n tile sizes in the list, the loop construct must be
53   // immediately followed by n tightly-nested loops.
54   template <typename C, typename D>
55   void CheckTileClauseRestriction(
56       const C &x, const parser::DoConstruct &outer) {
57     const auto &beginLoopDirective = std::get<D>(x.t);
58     const auto &accClauseList =
59         std::get<parser::AccClauseList>(beginLoopDirective.t);
60     for (const auto &clause : accClauseList.v) {
61       if (const auto *tileClause =
62               std::get_if<parser::AccClause::Tile>(&clause.u)) {
63         const parser::AccTileExprList &tileExprList = tileClause->v;
64         const std::list<parser::AccTileExpr> &listTileExpr = tileExprList.v;
65         std::size_t tileArgNb = listTileExpr.size();
66 
67         if (outer.IsDoConcurrent()) {
68           return; // Tile is not allowed on DO CONCURRENT
69         }
70         for (const parser::DoConstruct *loop{&outer}; loop && tileArgNb > 0;
71              --tileArgNb) {
72           const auto &block{std::get<parser::Block>(loop->t)};
73           const auto it{block.begin()};
74           loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
75                                    : nullptr;
76         }
77 
78         if (tileArgNb > 0) {
79           messages_.Say(beginLoopDirective.source,
80               "The loop construct with the TILE clause must be followed by %d "
81               "tightly-nested loops"_err_en_US,
82               listTileExpr.size());
83         }
84       }
85     }
86   }
87 
88   // Check constraint on line 1835 in Section 2.9
89   // A tile and collapse clause may not appear on loop that is associated with
90   // do concurrent.
91   template <typename C, typename D>
92   void CheckDoConcurrentClauseRestriction(
93       const C &x, const parser::DoConstruct &doCons) {
94     if (!doCons.IsDoConcurrent()) {
95       return;
96     }
97     const auto &beginLoopDirective = std::get<D>(x.t);
98     const auto &accClauseList =
99         std::get<parser::AccClauseList>(beginLoopDirective.t);
100     for (const auto &clause : accClauseList.v) {
101       if (std::holds_alternative<parser::AccClause::Collapse>(clause.u) ||
102           std::holds_alternative<parser::AccClause::Tile>(clause.u)) {
103         messages_.Say(beginLoopDirective.source,
104             "TILE and COLLAPSE clause may not appear on loop construct "
105             "associated with DO CONCURRENT"_err_en_US);
106       }
107     }
108   }
109 
110   // Utility to move all parser::CompilerDirective right after it to right
111   // before it.  This allows preserving loop directives $DIR that may lie
112   // between an $acc directive and loop and leave lowering decide if it should
113   // ignore them or lower/apply them to the acc loops.
114   void moveCompilerDirectivesBefore(
115       parser::Block &block, parser::Block::iterator it) {
116     parser::Block::iterator nextIt = std::next(it);
117     while (nextIt != block.end() &&
118         parser::Unwrap<parser::CompilerDirective>(*nextIt)) {
119       block.emplace(it, std::move(*nextIt));
120       nextIt = block.erase(nextIt);
121     }
122   }
123 
124   void RewriteOpenACCLoopConstruct(parser::OpenACCLoopConstruct &x,
125       parser::Block &block, parser::Block::iterator it) {
126     parser::Block::iterator nextIt;
127     auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
128     auto &dir{std::get<parser::AccLoopDirective>(beginDir.t)};
129     auto &nestedDo{std::get<std::optional<parser::DoConstruct>>(x.t)};
130 
131     if (!nestedDo) {
132       moveCompilerDirectivesBefore(block, it);
133       nextIt = it;
134       if (++nextIt != block.end()) {
135         if (auto *doCons{parser::Unwrap<parser::DoConstruct>(*nextIt)}) {
136           nestedDo = std::move(*doCons);
137           nextIt = block.erase(nextIt);
138         }
139       }
140     }
141 
142     if (nestedDo) {
143       if (!nestedDo->GetLoopControl()) {
144         messages_.Say(dir.source,
145             "DO loop after the %s directive must have loop control"_err_en_US,
146             parser::ToUpperCaseLetters(dir.source.ToString()));
147         return;
148       }
149       CheckDoConcurrentClauseRestriction<parser::OpenACCLoopConstruct,
150           parser::AccBeginLoopDirective>(x, *nestedDo);
151       CheckTileClauseRestriction<parser::OpenACCLoopConstruct,
152           parser::AccBeginLoopDirective>(x, *nestedDo);
153       return;
154     }
155     messages_.Say(dir.source,
156         "A DO loop must follow the %s directive"_err_en_US,
157         parser::ToUpperCaseLetters(dir.source.ToString()));
158   }
159 
160   void RewriteOpenACCCombinedConstruct(parser::OpenACCCombinedConstruct &x,
161       parser::Block &block, parser::Block::iterator it) {
162     // Check the sequence of DoConstruct in the same iteration.
163     parser::Block::iterator nextIt;
164     auto &beginDir{std::get<parser::AccBeginCombinedDirective>(x.t)};
165     auto &dir{std::get<parser::AccCombinedDirective>(beginDir.t)};
166     auto &nestedDo{std::get<std::optional<parser::DoConstruct>>(x.t)};
167 
168     if (!nestedDo) {
169       moveCompilerDirectivesBefore(block, it);
170       nextIt = it;
171       if (++nextIt != block.end()) {
172         if (auto *doCons{parser::Unwrap<parser::DoConstruct>(*nextIt)}) {
173           nestedDo = std::move(*doCons);
174           nextIt = block.erase(nextIt);
175         }
176       }
177     }
178 
179     if (nestedDo) {
180       CheckDoConcurrentClauseRestriction<parser::OpenACCCombinedConstruct,
181           parser::AccBeginCombinedDirective>(x, *nestedDo);
182       CheckTileClauseRestriction<parser::OpenACCCombinedConstruct,
183           parser::AccBeginCombinedDirective>(x, *nestedDo);
184       if (!nestedDo->GetLoopControl()) {
185         messages_.Say(dir.source,
186             "DO loop after the %s directive must have loop control"_err_en_US,
187             parser::ToUpperCaseLetters(dir.source.ToString()));
188         return;
189       }
190       return;
191     }
192     messages_.Say(dir.source,
193         "A DO loop must follow the %s directive"_err_en_US,
194         parser::ToUpperCaseLetters(dir.source.ToString()));
195   }
196 
197   parser::Messages &messages_;
198 };
199 
200 bool CanonicalizeAcc(parser::Messages &messages, parser::Program &program) {
201   CanonicalizationOfAcc acc{messages};
202   Walk(program, acc);
203   return !messages.AnyFatalError();
204 }
205 } // namespace Fortran::semantics
206