387 lines · cpp
1//===-- lib/Semantics/canonicalize-omp.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-exception6//7//===----------------------------------------------------------------------===//8 9#include "canonicalize-omp.h"10#include "flang/Parser/parse-tree-visitor.h"11#include "flang/Parser/parse-tree.h"12#include "flang/Semantics/openmp-directive-sets.h"13#include "flang/Semantics/semantics.h"14 15// After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP16// Constructs more structured which provide explicit scopes for later17// structural checks and semantic analysis.18// 1. move structured DoConstruct and OmpEndLoopDirective into19// OpenMPLoopConstruct. Compilation will not proceed in case of errors20// after this pass.21// 2. Associate declarative OMP allocation directives with their22// respective executable allocation directive23// 3. TBD24namespace Fortran::semantics {25 26using namespace parser::literals;27 28class CanonicalizationOfOmp {29public:30 template <typename T> bool Pre(T &) { return true; }31 template <typename T> void Post(T &) {}32 CanonicalizationOfOmp(SemanticsContext &context)33 : context_{context}, messages_{context.messages()} {}34 35 // Pre-visit all constructs that have both a specification part and36 // an execution part, and store the connection between the two.37 bool Pre(parser::BlockConstruct &x) {38 auto *spec = &std::get<parser::BlockSpecificationPart>(x.t).v;39 auto *block = &std::get<parser::Block>(x.t);40 blockForSpec_.insert(std::make_pair(spec, block));41 return true;42 }43 bool Pre(parser::MainProgram &x) {44 auto *spec = &std::get<parser::SpecificationPart>(x.t);45 auto *block = &std::get<parser::ExecutionPart>(x.t).v;46 blockForSpec_.insert(std::make_pair(spec, block));47 return true;48 }49 bool Pre(parser::FunctionSubprogram &x) {50 auto *spec = &std::get<parser::SpecificationPart>(x.t);51 auto *block = &std::get<parser::ExecutionPart>(x.t).v;52 blockForSpec_.insert(std::make_pair(spec, block));53 return true;54 }55 bool Pre(parser::SubroutineSubprogram &x) {56 auto *spec = &std::get<parser::SpecificationPart>(x.t);57 auto *block = &std::get<parser::ExecutionPart>(x.t).v;58 blockForSpec_.insert(std::make_pair(spec, block));59 return true;60 }61 bool Pre(parser::SeparateModuleSubprogram &x) {62 auto *spec = &std::get<parser::SpecificationPart>(x.t);63 auto *block = &std::get<parser::ExecutionPart>(x.t).v;64 blockForSpec_.insert(std::make_pair(spec, block));65 return true;66 }67 68 void Post(parser::SpecificationPart &spec) {69 CanonicalizeUtilityConstructs(spec);70 CanonicalizeAllocateDirectives(spec);71 }72 73 void Post(parser::OmpMapClause &map) { CanonicalizeMapModifiers(map); }74 75private:76 // Canonicalization of allocate directives77 //78 // In OpenMP 5.0 and 5.1 the allocate directive could either be a declarative79 // one or an executable one. As usual in such cases, this poses a problem80 // when the directive appears at the boundary between the specification part81 // and the execution part.82 // The executable form can actually consist of several adjacent directives,83 // whereas the declarative form is always standalone. Additionally, the84 // executable form must be associated with an allocate statement.85 //86 // The parser tries to parse declarative statements first, so in the87 // following case, the two directives will be declarative, even though88 // they should be treated as a single executable form:89 // integer, allocatable :: x, y ! Specification90 // !$omp allocate(x)91 // !$omp allocate(y)92 // allocate(x, y) ! Execution93 //94 void CanonicalizeAllocateDirectives(parser::SpecificationPart &spec) {95 auto found = blockForSpec_.find(&spec);96 if (found == blockForSpec_.end()) {97 // There is no corresponding execution part, so there is nothing to do.98 return;99 }100 parser::Block &block = *found->second;101 102 auto isAllocateStmt = [](const parser::ExecutionPartConstruct &epc) {103 if (auto *ec = std::get_if<parser::ExecutableConstruct>(&epc.u)) {104 if (auto *as =105 std::get_if<parser::Statement<parser::ActionStmt>>(&ec->u)) {106 return std::holds_alternative<107 common::Indirection<parser::AllocateStmt>>(as->statement.u);108 }109 }110 return false;111 };112 113 if (!block.empty() && isAllocateStmt(block.front())) {114 // There are two places where an OpenMP declarative construct can115 // show up in the tuple in specification part:116 // (1) in std::list<OpenMPDeclarativeConstruct>, or117 // (2) in std::list<DeclarationConstruct>.118 // The case (1) is only possible if the list (2) is empty.119 120 auto &omps =121 std::get<std::list<parser::OpenMPDeclarativeConstruct>>(spec.t);122 auto &decls = std::get<std::list<parser::DeclarationConstruct>>(spec.t);123 124 if (!decls.empty()) {125 MakeExecutableAllocateFromDecls(decls, block);126 } else {127 MakeExecutableAllocateFromOmps(omps, block);128 }129 }130 }131 132 parser::ExecutionPartConstruct EmbedInExec(133 parser::OmpAllocateDirective *alo, parser::ExecutionPartConstruct &&epc) {134 // Nest current epc inside the allocate directive.135 std::get<parser::Block>(alo->t).push_front(std::move(epc));136 // Set the new epc to be the ExecutionPartConstruct made from137 // the allocate directive.138 parser::OpenMPConstruct opc(std::move(*alo));139 common::Indirection<parser::OpenMPConstruct> ind(std::move(opc));140 parser::ExecutableConstruct ec(std::move(ind));141 return parser::ExecutionPartConstruct(std::move(ec));142 }143 144 void MakeExecutableAllocateFromDecls(145 std::list<parser::DeclarationConstruct> &decls, parser::Block &body) {146 using OpenMPDeclarativeConstruct =147 common::Indirection<parser::OpenMPDeclarativeConstruct>;148 149 auto getAllocate = [](parser::DeclarationConstruct *dc) {150 if (auto *sc = std::get_if<parser::SpecificationConstruct>(&dc->u)) {151 if (auto *odc = std::get_if<OpenMPDeclarativeConstruct>(&sc->u)) {152 if (auto *alo =153 std::get_if<parser::OmpAllocateDirective>(&odc->value().u)) {154 return alo;155 }156 }157 }158 return static_cast<parser::OmpAllocateDirective *>(nullptr);159 };160 161 std::list<parser::DeclarationConstruct>::reverse_iterator rlast = [&]() {162 for (auto rit = decls.rbegin(), rend = decls.rend(); rit != rend; ++rit) {163 if (getAllocate(&*rit) == nullptr) {164 return rit;165 }166 }167 return decls.rend();168 }();169 170 if (rlast != decls.rbegin()) {171 // We have already checked that the first statement in body is172 // ALLOCATE.173 parser::ExecutionPartConstruct epc(std::move(body.front()));174 for (auto rit = decls.rbegin(); rit != rlast; ++rit) {175 epc = EmbedInExec(getAllocate(&*rit), std::move(epc));176 }177 178 body.pop_front();179 body.push_front(std::move(epc));180 decls.erase(rlast.base(), decls.end());181 }182 }183 184 void MakeExecutableAllocateFromOmps(185 std::list<parser::OpenMPDeclarativeConstruct> &omps,186 parser::Block &body) {187 using OpenMPDeclarativeConstruct = parser::OpenMPDeclarativeConstruct;188 189 std::list<OpenMPDeclarativeConstruct>::reverse_iterator rlast = [&]() {190 for (auto rit = omps.rbegin(), rend = omps.rend(); rit != rend; ++rit) {191 if (!std::holds_alternative<parser::OmpAllocateDirective>(rit->u)) {192 return rit;193 }194 }195 return omps.rend();196 }();197 198 if (rlast != omps.rbegin()) {199 parser::ExecutionPartConstruct epc(std::move(body.front()));200 for (auto rit = omps.rbegin(); rit != rlast; ++rit) {201 epc = EmbedInExec(202 &std::get<parser::OmpAllocateDirective>(rit->u), std::move(epc));203 }204 205 body.pop_front();206 body.push_front(std::move(epc));207 omps.erase(rlast.base(), omps.end());208 }209 }210 211 // Canonicalization of utility constructs.212 //213 // This addresses the issue of utility constructs that appear at the214 // boundary between the specification and the execution parts, e.g.215 // subroutine foo216 // integer :: x ! Specification217 // !$omp nothing218 // x = 1 ! Execution219 // ...220 // end221 //222 // Utility constructs (error and nothing) can appear in both the223 // specification part and the execution part, except "error at(execution)",224 // which cannot be present in the specification part (whereas any utility225 // construct can be in the execution part).226 // When a utility construct is at the boundary, it should preferably be227 // parsed as an element of the execution part, but since the specification228 // part is parsed first, the utility construct ends up belonging to the229 // specification part.230 //231 // To allow the likes of the following code to compile, move all utility232 // construct that are at the end of the specification part to the beginning233 // of the execution part.234 //235 // subroutine foo236 // !$omp error at(execution) ! Initially parsed as declarative construct.237 // ! Move it to the execution part.238 // end239 240 void CanonicalizeUtilityConstructs(parser::SpecificationPart &spec) {241 auto found = blockForSpec_.find(&spec);242 if (found == blockForSpec_.end()) {243 // There is no corresponding execution part, so there is nothing to do.244 return;245 }246 parser::Block &block = *found->second;247 248 // There are two places where an OpenMP declarative construct can249 // show up in the tuple in specification part:250 // (1) in std::list<OpenMPDeclarativeConstruct>, or251 // (2) in std::list<DeclarationConstruct>.252 // The case (1) is only possible is the list (2) is empty.253 254 auto &omps =255 std::get<std::list<parser::OpenMPDeclarativeConstruct>>(spec.t);256 auto &decls = std::get<std::list<parser::DeclarationConstruct>>(spec.t);257 258 if (!decls.empty()) {259 MoveUtilityConstructsFromDecls(decls, block);260 } else {261 MoveUtilityConstructsFromOmps(omps, block);262 }263 }264 265 void MoveUtilityConstructsFromDecls(266 std::list<parser::DeclarationConstruct> &decls, parser::Block &block) {267 // Find the trailing range of DeclarationConstructs that are OpenMP268 // utility construct, that are to be moved to the execution part.269 std::list<parser::DeclarationConstruct>::reverse_iterator rlast = [&]() {270 for (auto rit = decls.rbegin(), rend = decls.rend(); rit != rend; ++rit) {271 parser::DeclarationConstruct &dc = *rit;272 if (!std::holds_alternative<parser::SpecificationConstruct>(dc.u)) {273 return rit;274 }275 auto &sc = std::get<parser::SpecificationConstruct>(dc.u);276 using OpenMPDeclarativeConstruct =277 common::Indirection<parser::OpenMPDeclarativeConstruct>;278 if (!std::holds_alternative<OpenMPDeclarativeConstruct>(sc.u)) {279 return rit;280 }281 // Got OpenMPDeclarativeConstruct. If it's not a utility construct282 // then stop.283 auto &odc = std::get<OpenMPDeclarativeConstruct>(sc.u).value();284 if (!std::holds_alternative<parser::OpenMPUtilityConstruct>(odc.u)) {285 return rit;286 }287 }288 return decls.rend();289 }();290 291 std::transform(decls.rbegin(), rlast, std::front_inserter(block),292 [](parser::DeclarationConstruct &dc) {293 auto &sc = std::get<parser::SpecificationConstruct>(dc.u);294 using OpenMPDeclarativeConstruct =295 common::Indirection<parser::OpenMPDeclarativeConstruct>;296 auto &oc = std::get<OpenMPDeclarativeConstruct>(sc.u).value();297 auto &ut = std::get<parser::OpenMPUtilityConstruct>(oc.u);298 299 return parser::ExecutionPartConstruct(parser::ExecutableConstruct(300 common::Indirection(parser::OpenMPConstruct(std::move(ut)))));301 });302 303 decls.erase(rlast.base(), decls.end());304 }305 306 void MoveUtilityConstructsFromOmps(307 std::list<parser::OpenMPDeclarativeConstruct> &omps,308 parser::Block &block) {309 using OpenMPDeclarativeConstruct = parser::OpenMPDeclarativeConstruct;310 // Find the trailing range of OpenMPDeclarativeConstruct that are OpenMP311 // utility construct, that are to be moved to the execution part.312 std::list<OpenMPDeclarativeConstruct>::reverse_iterator rlast = [&]() {313 for (auto rit = omps.rbegin(), rend = omps.rend(); rit != rend; ++rit) {314 OpenMPDeclarativeConstruct &dc = *rit;315 if (!std::holds_alternative<parser::OpenMPUtilityConstruct>(dc.u)) {316 return rit;317 }318 }319 return omps.rend();320 }();321 322 std::transform(omps.rbegin(), rlast, std::front_inserter(block),323 [](parser::OpenMPDeclarativeConstruct &dc) {324 auto &ut = std::get<parser::OpenMPUtilityConstruct>(dc.u);325 return parser::ExecutionPartConstruct(parser::ExecutableConstruct(326 common::Indirection(parser::OpenMPConstruct(std::move(ut)))));327 });328 329 omps.erase(rlast.base(), omps.end());330 }331 332 // Map clause modifiers are parsed as per OpenMP 6.0 spec. That spec has333 // changed properties of some of the modifiers, for example it has expanded334 // map-type-modifier into 3 individual modifiers (one for each of the335 // possible values of the original modifier), and the "map-type" modifier336 // is no longer ultimate.337 // To utilize the modifier validation framework for semantic checks,338 // if the specified OpenMP version is less than 6.0, rewrite the affected339 // modifiers back into the pre-6.0 forms.340 void CanonicalizeMapModifiers(parser::OmpMapClause &map) {341 unsigned version{context_.langOptions().OpenMPVersion};342 if (version >= 60) {343 return;344 }345 346 // Omp{Always, Close, Present, xHold}Modifier -> OmpMapTypeModifier347 // OmpDeleteModifier -> OmpMapType348 using Modifier = parser::OmpMapClause::Modifier;349 using Modifiers = std::optional<std::list<Modifier>>;350 auto &modifiers{std::get<Modifiers>(map.t)};351 if (!modifiers) {352 return;353 }354 355 using MapTypeModifier = parser::OmpMapTypeModifier;356 using MapType = parser::OmpMapType;357 358 for (auto &mod : *modifiers) {359 if (std::holds_alternative<parser::OmpAlwaysModifier>(mod.u)) {360 mod.u = MapTypeModifier(MapTypeModifier::Value::Always);361 } else if (std::holds_alternative<parser::OmpCloseModifier>(mod.u)) {362 mod.u = MapTypeModifier(MapTypeModifier::Value::Close);363 } else if (std::holds_alternative<parser::OmpPresentModifier>(mod.u)) {364 mod.u = MapTypeModifier(MapTypeModifier::Value::Present);365 } else if (std::holds_alternative<parser::OmpxHoldModifier>(mod.u)) {366 mod.u = MapTypeModifier(MapTypeModifier::Value::Ompx_Hold);367 } else if (std::holds_alternative<parser::OmpDeleteModifier>(mod.u)) {368 mod.u = MapType(MapType::Value::Delete);369 }370 }371 }372 373 // Mapping from the specification parts to the blocks that follow in the374 // same construct. This is for converting utility constructs to executable375 // constructs.376 std::map<parser::SpecificationPart *, parser::Block *> blockForSpec_;377 SemanticsContext &context_;378 parser::Messages &messages_;379};380 381bool CanonicalizeOmp(SemanticsContext &context, parser::Program &program) {382 CanonicalizationOfOmp omp{context};383 Walk(program, omp);384 return !context.messages().AnyFatalError();385}386} // namespace Fortran::semantics387