brintos

brintos / llvm-project-archived public Read only

0
0
Text · 15.2 KiB · 802b2ac Raw
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