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         }
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>
CheckDoConcurrentClauseRestriction(const C & x)92   void CheckDoConcurrentClauseRestriction(const C &x) {
93     const auto &doCons{std::get<std::optional<parser::DoConstruct>>(x.t)};
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 
RewriteOpenACCLoopConstruct(parser::OpenACCLoopConstruct & x,parser::Block & block,parser::Block::iterator it)110   void RewriteOpenACCLoopConstruct(parser::OpenACCLoopConstruct &x,
111       parser::Block &block, parser::Block::iterator it) {
112     // Check the sequence of DoConstruct in the same iteration
113     //
114     // Original:
115     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCLoopConstruct
116     //     ACCBeginLoopDirective
117     //   ExecutableConstruct -> DoConstruct
118     //
119     // After rewriting:
120     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCLoopConstruct
121     //     AccBeginLoopDirective
122     //     DoConstruct
123     parser::Block::iterator nextIt;
124     auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
125     auto &dir{std::get<parser::AccLoopDirective>(beginDir.t)};
126 
127     nextIt = it;
128     if (++nextIt != block.end()) {
129       if (auto *doCons{parser::Unwrap<parser::DoConstruct>(*nextIt)}) {
130         if (doCons->GetLoopControl()) {
131           // move DoConstruct
132           std::get<std::optional<parser::DoConstruct>>(x.t) =
133               std::move(*doCons);
134           nextIt = block.erase(nextIt);
135         } else {
136           messages_.Say(dir.source,
137               "DO loop after the %s directive must have loop control"_err_en_US,
138               parser::ToUpperCaseLetters(dir.source.ToString()));
139         }
140 
141         CheckDoConcurrentClauseRestriction<parser::OpenACCLoopConstruct,
142             parser::AccBeginLoopDirective>(x);
143         CheckTileClauseRestriction<parser::OpenACCLoopConstruct,
144             parser::AccBeginLoopDirective>(x);
145 
146         return; // found do-loop
147       }
148     }
149     messages_.Say(dir.source,
150         "A DO loop must follow the %s directive"_err_en_US,
151         parser::ToUpperCaseLetters(dir.source.ToString()));
152   }
153 
RewriteOpenACCCombinedConstruct(parser::OpenACCCombinedConstruct & x,parser::Block & block,parser::Block::iterator it)154   void RewriteOpenACCCombinedConstruct(parser::OpenACCCombinedConstruct &x,
155       parser::Block &block, parser::Block::iterator it) {
156     // Check the sequence of DoConstruct in the same iteration
157     //
158     // Original:
159     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCCombinedConstruct
160     //     ACCBeginCombinedDirective
161     //   ExecutableConstruct -> DoConstruct
162     //   ExecutableConstruct -> AccEndCombinedDirective (if available)
163     //
164     // After rewriting:
165     //   ExecutableConstruct -> OpenACCConstruct -> OpenACCCombinedConstruct
166     //     ACCBeginCombinedDirective
167     //     DoConstruct
168     //     AccEndCombinedDirective (if available)
169     parser::Block::iterator nextIt;
170     auto &beginDir{std::get<parser::AccBeginCombinedDirective>(x.t)};
171     auto &dir{std::get<parser::AccCombinedDirective>(beginDir.t)};
172 
173     nextIt = it;
174     if (++nextIt != block.end()) {
175       if (auto *doCons{parser::Unwrap<parser::DoConstruct>(*nextIt)}) {
176         if (doCons->GetLoopControl()) {
177           // move DoConstruct
178           std::get<std::optional<parser::DoConstruct>>(x.t) =
179               std::move(*doCons);
180           nextIt = block.erase(nextIt);
181           // try to match AccEndCombinedDirective
182           if (nextIt != block.end()) {
183             if (auto *endDir{
184                     parser::Unwrap<parser::AccEndCombinedDirective>(*nextIt)}) {
185               std::get<std::optional<parser::AccEndCombinedDirective>>(x.t) =
186                   std::move(*endDir);
187               block.erase(nextIt);
188             }
189           }
190         } else {
191           messages_.Say(dir.source,
192               "DO loop after the %s directive must have loop control"_err_en_US,
193               parser::ToUpperCaseLetters(dir.source.ToString()));
194         }
195 
196         CheckDoConcurrentClauseRestriction<parser::OpenACCCombinedConstruct,
197             parser::AccBeginCombinedDirective>(x);
198         CheckTileClauseRestriction<parser::OpenACCCombinedConstruct,
199             parser::AccBeginCombinedDirective>(x);
200 
201         return; // found do-loop
202       }
203     }
204     messages_.Say(dir.source,
205         "A DO loop must follow the %s directive"_err_en_US,
206         parser::ToUpperCaseLetters(dir.source.ToString()));
207   }
208 
209   parser::Messages &messages_;
210 };
211 
CanonicalizeAcc(parser::Messages & messages,parser::Program & program)212 bool CanonicalizeAcc(parser::Messages &messages, parser::Program &program) {
213   CanonicalizationOfAcc acc{messages};
214   Walk(program, acc);
215   return !messages.AnyFatalError();
216 }
217 } // namespace Fortran::semantics
218