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:
Pre(T &)28   template <typename T> bool Pre(T &) { return true; }
Post(T &)29   template <typename T> void Post(T &) {}
CanonicalizationOfAcc(parser::Messages & messages)30   CanonicalizationOfAcc(parser::Messages &messages) : messages_{messages} {}
31 
Post(parser::Block & block)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>
CheckTileClauseRestriction(const C & x)55   void CheckTileClauseRestriction(const C &x) {
56     const auto &beginLoopDirective = std::get<D>(x.t);
57     const auto &accClauseList =
58         std::get<parser::AccClauseList>(beginLoopDirective.t);
59     for (const auto &clause : accClauseList.v) {
60       if (const auto *tileClause =
61               std::get_if<parser::AccClause::Tile>(&clause.u)) {
62         const parser::AccTileExprList &tileExprList = tileClause->v;
63         const std::list<parser::AccTileExpr> &listTileExpr = tileExprList.v;
64         std::size_t tileArgNb = listTileExpr.size();
65 
66         const auto &outer{std::get<std::optional<parser::DoConstruct>>(x.t)};
67         if (outer->IsDoConcurrent())
68           return; // Tile is not allowed on DO CONURRENT
69         for (const parser::DoConstruct *loop{&*outer}; loop && tileArgNb > 0;
70              --tileArgNb) {
71           const auto &block{std::get<parser::Block>(loop->t)};
72           const auto it{block.begin()};
73           loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
74                                    : nullptr;
75         }
76 
77         if (tileArgNb > 0) {
78           messages_.Say(beginLoopDirective.source,
79               "The loop construct with the TILE clause must be followed by %d "
80               "tightly-nested loops"_err_en_US,
81               listTileExpr.size());
82         }
83       }
84     }
85   }
86 
87   // Check constraint on line 1835 in Section 2.9
88   // A tile and collapse clause may not appear on loop that is associated with
89   // do concurrent.
90   template <typename C, typename D>
CheckDoConcurrentClauseRestriction(const C & x)91   void CheckDoConcurrentClauseRestriction(const C &x) {
92     const auto &doCons{std::get<std::optional<parser::DoConstruct>>(x.t)};
93     if (!doCons->IsDoConcurrent())
94       return;
95     const auto &beginLoopDirective = std::get<D>(x.t);
96     const auto &accClauseList =
97         std::get<parser::AccClauseList>(beginLoopDirective.t);
98     for (const auto &clause : accClauseList.v) {
99       if (std::holds_alternative<parser::AccClause::Collapse>(clause.u) ||
100           std::holds_alternative<parser::AccClause::Tile>(clause.u)) {
101         messages_.Say(beginLoopDirective.source,
102             "TILE and COLLAPSE clause may not appear on loop construct "
103             "associated with DO CONCURRENT"_err_en_US);
104       }
105     }
106   }
107 
RewriteOpenACCLoopConstruct(parser::OpenACCLoopConstruct & x,parser::Block & block,parser::Block::iterator it)108   void RewriteOpenACCLoopConstruct(parser::OpenACCLoopConstruct &x,
109       parser::Block &block, parser::Block::iterator it) {
110     // Check the sequence of DoConstruct in the same iteration
111     //
112     // Original:
113     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCLoopConstruct
114     //     ACCBeginLoopDirective
115     //   ExecutableConstruct -> DoConstruct
116     //
117     // After rewriting:
118     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCLoopConstruct
119     //     AccBeginLoopDirective
120     //     DoConstruct
121     parser::Block::iterator nextIt;
122     auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
123     auto &dir{std::get<parser::AccLoopDirective>(beginDir.t)};
124 
125     nextIt = it;
126     if (++nextIt != block.end()) {
127       if (auto *doCons{parser::Unwrap<parser::DoConstruct>(*nextIt)}) {
128         if (doCons->GetLoopControl()) {
129           // move DoConstruct
130           std::get<std::optional<parser::DoConstruct>>(x.t) =
131               std::move(*doCons);
132           nextIt = block.erase(nextIt);
133         } else {
134           messages_.Say(dir.source,
135               "DO loop after the %s directive must have loop control"_err_en_US,
136               parser::ToUpperCaseLetters(dir.source.ToString()));
137         }
138 
139         CheckDoConcurrentClauseRestriction<parser::OpenACCLoopConstruct,
140             parser::AccBeginLoopDirective>(x);
141         CheckTileClauseRestriction<parser::OpenACCLoopConstruct,
142             parser::AccBeginLoopDirective>(x);
143 
144         return; // found do-loop
145       }
146     }
147     messages_.Say(dir.source,
148         "A DO loop must follow the %s directive"_err_en_US,
149         parser::ToUpperCaseLetters(dir.source.ToString()));
150   }
151 
RewriteOpenACCCombinedConstruct(parser::OpenACCCombinedConstruct & x,parser::Block & block,parser::Block::iterator it)152   void RewriteOpenACCCombinedConstruct(parser::OpenACCCombinedConstruct &x,
153       parser::Block &block, parser::Block::iterator it) {
154     // Check the sequence of DoConstruct in the same iteration
155     //
156     // Original:
157     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCCombinedConstruct
158     //     ACCBeginCombinedDirective
159     //   ExecutableConstruct -> DoConstruct
160     //   ExecutableConstruct -> AccEndCombinedDirective (if available)
161     //
162     // After rewriting:
163     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCCombinedConstruct
164     //     ACCBeginCombinedDirective
165     //     DoConstruct
166     //     AccEndCombinedDirective (if available)
167     parser::Block::iterator nextIt;
168     auto &beginDir{std::get<parser::AccBeginCombinedDirective>(x.t)};
169     auto &dir{std::get<parser::AccCombinedDirective>(beginDir.t)};
170 
171     nextIt = it;
172     if (++nextIt != block.end()) {
173       if (auto *doCons{parser::Unwrap<parser::DoConstruct>(*nextIt)}) {
174         if (doCons->GetLoopControl()) {
175           // move DoConstruct
176           std::get<std::optional<parser::DoConstruct>>(x.t) =
177               std::move(*doCons);
178           nextIt = block.erase(nextIt);
179           // try to match AccEndCombinedDirective
180           if (nextIt != block.end()) {
181             if (auto *endDir{
182                     parser::Unwrap<parser::AccEndCombinedDirective>(*nextIt)}) {
183               std::get<std::optional<parser::AccEndCombinedDirective>>(x.t) =
184                   std::move(*endDir);
185               block.erase(nextIt);
186             }
187           }
188         } else {
189           messages_.Say(dir.source,
190               "DO loop after the %s directive must have loop control"_err_en_US,
191               parser::ToUpperCaseLetters(dir.source.ToString()));
192         }
193 
194         CheckDoConcurrentClauseRestriction<parser::OpenACCCombinedConstruct,
195             parser::AccBeginCombinedDirective>(x);
196         CheckTileClauseRestriction<parser::OpenACCCombinedConstruct,
197             parser::AccBeginCombinedDirective>(x);
198 
199         return; // found do-loop
200       }
201     }
202     messages_.Say(dir.source,
203         "A DO loop must follow the %s directive"_err_en_US,
204         parser::ToUpperCaseLetters(dir.source.ToString()));
205   }
206 
207   parser::Messages &messages_;
208 };
209 
CanonicalizeAcc(parser::Messages & messages,parser::Program & program)210 bool CanonicalizeAcc(parser::Messages &messages, parser::Program &program) {
211   CanonicalizationOfAcc acc{messages};
212   Walk(program, acc);
213   return !messages.AnyFatalError();
214 }
215 } // namespace Fortran::semantics
216