| //===-- OpenMP.cpp -- Open MP directive lowering --------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "flang/Lower/OpenMP.h" |
| |
| #include "Atomic.h" |
| #include "ClauseProcessor.h" |
| #include "DataSharingProcessor.h" |
| #include "Decomposer.h" |
| #include "Utils.h" |
| #include "flang/Common/idioms.h" |
| #include "flang/Evaluate/expression.h" |
| #include "flang/Evaluate/fold.h" |
| #include "flang/Evaluate/tools.h" |
| #include "flang/Evaluate/type.h" |
| #include "flang/Lower/Bridge.h" |
| #include "flang/Lower/ConvertCall.h" |
| #include "flang/Lower/ConvertExprToHLFIR.h" |
| #include "flang/Lower/ConvertVariable.h" |
| #include "flang/Lower/DirectivesCommon.h" |
| #include "flang/Lower/OpenMP/Clauses.h" |
| #include "flang/Lower/PFTBuilder.h" |
| #include "flang/Lower/StatementContext.h" |
| #include "flang/Lower/Support/PrivateReductionUtils.h" |
| #include "flang/Lower/Support/ReductionProcessor.h" |
| #include "flang/Lower/SymbolMap.h" |
| #include "flang/Optimizer/Builder/BoxValue.h" |
| #include "flang/Optimizer/Builder/Character.h" |
| #include "flang/Optimizer/Builder/FIRBuilder.h" |
| #include "flang/Optimizer/Builder/Todo.h" |
| #include "flang/Optimizer/Dialect/FIROpsSupport.h" |
| #include "flang/Optimizer/Dialect/FIRType.h" |
| #include "flang/Optimizer/HLFIR/HLFIROps.h" |
| #include "flang/Parser/openmp-utils.h" |
| #include "flang/Parser/parse-tree.h" |
| #include "flang/Parser/tools.h" |
| #include "flang/Semantics/expression.h" |
| #include "flang/Semantics/openmp-directive-sets.h" |
| #include "flang/Semantics/openmp-utils.h" |
| #include "flang/Semantics/tools.h" |
| #include "flang/Support/Flags.h" |
| #include "flang/Support/OpenMP-utils.h" |
| #include "flang/Utils/OpenMP.h" |
| #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" |
| #include "mlir/Dialect/LLVMIR/LLVMDialect.h" |
| #include "mlir/Dialect/OpenMP/OpenMPDialect.h" |
| #include "mlir/IR/IRMapping.h" |
| #include "mlir/Support/StateStack.h" |
| #include "mlir/Transforms/RegionUtils.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/MapVector.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallPtrSet.h" |
| #include "llvm/ADT/SmallSet.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include "llvm/Frontend/OpenMP/OMP.h" |
| #include <atomic> |
| |
| using namespace Fortran::lower::omp; |
| using namespace Fortran::common::openmp; |
| using namespace Fortran::utils::openmp; |
| |
| // Forward declarations |
| static fir::RecordType buildConditionalLpType( |
| Fortran::lower::AbstractConverter &converter, |
| const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms, |
| mlir::Location loc); |
| |
| static mlir::omp::DeclareReductionOp buildConditionalLastPrivateReduction( |
| Fortran::lower::AbstractConverter &converter, fir::RecordType lpCondType, |
| const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms); |
| |
| static llvm::MapVector<mlir::Value, std::string> bindCondLpSymsToStructFields( |
| Fortran::lower::AbstractConverter &converter, mlir::Location loc, |
| fir::RecordType lpType, mlir::Value structArg, |
| const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms); |
| |
| static void injectCondLpIndexStores( |
| fir::FirOpBuilder &builder, mlir::Location loc, fir::RecordType lpType, |
| mlir::Value structArg, mlir::Region ®ion, |
| const llvm::MapVector<mlir::Value, std::string> &valAddrToSymName, |
| llvm::function_ref<mlir::Value(fir::FirOpBuilder &, mlir::Location)> |
| genIndexVal); |
| |
| static mlir::Value |
| computeFlattenedCanonicalIV(fir::FirOpBuilder &builder, mlir::Location loc, |
| mlir::omp::LoopNestOp loopNestOp); |
| |
| static void initConditionalLpStructDefault(fir::FirOpBuilder &builder, |
| mlir::Location loc, |
| fir::RecordType lpCondType, |
| mlir::Value structRef); |
| |
| static void initConditionalLpStruct( |
| Fortran::lower::AbstractConverter &converter, mlir::Location loc, |
| fir::RecordType lpCondType, mlir::Value structRef, |
| const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms); |
| |
| static mlir::Value |
| getOrCreateConditionalLpGlobal(Fortran::lower::AbstractConverter &converter, |
| mlir::Location loc, fir::RecordType lpType); |
| |
| static void |
| emitNestedParallelGuardForCondLp(lower::AbstractConverter &converter, |
| mlir::Location loc); |
| |
| //===----------------------------------------------------------------------===// |
| // Code generation helper functions |
| //===----------------------------------------------------------------------===// |
| |
| static void genOMPDispatch(lower::AbstractConverter &converter, |
| lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item); |
| |
| /// Return the directive that is immediately nested inside of the given |
| /// \c parent evaluation, if it is its only non-end-statement nested evaluation |
| /// and it represents an OpenMP construct. |
| static lower::pft::Evaluation * |
| extractOnlyOmpNestedEval(lower::pft::Evaluation &parent) { |
| if (!parent.hasNestedEvaluations()) |
| return nullptr; |
| |
| auto &nested = parent.getFirstNestedEvaluation(); |
| if (!nested.isA<parser::OpenMPConstruct>()) |
| return nullptr; |
| |
| for (auto &sibling : parent.getNestedEvaluations()) |
| if (&sibling != &nested && !sibling.isEndStmt()) |
| return nullptr; |
| |
| return &nested; |
| } |
| |
| static llvm::SmallVector<Object> |
| makeObjects(llvm::ArrayRef<const semantics::Symbol *> syms) { |
| llvm::SmallVector<Object> objects; |
| objects.reserve(syms.size()); |
| llvm::transform( |
| syms, std::back_inserter(objects), [](const semantics::Symbol *sym) { |
| return Object{const_cast<semantics::Symbol *>(sym), std::nullopt}; |
| }); |
| return objects; |
| } |
| |
| static bool hasPrivatizedArrayElementReduction( |
| llvm::ArrayRef<Object> reductionObjects, |
| const llvm::SetVector<const semantics::Symbol *> &privatizedSymbols) { |
| for (const Object &object : reductionObjects) { |
| if (!object.sym() || !object.ref()) |
| continue; |
| std::optional<evaluate::DataRef> dataRef = |
| evaluate::ExtractDataRef(*object.ref()); |
| if (!dataRef) |
| continue; |
| const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u); |
| if (!arrayRef || |
| llvm::any_of(arrayRef->subscript(), [](const auto &subscript) { |
| return std::holds_alternative<evaluate::Triplet>(subscript.u); |
| })) |
| continue; |
| |
| const semantics::Symbol &ultimate = object.sym()->GetUltimate(); |
| if (llvm::any_of(privatizedSymbols, |
| [&](const semantics::Symbol *privatizedSymbol) { |
| return privatizedSymbol->GetUltimate() == ultimate; |
| })) |
| return true; |
| } |
| return false; |
| } |
| |
| /// Structure holding the information needed to create and bind entry block |
| /// arguments associated to a single clause during OpenMP lowering. |
| struct ObjectEntryBlockArgsEntry { |
| llvm::SmallVector<Object> objects; |
| llvm::ArrayRef<mlir::Value> vars; |
| |
| bool isValid() const { return objects.size() <= vars.size(); } |
| |
| llvm::SmallVector<const semantics::Symbol *> getSyms() const { |
| llvm::SmallVector<const semantics::Symbol *> syms; |
| syms.reserve(objects.size()); |
| llvm::transform(objects, std::back_inserter(syms), |
| [](const Object &object) { return object.sym(); }); |
| return syms; |
| } |
| }; |
| |
| struct ObjectEntryBlockArgs { |
| ObjectEntryBlockArgsEntry hasDeviceAddr; |
| llvm::ArrayRef<mlir::Value> hostEvalVars; |
| ObjectEntryBlockArgsEntry inReduction; |
| ObjectEntryBlockArgsEntry map; |
| ObjectEntryBlockArgsEntry priv; |
| ObjectEntryBlockArgsEntry reduction; |
| ObjectEntryBlockArgsEntry taskReduction; |
| ObjectEntryBlockArgsEntry useDeviceAddr; |
| ObjectEntryBlockArgsEntry useDevicePtr; |
| |
| bool isValid() const { |
| return hasDeviceAddr.isValid() && inReduction.isValid() && map.isValid() && |
| priv.isValid() && reduction.isValid() && taskReduction.isValid() && |
| useDeviceAddr.isValid() && useDevicePtr.isValid(); |
| } |
| |
| llvm::SmallVector<const semantics::Symbol *> getSyms() const { |
| llvm::SmallVector<const semantics::Symbol *> syms; |
| auto appendSyms = [&syms](const ObjectEntryBlockArgsEntry &entry) { |
| syms.reserve(syms.size() + entry.objects.size()); |
| llvm::transform(entry.objects, std::back_inserter(syms), |
| [](const Object &object) { return object.sym(); }); |
| }; |
| appendSyms(hasDeviceAddr); |
| appendSyms(inReduction); |
| appendSyms(map); |
| appendSyms(priv); |
| appendSyms(reduction); |
| appendSyms(taskReduction); |
| appendSyms(useDeviceAddr); |
| appendSyms(useDevicePtr); |
| return syms; |
| } |
| |
| auto getVars() const { |
| return llvm::concat<const mlir::Value>( |
| hasDeviceAddr.vars, hostEvalVars, inReduction.vars, map.vars, priv.vars, |
| reduction.vars, taskReduction.vars, useDeviceAddr.vars, |
| useDevicePtr.vars); |
| } |
| |
| Fortran::common::openmp::EntryBlockArgs asEntryBlockArgs() const { |
| Fortran::common::openmp::EntryBlockArgs args; |
| args.hasDeviceAddrVars = hasDeviceAddr.vars; |
| args.hostEvalVars = hostEvalVars; |
| args.inReductionVars = inReduction.vars; |
| args.mapVars = map.vars; |
| args.privVars = priv.vars; |
| args.reductionVars = reduction.vars; |
| args.taskReductionVars = taskReduction.vars; |
| args.useDeviceAddrVars = useDeviceAddr.vars; |
| args.useDevicePtrVars = useDevicePtr.vars; |
| return args; |
| } |
| }; |
| |
| namespace { |
| /// Structure holding information that is needed to pass host-evaluated |
| /// information to later lowering stages. |
| class HostEvalInfo { |
| public: |
| friend class HostEvalVisitor; |
| |
| /// Fill \c vars with values stored in \c ops. |
| /// |
| /// The order in which values are stored matches the one expected by \see |
| /// bindOperands(). |
| void collectValues(llvm::SmallVectorImpl<mlir::Value> &vars) const { |
| vars.append(ops.loopLowerBounds); |
| vars.append(ops.loopUpperBounds); |
| vars.append(ops.loopSteps); |
| |
| if (ops.numTeamsLower) |
| vars.push_back(ops.numTeamsLower); |
| |
| for (auto numTeamsUpper : ops.numTeamsUpperVars) |
| vars.push_back(numTeamsUpper); |
| |
| for (auto numThreads : ops.numThreadsVars) |
| vars.push_back(numThreads); |
| |
| for (mlir::Value val : ops.threadLimitVars) |
| vars.push_back(val); |
| } |
| |
| /// Update \c ops, replacing all values with the corresponding block argument |
| /// in \c args. |
| /// |
| /// The order in which values are stored in \c args is the same as the one |
| /// used by \see collectValues(). |
| void bindOperands(llvm::ArrayRef<mlir::BlockArgument> args) { |
| assert(args.size() == |
| ops.loopLowerBounds.size() + ops.loopUpperBounds.size() + |
| ops.loopSteps.size() + (ops.numTeamsLower ? 1 : 0) + |
| ops.numTeamsUpperVars.size() + ops.numThreadsVars.size() + |
| ops.threadLimitVars.size() && |
| "invalid block argument list"); |
| int argIndex = 0; |
| for (size_t i = 0; i < ops.loopLowerBounds.size(); ++i) |
| ops.loopLowerBounds[i] = args[argIndex++]; |
| |
| for (size_t i = 0; i < ops.loopUpperBounds.size(); ++i) |
| ops.loopUpperBounds[i] = args[argIndex++]; |
| |
| for (size_t i = 0; i < ops.loopSteps.size(); ++i) |
| ops.loopSteps[i] = args[argIndex++]; |
| |
| if (ops.numTeamsLower) |
| ops.numTeamsLower = args[argIndex++]; |
| |
| for (size_t i = 0; i < ops.numTeamsUpperVars.size(); ++i) |
| ops.numTeamsUpperVars[i] = args[argIndex++]; |
| |
| for (size_t i = 0; i < ops.numThreadsVars.size(); ++i) |
| ops.numThreadsVars[i] = args[argIndex++]; |
| |
| for (size_t i = 0; i < ops.threadLimitVars.size(); ++i) |
| ops.threadLimitVars[i] = args[argIndex++]; |
| } |
| |
| /// Update \p clauseOps and \p ivOut with the corresponding host-evaluated |
| /// values and Fortran symbols, respectively, if they have already been |
| /// initialized but not yet applied. |
| /// |
| /// \returns whether an update was performed. If not, these clauses were not |
| /// evaluated in the host device. |
| bool apply(mlir::omp::LoopNestOperands &clauseOps, |
| llvm::SmallVectorImpl<const semantics::Symbol *> &ivOut) { |
| if (iv.empty() || loopNestApplied) { |
| loopNestApplied = true; |
| return false; |
| } |
| |
| loopNestApplied = true; |
| clauseOps.loopLowerBounds = ops.loopLowerBounds; |
| clauseOps.loopUpperBounds = ops.loopUpperBounds; |
| clauseOps.loopSteps = ops.loopSteps; |
| clauseOps.collapseNumLoops = ops.collapseNumLoops; |
| ivOut.append(iv); |
| return true; |
| } |
| |
| /// Update \p clauseOps with the corresponding host-evaluated values if they |
| /// have already been initialized but not yet applied. |
| /// |
| /// \returns whether an update was performed. If not, these clauses were not |
| /// evaluated in the host device. |
| bool apply(mlir::omp::ParallelOperands &clauseOps) { |
| if (ops.numThreadsVars.empty() || parallelApplied) { |
| parallelApplied = true; |
| return false; |
| } |
| |
| parallelApplied = true; |
| clauseOps.numThreadsVars = ops.numThreadsVars; |
| return true; |
| } |
| |
| /// Update \p clauseOps with the corresponding host-evaluated values if they |
| /// have already been initialized. |
| /// |
| /// \returns whether an update was performed. If not, these clauses were not |
| /// evaluated in the host device. |
| bool apply(mlir::omp::TeamsOperands &clauseOps) { |
| if (!ops.numTeamsLower && ops.numTeamsUpperVars.empty() && |
| ops.threadLimitVars.empty()) |
| return false; |
| |
| clauseOps.numTeamsLower = ops.numTeamsLower; |
| clauseOps.numTeamsUpperVars = ops.numTeamsUpperVars; |
| clauseOps.threadLimitVars = ops.threadLimitVars; |
| return true; |
| } |
| |
| private: |
| mlir::omp::HostEvaluatedOperands ops; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| bool loopNestApplied = false, parallelApplied = false; |
| }; |
| |
| /// A base class to help iterate over OpenMP constructs based on an expected |
| /// sequence. |
| /// |
| /// The main entry point visit() will call visitDirective() for the OpenMP |
| /// directive associated to the initial given evaluation based on whether it is |
| /// part of the initial set of directives of interest. A nested OpenMP |
| /// evaluation might optionally be also visited by the pattern recursively if it |
| /// meets all of the following conditions: |
| /// - It is the only nested evaluation, apart from an optional END statement |
| /// associated to the same directive. |
| /// - The OpenMP directive is part of the directive set returned by the |
| /// `visitDirective` call for the parent. |
| /// |
| /// Subclasses define the expected pattern by implementing the initialize() and |
| /// visitDirective() methods, and users are expected to use visit() to trigger |
| /// the complete pattern visit. |
| class DirectivePatternVisitor { |
| public: |
| DirectivePatternVisitor(semantics::SemanticsContext &semaCtx) |
| : semaCtx{semaCtx} {} |
| virtual ~DirectivePatternVisitor() = default; |
| |
| /// Run the pattern from the given evaluation. |
| void visit(lower::pft::Evaluation &eval) { |
| directivesOfInterest = initialize(); |
| visitEval(eval); |
| } |
| |
| protected: |
| /// Initializes the visitor and returns the set of initial directives of |
| /// interest to be matched the beginning of the pattern. |
| virtual llvm::omp::DirectiveSet initialize() = 0; |
| |
| /// Visits a single directive and, based on it, returns the set of other |
| /// directives of interest that would be part of the pattern if nested inside. |
| virtual llvm::omp::DirectiveSet visitDirective(lower::pft::Evaluation &eval, |
| llvm::omp::Directive dir) = 0; |
| |
| /// Obtain the list of clauses of the given OpenMP block or loop construct |
| /// evaluation. If it's not an OpenMP construct, no modifications are made to |
| /// the \c clauses output argument. |
| void extractClauses(lower::pft::Evaluation &eval, List<Clause> &clauses) { |
| const auto *ompEval{eval.getIf<parser::OpenMPConstruct>()}; |
| if (!ompEval) |
| return; |
| |
| const parser::OmpClauseList *beginClauseList{nullptr}; |
| const parser::OmpClauseList *endClauseList{nullptr}; |
| common::visit( |
| [&](const auto &construct) { |
| using Type = llvm::remove_cvref_t<decltype(construct)>; |
| if constexpr (std::is_same_v<Type, parser::OmpBlockConstruct> || |
| std::is_same_v<Type, parser::OpenMPLoopConstruct>) { |
| beginClauseList = &construct.BeginDir().Clauses(); |
| if (auto &endSpec{construct.EndDir()}) |
| endClauseList = &endSpec->Clauses(); |
| } |
| }, |
| ompEval->u); |
| |
| assert(beginClauseList && "expected begin directive"); |
| clauses.append(makeClauses(*beginClauseList, semaCtx)); |
| |
| if (endClauseList) |
| clauses.append(makeClauses(*endClauseList, semaCtx)); |
| } |
| |
| private: |
| /// Decide whether an evaluation must be visited as part of the pattern. |
| /// |
| /// This is the case whenever it's an OpenMP construct and the associated |
| /// directive is part of the current set of directives of interest. |
| bool shouldVisitEval(lower::pft::Evaluation &eval) const { |
| const auto *ompEval{eval.getIf<parser::OpenMPConstruct>()}; |
| if (!ompEval) |
| return false; |
| |
| return directivesOfInterest.test( |
| parser::omp::GetOmpDirectiveName(*ompEval).v); |
| } |
| |
| /// Visits an evaluation and, potentially, recursively visits a single |
| /// nested evaluation. |
| /// |
| /// For a nested evaluation to be recursively visited, it must be an OpenMP |
| /// construct, have no sibling evaluations and match one of the |
| /// next-directives of interest set returned by a call to visitDirective() |
| /// on the parent evaluation. |
| void visitEval(lower::pft::Evaluation &eval) { |
| if (!shouldVisitEval(eval)) |
| return; |
| |
| const auto &ompEval{eval.get<parser::OpenMPConstruct>()}; |
| llvm::omp::DirectiveSet visitNested{ |
| visitDirective(eval, parser::omp::GetOmpDirectiveName(ompEval).v)}; |
| |
| if (visitNested.none()) |
| return; |
| |
| if (lower::pft::Evaluation *nestedEval = extractOnlyOmpNestedEval(eval)) { |
| llvm::omp::DirectiveSet prevDirs{directivesOfInterest}; |
| directivesOfInterest = visitNested; |
| visitEval(*nestedEval); |
| directivesOfInterest = prevDirs; |
| } |
| } |
| |
| protected: |
| semantics::SemanticsContext &semaCtx; |
| |
| private: |
| llvm::omp::DirectiveSet directivesOfInterest; |
| }; |
| |
| /// Helper pattern to navigate target SPMD. |
| class TargetSPMDVisitor : public DirectivePatternVisitor { |
| public: |
| using DirectivePatternVisitor::DirectivePatternVisitor; |
| virtual ~TargetSPMDVisitor() = default; |
| |
| protected: |
| virtual llvm::omp::DirectiveSet initialize() override { |
| teamsVisited = false; |
| return llvm::omp::allTargetSet; |
| } |
| |
| virtual llvm::omp::DirectiveSet |
| visitDirective(lower::pft::Evaluation &eval, |
| llvm::omp::Directive dir) override { |
| using namespace llvm::omp; |
| |
| // The default implementation does nothing, except it returns the allowed |
| // single nested directives for an SPMD kernel. If called by subclasses, it |
| // helps navigate SPMD patterns. |
| // |
| // Patterns considered SPMD: |
| // - target teams distribute parallel do [simd] |
| // - target teams loop |
| // - target parallel do [simd] |
| // - target parallel loop |
| switch (dir) { |
| case OMPD_target: |
| return topTeamsSet | topParallelSet; |
| case OMPD_target_teams: |
| // The 'bare' kernel type prevents the SPMD pattern from matching. |
| if (hasOmpxBareClause(eval)) |
| return {}; |
| [[fallthrough]]; |
| case OMPD_teams: |
| teamsVisited = true; |
| return topDistributeSet | topLoopSet; |
| case OMPD_target_parallel: |
| case OMPD_parallel: |
| return topLoopSet | topDoSet; |
| default: |
| return {}; |
| } |
| } |
| |
| bool hasOmpxBareClause(lower::pft::Evaluation &eval) { |
| List<lower::omp::Clause> clauses; |
| extractClauses(eval, clauses); |
| |
| return llvm::find_if(clauses, [](const Clause &clause) { |
| return std::holds_alternative<clause::OmpxBare>(clause.u); |
| }) != clauses.end(); |
| } |
| |
| protected: |
| /// Whether a `teams` construct has been visited by visitDirective(). |
| bool teamsVisited; |
| }; |
| |
| /// Populates the given HostEvalInfo structure after processing clauses for |
| /// the given \p eval OpenMP target construct, or nested constructs, if these |
| /// must be evaluated outside of the target region per the spec. |
| /// |
| /// In particular, this will ensure that in <tt>target teams</tt> and equivalent |
| /// nested constructs, the \c thread_limit, \c num_teams and \c num_threads |
| /// clauses will be evaluated in the host. Additionally, loop bounds and steps |
| /// will also be evaluated in the host if a <tt>target teams distribute</tt> or |
| /// target SPMD construct is detected (i.e. <tt>target teams distribute parallel |
| /// do [simd]</tt>, <tt>target parallel do [simd]</tt> or equivalent nesting). |
| /// |
| /// The resulting updated HostEvalInfo structure is intended to be used to |
| /// populate the \c host_eval operands of the associated \c omp.target |
| /// operation, and also to be checked and used by later lowering steps to |
| /// populate the corresponding operands of the \c omp.teams, \c omp.parallel or |
| /// \c omp.loop_nest operations. |
| class HostEvalVisitor : public TargetSPMDVisitor { |
| public: |
| HostEvalVisitor(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, mlir::Location loc, |
| HostEvalInfo &hostEvalInfo) |
| : TargetSPMDVisitor{semaCtx}, converter{converter}, stmtCtx{stmtCtx}, |
| loc{loc}, hostEvalInfo{hostEvalInfo} {} |
| virtual ~HostEvalVisitor() = default; |
| |
| protected: |
| virtual llvm::omp::DirectiveSet |
| visitDirective(lower::pft::Evaluation &eval, |
| llvm::omp::Directive dir) override { |
| using namespace llvm::omp; |
| |
| List<lower::omp::Clause> clauses; |
| extractClauses(eval, clauses); |
| ClauseProcessor cp{converter, semaCtx, clauses}; |
| |
| // Currently, we deal differently with e.g. `target parallel workshare` to |
| // `target parallel` with a single nested `workshare`. The first case would |
| // result in no clauses being evaluated in the host, as there's not a case |
| // for it in the below switch statement. The second case would evaluate |
| // `num_threads` clauses in the host, because `target parallel` could be |
| // followed by a `do` construct, which would make this an SPMD target |
| // region. |
| // |
| // TODO: We don't probably want to have such divergent behavior when dealing |
| // with combined directives. We need to revisit this logic without listing |
| // every possible combined directive containing a clause we'd otherwise |
| // evaluate in the host if the directive was split into its leafs. |
| switch (dir) { |
| case OMPD_teams_distribute_parallel_do: |
| case OMPD_teams_distribute_parallel_do_simd: |
| cp.processThreadLimit(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_target_teams_distribute_parallel_do: |
| case OMPD_target_teams_distribute_parallel_do_simd: |
| cp.processNumTeams(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_distribute_parallel_do: |
| case OMPD_distribute_parallel_do_simd: |
| case OMPD_target_parallel_do: |
| case OMPD_target_parallel_do_simd: |
| case OMPD_target_parallel_loop: |
| case OMPD_parallel_do: |
| case OMPD_parallel_do_simd: |
| case OMPD_parallel_loop: |
| cp.processNumThreads(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_distribute: |
| case OMPD_distribute_simd: |
| case OMPD_do: |
| case OMPD_do_simd: |
| cp.processCollapse(loc, eval, hostEvalInfo.ops, hostEvalInfo.ops, |
| hostEvalInfo.iv); |
| return {}; |
| |
| case OMPD_teams: |
| cp.processThreadLimit(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_target_teams: |
| cp.processNumTeams(stmtCtx, hostEvalInfo.ops); |
| break; |
| |
| case OMPD_teams_distribute: |
| case OMPD_teams_distribute_simd: |
| cp.processThreadLimit(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_target_teams_distribute: |
| case OMPD_target_teams_distribute_simd: |
| cp.processCollapse(loc, eval, hostEvalInfo.ops, hostEvalInfo.ops, |
| hostEvalInfo.iv); |
| cp.processNumTeams(stmtCtx, hostEvalInfo.ops); |
| return {}; |
| |
| case OMPD_teams_loop: |
| cp.processThreadLimit(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_target_teams_loop: |
| cp.processNumTeams(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_loop: |
| cp.processCollapse(loc, eval, hostEvalInfo.ops, hostEvalInfo.ops, |
| hostEvalInfo.iv); |
| return {}; |
| |
| case OMPD_teams_workdistribute: |
| cp.processThreadLimit(stmtCtx, hostEvalInfo.ops); |
| [[fallthrough]]; |
| case OMPD_target_teams_workdistribute: |
| cp.processNumTeams(stmtCtx, hostEvalInfo.ops); |
| break; |
| |
| case OMPD_target_parallel: |
| case OMPD_parallel: |
| cp.processNumThreads(stmtCtx, hostEvalInfo.ops); |
| break; |
| |
| case OMPD_target: |
| break; |
| |
| default: |
| return {}; |
| } |
| |
| // Visit nested directives as per the SPMD pattern. |
| return TargetSPMDVisitor::visitDirective(eval, dir); |
| } |
| |
| private: |
| lower::AbstractConverter &converter; |
| lower::StatementContext &stmtCtx; |
| mlir::Location loc; |
| HostEvalInfo &hostEvalInfo; |
| }; |
| |
| /// Checks target regions and, based on the directives and clauses encountered, |
| /// determines its associated kernel type. |
| class KernelTypeVisitor : protected TargetSPMDVisitor { |
| public: |
| KernelTypeVisitor(semantics::SemanticsContext &semaCtx, |
| mlir::ModuleOp moduleOp) |
| : TargetSPMDVisitor{semaCtx}, moduleOp{moduleOp} {} |
| virtual ~KernelTypeVisitor() = default; |
| |
| /// Executes the pattern and returns the kernel type of the given target |
| /// region, or \c mlir::omp::TargetExecMode::generic by default for non-target |
| /// evaluations. |
| mlir::omp::TargetExecMode getKernelType(lower::pft::Evaluation &eval) { |
| execMode = mlir::omp::TargetExecMode::generic; |
| visit(eval); |
| return execMode; |
| } |
| |
| protected: |
| virtual llvm::omp::DirectiveSet |
| visitDirective(lower::pft::Evaluation &eval, |
| llvm::omp::Directive dir) override { |
| using namespace llvm::omp; |
| |
| // We know this to be the case because any changes to the exec mode are made |
| // only when we know for sure what it is, so pattern matching is always |
| // stopped at these points. |
| assert(execMode == mlir::omp::TargetExecMode::generic && |
| "unexpected non-default exec mode during pattern match"); |
| |
| switch (dir) { |
| case OMPD_target: |
| case OMPD_target_parallel: |
| case OMPD_parallel: |
| case OMPD_teams: |
| break; |
| case OMPD_target_teams: |
| if (hasOmpxBareClause(eval)) { |
| execMode = mlir::omp::TargetExecMode::bare; |
| return {}; |
| } |
| break; |
| case OMPD_target_teams_distribute_parallel_do: |
| case OMPD_target_teams_distribute_parallel_do_simd: |
| case OMPD_target_teams_loop: |
| case OMPD_teams_distribute_parallel_do: |
| case OMPD_teams_distribute_parallel_do_simd: |
| case OMPD_teams_loop: |
| case OMPD_distribute_parallel_do: |
| case OMPD_distribute_parallel_do_simd: |
| execMode = canPromoteSPMDToNoLoop(eval) |
| ? mlir::omp::TargetExecMode::spmd_no_loop |
| : mlir::omp::TargetExecMode::spmd; |
| return {}; |
| case OMPD_target_parallel_do: |
| case OMPD_target_parallel_do_simd: |
| case OMPD_target_parallel_loop: |
| case OMPD_parallel_do: |
| case OMPD_parallel_do_simd: |
| case OMPD_parallel_loop: |
| case OMPD_do: |
| case OMPD_do_simd: |
| // SPMD kernels without a `teams` construct cannot be promoted to no-loop |
| // mode. |
| execMode = mlir::omp::TargetExecMode::spmd; |
| return {}; |
| case OMPD_loop: |
| // Prevent `target parallel loop` or equivalent nests to be promoted to |
| // no-loop mode. |
| execMode = teamsVisited && canPromoteSPMDToNoLoop(eval) |
| ? mlir::omp::TargetExecMode::spmd_no_loop |
| : mlir::omp::TargetExecMode::spmd; |
| return {}; |
| default: |
| return {}; |
| } |
| |
| // Visit nested directives as per the SPMD pattern. |
| return TargetSPMDVisitor::visitDirective(eval, dir); |
| } |
| |
| private: |
| bool canPromoteSPMDToNoLoop(lower::pft::Evaluation &eval) { |
| List<lower::omp::Clause> clauses; |
| extractClauses(eval, clauses); |
| |
| // First make sure the proper module attributes are present in order to |
| // perform this optimization. |
| auto ompFlags{ |
| llvm::cast<mlir::omp::OffloadModuleInterface>(*moduleOp).getFlags()}; |
| if (!ompFlags || !ompFlags.getAssumeTeamsOversubscription() || |
| !ompFlags.getAssumeThreadsOversubscription()) |
| return false; |
| |
| // The num_teams clause can break no-loop assumptions, and reductions are |
| // slower in no-loop mode. |
| return llvm::find_if(clauses, [](const Clause &clause) { |
| return std::holds_alternative<clause::NumTeams>(clause.u) || |
| std::holds_alternative<clause::Reduction>(clause.u); |
| }) == clauses.end(); |
| } |
| |
| private: |
| mlir::ModuleOp moduleOp; |
| mlir::omp::TargetExecMode execMode; |
| }; |
| |
| } // namespace |
| |
| /// Stack of \see HostEvalInfo to represent the current nest of \c omp.target |
| /// operations being created. |
| /// |
| /// The current implementation prevents nested 'target' regions from breaking |
| /// the handling of the outer region by keeping a stack of information |
| /// structures, but it will probably still require some further work to support |
| /// reverse offloading. |
| class HostEvalInfoStackFrame |
| : public mlir::StateStackFrameBase<HostEvalInfoStackFrame> { |
| public: |
| MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(HostEvalInfoStackFrame) |
| |
| HostEvalInfo info; |
| }; |
| |
| static HostEvalInfo * |
| getHostEvalInfoStackTop(lower::AbstractConverter &converter) { |
| HostEvalInfoStackFrame *frame = |
| converter.getStateStack().getStackTop<HostEvalInfoStackFrame>(); |
| return frame ? &frame->info : nullptr; |
| } |
| |
| /// Stack frame for storing the OpenMPSectionsConstruct currently being |
| /// processed so that it can be referred to when lowering the construct. |
| class SectionsConstructStackFrame |
| : public mlir::StateStackFrameBase<SectionsConstructStackFrame> { |
| public: |
| MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(SectionsConstructStackFrame) |
| |
| explicit SectionsConstructStackFrame( |
| const parser::OpenMPSectionsConstruct §ionsConstruct) |
| : sectionsConstruct{sectionsConstruct} {} |
| |
| const parser::OpenMPSectionsConstruct §ionsConstruct; |
| }; |
| |
| static const parser::OpenMPSectionsConstruct * |
| getSectionsConstructStackTop(lower::AbstractConverter &converter) { |
| SectionsConstructStackFrame *frame = |
| converter.getStateStack().getStackTop<SectionsConstructStackFrame>(); |
| return frame ? &frame->sectionsConstruct : nullptr; |
| } |
| |
| /// Bind objects to their corresponding entry block arguments. |
| /// |
| /// The binding will be performed inside of the current block, which does not |
| /// necessarily have to be part of the operation for which the binding is done. |
| /// However, block arguments must be accessible. This enables controlling the |
| /// insertion point of any new MLIR operations related to the binding of |
| /// arguments of a loop wrapper operation. |
| /// |
| /// \param [in] converter - PFT to MLIR conversion interface. |
| /// \param [in] op - owner operation of the block arguments to bind. |
| /// \param [in] args - entry block arguments information for the given |
| /// operation. |
| static void bindEntryBlockArgs(lower::AbstractConverter &converter, |
| mlir::omp::BlockArgOpenMPOpInterface op, |
| const ObjectEntryBlockArgs &args) { |
| assert(op != nullptr && "invalid block argument-defining operation"); |
| assert(args.isValid() && "invalid args"); |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| auto bindSingleMapLike = [&converter](const semantics::Symbol &sym, |
| const mlir::BlockArgument &arg) { |
| fir::ExtendedValue extVal = converter.getSymbolExtendedValue(sym); |
| auto refType = mlir::dyn_cast<fir::ReferenceType>(arg.getType()); |
| if (refType && fir::isa_builtin_cptr_type(refType.getElementType())) { |
| converter.bindSymbol(sym, arg); |
| } else { |
| extVal.match( |
| [&](const fir::BoxValue &v) { |
| converter.bindSymbol(sym, fir::BoxValue(arg, v.getLBounds(), |
| v.getExplicitParameters(), |
| v.getExplicitExtents())); |
| }, |
| [&](const fir::MutableBoxValue &v) { |
| converter.bindSymbol( |
| sym, fir::MutableBoxValue(arg, v.getLBounds(), |
| v.getMutableProperties())); |
| }, |
| [&](const fir::ArrayBoxValue &v) { |
| converter.bindSymbol(sym, fir::ArrayBoxValue(arg, v.getExtents(), |
| v.getLBounds(), |
| v.getSourceBox())); |
| }, |
| [&](const fir::CharArrayBoxValue &v) { |
| converter.bindSymbol(sym, fir::CharArrayBoxValue(arg, v.getLen(), |
| v.getExtents(), |
| v.getLBounds())); |
| }, |
| [&](const fir::CharBoxValue &v) { |
| converter.bindSymbol(sym, fir::CharBoxValue(arg, v.getLen())); |
| }, |
| [&](const fir::UnboxedValue &v) { converter.bindSymbol(sym, arg); }, |
| [&](const auto &) { |
| TODO(converter.getCurrentLocation(), |
| "target map clause operand unsupported type"); |
| }); |
| } |
| }; |
| |
| auto bindMapLike = |
| [&bindSingleMapLike](llvm::ArrayRef<Object> objects, |
| llvm::ArrayRef<mlir::BlockArgument> args) { |
| // Structure component symbols don't have bindings, and can only be |
| // explicitly mapped individually. If a member is captured implicitly |
| // we map the entirety of the derived type when we find its symbol. |
| llvm::SmallVector<const semantics::Symbol *> processedSyms; |
| for (const Object &object : objects) { |
| const semantics::Symbol *sym = object.sym(); |
| if (!sym->owner().IsDerivedType()) |
| processedSyms.push_back(sym); |
| } |
| |
| for (auto [sym, arg] : llvm::zip_equal(processedSyms, args)) |
| bindSingleMapLike(*sym, arg); |
| }; |
| |
| auto bindPrivateLike = [&converter, &firOpBuilder]( |
| llvm::ArrayRef<Object> objects, |
| llvm::ArrayRef<mlir::Value> vars, |
| llvm::ArrayRef<mlir::BlockArgument> args) { |
| llvm::SmallVector<const semantics::Symbol *> processedSyms; |
| for (const Object &object : objects) { |
| const semantics::Symbol *sym = object.sym(); |
| if (!sym) { |
| // Null sentinel: this entry corresponds to a compiler-synthesized |
| // reduction (e.g. the conditional lastprivate struct) that has no |
| // Fortran symbol. We must keep a placeholder so that processedSyms |
| // stays in lock-step with `vars` and `args` — the later |
| // llvm::zip_equal(processedSyms, vars, args) asserts equal lengths. |
| // The matching block argument is silently skipped below. |
| processedSyms.push_back(nullptr); |
| continue; |
| } |
| if (const auto *commonDet = |
| sym->detailsIf<semantics::CommonBlockDetails>()) { |
| llvm::transform(commonDet->objects(), std::back_inserter(processedSyms), |
| [&](const auto &mem) { return &*mem; }); |
| } else { |
| processedSyms.push_back(sym); |
| } |
| } |
| |
| for (auto [sym, var, arg] : llvm::zip_equal(processedSyms, vars, args)) { |
| if (!sym) |
| continue; // Skip synthetic reduction entries (no Fortran symbol). |
| converter.bindSymbol( |
| *sym, |
| hlfir::translateToExtendedValue( |
| var.getLoc(), firOpBuilder, hlfir::Entity{arg}, |
| /*contiguousHint=*/ |
| evaluate::IsSimplyContiguous(*sym, converter.getFoldingContext())) |
| .first); |
| } |
| }; |
| |
| // Process in clause name alphabetical order to match block arguments order. |
| // Do not bind host_eval variables because they cannot be used inside of the |
| // corresponding region, except for very specific cases handled separately. |
| bindMapLike(args.hasDeviceAddr.objects, op.getHasDeviceAddrBlockArgs()); |
| bindPrivateLike(args.inReduction.objects, args.inReduction.vars, |
| op.getInReductionBlockArgs()); |
| bindMapLike(args.map.objects, op.getMapBlockArgs()); |
| bindPrivateLike(args.priv.objects, args.priv.vars, op.getPrivateBlockArgs()); |
| bindPrivateLike(args.reduction.objects, args.reduction.vars, |
| op.getReductionBlockArgs()); |
| bindPrivateLike(args.taskReduction.objects, args.taskReduction.vars, |
| op.getTaskReductionBlockArgs()); |
| bindMapLike(args.useDeviceAddr.objects, op.getUseDeviceAddrBlockArgs()); |
| bindMapLike(args.useDevicePtr.objects, op.getUseDevicePtrBlockArgs()); |
| } |
| |
| /// Get the list of base values that the specified map-like variables point to. |
| /// |
| /// This function must be kept in sync with changes to the `createMapInfoOp` |
| /// utility function, since it must take into account the potential introduction |
| /// of levels of indirection (i.e. intermediate ops). |
| /// |
| /// \param [in] vars - list of values passed to map-like clauses, returned |
| /// by an `omp.map.info` operation. |
| /// \param [out] baseOps - populated with the `var_ptr` values of the |
| /// corresponding defining operations. |
| static void |
| extractMappedBaseValues(llvm::ArrayRef<mlir::Value> vars, |
| llvm::SmallVectorImpl<mlir::Value> &baseOps) { |
| llvm::transform(vars, std::back_inserter(baseOps), [](mlir::Value map) { |
| auto mapInfo = map.getDefiningOp<mlir::omp::MapInfoOp>(); |
| assert(mapInfo && "expected all map vars to be defined by omp.map.info"); |
| |
| mlir::Value varPtr = mapInfo.getVarPtr(); |
| if (auto boxAddr = varPtr.getDefiningOp<fir::BoxAddrOp>()) |
| return boxAddr.getVal(); |
| |
| return varPtr; |
| }); |
| } |
| |
| static lower::pft::Evaluation * |
| getCollapsedLoopEval(lower::pft::Evaluation &eval, int collapseValue) { |
| // Return the Evaluation of the innermost collapsed loop, or the current one |
| // if there was no COLLAPSE. |
| if (collapseValue == 0) |
| return &eval; |
| |
| lower::pft::Evaluation *curEval = &eval; |
| for (int i = 0; i < collapseValue; i++) |
| curEval = getNestedDoConstruct(*curEval); |
| return curEval; |
| } |
| |
| static void genNestedEvaluations(lower::AbstractConverter &converter, |
| lower::pft::Evaluation &eval, |
| int collapseValue = 0) { |
| lower::pft::Evaluation *curEval = getCollapsedLoopEval(eval, collapseValue); |
| |
| for (lower::pft::Evaluation &e : curEval->getNestedEvaluations()) |
| converter.genEval(e); |
| } |
| |
| static mlir::Operation *setLoopVar(lower::AbstractConverter &converter, |
| mlir::Location loc, mlir::Value indexVal, |
| const semantics::Symbol *sym); |
| |
| /// Emit the body of a collapsed loop nest, including any intervening code |
| /// from imperfect nesting at intermediate levels (CLN relaxation, applied |
| /// retroactively for all OMP versions). |
| /// |
| /// Because omp.loop_nest places its entire body at the innermost nesting |
| /// level, intervening code must be guarded so that it only executes on the |
| /// iterations where the corresponding inner induction variables are at their |
| /// initial (for intervening code before nested loop) or final (for intervening |
| /// code after nested loop) values. |
| /// |
| /// \param [in] converter - PFT to MLIR conversion interface. |
| /// \param [in] outerEval - the evaluation containing the outermost loop |
| /// (typically the OpenMP construct evaluation). |
| /// \param [in] collapseValue - number of loops being collapsed (>= 1). |
| static void genCollapsedLoopNestBody(lower::AbstractConverter &converter, |
| lower::pft::Evaluation &outerEval, |
| int collapseValue) { |
| assert(collapseValue >= 1); |
| if (collapseValue == 1) { |
| genNestedEvaluations(converter, outerEval, /*collapseValue=*/1); |
| return; |
| } |
| |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| const mlir::Location loc = converter.getCurrentLocation(); |
| |
| // Get the enclosing omp.loop_nest to access induction variables and bounds. |
| auto loopNestOp = mlir::dyn_cast<mlir::omp::LoopNestOp>( |
| firOpBuilder.getInsertionBlock()->getParentOp()); |
| assert(loopNestOp && "expected to be inside omp.loop_nest"); |
| |
| // Collect before/after evaluations at each intermediate level. |
| struct LevelInfo { |
| llvm::SmallVector<lower::pft::Evaluation *> before; |
| llvm::SmallVector<lower::pft::Evaluation *> after; |
| |
| // Whether this level carries intervening code (i.e. imperfect nesting). |
| bool hasInterveningCode() const { |
| return !before.empty() || !after.empty(); |
| } |
| }; |
| llvm::SmallVector<LevelInfo> levels; |
| |
| // DO-variable symbol of each collapsed level (index 0 = outermost). Used to |
| // restore an inner loop's variable to its Fortran terminal value before |
| // emitting "after" intervening code (see below). |
| llvm::SmallVector<const semantics::Symbol *> ivSyms; |
| |
| lower::pft::Evaluation *curEval = &outerEval; |
| for (int i = 0; i < collapseValue - 1; ++i) { |
| lower::pft::Evaluation *doEval = getNestedDoConstruct(*curEval); |
| const semantics::Symbol *ivSym = getIterationVariableSymbol(*doEval); |
| assert(ivSym && "expected iteration variable on collapsed DO loop"); |
| ivSyms.push_back(ivSym); |
| LevelInfo level; |
| bool pastDo = false; |
| for (lower::pft::Evaluation &e : doEval->getNestedEvaluations()) { |
| // A labeled DO loop leaves a no-op ContinueStmt for its terminating |
| // labeled statement (alongside the EndDoStmt). Skip it so it isn't |
| // misclassified as intervening code. |
| if (e.getIf<parser::NonLabelDoStmt>() || e.getIf<parser::EndDoStmt>() || |
| e.getIf<parser::ContinueStmt>() || |
| e.getIf<parser::CompilerDirective>()) |
| continue; |
| // Semantics guarantees the only DoConstruct here is the next associated |
| // loop (non-associated DO loops are rejected as intervening code). |
| if (e.getIf<parser::DoConstruct>()) { |
| pastDo = true; |
| continue; |
| } |
| if (!pastDo) |
| level.before.push_back(&e); |
| else |
| level.after.push_back(&e); |
| } |
| levels.push_back(std::move(level)); |
| curEval = doEval; |
| } |
| // DO-variable symbol of the innermost collapsed loop must be restored |
| // inside enclosing "after" regions. |
| const semantics::Symbol *innermostIvSym = |
| getIterationVariableSymbol(*getNestedDoConstruct(*curEval)); |
| assert(innermostIvSym && "expected iteration variable on collapsed DO loop"); |
| ivSyms.push_back(innermostIvSym); |
| |
| // Build a guard condition: all induction variables from |
| // startLevel..endLevel-1 equal their respective bound values. |
| // For "before" guards (useLowerBound=true), compare iv == lb (first iter). |
| // For "after" guards (useLowerBound=false), compare iv == last_iv. |
| const auto lbs = loopNestOp.getLoopLowerBounds(); |
| const auto ubs = loopNestOp.getLoopUpperBounds(); |
| const auto steps = loopNestOp.getLoopSteps(); |
| |
| // The intervening-code guards and terminal-value restoration do arithmetic |
| // on the collapsed loop bounds. If those bounds are host_eval block arguments |
| // of an enclosing omp.target region, such uses are illegal, so diagnose |
| // instead of emitting IR the omp.target verifier rejects. |
| const bool hasInterveningCode = llvm::any_of( |
| levels, [](const LevelInfo &l) { return l.hasInterveningCode(); }); |
| if (hasInterveningCode) { |
| auto isHostEvalValue = [](mlir::Value v) { |
| auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(v); |
| if (!blockArg) |
| return false; |
| auto iface = mlir::dyn_cast<mlir::omp::BlockArgOpenMPOpInterface>( |
| blockArg.getOwner()->getParentOp()); |
| return iface && |
| llvm::is_contained(iface.getHostEvalBlockArgs(), blockArg); |
| }; |
| if (llvm::any_of(lbs, isHostEvalValue) || |
| llvm::any_of(ubs, isHostEvalValue) || |
| llvm::any_of(steps, isHostEvalValue)) |
| TODO(loc, "collapsed loop nest with intervening code whose loop bounds " |
| "are evaluated on the host for an enclosing 'target' region"); |
| } |
| |
| // Last value the induction variable at \p lvl actually takes: |
| // lb + ((ub - lb) / step) * step. For unit steps this is exactly ub. |
| auto computeLastIV = [&](const int lvl) -> mlir::Value { |
| const std::optional<llvm::APInt> constStep = |
| fir::getIntIfConstant(steps[lvl]); |
| if (constStep && (constStep->isOne() || constStep->isAllOnes())) |
| return ubs[lvl]; |
| const mlir::Value lb = lbs[lvl]; |
| const mlir::Value ub = ubs[lvl]; |
| const mlir::Value step = steps[lvl]; |
| const mlir::Value range = |
| mlir::arith::SubIOp::create(firOpBuilder, loc, ub, lb); |
| const mlir::Value tripMinus1 = |
| mlir::arith::DivSIOp::create(firOpBuilder, loc, range, step); |
| const mlir::Value lastOffset = |
| mlir::arith::MulIOp::create(firOpBuilder, loc, tripMinus1, step); |
| return mlir::arith::AddIOp::create(firOpBuilder, loc, lb, lastOffset); |
| }; |
| |
| auto buildGuard = [&](const int startLevel, const int endLevel, |
| const bool useLowerBound) -> mlir::Value { |
| mlir::Value cond; |
| for (int lvl = startLevel; lvl < endLevel; ++lvl) { |
| const mlir::Value iv = loopNestOp.getRegion().getArgument(lvl); |
| const mlir::Value target = useLowerBound ? lbs[lvl] : computeLastIV(lvl); |
| const mlir::Value cmp = mlir::arith::CmpIOp::create( |
| firOpBuilder, loc, mlir::arith::CmpIPredicate::eq, iv, target); |
| if (!cond) |
| cond = cmp; |
| else |
| cond = mlir::arith::AndIOp::create(firOpBuilder, loc, cond, cmp); |
| } |
| return cond; |
| }; |
| |
| // Emit "before" code at each level, guarded by inner IVs == lower bounds. |
| for (int i = 0; i < static_cast<int>(levels.size()); ++i) { |
| if (levels[i].before.empty()) |
| continue; |
| const mlir::Value guard = |
| buildGuard(i + 1, collapseValue, /*useLowerBound=*/true); |
| auto ifOp = fir::IfOp::create(firOpBuilder, loc, guard, /*else*/ false); |
| firOpBuilder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| for (auto *e : levels[i].before) |
| converter.genEval(*e); |
| firOpBuilder.setInsertionPointAfter(ifOp); |
| } |
| |
| // Emit innermost loop body. |
| genNestedEvaluations(converter, *curEval, /*collapseValue=*/1); |
| |
| // Emit "after" code at each level (innermost first), guarded by |
| // inner IVs == last iteration values (accounts for non-unit steps). |
| for (int i = static_cast<int>(levels.size()) - 1; i >= 0; --i) { |
| if (levels[i].after.empty()) |
| continue; |
| const mlir::Value guard = |
| buildGuard(i + 1, collapseValue, /*useLowerBound=*/false); |
| auto ifOp = fir::IfOp::create(firOpBuilder, loc, guard, /*else*/ false); |
| firOpBuilder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| // A normally-terminated Fortran DO loop leaves its variable one step past |
| // the last executed value, but the flattened nest leaves each at its last |
| // executed value. Restore the terminal value before running "after" code |
| // that may read it. |
| for (int lvl = i + 1; lvl < collapseValue; ++lvl) { |
| const mlir::Value terminal = mlir::arith::AddIOp::create( |
| firOpBuilder, loc, computeLastIV(lvl), steps[lvl]); |
| setLoopVar(converter, loc, terminal, ivSyms[lvl]); |
| } |
| for (auto *e : levels[i].after) |
| converter.genEval(*e); |
| firOpBuilder.setInsertionPointAfter(ifOp); |
| } |
| } |
| |
| static fir::GlobalOp globalInitialization(lower::AbstractConverter &converter, |
| fir::FirOpBuilder &firOpBuilder, |
| const semantics::Symbol &sym, |
| const lower::pft::Variable &var, |
| mlir::Location currentLocation) { |
| std::string globalName = converter.mangleName(sym); |
| mlir::StringAttr linkage = firOpBuilder.createInternalLinkage(); |
| return Fortran::lower::defineGlobal(converter, var, globalName, linkage); |
| } |
| |
| // Get the extended value for \p val by extracting additional variable |
| // information from \p base. |
| static fir::ExtendedValue getExtendedValue(fir::ExtendedValue base, |
| mlir::Value val) { |
| return base.match( |
| [&](const fir::MutableBoxValue &box) -> fir::ExtendedValue { |
| return fir::MutableBoxValue(val, box.nonDeferredLenParams(), {}); |
| }, |
| [&](const auto &) -> fir::ExtendedValue { |
| return fir::substBase(base, val); |
| }); |
| } |
| |
| #ifndef NDEBUG |
| static bool isThreadPrivate(lower::SymbolRef sym) { |
| if (const auto *details = sym->detailsIf<semantics::CommonBlockDetails>()) { |
| for (const auto &obj : details->objects()) |
| if (!obj->test(semantics::Symbol::Flag::OmpThreadprivate)) |
| return false; |
| return true; |
| } |
| return sym->test(semantics::Symbol::Flag::OmpThreadprivate); |
| } |
| #endif |
| |
| static void threadPrivatizeVars(lower::AbstractConverter &converter, |
| lower::pft::Evaluation &eval) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::Location currentLocation = converter.getCurrentLocation(); |
| mlir::OpBuilder::InsertionGuard guard(firOpBuilder); |
| firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); |
| |
| // If the symbol corresponds to the original ThreadprivateOp, use the symbol |
| // value from that operation to create one ThreadprivateOp copy operation |
| // inside the parallel region. |
| // In some cases, however, the symbol will correspond to the original, |
| // non-threadprivate variable. This can happen, for instance, with a common |
| // block, declared in a separate module, used by a parent procedure and |
| // privatized in its child procedure. |
| auto genThreadprivateOp = [&](lower::SymbolRef sym) -> mlir::Value { |
| assert(isThreadPrivate(sym)); |
| mlir::Value symValue = converter.getSymbolAddress(sym); |
| mlir::Operation *op = symValue.getDefiningOp(); |
| if (auto declOp = mlir::dyn_cast<hlfir::DeclareOp>(op)) |
| op = declOp.getMemref().getDefiningOp(); |
| if (mlir::isa<mlir::omp::ThreadprivateOp>(op)) |
| symValue = mlir::dyn_cast<mlir::omp::ThreadprivateOp>(op).getSymAddr(); |
| return mlir::omp::ThreadprivateOp::create(firOpBuilder, currentLocation, |
| symValue.getType(), symValue); |
| }; |
| |
| llvm::SetVector<const semantics::Symbol *> threadprivateSyms; |
| converter.collectSymbolSet(eval, threadprivateSyms, |
| semantics::Symbol::Flag::OmpThreadprivate, |
| /*collectSymbols=*/true, |
| /*collectHostAssociatedSymbols=*/true); |
| std::set<semantics::SourceName> threadprivateSymNames; |
| |
| // For a COMMON block, the ThreadprivateOp is generated for itself instead of |
| // its members, so only bind the value of the new copied ThreadprivateOp |
| // inside the parallel region to the common block symbol only once for |
| // multiple members in one COMMON block. |
| llvm::SetVector<const semantics::Symbol *> commonSyms; |
| for (std::size_t i = 0; i < threadprivateSyms.size(); i++) { |
| const semantics::Symbol *sym = threadprivateSyms[i]; |
| mlir::Value symThreadprivateValue; |
| // The variable may be used more than once, and each reference has one |
| // symbol with the same name. Only do once for references of one variable. |
| if (threadprivateSymNames.find(sym->name()) != threadprivateSymNames.end()) |
| continue; |
| threadprivateSymNames.insert(sym->name()); |
| if (const semantics::Symbol *common = |
| semantics::FindCommonBlockContaining(sym->GetUltimate())) { |
| mlir::Value commonThreadprivateValue; |
| if (commonSyms.contains(common)) { |
| commonThreadprivateValue = converter.getSymbolAddress(*common); |
| } else { |
| commonThreadprivateValue = genThreadprivateOp(*common); |
| converter.bindSymbol(*common, commonThreadprivateValue); |
| commonSyms.insert(common); |
| } |
| symThreadprivateValue = lower::genCommonBlockMember( |
| converter, currentLocation, sym->GetUltimate(), |
| commonThreadprivateValue, common->size()); |
| } else { |
| symThreadprivateValue = genThreadprivateOp(*sym); |
| } |
| |
| fir::ExtendedValue sexv = converter.getSymbolExtendedValue(*sym); |
| fir::ExtendedValue symThreadprivateExv = |
| getExtendedValue(sexv, symThreadprivateValue); |
| converter.bindSymbol(*sym, symThreadprivateExv); |
| } |
| } |
| |
| // Translate a semantics-layer device_type to the MLIR enum used by |
| // omp.groupprivate. |
| static mlir::omp::DeclareTargetDeviceType |
| toMLIRDeclareTargetDeviceType(Fortran::common::OmpDeviceType deviceType) { |
| switch (deviceType) { |
| case Fortran::common::OmpDeviceType::Any: |
| return mlir::omp::DeclareTargetDeviceType::any; |
| case Fortran::common::OmpDeviceType::Host: |
| return mlir::omp::DeclareTargetDeviceType::host; |
| case Fortran::common::OmpDeviceType::Nohost: |
| return mlir::omp::DeclareTargetDeviceType::nohost; |
| } |
| llvm_unreachable("invalid OmpDeviceType"); |
| } |
| |
| static void groupprivatizeVars(lower::AbstractConverter &converter, |
| lower::pft::Evaluation &eval) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::Location currentLocation = converter.getCurrentLocation(); |
| mlir::OpBuilder::InsertionGuard guard(firOpBuilder); |
| firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); |
| |
| auto module = converter.getModuleOp(); |
| |
| // Create a groupprivate operation for the symbol. |
| auto genGroupprivateOp = [&](const semantics::Symbol &sym) -> mlir::Value { |
| std::string globalName = converter.mangleName(sym); |
| fir::GlobalOp global = module.lookupSymbol<fir::GlobalOp>(globalName); |
| if (!global) |
| return mlir::Value(); |
| |
| // The device_type modifier was recorded on the symbol during semantic |
| // analysis. |
| mlir::omp::DeclareTargetDeviceType deviceTypeEnum = |
| mlir::omp::DeclareTargetDeviceType::any; |
| Fortran::common::visit( |
| [&](auto &&details) { |
| using TypeD = llvm::remove_cvref_t<decltype(details)>; |
| if constexpr (std::is_base_of_v<semantics::WithOmpDeclarative, |
| TypeD>) { |
| if (auto dt = details.ompGroupprivateDeviceType()) |
| deviceTypeEnum = toMLIRDeclareTargetDeviceType(*dt); |
| } |
| }, |
| sym.GetUltimate().details()); |
| mlir::omp::DeclareTargetDeviceTypeAttr deviceTypeAttr = |
| mlir::omp::DeclareTargetDeviceTypeAttr::get(firOpBuilder.getContext(), |
| deviceTypeEnum); |
| |
| // omp.groupprivate takes a flat symbol reference and returns |
| // the address of the per-contention group copy of the global variable. |
| return mlir::omp::GroupprivateOp::create( |
| firOpBuilder, currentLocation, global.resultType(), global.getSymbol(), |
| deviceTypeAttr); |
| }; |
| |
| llvm::SetVector<const semantics::Symbol *> groupprivateSyms; |
| converter.collectSymbolSet(eval, groupprivateSyms, |
| semantics::Symbol::Flag::OmpGroupPrivate, |
| /*collectSymbols=*/true, |
| /*collectHostAssociatedSymbols=*/true); |
| llvm::SmallSet<semantics::SourceName, 8> groupprivateSymNames; |
| |
| // For a COMMON block, the GroupprivateOp is generated for the block itself |
| // instead of its members. |
| llvm::SmallPtrSet<const semantics::Symbol *, 8> commonSyms; |
| |
| for (const semantics::Symbol *sym : groupprivateSyms) { |
| mlir::Value symGroupprivateValue; |
| // The variable may be used more than once, and each reference has one |
| // symbol with the same name. Only do once for references of one variable. |
| if (!groupprivateSymNames.insert(sym->name()).second) |
| continue; |
| |
| if (const semantics::Symbol *common = |
| semantics::FindCommonBlockContaining(sym->GetUltimate())) { |
| // Handle common block members: create groupprivate op for the entire |
| // common block, then compute member offset. |
| mlir::Value commonGroupprivateValue; |
| if (commonSyms.contains(common)) { |
| commonGroupprivateValue = converter.getSymbolAddress(*common); |
| } else { |
| commonGroupprivateValue = genGroupprivateOp(*common); |
| if (!commonGroupprivateValue) |
| continue; |
| converter.bindSymbol(*common, commonGroupprivateValue); |
| commonSyms.insert(common); |
| } |
| symGroupprivateValue = lower::genCommonBlockMember( |
| converter, currentLocation, sym->GetUltimate(), |
| commonGroupprivateValue, common->size()); |
| } else { |
| symGroupprivateValue = genGroupprivateOp(*sym); |
| } |
| |
| if (!symGroupprivateValue) |
| continue; |
| |
| fir::ExtendedValue sexv = converter.getSymbolExtendedValue(*sym); |
| fir::ExtendedValue symGroupprivateExv = |
| getExtendedValue(sexv, symGroupprivateValue); |
| converter.bindSymbol(*sym, symGroupprivateExv); |
| } |
| } |
| |
| static mlir::Operation *setLoopVar(lower::AbstractConverter &converter, |
| mlir::Location loc, mlir::Value indexVal, |
| const semantics::Symbol *sym) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| mlir::OpBuilder::InsertPoint insPt = firOpBuilder.saveInsertionPoint(); |
| firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); |
| mlir::Type tempTy = converter.genType(*sym); |
| firOpBuilder.restoreInsertionPoint(insPt); |
| |
| mlir::Value cvtVal = firOpBuilder.createConvert(loc, tempTy, indexVal); |
| hlfir::Entity lhs{converter.getSymbolAddress(*sym)}; |
| |
| lhs = hlfir::derefPointersAndAllocatables(loc, firOpBuilder, lhs); |
| |
| mlir::Operation *storeOp = |
| hlfir::AssignOp::create(firOpBuilder, loc, cvtVal, lhs); |
| return storeOp; |
| } |
| |
| static mlir::Operation * |
| createAndSetPrivatizedLoopVar(lower::AbstractConverter &converter, |
| mlir::Location loc, mlir::Value indexVal, |
| const semantics::Symbol *sym) { |
| // The handling of linear symbols is deferred to the OpenMP IRBuilder, |
| // which is responsible for all its aspects, including privatization. |
| assert((converter.isPresentShallowLookup(*sym) || |
| sym->test(semantics::Symbol::Flag::OmpLinear)) && |
| "Expected symbol to be in symbol table."); |
| return setLoopVar(converter, loc, indexVal, sym); |
| } |
| |
| // This helper function implements the functionality of "promoting" non-CPTR |
| // arguments of use_device_ptr to use_device_addr arguments (automagic |
| // conversion of use_device_ptr -> use_device_addr in these cases). The way we |
| // do so currently is through the shuffling of operands from the |
| // devicePtrOperands to deviceAddrOperands, as well as the types, locations and |
| // symbols. |
| // |
| // This effectively implements some deprecated OpenMP functionality that some |
| // legacy applications unfortunately depend on (deprecated in specification |
| // version 5.2): |
| // |
| // "If a list item in a use_device_ptr clause is not of type C_PTR, the behavior |
| // is as if the list item appeared in a use_device_addr clause. Support for |
| // such list items in a use_device_ptr clause is deprecated." |
| static void promoteNonCPtrUseDevicePtrArgsToUseDeviceAddr( |
| llvm::SmallVectorImpl<mlir::Value> &useDeviceAddrVars, |
| llvm::SmallVectorImpl<Object> &useDeviceAddrObjects, |
| llvm::SmallVectorImpl<mlir::Value> &useDevicePtrVars, |
| llvm::SmallVectorImpl<Object> &useDevicePtrObjects) { |
| // Iterate over our use_device_ptr list and shift all non-cptr arguments into |
| // use_device_addr. |
| auto *varIt = useDevicePtrVars.begin(); |
| auto *objectIt = useDevicePtrObjects.begin(); |
| while (varIt != useDevicePtrVars.end()) { |
| if (fir::isa_builtin_cptr_type(fir::unwrapRefType(varIt->getType()))) { |
| ++varIt; |
| ++objectIt; |
| continue; |
| } |
| |
| useDeviceAddrVars.push_back(*varIt); |
| useDeviceAddrObjects.push_back(*objectIt); |
| |
| varIt = useDevicePtrVars.erase(varIt); |
| objectIt = useDevicePtrObjects.erase(objectIt); |
| } |
| } |
| |
| /// Extract the list of function and variable symbols affected by the given |
| /// 'declare target' directive and return the intended device type for them. |
| static void getDeclareTargetInfo( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpDeclareTargetDirective &construct, |
| mlir::omp::DeclareTargetOperands &clauseOps, |
| llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &symbolAndClause) { |
| |
| if (!construct.v.Arguments().v.empty()) { |
| ObjectList objects{makeObjects(construct.v.Arguments(), semaCtx)}; |
| // Case: declare target(func, var1, var2) |
| gatherFuncAndVarSyms(objects, mlir::omp::DeclareTargetCaptureClause::to, |
| symbolAndClause, /*automap=*/false); |
| } else { |
| List<Clause> clauses = makeClauses(construct.v.Clauses(), semaCtx); |
| if (clauses.empty()) { |
| Fortran::lower::pft::FunctionLikeUnit *owningProc = |
| eval.getOwningProcedure(); |
| // Main programs are never device routines. Skip them so that a bare |
| // '!$omp declare target' inside an interface body that lives in a named |
| // main program does not incorrectly mark _QQmain as a device function. |
| if (owningProc && !owningProc->isMainProgram()) { |
| // Case: declare target, implicit capture of enclosing |
| // function/subroutine. |
| symbolAndClause.emplace_back(mlir::omp::DeclareTargetCaptureClause::to, |
| owningProc->getSubprogramSymbol()); |
| } |
| } |
| |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processDeviceType(clauseOps); |
| cp.processEnter(symbolAndClause); |
| cp.processLink(symbolAndClause); |
| cp.processTo(symbolAndClause); |
| |
| cp.processTODO<clause::Indirect>(converter.getCurrentLocation(), |
| llvm::omp::Directive::OMPD_declare_target); |
| } |
| } |
| |
| static void collectDeferredDeclareTargets( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpDeclareTargetDirective &declareTargetConstruct, |
| llvm::SmallVectorImpl<lower::OMPDeferredDeclareTargetInfo> |
| &deferredDeclareTarget) { |
| mlir::omp::DeclareTargetOperands clauseOps; |
| llvm::SmallVector<DeclareTargetCaptureInfo> symbolAndClause; |
| getDeclareTargetInfo(converter, semaCtx, eval, declareTargetConstruct, |
| clauseOps, symbolAndClause); |
| // Return the device type only if at least one of the targets for the |
| // directive is a function or subroutine |
| mlir::ModuleOp mod = converter.getFirOpBuilder().getModule(); |
| |
| for (const DeclareTargetCaptureInfo &symClause : symbolAndClause) { |
| mlir::Operation *op = |
| mod.lookupSymbol(converter.mangleName(symClause.symbol)); |
| |
| if (!op) { |
| deferredDeclareTarget.push_back({symClause.clause, clauseOps.deviceType, |
| symClause.automap, symClause.symbol}); |
| } |
| } |
| } |
| |
| static std::optional<mlir::omp::DeclareTargetDeviceType> |
| getDeclareTargetFunctionDevice( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpDeclareTargetDirective &declareTargetConstruct) { |
| mlir::omp::DeclareTargetOperands clauseOps; |
| llvm::SmallVector<DeclareTargetCaptureInfo> symbolAndClause; |
| getDeclareTargetInfo(converter, semaCtx, eval, declareTargetConstruct, |
| clauseOps, symbolAndClause); |
| |
| // Return the device type only if at least one of the targets for the |
| // directive is a function or subroutine |
| mlir::ModuleOp mod = converter.getFirOpBuilder().getModule(); |
| for (const DeclareTargetCaptureInfo &symClause : symbolAndClause) { |
| mlir::Operation *op = |
| mod.lookupSymbol(converter.mangleName(symClause.symbol)); |
| |
| if (mlir::isa_and_nonnull<mlir::func::FuncOp>(op)) |
| return clauseOps.deviceType; |
| } |
| |
| return std::nullopt; |
| } |
| |
| /// Set up the entry block of the given `omp.loop_nest` operation, adding a |
| /// block argument for each loop induction variable and allocating and |
| /// initializing a private value to hold each of them. |
| /// |
| /// This function can also bind the symbols of any variables that should match |
| /// block arguments on parent loop wrapper operations attached to the same |
| /// loop. This allows the introduction of any necessary `hlfir.declare` |
| /// operations inside of the entry block of the `omp.loop_nest` operation and |
| /// not directly under any of the wrappers, which would invalidate them. |
| /// |
| /// \param [in] op - the loop nest operation. |
| /// \param [in] converter - PFT to MLIR conversion interface. |
| /// \param [in] loc - location. |
| /// \param [in] args - symbols of induction variables. |
| /// \param [in] wrapperArgs - list of parent loop wrappers and their associated |
| /// entry block arguments. |
| static void |
| genLoopVars(mlir::Operation *op, lower::AbstractConverter &converter, |
| mlir::Location &loc, llvm::ArrayRef<const semantics::Symbol *> args, |
| llvm::ArrayRef<std::pair<mlir::omp::BlockArgOpenMPOpInterface, |
| const ObjectEntryBlockArgs &>> |
| wrapperArgs = {}) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| auto ®ion = op->getRegion(0); |
| |
| std::size_t loopVarTypeSize = 0; |
| for (const semantics::Symbol *arg : args) |
| loopVarTypeSize = std::max(loopVarTypeSize, arg->GetUltimate().size()); |
| mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize); |
| llvm::SmallVector<mlir::Type> tiv(args.size(), loopVarType); |
| llvm::SmallVector<mlir::Location> locs(args.size(), loc); |
| firOpBuilder.createBlock(®ion, {}, tiv, locs); |
| |
| // Update nested wrapper operands if parent wrappers have mapped these values |
| // to block arguments. |
| // |
| // Binding these values earlier would take care of this, but we cannot rely on |
| // that approach because binding in between the creation of a wrapper and the |
| // next one would result in 'hlfir.declare' operations being introduced inside |
| // of a wrapper, which is illegal. |
| mlir::IRMapping mapper; |
| for (auto [argGeneratingOp, blockArgs] : wrapperArgs) { |
| for (mlir::OpOperand &operand : argGeneratingOp->getOpOperands()) |
| operand.set(mapper.lookupOrDefault(operand.get())); |
| |
| for (const auto [arg, var] : llvm::zip_equal( |
| argGeneratingOp->getRegion(0).getArguments(), blockArgs.getVars())) |
| mapper.map(var, arg); |
| } |
| |
| // Bind the entry block arguments of parent wrappers to the corresponding |
| // symbols. |
| for (auto [argGeneratingOp, blockArgs] : wrapperArgs) |
| bindEntryBlockArgs(converter, argGeneratingOp, blockArgs); |
| |
| // The argument is not currently in memory, so make a temporary for the |
| // argument, and store it there, then bind that location to the argument. |
| mlir::Operation *storeOp = nullptr; |
| for (auto [argIndex, argSymbol] : llvm::enumerate(args)) { |
| mlir::Value indexVal = fir::getBase(region.front().getArgument(argIndex)); |
| storeOp = |
| createAndSetPrivatizedLoopVar(converter, loc, indexVal, argSymbol); |
| } |
| firOpBuilder.setInsertionPointAfter(storeOp); |
| } |
| |
| static clause::Defaultmap::ImplicitBehavior |
| getDefaultmapIfPresent(const DefaultMapsTy &defaultMaps, mlir::Type varType) { |
| using DefMap = clause::Defaultmap; |
| |
| if (defaultMaps.empty()) |
| return DefMap::ImplicitBehavior::Default; |
| |
| if (llvm::is_contained(defaultMaps, DefMap::VariableCategory::All)) |
| return defaultMaps.at(DefMap::VariableCategory::All); |
| |
| // NOTE: Unsure if complex and/or vector falls into a scalar type |
| // or aggregate, but the current default implicit behaviour is to |
| // treat them as such (c_ptr has its own behaviour, so perhaps |
| // being lumped in as a scalar isn't the right thing). |
| if ((fir::isa_trivial(varType) || fir::isa_char(varType) || |
| fir::isa_builtin_cptr_type(varType)) && |
| llvm::is_contained(defaultMaps, DefMap::VariableCategory::Scalar)) |
| return defaultMaps.at(DefMap::VariableCategory::Scalar); |
| |
| if (fir::isPointerType(varType) && |
| llvm::is_contained(defaultMaps, DefMap::VariableCategory::Pointer)) |
| return defaultMaps.at(DefMap::VariableCategory::Pointer); |
| |
| if (fir::isAllocatableType(varType) && |
| llvm::is_contained(defaultMaps, DefMap::VariableCategory::Allocatable)) |
| return defaultMaps.at(DefMap::VariableCategory::Allocatable); |
| |
| if (fir::isa_aggregate(varType) && |
| llvm::is_contained(defaultMaps, DefMap::VariableCategory::Aggregate)) |
| return defaultMaps.at(DefMap::VariableCategory::Aggregate); |
| |
| return DefMap::ImplicitBehavior::Default; |
| } |
| |
| static std::pair<mlir::omp::ClauseMapFlags, mlir::omp::VariableCaptureKind> |
| getImplicitMapTypeAndKind(fir::FirOpBuilder &firOpBuilder, |
| lower::AbstractConverter &converter, |
| const DefaultMapsTy &defaultMaps, mlir::Type varType, |
| mlir::Location loc, const semantics::Symbol &sym) { |
| using DefMap = clause::Defaultmap; |
| // Check if a value of type `type` can be passed to the kernel by value. |
| // All kernel parameters are of pointer type, so if the value can be |
| // represented inside of a pointer, then it can be passed by value. |
| auto isLiteralType = [&](mlir::Type type) { |
| const mlir::DataLayout &dl = firOpBuilder.getDataLayout(); |
| mlir::Type ptrTy = |
| mlir::LLVM::LLVMPointerType::get(&converter.getMLIRContext()); |
| uint64_t ptrSize = dl.getTypeSize(ptrTy); |
| uint64_t ptrAlign = dl.getTypePreferredAlignment(ptrTy); |
| |
| auto [size, align] = fir::getTypeSizeAndAlignmentOrCrash( |
| loc, type, dl, converter.getKindMap()); |
| return size <= ptrSize && align <= ptrAlign; |
| }; |
| |
| mlir::omp::ClauseMapFlags mapFlag = mlir::omp::ClauseMapFlags::implicit; |
| |
| auto implicitBehaviour = getDefaultmapIfPresent(defaultMaps, varType); |
| if (implicitBehaviour == DefMap::ImplicitBehavior::Default) { |
| mlir::omp::VariableCaptureKind captureKind = |
| mlir::omp::VariableCaptureKind::ByRef; |
| |
| // If a variable is specified in declare target link and if device |
| // type is not specified as `nohost`, it needs to be mapped tofrom |
| mlir::ModuleOp mod = firOpBuilder.getModule(); |
| mlir::Operation *op = mod.lookupSymbol(converter.mangleName(sym)); |
| auto declareTargetOp = |
| llvm::dyn_cast_if_present<mlir::omp::DeclareTargetInterface>(op); |
| |
| // Double check it's not part of a common block, and that the common block |
| // isn't marked declare target. |
| if (!declareTargetOp) { |
| if (const semantics::Symbol *common = |
| semantics::FindCommonBlockContaining(sym.GetUltimate())) { |
| mlir::Operation *commonOp = |
| mod.lookupSymbol(converter.mangleName(*common)); |
| declareTargetOp = |
| llvm::dyn_cast_if_present<mlir::omp::DeclareTargetInterface>( |
| commonOp); |
| } |
| } |
| |
| if (declareTargetOp && declareTargetOp.isDeclareTarget()) { |
| if (declareTargetOp.getDeclareTargetCaptureClause() == |
| mlir::omp::DeclareTargetCaptureClause::link && |
| declareTargetOp.getDeclareTargetDeviceType() != |
| mlir::omp::DeclareTargetDeviceType::nohost) { |
| mapFlag |= mlir::omp::ClauseMapFlags::to; |
| mapFlag |= mlir::omp::ClauseMapFlags::from; |
| } |
| } else if (fir::isa_trivial(varType) || fir::isa_char(varType)) { |
| // Scalars behave as if they were "firstprivate". |
| // TODO: Handle objects that are shared/lastprivate or were listed |
| // in an in_reduction clause. |
| if (isLiteralType(varType)) { |
| captureKind = mlir::omp::VariableCaptureKind::ByCopy; |
| } else { |
| mapFlag |= mlir::omp::ClauseMapFlags::to; |
| } |
| } else if (semantics::IsNamedConstant(sym)) { |
| // Parameter constants should be mapped as read-only (to) since they |
| // cannot be modified. Mapping them as tofrom would cause a crash when |
| // trying to write back to read-only memory. |
| mapFlag |= mlir::omp::ClauseMapFlags::to; |
| } else if (!fir::isa_builtin_cptr_type(varType)) { |
| mapFlag |= mlir::omp::ClauseMapFlags::to; |
| mapFlag |= mlir::omp::ClauseMapFlags::from; |
| } |
| return std::make_pair(mapFlag, captureKind); |
| } |
| |
| switch (implicitBehaviour) { |
| case DefMap::ImplicitBehavior::Alloc: |
| return std::make_pair(mlir::omp::ClauseMapFlags::storage, |
| mlir::omp::VariableCaptureKind::ByRef); |
| break; |
| case DefMap::ImplicitBehavior::Firstprivate: |
| TODO(loc, "Firstprivate is currently unsupported defaultmap behaviour"); |
| break; |
| case DefMap::ImplicitBehavior::From: |
| return std::make_pair(mapFlag |= mlir::omp::ClauseMapFlags::from, |
| mlir::omp::VariableCaptureKind::ByRef); |
| break; |
| case DefMap::ImplicitBehavior::Present: |
| return std::make_pair(mapFlag |= mlir::omp::ClauseMapFlags::present, |
| mlir::omp::VariableCaptureKind::ByRef); |
| break; |
| case DefMap::ImplicitBehavior::To: |
| return std::make_pair(mapFlag |= mlir::omp::ClauseMapFlags::to, |
| (fir::isa_trivial(varType) || fir::isa_char(varType)) |
| ? mlir::omp::VariableCaptureKind::ByCopy |
| : mlir::omp::VariableCaptureKind::ByRef); |
| break; |
| case DefMap::ImplicitBehavior::Tofrom: |
| return std::make_pair(mapFlag |= mlir::omp::ClauseMapFlags::from | |
| mlir::omp::ClauseMapFlags::to, |
| mlir::omp::VariableCaptureKind::ByRef); |
| break; |
| case DefMap::ImplicitBehavior::Default: |
| case DefMap::ImplicitBehavior::None: |
| llvm_unreachable( |
| "Implicit None and Default behaviour should have been handled earlier"); |
| break; |
| } |
| |
| return std::make_pair(mapFlag |= mlir::omp::ClauseMapFlags::from | |
| mlir::omp::ClauseMapFlags::to, |
| mlir::omp::VariableCaptureKind::ByRef); |
| } |
| |
| static void |
| markDeclareTarget(mlir::Operation *op, lower::AbstractConverter &converter, |
| mlir::omp::DeclareTargetCaptureClause captureClause, |
| mlir::omp::DeclareTargetDeviceType deviceType, bool automap) { |
| // TODO: Add support for program local variables with declare target applied |
| auto declareTargetOp = llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(op); |
| if (!declareTargetOp) |
| fir::emitFatalError( |
| converter.getCurrentLocation(), |
| "Attempt to apply declare target on unsupported operation"); |
| |
| // The function or global already has a declare target applied to it, very |
| // likely through implicit capture (usage in another declare target |
| // function/subroutine). It should be marked as any if it has been assigned |
| // both host and nohost, else we skip, as there is no change |
| if (declareTargetOp.isDeclareTarget()) { |
| if (declareTargetOp.getDeclareTargetDeviceType() != deviceType) |
| declareTargetOp.setDeclareTarget(mlir::omp::DeclareTargetDeviceType::any, |
| captureClause, automap); |
| return; |
| } |
| |
| declareTargetOp.setDeclareTarget(deviceType, captureClause, automap); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Op body generation helper structures and functions |
| //===----------------------------------------------------------------------===// |
| |
| struct OpWithBodyGenInfo { |
| /// A type for a code-gen callback function. This takes as argument the op for |
| /// which the code is being generated and returns the arguments of the op's |
| /// region. |
| using GenOMPRegionEntryCBFn = |
| std::function<llvm::SmallVector<const semantics::Symbol *>( |
| mlir::Operation *)>; |
| |
| OpWithBodyGenInfo(lower::AbstractConverter &converter, |
| lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, mlir::Location loc, |
| lower::pft::Evaluation &eval, llvm::omp::Directive dir) |
| : converter(converter), symTable(symTable), semaCtx(semaCtx), loc(loc), |
| eval(eval), dir(dir) {} |
| |
| OpWithBodyGenInfo &setClauses(const List<Clause> *value) { |
| clauses = value; |
| return *this; |
| } |
| |
| OpWithBodyGenInfo &setDataSharingProcessor(DataSharingProcessor *value) { |
| dsp = value; |
| return *this; |
| } |
| |
| OpWithBodyGenInfo &setEntryBlockArgs(const ObjectEntryBlockArgs *value) { |
| blockArgs = value; |
| return *this; |
| } |
| |
| OpWithBodyGenInfo &setGenRegionEntryCb(GenOMPRegionEntryCBFn value) { |
| genRegionEntryCB = value; |
| return *this; |
| } |
| |
| OpWithBodyGenInfo &setGenSkeletonOnly(bool value) { |
| genSkeletonOnly = value; |
| return *this; |
| } |
| |
| OpWithBodyGenInfo &setPrivatize(bool value) { |
| privatize = value; |
| return *this; |
| } |
| |
| OpWithBodyGenInfo &setCollapseInfo(int value, |
| lower::pft::Evaluation &outerEval) { |
| collapseValue = value; |
| outerCollapseEval = &outerEval; |
| return *this; |
| } |
| |
| /// [inout] converter to use for the clauses. |
| lower::AbstractConverter &converter; |
| /// [in] Symbol table |
| lower::SymMap &symTable; |
| /// [in] Semantics context |
| semantics::SemanticsContext &semaCtx; |
| /// [in] location in source code. |
| mlir::Location loc; |
| /// [in] current PFT node/evaluation. |
| lower::pft::Evaluation &eval; |
| /// [in] leaf directive for which to generate the op body. |
| llvm::omp::Directive dir; |
| /// [in] list of clauses to process. |
| const List<Clause> *clauses = nullptr; |
| /// [in] if provided, processes the construct's data-sharing attributes. |
| DataSharingProcessor *dsp = nullptr; |
| /// [in] if provided, it is used to create the op's region entry block. It is |
| /// overriden when a \see genRegionEntryCB is provided. This is only valid for |
| /// operations implementing the \see mlir::omp::BlockArgOpenMPOpInterface. |
| const ObjectEntryBlockArgs *blockArgs = nullptr; |
| /// [in] if provided, it overrides the default op's region entry block |
| /// creation. |
| GenOMPRegionEntryCBFn genRegionEntryCB = nullptr; |
| /// [in] if set to `true`, skip generating nested evaluations and dispatching |
| /// any further leaf constructs. |
| bool genSkeletonOnly = false; |
| /// [in] enables handling of privatized variable unless set to `false`. |
| bool privatize = true; |
| /// [in] if set, outermost evaluation and collapse depth for emitting |
| /// intervening code from imperfect collapsed loop nests. |
| lower::pft::Evaluation *outerCollapseEval = nullptr; |
| int collapseValue = 0; |
| }; |
| |
| /// Create the body (block) for an OpenMP Operation. |
| /// |
| /// \param [in] op - the operation the body belongs to. |
| /// \param [in] info - options controlling code-gen for the construction. |
| /// \param [in] queue - work queue with nested constructs. |
| /// \param [in] item - item in the queue to generate body for. |
| static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = info.converter.getFirOpBuilder(); |
| |
| auto insertMarker = [](fir::FirOpBuilder &builder) { |
| mlir::Value undef = fir::UndefOp::create(builder, builder.getUnknownLoc(), |
| builder.getIndexType()); |
| return undef.getDefiningOp(); |
| }; |
| |
| // Create the entry block for the region and collect its arguments for use |
| // within the region. The entry block will be created as follows: |
| // - By default, it will be empty and have no arguments. |
| // - Operations implementing the omp::BlockArgOpenMPOpInterface can set the |
| // `info.blockArgs` pointer so that block arguments will be those |
| // corresponding to entry block argument-generating clauses. Binding of |
| // Fortran symbols to the new MLIR values is done automatically. |
| // - If the `info.genRegionEntryCB` callback is set, it takes precedence and |
| // allows callers to manually create the entry block with its intended |
| // list of arguments and to bind these arguments to their corresponding |
| // Fortran symbols. This is used for e.g. loop induction variables. |
| auto regionArgs = [&]() -> llvm::SmallVector<const semantics::Symbol *> { |
| if (info.genRegionEntryCB) |
| return info.genRegionEntryCB(&op); |
| |
| if (info.blockArgs) { |
| genEntryBlock(firOpBuilder, info.blockArgs->asEntryBlockArgs(), |
| op.getRegion(0)); |
| bindEntryBlockArgs(info.converter, |
| llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(op), |
| *info.blockArgs); |
| return llvm::to_vector(info.blockArgs->getSyms()); |
| } |
| |
| firOpBuilder.createBlock(&op.getRegion(0)); |
| return {}; |
| }(); |
| |
| // Mark the earliest insertion point. |
| mlir::Operation *marker = insertMarker(firOpBuilder); |
| |
| // If it is an unstructured region, create empty blocks for all evaluations. |
| if (lower::omp::isLastItemInQueue(item, queue) && |
| info.eval.lowerAsUnstructured()) { |
| lower::createEmptyRegionBlocks<mlir::omp::TerminatorOp, mlir::omp::YieldOp>( |
| firOpBuilder, info.eval.getNestedEvaluations()); |
| } |
| |
| // Start with privatization, so that the lowering of the nested |
| // code will use the right symbols. |
| bool isLoop = llvm::omp::getDirectiveAssociation(info.dir) == |
| llvm::omp::Association::LoopNest; |
| bool privatize = info.clauses && info.privatize; |
| |
| firOpBuilder.setInsertionPoint(marker); |
| std::optional<DataSharingProcessor> tempDsp; |
| if (privatize && !info.dsp) { |
| tempDsp.emplace(info.converter, info.semaCtx, *info.clauses, info.eval, |
| Fortran::lower::omp::isLastItemInQueue(item, queue), |
| /*useDelayedPrivatization=*/false, info.symTable); |
| tempDsp->processStep1(); |
| } |
| |
| if (info.dir == llvm::omp::Directive::OMPD_parallel) { |
| threadPrivatizeVars(info.converter, info.eval); |
| if (info.clauses) { |
| firOpBuilder.setInsertionPoint(marker); |
| ClauseProcessor(info.converter, info.semaCtx, *info.clauses) |
| .processCopyin(); |
| } |
| } |
| |
| if (info.dir == llvm::omp::Directive::OMPD_teams) |
| groupprivatizeVars(info.converter, info.eval); |
| |
| if (!info.genSkeletonOnly) { |
| if (ConstructQueue::const_iterator next = std::next(item); |
| next != queue.end()) { |
| genOMPDispatch(info.converter, info.symTable, info.semaCtx, info.eval, |
| info.loc, queue, next); |
| } else { |
| // genFIR(Evaluation&) tries to patch up unterminated blocks, causing |
| // a lot of complications for our approach if the terminator generation |
| // is delayed past this point. Insert a temporary terminator here, then |
| // delete it. |
| firOpBuilder.setInsertionPointToEnd(&op.getRegion(0).back()); |
| auto *temp = lower::genOpenMPTerminator(firOpBuilder, &op, info.loc); |
| firOpBuilder.setInsertionPointAfter(marker); |
| if (info.outerCollapseEval) |
| genCollapsedLoopNestBody(info.converter, *info.outerCollapseEval, |
| info.collapseValue); |
| else |
| genNestedEvaluations(info.converter, info.eval); |
| temp->erase(); |
| } |
| } |
| |
| // Get or create a unique exiting block from the given region, or |
| // return nullptr if there is no exiting block. |
| auto getUniqueExit = [&](mlir::Region ®ion) -> mlir::Block * { |
| // Find the blocks where the OMP terminator should go. In simple cases |
| // it is the single block in the operation's region. When the region |
| // is more complicated, especially with unstructured control flow, there |
| // may be multiple blocks, and some of them may have non-OMP terminators |
| // resulting from lowering of the code contained within the operation. |
| // All the remaining blocks are potential exit points from the op's region. |
| // |
| // Explicit control flow cannot exit any OpenMP region (other than via |
| // STOP), and that is enforced by semantic checks prior to lowering. STOP |
| // statements are lowered to a function call. |
| |
| // Collect unterminated blocks. |
| llvm::SmallVector<mlir::Block *> exits; |
| for (mlir::Block &b : region) { |
| if (b.empty() || !b.back().hasTrait<mlir::OpTrait::IsTerminator>()) |
| exits.push_back(&b); |
| } |
| |
| if (exits.empty()) |
| return nullptr; |
| // If there already is a unique exiting block, do not create another one. |
| // Additionally, some ops (e.g. omp.sections) require only 1 block in |
| // its region. |
| if (exits.size() == 1) |
| return exits[0]; |
| mlir::Block *exit = firOpBuilder.createBlock(®ion); |
| for (mlir::Block *b : exits) { |
| firOpBuilder.setInsertionPointToEnd(b); |
| mlir::cf::BranchOp::create(firOpBuilder, info.loc, exit); |
| } |
| return exit; |
| }; |
| |
| if (auto *exitBlock = getUniqueExit(op.getRegion(0))) { |
| firOpBuilder.setInsertionPointToEnd(exitBlock); |
| auto *term = lower::genOpenMPTerminator(firOpBuilder, &op, info.loc); |
| // Only insert lastprivate code when there actually is an exit block. |
| // Such a block may not exist if the nested code produced an infinite |
| // loop (this may not make sense in production code, but a user could |
| // write that and we should handle it). |
| firOpBuilder.setInsertionPoint(term); |
| if (privatize) { |
| // DataSharingProcessor::processStep2() may create operations before/after |
| // the one passed as argument. We need to treat loop wrappers and their |
| // nested loop as a unit, so we need to pass the bottom level wrapper (if |
| // present). Otherwise, these operations will be inserted within a |
| // wrapper region. |
| mlir::Operation *privatizationBottomLevelOp = &op; |
| if (auto loopNest = llvm::dyn_cast<mlir::omp::LoopNestOp>(op)) { |
| llvm::SmallVector<mlir::omp::LoopWrapperInterface> wrappers; |
| loopNest.gatherWrappers(wrappers); |
| if (!wrappers.empty()) |
| privatizationBottomLevelOp = &*wrappers.front(); |
| } |
| |
| if (!info.dsp) { |
| assert(tempDsp.has_value()); |
| tempDsp->processStep2(privatizationBottomLevelOp, isLoop); |
| } else { |
| if (isLoop && regionArgs.size() > 0) { |
| for (const auto ®ionArg : regionArgs) { |
| info.dsp->pushLoopIV(info.converter.getSymbolAddress(*regionArg)); |
| } |
| } |
| info.dsp->processStep2(privatizationBottomLevelOp, isLoop); |
| } |
| } |
| } |
| |
| firOpBuilder.setInsertionPointAfter(marker); |
| marker->erase(); |
| } |
| |
| static void genBodyOfTargetDataOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::omp::TargetDataOp &dataOp, const ObjectEntryBlockArgs &args, |
| const mlir::Location ¤tLocation, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| genEntryBlock(firOpBuilder, args.asEntryBlockArgs(), dataOp.getRegion()); |
| bindEntryBlockArgs(converter, dataOp, args); |
| |
| // Insert dummy instruction to remember the insertion position. The |
| // marker will be deleted by clean up passes since there are no uses. |
| // Remembering the position for further insertion is important since |
| // there are hlfir.declares inserted above while setting block arguments |
| // and new code from the body should be inserted after that. |
| mlir::Value undefMarker = fir::UndefOp::create(firOpBuilder, dataOp.getLoc(), |
| firOpBuilder.getIndexType()); |
| |
| // Create blocks for unstructured regions. This has to be done since |
| // blocks are initially allocated with the function as the parent region. |
| if (eval.lowerAsUnstructured()) { |
| lower::createEmptyRegionBlocks<mlir::omp::TerminatorOp, mlir::omp::YieldOp>( |
| firOpBuilder, eval.getNestedEvaluations()); |
| } |
| |
| mlir::omp::TerminatorOp::create(firOpBuilder, currentLocation); |
| |
| // Set the insertion point after the marker. |
| firOpBuilder.setInsertionPointAfter(undefMarker.getDefiningOp()); |
| |
| if (ConstructQueue::const_iterator next = std::next(item); |
| next != queue.end()) { |
| genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue, |
| next); |
| } else { |
| genNestedEvaluations(converter, eval); |
| } |
| } |
| |
| // This generates intermediate common block member accesses within a region |
| // and then rebinds the members symbol to the intermediate accessors we have |
| // generated so that subsequent code generation will utilise these instead. |
| // |
| // When the scope changes, the bindings to the intermediate accessors should |
| // be dropped in place of the original symbol bindings. |
| // |
| // This is for utilisation with TargetOp. |
| static void genIntermediateCommonBlockAccessors( |
| Fortran::lower::AbstractConverter &converter, |
| const mlir::Location ¤tLocation, |
| llvm::ArrayRef<mlir::BlockArgument> mapBlockArgs, |
| llvm::ArrayRef<const Fortran::semantics::Symbol *> mapSyms) { |
| // Iterate over the symbol list, which will be shorter than the list of |
| // arguments if new entry block arguments were introduced to implicitly map |
| // outside values used by the bounds cloned into the target region. In that |
| // case, the additional block arguments do not need processing here. |
| for (auto [mapSym, mapArg] : llvm::zip_first(mapSyms, mapBlockArgs)) { |
| auto *details = mapSym->detailsIf<Fortran::semantics::CommonBlockDetails>(); |
| if (!details) |
| continue; |
| |
| for (auto obj : details->objects()) { |
| auto targetCBMemberBind = Fortran::lower::genCommonBlockMember( |
| converter, currentLocation, *obj, mapArg, mapSym->size()); |
| fir::ExtendedValue sexv = converter.getSymbolExtendedValue(*obj); |
| fir::ExtendedValue targetCBExv = |
| getExtendedValue(sexv, targetCBMemberBind); |
| converter.bindSymbol(*obj, targetCBExv); |
| } |
| } |
| } |
| |
| // This functions creates a block for the body of the targetOp's region. It adds |
| // all the symbols present in mapSymbols as block arguments to this block. |
| static void genBodyOfTargetOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::omp::TargetOp &targetOp, const ObjectEntryBlockArgs &args, |
| const mlir::Location ¤tLocation, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item, DataSharingProcessor &dsp) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| auto argIface = llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(*targetOp); |
| |
| mlir::Region ®ion = targetOp.getRegion(); |
| genEntryBlock(firOpBuilder, args.asEntryBlockArgs(), region); |
| bindEntryBlockArgs(converter, targetOp, args); |
| if (HostEvalInfo *hostEvalInfo = getHostEvalInfoStackTop(converter)) |
| hostEvalInfo->bindOperands(argIface.getHostEvalBlockArgs()); |
| |
| // If we map a common block using it's symbol e.g. map(tofrom: /common_block/) |
| // and accessing its members within the target region, there is a large |
| // chance we will end up with uses external to the region accessing the common |
| // resolve these, we do so by generating new common block member accesses |
| // within the region, binding them to the member symbol for the scope of the |
| // region so that subsequent code generation within the region will utilise |
| // our new member accesses we have created. |
| genIntermediateCommonBlockAccessors(converter, currentLocation, |
| argIface.getMapBlockArgs(), |
| args.map.getSyms()); |
| |
| // Check if cloning the bounds introduced any dependency on the outer region. |
| // If so, then either clone them as well if they are MemoryEffectFree, or else |
| // copy them to a new temporary and add them to the map and block_argument |
| // lists and replace their uses with the new temporary. |
| cloneOrMapRegionOutsiders(firOpBuilder, targetOp); |
| |
| // Insert dummy instruction to remember the insertion position. The |
| // marker will be deleted since there are not uses. |
| // In the HLFIR flow there are hlfir.declares inserted above while |
| // setting block arguments. |
| mlir::Value undefMarker = fir::UndefOp::create( |
| firOpBuilder, targetOp.getLoc(), firOpBuilder.getIndexType()); |
| |
| // Create blocks for unstructured regions. This has to be done since |
| // blocks are initially allocated with the function as the parent region. |
| if (lower::omp::isLastItemInQueue(item, queue) && |
| eval.lowerAsUnstructured()) { |
| lower::createEmptyRegionBlocks<mlir::omp::TerminatorOp, mlir::omp::YieldOp>( |
| firOpBuilder, eval.getNestedEvaluations()); |
| } |
| |
| mlir::omp::TerminatorOp::create(firOpBuilder, currentLocation); |
| |
| // Create the insertion point after the marker. |
| firOpBuilder.setInsertionPointAfter(undefMarker.getDefiningOp()); |
| |
| bool immediatelyNestsTeams = false; |
| if (std::next(item) != queue.end()) { |
| immediatelyNestsTeams = llvm::omp::topTeamsSet.test(std::next(item)->id); |
| } else if (lower::pft::Evaluation *nestedEval = |
| extractOnlyOmpNestedEval(eval)) { |
| const auto &ompEval = nestedEval->get<parser::OpenMPConstruct>(); |
| llvm::omp::Directive nestedDir = |
| parser::omp::GetOmpDirectiveName(ompEval).v; |
| llvm::omp::Directive firstLeafDir = |
| llvm::omp::getLeafConstructsOrSelf(nestedDir).front(); |
| immediatelyNestsTeams = llvm::omp::topTeamsSet.test(firstLeafDir); |
| } |
| // No enclosing teams: materialise the copy on the target itself |
| if (!immediatelyNestsTeams) |
| groupprivatizeVars(converter, eval); |
| |
| if (ConstructQueue::const_iterator next = std::next(item); |
| next != queue.end()) { |
| genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue, |
| next); |
| } else { |
| genNestedEvaluations(converter, eval); |
| } |
| |
| dsp.processStep2(targetOp, /*isLoop=*/false); |
| } |
| |
| template <typename OpTy, typename... Args> |
| static OpTy genOpWithBody(const OpWithBodyGenInfo &info, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item, Args &&...args) { |
| auto op = OpTy::create(info.converter.getFirOpBuilder(), info.loc, |
| std::forward<Args>(args)...); |
| createBodyOfOp(*op, info, queue, item); |
| return op; |
| } |
| |
| template <typename OpTy, typename ClauseOpsTy> |
| static OpTy genWrapperOp(lower::AbstractConverter &converter, |
| mlir::Location loc, const ClauseOpsTy &clauseOps, |
| const ObjectEntryBlockArgs &args) { |
| static_assert( |
| OpTy::template hasTrait<mlir::omp::LoopWrapperInterface::Trait>(), |
| "expected a loop wrapper"); |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| // Create wrapper. |
| auto op = OpTy::create(firOpBuilder, loc, clauseOps); |
| |
| // Create entry block with arguments. |
| genEntryBlock(firOpBuilder, args.asEntryBlockArgs(), op.getRegion()); |
| |
| return op; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Code generation functions for clauses |
| //===----------------------------------------------------------------------===// |
| |
| static void genAllocateClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, |
| const ObjectList &objects, |
| const List<Clause> &clauses, mlir::Location loc, |
| llvm::SmallVectorImpl<mlir::Value> &operandRange, |
| mlir::omp::AllocateDirOperands &clauseOps) { |
| if (!objects.empty()) |
| genObjectList(objects, converter, operandRange); |
| |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAlign(clauseOps); |
| cp.processAllocator(stmtCtx, clauseOps); |
| } |
| |
| static void genCancelClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::CancelOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processCancelDirectiveName(clauseOps); |
| cp.processIf(llvm::omp::Directive::OMPD_cancel, clauseOps); |
| } |
| |
| static void |
| genCancellationPointClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::CancellationPointOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processCancelDirectiveName(clauseOps); |
| } |
| |
| static void genCriticalDeclareClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::CriticalDeclareOperands &clauseOps, llvm::StringRef name) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processHint(clauseOps); |
| clauseOps.symName = |
| mlir::StringAttr::get(converter.getFirOpBuilder().getContext(), name); |
| } |
| |
| static void genDistributeClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, |
| const List<Clause> &clauses, |
| mlir::Location loc, |
| mlir::omp::DistributeOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processDistSchedule(stmtCtx, clauseOps); |
| cp.processOrder(clauseOps); |
| } |
| |
| static void genFlushClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const ObjectList &objects, |
| const List<Clause> &clauses, mlir::Location loc, |
| llvm::SmallVectorImpl<mlir::Value> &operandRange) { |
| if (!objects.empty()) |
| genObjectList(objects, converter, operandRange); |
| |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processTODO<clause::AcqRel, clause::Acquire, clause::Release, |
| clause::SeqCst>(loc, llvm::omp::OMPD_flush); |
| } |
| |
| static void |
| genLoopNestClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, const List<Clause> &clauses, |
| mlir::Location loc, mlir::omp::LoopNestOperands &clauseOps, |
| llvm::SmallVectorImpl<const semantics::Symbol *> &iv) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| |
| HostEvalInfo *hostEvalInfo = getHostEvalInfoStackTop(converter); |
| if (!hostEvalInfo || !hostEvalInfo->apply(clauseOps, iv)) |
| cp.processCollapse(loc, eval, clauseOps, clauseOps, iv); |
| |
| clauseOps.loopInclusive = converter.getFirOpBuilder().getUnitAttr(); |
| cp.processTileSizes(eval, clauseOps); |
| } |
| |
| static void genLoopClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::LoopOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processBind(clauseOps); |
| cp.processOrder(clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects); |
| cp.processTODO<clause::Lastprivate>(loc, llvm::omp::Directive::OMPD_loop); |
| } |
| |
| static void genMaskedClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::MaskedOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processFilter(stmtCtx, clauseOps); |
| } |
| |
| static void |
| genOrderedRegionClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::OrderedRegionOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processSimd(clauseOps); |
| } |
| |
| static void genParallelClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, const List<Clause> &clauses, |
| mlir::Location loc, mlir::omp::ParallelOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps, /*supportAlignment=*/true); |
| cp.processIf(llvm::omp::Directive::OMPD_parallel, clauseOps); |
| |
| HostEvalInfo *hostEvalInfo = getHostEvalInfoStackTop(converter); |
| if (!hostEvalInfo || !hostEvalInfo->apply(clauseOps)) |
| cp.processNumThreads(stmtCtx, clauseOps); |
| |
| cp.processProcBind(clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects); |
| } |
| |
| static void genScanClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::ScanOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processInclusive(loc, clauseOps); |
| cp.processExclusive(loc, clauseOps); |
| } |
| |
| static void |
| genSectionsClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::SectionsOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processNowait(clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects); |
| // TODO Support delayed privatization. |
| } |
| |
| static void genSimdClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::SimdOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects, |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> *reductionVarCache = |
| nullptr) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAligned(clauseOps); |
| cp.processIf(llvm::omp::Directive::OMPD_simd, clauseOps); |
| cp.processNontemporal(clauseOps); |
| cp.processOrder(clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects, reductionVarCache); |
| cp.processSafelen(clauseOps); |
| cp.processSimdlen(clauseOps); |
| cp.processLinear(clauseOps); |
| } |
| |
| // SIMD construct may have implicit |
| // linear semantics on IV. Process the same here. |
| static void |
| genSimdImplicitLinear(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| mlir::omp::SimdOperands &clauseOps, |
| mlir::omp::LoopNestOperands loopNestClauseOps, |
| llvm::SmallVector<const semantics::Symbol *> iv) { |
| |
| // If the (standalone/composite) SIMD is enclosed within TARGET, |
| // implicit linearization will cause invalid FIR due to |
| // target operation `host_eval` argument's illegal use in omp.simd. |
| // Hence skip implicit linearization if TARGET encloses the current |
| // SIMD. |
| auto *currentOp = |
| converter.getFirOpBuilder().getInsertionBlock()->getParentOp(); |
| while (currentOp) { |
| if (auto targetOp = mlir::dyn_cast<mlir::omp::TargetOp>(currentOp)) |
| return; |
| currentOp = currentOp->getParentOp(); |
| } |
| |
| std::vector<mlir::Attribute> typeAttrs; |
| std::vector<mlir::Attribute> linearModAttrs; |
| // If attributes from explicit `linear(...)` clause are present, |
| // carry them forward. |
| if (clauseOps.linearVarTypes && !clauseOps.linearVarTypes.empty()) |
| typeAttrs.assign(clauseOps.linearVarTypes.begin(), |
| clauseOps.linearVarTypes.end()); |
| if (clauseOps.linearModifiers && !clauseOps.linearModifiers.empty()) |
| linearModAttrs.assign(clauseOps.linearModifiers.begin(), |
| clauseOps.linearModifiers.end()); |
| |
| for (auto [loopVar, loopStep] : llvm::zip(iv, loopNestClauseOps.loopSteps)) { |
| const mlir::Value variable = converter.getSymbolAddress(*loopVar); |
| |
| // If the loop variable is already linearized (through an explicit |
| // `linear()` clause, skip. |
| if (std::find(clauseOps.linearVars.begin(), clauseOps.linearVars.end(), |
| variable) != clauseOps.linearVars.end()) |
| continue; |
| |
| // TODO: Implicit linearization is skipped if iv is a pointer |
| // or an allocatable, due to potential mismatch between the linear |
| // variable type (example !fir.ref<!fir.box<!fir.heap<i32>>>) |
| // and the linear step size (example: i64). Handle this type mismatch |
| // gracefully. |
| if (loopVar->test(Fortran::semantics::Symbol::Flag::OmpLinear) && |
| !(Fortran::semantics::IsAllocatableOrPointer(*loopVar) || |
| Fortran::semantics::IsAllocatableOrPointer(loopVar->GetUltimate()))) { |
| mlir::Type ty = converter.genType(*loopVar); |
| typeAttrs.push_back(mlir::TypeAttr::get(ty)); |
| if (semaCtx.langOptions().OpenMPVersion >= 52) |
| linearModAttrs.push_back(mlir::omp::LinearModifierAttr::get( |
| &converter.getMLIRContext(), mlir::omp::LinearModifier::val)); |
| else |
| linearModAttrs.push_back( |
| mlir::UnitAttr::get(&converter.getMLIRContext())); |
| clauseOps.linearVars.push_back(variable); |
| clauseOps.linearStepVars.push_back(loopStep); |
| } |
| } |
| if (!typeAttrs.empty()) { |
| clauseOps.linearVarTypes = |
| mlir::ArrayAttr::get(&converter.getMLIRContext(), typeAttrs); |
| clauseOps.linearModifiers = |
| mlir::ArrayAttr::get(&converter.getMLIRContext(), linearModAttrs); |
| } |
| } |
| |
| static void genScopeClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::ScopeOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processNowait(clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects); |
| } |
| |
| static void genSingleClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::SingleOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processCopyprivate(loc, clauseOps); |
| cp.processNowait(clauseOps); |
| // TODO Support delayed privatization. |
| } |
| |
| static void |
| genTargetClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, lower::pft::Evaluation &eval, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::TargetExtOperands &clauseOps, |
| DefaultMapsTy &defaultMaps, |
| llvm::SmallVectorImpl<Object> &hasDeviceAddrObjects, |
| llvm::SmallVectorImpl<Object> &inReductionObjects, |
| llvm::SmallVectorImpl<Object> &isDevicePtrObjects, |
| llvm::SmallVectorImpl<Object> &mapObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processDefaultMap(stmtCtx, defaultMaps); |
| cp.processDepend(symTable, stmtCtx, clauseOps); |
| cp.processDevice(stmtCtx, clauseOps); |
| cp.processDynGroupprivate(stmtCtx, clauseOps); |
| cp.processHasDeviceAddr(stmtCtx, clauseOps, hasDeviceAddrObjects); |
| if (HostEvalInfo *hostEvalInfo = getHostEvalInfoStackTop(converter)) { |
| // Only process host_eval if compiling for the host device. |
| HostEvalVisitor visitor(converter, semaCtx, stmtCtx, loc, *hostEvalInfo); |
| visitor.visit(eval); |
| hostEvalInfo->collectValues(clauseOps.hostEvalVars); |
| } |
| cp.processIf(llvm::omp::Directive::OMPD_target, clauseOps); |
| cp.processInReduction(loc, clauseOps, inReductionObjects); |
| // The host lowering of `target in_reduction` redirects each list item's |
| // dedicated implicit map entry to the per-task reduction-private storage |
| // returned by the runtime. A COMMON block member is accessed in the target |
| // body through the COMMON block storage map, not through its own member map |
| // entry, so the redirect would rewrite an unused map argument and the body |
| // would accumulate into the mapped original rather than the private copy. |
| // An EQUIVALENCE-associated variable has the same problem: it shares storage |
| // with another variable that may be mapped separately (e.g. `map(tofrom: j)` |
| // with `equivalence(i, j)`), so the body updates the aliased storage while |
| // the redirected runtime-private copy stays dead. |
| // Reject both cases until the redirect can target the storage map. |
| for (const Object &object : inReductionObjects) |
| if (const semantics::Symbol *sym = object.sym()) { |
| if (semantics::FindCommonBlockContaining(sym->GetUltimate())) |
| TODO(loc, |
| "TARGET construct with IN_REDUCTION of a COMMON block member"); |
| if (semantics::FindEquivalenceSet(sym->GetUltimate())) |
| TODO(loc, "TARGET construct with IN_REDUCTION of an EQUIVALENCE " |
| "storage-associated variable"); |
| } |
| cp.processIsDevicePtr(stmtCtx, clauseOps, isDevicePtrObjects); |
| cp.processMap(loc, stmtCtx, clauseOps, llvm::omp::Directive::OMPD_unknown, |
| &mapObjects); |
| cp.processNowait(clauseOps); |
| cp.processThreadLimit(stmtCtx, clauseOps); |
| cp.processTODO<clause::Allocate, clause::UsesAllocators>( |
| loc, llvm::omp::Directive::OMPD_target); |
| |
| // `target private(..)` is only supported in delayed privatization mode. |
| if (!enableDelayedPrivatization) |
| cp.processTODO<clause::Firstprivate, clause::Private>( |
| loc, llvm::omp::Directive::OMPD_target); |
| } |
| |
| static void genTargetDataClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, const List<Clause> &clauses, |
| mlir::Location loc, mlir::omp::TargetDataOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &useDeviceAddrObjects, |
| llvm::SmallVectorImpl<Object> &useDevicePtrObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processDevice(stmtCtx, clauseOps); |
| cp.processIf(llvm::omp::Directive::OMPD_target_data, clauseOps); |
| cp.processMap(loc, stmtCtx, clauseOps); |
| cp.processUseDeviceAddr(stmtCtx, clauseOps, useDeviceAddrObjects); |
| cp.processUseDevicePtr(stmtCtx, clauseOps, useDevicePtrObjects); |
| |
| // This function implements the deprecated functionality of use_device_ptr |
| // that allows users to provide non-CPTR arguments to it with the caveat |
| // that the compiler will treat them as use_device_addr. A lot of legacy |
| // code may still depend on this functionality, so we should support it |
| // in some manner. We do so currently by simply shifting non-cptr operands |
| // from the use_device_ptr lists into the use_device_addr lists. |
| // TODO: Perhaps create a user provideable compiler option that will |
| // re-introduce a hard-error rather than a warning in these cases. |
| promoteNonCPtrUseDevicePtrArgsToUseDeviceAddr( |
| clauseOps.useDeviceAddrVars, useDeviceAddrObjects, |
| clauseOps.useDevicePtrVars, useDevicePtrObjects); |
| } |
| |
| static void genTargetEnterExitUpdateDataClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::SymMap &symTable, lower::StatementContext &stmtCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| llvm::omp::Directive directive, |
| mlir::omp::TargetEnterExitUpdateDataOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processDepend(symTable, stmtCtx, clauseOps); |
| cp.processDevice(stmtCtx, clauseOps); |
| cp.processIf(directive, clauseOps); |
| |
| if (directive == llvm::omp::Directive::OMPD_target_update) |
| cp.processMotionClauses(stmtCtx, clauseOps); |
| else |
| cp.processMap(loc, stmtCtx, clauseOps, directive); |
| |
| cp.processNowait(clauseOps); |
| } |
| |
| static void genTaskClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::TaskOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &inReductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAffinity(clauseOps); |
| cp.processAllocate(clauseOps); |
| cp.processDepend(symTable, stmtCtx, clauseOps); |
| cp.processFinal(stmtCtx, clauseOps); |
| cp.processIf(llvm::omp::Directive::OMPD_task, clauseOps); |
| cp.processInReduction(loc, clauseOps, inReductionObjects); |
| cp.processMergeable(clauseOps); |
| cp.processPriority(stmtCtx, clauseOps); |
| cp.processUntied(clauseOps); |
| cp.processDetach(clauseOps); |
| } |
| |
| static void |
| genTaskgroupClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::TaskgroupOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &taskReductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processTaskReduction(loc, clauseOps, taskReductionObjects); |
| } |
| |
| static void genTaskloopClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, const List<Clause> &clauses, |
| mlir::Location loc, mlir::omp::TaskloopContextOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects, |
| llvm::SmallVectorImpl<Object> &inReductionObjects) { |
| |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processFinal(stmtCtx, clauseOps); |
| cp.processGrainsize(stmtCtx, clauseOps); |
| cp.processIf(llvm::omp::Directive::OMPD_taskloop, clauseOps); |
| cp.processInReduction(loc, clauseOps, inReductionObjects); |
| cp.processMergeable(clauseOps); |
| cp.processNogroup(clauseOps); |
| cp.processNumTasks(stmtCtx, clauseOps); |
| cp.processPriority(stmtCtx, clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects); |
| cp.processUntied(clauseOps); |
| } |
| |
| static void genTaskwaitClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::TaskwaitOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| lower::StatementContext stmtCtx; |
| lower::SymMap &symTable = converter.getSymbolMap(); |
| cp.processDepend(symTable, stmtCtx, clauseOps); |
| cp.processTODO<clause::Nowait>(loc, llvm::omp::Directive::OMPD_taskwait); |
| } |
| |
| static void genWorkshareClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::WorkshareOperands &clauseOps) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processNowait(clauseOps); |
| } |
| |
| static void genTeamsClauses(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, |
| const List<Clause> &clauses, mlir::Location loc, |
| mlir::omp::TeamsOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| // TODO: Only evaluate it here if it's not host-evaluated, like num_teams and |
| // thread_limit. |
| cp.processDynGroupprivate(stmtCtx, clauseOps); |
| cp.processIf(llvm::omp::Directive::OMPD_teams, clauseOps); |
| |
| HostEvalInfo *hostEvalInfo = getHostEvalInfoStackTop(converter); |
| if (!hostEvalInfo || !hostEvalInfo->apply(clauseOps)) { |
| cp.processNumTeams(stmtCtx, clauseOps); |
| cp.processThreadLimit(stmtCtx, clauseOps); |
| } |
| |
| cp.processReduction(loc, clauseOps, reductionObjects); |
| cp.processDynGroupprivate(stmtCtx, clauseOps); |
| // TODO Support delayed privatization. |
| } |
| |
| static void genWsloopClauses( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, const List<Clause> &clauses, |
| mlir::Location loc, mlir::omp::WsloopOperands &clauseOps, |
| llvm::SmallVectorImpl<Object> &reductionObjects, |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> *reductionVarCache = |
| nullptr) { |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAllocate(clauseOps); |
| cp.processNowait(clauseOps); |
| cp.processOrder(clauseOps); |
| cp.processOrdered(clauseOps); |
| cp.processReduction(loc, clauseOps, reductionObjects, reductionVarCache); |
| cp.processSchedule(stmtCtx, clauseOps); |
| cp.processLinear(clauseOps); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Code generation functions for leaf constructs |
| //===----------------------------------------------------------------------===// |
| static mlir::omp::AllocateDirOp genAllocateDirOp( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ObjectList &objects, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| llvm::SmallVector<mlir::Value> operandRange; |
| mlir::omp::AllocateDirOperands clauseOps; |
| genAllocateClauses(converter, semaCtx, stmtCtx, objects, item->clauses, loc, |
| operandRange, clauseOps); |
| |
| auto allocDirOp = mlir::omp::AllocateDirOp::create( |
| converter.getFirOpBuilder(), loc, operandRange, clauseOps.align, |
| clauseOps.allocator); |
| |
| // Register a cleanup at the Fortran scope exit. |
| fir::FirOpBuilder *builder = &converter.getFirOpBuilder(); |
| mlir::Value allocator = clauseOps.allocator; |
| converter.getFctCtx().attachCleanup([builder, loc, operandRange, |
| allocator]() { |
| mlir::omp::AllocateFreeOp::create(*builder, loc, operandRange, allocator); |
| }); |
| |
| return allocDirOp; |
| } |
| |
| static mlir::omp::BarrierOp |
| genBarrierOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| return mlir::omp::BarrierOp::create(converter.getFirOpBuilder(), loc); |
| } |
| |
| static mlir::omp::CancelOp genCancelOp(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::CancelOperands clauseOps; |
| genCancelClauses(converter, semaCtx, item->clauses, loc, clauseOps); |
| |
| return mlir::omp::CancelOp::create(converter.getFirOpBuilder(), loc, |
| clauseOps); |
| } |
| |
| static mlir::omp::CancellationPointOp genCancellationPointOp( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::CancellationPointOperands clauseOps; |
| genCancellationPointClauses(converter, semaCtx, item->clauses, loc, |
| clauseOps); |
| |
| return mlir::omp::CancellationPointOp::create(converter.getFirOpBuilder(), |
| loc, clauseOps); |
| } |
| |
| static mlir::omp::CriticalOp |
| genCriticalOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item, |
| const std::optional<parser::Name> &name) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::FlatSymbolRefAttr nameAttr; |
| |
| if (name) { |
| std::string nameStr = name->ToString(); |
| mlir::ModuleOp mod = firOpBuilder.getModule(); |
| auto global = mod.lookupSymbol<mlir::omp::CriticalDeclareOp>(nameStr); |
| if (!global) { |
| mlir::omp::CriticalDeclareOperands clauseOps; |
| genCriticalDeclareClauses(converter, semaCtx, item->clauses, loc, |
| clauseOps, nameStr); |
| |
| mlir::OpBuilder modBuilder(mod.getBodyRegion()); |
| global = mlir::omp::CriticalDeclareOp::create(modBuilder, loc, clauseOps); |
| } |
| nameAttr = mlir::FlatSymbolRefAttr::get(firOpBuilder.getContext(), |
| global.getSymName()); |
| } |
| |
| return genOpWithBody<mlir::omp::CriticalOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_critical), |
| queue, item, nameAttr); |
| } |
| |
| static mlir::omp::FlushOp |
| genFlushOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ObjectList &objects, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| llvm::SmallVector<mlir::Value> operandRange; |
| genFlushClauses(converter, semaCtx, objects, item->clauses, loc, |
| operandRange); |
| |
| return mlir::omp::FlushOp::create(converter.getFirOpBuilder(), |
| converter.getCurrentLocation(), |
| operandRange); |
| } |
| |
| static mlir::omp::LoopNestOp genLoopNestOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item, mlir::omp::LoopNestOperands &clauseOps, |
| llvm::ArrayRef<const semantics::Symbol *> iv, |
| llvm::ArrayRef<std::pair<mlir::omp::BlockArgOpenMPOpInterface, |
| const ObjectEntryBlockArgs &>> |
| wrapperArgs, |
| llvm::omp::Directive directive, DataSharingProcessor &dsp, |
| llvm::function_ref<void(mlir::Operation *)> loopPostIvCb = nullptr) { |
| auto ivCallback = [&](mlir::Operation *op) { |
| genLoopVars(op, converter, loc, iv, wrapperArgs); |
| if (loopPostIvCb) |
| loopPostIvCb(op); |
| return llvm::SmallVector<const semantics::Symbol *>(iv); |
| }; |
| |
| uint64_t nestValue = getCollapseValue(item->clauses); |
| nestValue = nestValue < iv.size() ? iv.size() : nestValue; |
| auto *nestedEval = getCollapsedLoopEval(eval, nestValue); |
| return genOpWithBody<mlir::omp::LoopNestOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, *nestedEval, |
| directive) |
| .setClauses(&item->clauses) |
| .setDataSharingProcessor(&dsp) |
| .setGenRegionEntryCb(ivCallback) |
| .setCollapseInfo(nestValue, eval), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::LoopOp |
| genLoopOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::LoopOperands loopClauseOps; |
| llvm::SmallVector<Object> loopReductionObjects; |
| genLoopClauses(converter, semaCtx, item->clauses, loc, loopClauseOps, |
| loopReductionObjects); |
| |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| /*useDelayedPrivatization=*/true, symTable); |
| dsp.processStep1(&loopClauseOps); |
| |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc, |
| loopNestClauseOps, iv); |
| |
| ObjectEntryBlockArgs loopArgs; |
| loopArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| loopArgs.priv.vars = loopClauseOps.privateVars; |
| loopArgs.reduction.objects = loopReductionObjects; |
| loopArgs.reduction.vars = loopClauseOps.reductionVars; |
| |
| auto loopOp = |
| genWrapperOp<mlir::omp::LoopOp>(converter, loc, loopClauseOps, loopArgs); |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, item, |
| loopNestClauseOps, iv, {{loopOp, loopArgs}}, |
| llvm::omp::Directive::OMPD_loop, dsp); |
| return loopOp; |
| } |
| |
| // ´nestedEval´ is the Evaluation of a children loop of ´eval´. |
| // In a regular OpenMP Construct Evaluation ´nestedEval´ is the only children. |
| // Can be retrieved with getNestedDoConstruct(Evaluation). |
| // <<OpenMPConstruct>> |
| // Loop |
| // <<End OpenMPConstruct>> |
| // |
| // ´nestedEval´ is most useful in the case that ´eval´ contains a sequence |
| // of loops. Then this function generates Canonical loop nests for individual |
| // loops. |
| // <<OpenMPConstruct>> |
| // Loop 1 |
| // Loop 2 |
| // <<End OpenMPConstruct>> |
| // |
| static void genCanonicalLoopNest( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| lower::pft::Evaluation *nestedEval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item, |
| size_t numLoops, llvm::SmallVectorImpl<mlir::omp::CanonicalLoopOp> &loops) { |
| assert(loops.empty() && "Expecting empty list to fill"); |
| assert(numLoops >= 1 && "Expecting at least one loop"); |
| |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| mlir::omp::LoopRelatedClauseOps loopInfo; |
| llvm::SmallVector<const semantics::Symbol *, 3> ivs; |
| collectLoopRelatedInfo(converter, loc, eval, nestedEval, numLoops, loopInfo, |
| ivs); |
| assert(ivs.size() == numLoops && |
| "Expected to parse as many loop variables as there are loops"); |
| |
| // Steps that follow: |
| // 1. Emit all of the loop's prologues (compute the tripcount) |
| // 2. Emit omp.canonical_loop nested inside each other (iteratively) |
| // 2.1. In the innermost omp.canonical_loop, emit the loop body prologue (in |
| // the body callback) |
| // |
| // Since emitting prologues and body code is split, remember prologue values |
| // for use when emitting the same loop's epilogues. |
| llvm::SmallVector<mlir::Value> tripcounts; |
| llvm::SmallVector<mlir::Value> clis; |
| llvm::SmallVector<lower::pft::Evaluation *> evals; |
| llvm::SmallVector<mlir::Type> loopVarTypes; |
| llvm::SmallVector<mlir::Value> loopStepVars; |
| llvm::SmallVector<mlir::Value> loopLBVars; |
| llvm::SmallVector<mlir::Value> blockArgs; |
| |
| // Step 1: Loop prologues |
| // Computing the trip count must happen before entering the outermost loop |
| lower::pft::Evaluation *innermostEval = nestedEval; |
| for (std::size_t i = 0; i < ivs.size(); ++i) { |
| if (innermostEval->getIf<parser::DoConstruct>()->IsDoConcurrent()) { |
| // OpenMP specifies DO CONCURRENT only with the `!omp loop` construct. |
| // Will need to add special cases for this combination. |
| TODO(loc, "DO CONCURRENT as canonical loop not supported"); |
| } |
| |
| auto &doLoopEval = innermostEval->getFirstNestedEvaluation(); |
| evals.push_back(innermostEval); |
| |
| // Get the loop bounds (and increment) |
| // auto &doLoopEval = nestedEval.getFirstNestedEvaluation(); |
| auto *doStmt = doLoopEval.getIf<parser::NonLabelDoStmt>(); |
| assert(doStmt && "Expected do loop to be in the nested evaluation"); |
| auto &loopControl = std::get<std::optional<parser::LoopControl>>(doStmt->t); |
| assert(loopControl.has_value()); |
| auto *bounds = std::get_if<parser::LoopControl::Bounds>(&loopControl->u); |
| assert(bounds && "Expected bounds for canonical loop"); |
| lower::StatementContext stmtCtx; |
| mlir::Value loopLBVar = fir::getBase( |
| converter.genExprValue(*semantics::GetExpr(bounds->Lower()), stmtCtx)); |
| mlir::Value loopUBVar = fir::getBase( |
| converter.genExprValue(*semantics::GetExpr(bounds->Upper()), stmtCtx)); |
| |
| // Get the integer kind for the loop variable and cast the loop bounds. |
| size_t loopVarTypeSize = bounds->Name().thing.symbol->GetUltimate().size(); |
| mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize); |
| loopVarTypes.push_back(loopVarType); |
| mlir::Value loopStepVar = [&]() { |
| if (auto &step = bounds->Step()) { |
| return fir::getBase( |
| converter.genExprValue(*semantics::GetExpr(step), stmtCtx)); |
| } |
| |
| // If `step` is not present, assume it is `1`. |
| return firOpBuilder.createIntegerConstant(loc, loopVarType, 1); |
| }(); |
| |
| auto convertToLoopVarType = [&](mlir::Value value) -> mlir::Value { |
| if (value.getType() == loopVarType) |
| return value; |
| if (std::optional<llvm::APInt> constant = fir::getIntIfConstant(value)) { |
| unsigned width = mlir::cast<mlir::IntegerType>(loopVarType).getWidth(); |
| llvm::APInt converted = constant->sextOrTrunc(width); |
| return mlir::arith::ConstantOp::create( |
| firOpBuilder, loc, loopVarType, |
| mlir::IntegerAttr::get(loopVarType, converted)); |
| } |
| return firOpBuilder.createConvert(loc, loopVarType, value); |
| }; |
| loopLBVar = convertToLoopVarType(loopLBVar); |
| loopUBVar = convertToLoopVarType(loopUBVar); |
| loopStepVar = convertToLoopVarType(loopStepVar); |
| loopLBVars.push_back(loopLBVar); |
| loopStepVars.push_back(loopStepVar); |
| |
| // Start lowering |
| mlir::Value zero = firOpBuilder.createIntegerConstant(loc, loopVarType, 0); |
| mlir::Value one = firOpBuilder.createIntegerConstant(loc, loopVarType, 1); |
| mlir::Value isDownwards = firOpBuilder.createOrFold<mlir::arith::CmpIOp>( |
| loc, mlir::arith::CmpIPredicate::slt, loopStepVar, zero); |
| |
| // Ensure we are counting upwards. If not, negate step and swap lb and ub. |
| mlir::Value negStep = |
| firOpBuilder.createOrFold<mlir::arith::SubIOp>(loc, zero, loopStepVar); |
| mlir::Value incr = firOpBuilder.createOrFold<mlir::arith::SelectOp>( |
| loc, isDownwards, negStep, loopStepVar); |
| mlir::Value lb = firOpBuilder.createOrFold<mlir::arith::SelectOp>( |
| loc, isDownwards, loopUBVar, loopLBVar); |
| mlir::Value ub = firOpBuilder.createOrFold<mlir::arith::SelectOp>( |
| loc, isDownwards, loopLBVar, loopUBVar); |
| |
| // Compute the trip count assuming lb <= ub. This guarantees that the result |
| // is non-negative and we can use unsigned arithmetic. |
| mlir::Value span = firOpBuilder.createOrFold<mlir::arith::SubIOp>( |
| loc, ub, lb, ::mlir::arith::IntegerOverflowFlags::nuw); |
| mlir::Value tcMinusOne = |
| firOpBuilder.createOrFold<mlir::arith::DivUIOp>(loc, span, incr); |
| mlir::Value tcIfLooping = firOpBuilder.createOrFold<mlir::arith::AddIOp>( |
| loc, tcMinusOne, one, ::mlir::arith::IntegerOverflowFlags::nuw); |
| |
| // Fall back to 0 if lb > ub |
| mlir::Value isZeroTC = firOpBuilder.createOrFold<mlir::arith::CmpIOp>( |
| loc, mlir::arith::CmpIPredicate::slt, ub, lb); |
| mlir::Value tripcount = firOpBuilder.createOrFold<mlir::arith::SelectOp>( |
| loc, isZeroTC, zero, tcIfLooping); |
| tripcounts.push_back(tripcount); |
| |
| // Create the CLI handle. |
| auto newcli = mlir::omp::NewCliOp::create(firOpBuilder, loc); |
| mlir::Value cli = newcli.getResult(); |
| clis.push_back(cli); |
| |
| if (i + 1 < ivs.size()) |
| innermostEval = getNestedDoConstruct(*innermostEval); |
| } |
| |
| // Step 2: Create nested canoncial loops |
| for (auto i : llvm::seq<size_t>(numLoops)) { |
| bool isInnermost = (i == numLoops - 1); |
| mlir::Type loopVarType = loopVarTypes[i]; |
| mlir::Value tripcount = tripcounts[i]; |
| mlir::Value cli = clis[i]; |
| auto &&eval = evals[i]; |
| |
| auto ivCallback = [&, i, isInnermost](mlir::Operation *op) |
| -> llvm::SmallVector<const Fortran::semantics::Symbol *> { |
| mlir::Region ®ion = op->getRegion(0); |
| |
| // Create the op's region skeleton (BB taking the iv as argument) |
| firOpBuilder.createBlock(®ion, {}, {loopVarType}, {loc}); |
| blockArgs.push_back(region.front().getArgument(0)); |
| |
| // Step 2.1: Emit body prologue code |
| // Compute the translation from logical iteration number to the value of |
| // the loop's iteration variable only in the innermost body. Currently, |
| // loop transformations do not allow any instruction between loops, but |
| // this will change with |
| if (isInnermost) { |
| assert(blockArgs.size() == numLoops && |
| "Expecting all block args to have been collected by now"); |
| for (auto j : llvm::seq<size_t>(numLoops)) { |
| mlir::Value natIterNum = fir::getBase(blockArgs[j]); |
| mlir::Value scaled = mlir::arith::MulIOp::create( |
| firOpBuilder, loc, natIterNum, loopStepVars[j]); |
| mlir::Value userVal = mlir::arith::AddIOp::create( |
| firOpBuilder, loc, loopLBVars[j], scaled); |
| |
| mlir::OpBuilder::InsertPoint insPt = |
| firOpBuilder.saveInsertionPoint(); |
| firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); |
| mlir::Type tempTy = converter.genType(*ivs[j]); |
| firOpBuilder.restoreInsertionPoint(insPt); |
| |
| // Write the loop value into loop variable |
| mlir::Value cvtVal = firOpBuilder.createConvert(loc, tempTy, userVal); |
| hlfir::Entity lhs{converter.getSymbolAddress(*ivs[j])}; |
| lhs = hlfir::derefPointersAndAllocatables(loc, firOpBuilder, lhs); |
| mlir::Operation *storeOp = |
| hlfir::AssignOp::create(firOpBuilder, loc, cvtVal, lhs); |
| firOpBuilder.setInsertionPointAfter(storeOp); |
| } |
| } |
| |
| return {ivs[i]}; |
| }; |
| |
| // Create the omp.canonical_loop operation |
| auto opGenInfo = OpWithBodyGenInfo(converter, symTable, semaCtx, loc, *eval, |
| llvm::omp::Directive::OMPD_unknown) |
| .setGenSkeletonOnly(!isInnermost) |
| .setClauses(&item->clauses) |
| .setPrivatize(false) |
| .setGenRegionEntryCb(ivCallback); |
| auto canonLoop = genOpWithBody<mlir::omp::CanonicalLoopOp>( |
| std::move(opGenInfo), queue, item, tripcount, cli); |
| loops.push_back(canonLoop); |
| |
| // Insert next loop nested inside last loop |
| firOpBuilder.setInsertionPoint( |
| canonLoop.getRegion().back().getTerminator()); |
| } |
| |
| firOpBuilder.setInsertionPointAfter(loops.front()); |
| } |
| |
| static void genInterchangeOp(Fortran::lower::AbstractConverter &converter, |
| Fortran::lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| Fortran::semantics::SemanticsContext &semaCtx, |
| Fortran::lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| TODO(converter.getCurrentLocation(), "OpenMP Interchange"); |
| } |
| |
| static void genTileOp(Fortran::lower::AbstractConverter &converter, |
| Fortran::lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| Fortran::semantics::SemanticsContext &semaCtx, |
| Fortran::lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| mlir::omp::SizesClauseOps sizesClause; |
| ClauseProcessor cp(converter, semaCtx, item->clauses); |
| cp.processSizes(stmtCtx, sizesClause); |
| |
| size_t numLoops = sizesClause.sizes.size(); |
| llvm::SmallVector<mlir::omp::CanonicalLoopOp, 3> canonLoops; |
| canonLoops.reserve(numLoops); |
| |
| genCanonicalLoopNest(converter, symTable, semaCtx, eval, |
| getNestedDoConstruct(eval), loc, queue, item, numLoops, |
| canonLoops); |
| assert((canonLoops.size() == numLoops) && |
| "Expecting the predetermined number of loops"); |
| |
| llvm::SmallVector<mlir::Value, 3> applyees; |
| applyees.reserve(numLoops); |
| for (mlir::omp::CanonicalLoopOp l : canonLoops) |
| applyees.push_back(l.getCli()); |
| |
| // Emit the associated loops and create a CLI for each affected loop |
| llvm::SmallVector<mlir::Value, 3> gridGeneratees; |
| llvm::SmallVector<mlir::Value, 3> intratileGeneratees; |
| gridGeneratees.reserve(numLoops); |
| intratileGeneratees.reserve(numLoops); |
| for ([[maybe_unused]] auto i : llvm::seq<int>(0, sizesClause.sizes.size())) { |
| auto gridCLI = mlir::omp::NewCliOp::create(firOpBuilder, loc); |
| gridGeneratees.push_back(gridCLI.getResult()); |
| auto intratileCLI = mlir::omp::NewCliOp::create(firOpBuilder, loc); |
| intratileGeneratees.push_back(intratileCLI.getResult()); |
| } |
| |
| llvm::SmallVector<mlir::Value, 6> generatees; |
| generatees.reserve(2 * numLoops); |
| generatees.append(gridGeneratees); |
| generatees.append(intratileGeneratees); |
| |
| mlir::omp::TileOp::create(firOpBuilder, loc, generatees, applyees, |
| sizesClause.sizes); |
| } |
| |
| static void genFuseOp(Fortran::lower::AbstractConverter &converter, |
| Fortran::lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| Fortran::semantics::SemanticsContext &semaCtx, |
| Fortran::lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| int64_t count = 0; |
| mlir::omp::LooprangeClauseOps looprangeClause; |
| ClauseProcessor cp(converter, semaCtx, item->clauses); |
| bool looprange = cp.processLooprange(stmtCtx, looprangeClause, count); |
| cp.processTODO<clause::Depth>(loc, llvm::omp::Directive::OMPD_fuse); |
| |
| llvm::SmallVector<mlir::Value> applyees; |
| for (auto &child : eval.getNestedEvaluations()) { |
| // Skip any Compiler Directive |
| if (child.getIf<parser::CompilerDirective>()) |
| continue; |
| |
| // Emit the associated loop |
| llvm::SmallVector<mlir::omp::CanonicalLoopOp> canonLoops; |
| genCanonicalLoopNest(converter, symTable, semaCtx, eval, &child, loc, queue, |
| item, 1, canonLoops); |
| |
| auto cli = llvm::getSingleElement(canonLoops).getCli(); |
| applyees.push_back(cli); |
| } |
| // One generated loop + one for each loop not inside the specified looprange |
| // if present |
| llvm::SmallVector<mlir::Value> generatees; |
| int64_t numGeneratees = !looprange ? 1 : applyees.size() - count + 1; |
| for (int i = 0; i < numGeneratees; i++) { |
| auto fusedCLI = mlir::omp::NewCliOp::create(firOpBuilder, loc); |
| generatees.push_back(fusedCLI); |
| } |
| |
| mlir::omp::FuseOp::create(firOpBuilder, loc, generatees, applyees, |
| looprangeClause.first, looprangeClause.count); |
| } |
| |
| // Returns true when an OpenMP construct sits between \p eval and the DO loop it |
| // applies to. getNestedDoConstruct descends through such a construct to reach |
| // the loop, which means the intervening construct is never lowered. |
| static bool hasNestedLoopTransformation(lower::pft::Evaluation &eval) { |
| for (lower::pft::Evaluation &nested : eval.getNestedEvaluations()) { |
| if (nested.getIf<parser::CompilerDirective>() || |
| nested.getIf<parser::NonLabelDoStmt>()) |
| continue; |
| if (nested.getIf<parser::DoConstruct>()) |
| return false; |
| if (nested.getIf<parser::OpenMPConstruct>()) |
| return true; |
| } |
| return false; |
| } |
| |
| static void genUnrollOp(Fortran::lower::AbstractConverter &converter, |
| Fortran::lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| Fortran::semantics::SemanticsContext &semaCtx, |
| Fortran::lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| ClauseProcessor cp(converter, semaCtx, item->clauses); |
| |
| // Process the `full` clause, which requests complete unrolling. |
| bool hasFull = cp.processFull(); |
| |
| // Process the `partial` clause. If present, it may carry a constant unroll |
| // factor. |
| std::optional<int64_t> partialFactor; |
| bool hasPartial = cp.processPartial(partialFactor); |
| if (hasPartial && !partialFactor.has_value()) |
| TODO(loc, "PARTIAL clause on UNROLL without a constant factor"); |
| |
| // Chaining a loop transformation onto the result of UNROLL needs the |
| // unrolled loop to be available as a generatee, which omp.unroll_* does not |
| // provide yet. Diagnose instead of silently dropping the nested construct. |
| if (hasNestedLoopTransformation(eval)) |
| TODO(loc, "loop transformation nested inside an UNROLL construct"); |
| |
| // Emit the associated loop |
| llvm::SmallVector<mlir::omp::CanonicalLoopOp, 1> canonLoops; |
| genCanonicalLoopNest(converter, symTable, semaCtx, eval, |
| getNestedDoConstruct(eval), loc, queue, item, 1, |
| canonLoops); |
| |
| llvm::SmallVector<mlir::Value, 1> applyees; |
| for (auto &&canonLoop : canonLoops) |
| applyees.push_back(canonLoop.getCli()); |
| |
| auto cli = llvm::getSingleElement(canonLoops).getCli(); |
| |
| if (hasFull) { |
| // Fully unroll the loop. |
| mlir::omp::UnrollFullOp::create(firOpBuilder, loc, cli); |
| } else if (partialFactor.has_value()) { |
| // Partially unroll the loop by the given constant factor. |
| mlir::omp::UnrollPartialOp::create(firOpBuilder, loc, cli, |
| static_cast<uint64_t>(*partialFactor)); |
| } else { |
| // Apply heuristic unrolling to it. |
| mlir::omp::UnrollHeuristicOp::create(firOpBuilder, loc, cli); |
| } |
| } |
| |
| static mlir::omp::MaskedOp |
| genMaskedOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::MaskedOperands clauseOps; |
| genMaskedClauses(converter, semaCtx, stmtCtx, item->clauses, loc, clauseOps); |
| |
| return genOpWithBody<mlir::omp::MaskedOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_masked), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::MasterOp |
| genMasterOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| return genOpWithBody<mlir::omp::MasterOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_master), |
| queue, item); |
| } |
| |
| static mlir::omp::OrderedOp |
| genOrderedOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| if (!semaCtx.langOptions().OpenMPSimd) |
| TODO(loc, "OMPD_ordered_standalone"); |
| return nullptr; |
| } |
| |
| static mlir::omp::OrderedRegionOp |
| genOrderedRegionOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::OrderedRegionOperands clauseOps; |
| genOrderedRegionClauses(converter, semaCtx, item->clauses, loc, clauseOps); |
| |
| return genOpWithBody<mlir::omp::OrderedRegionOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_ordered_blockassoc), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::ParallelOp |
| genParallelOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item, |
| mlir::omp::ParallelOperands &clauseOps, |
| const ObjectEntryBlockArgs &args, DataSharingProcessor *dsp, |
| bool isComposite = false) { |
| assert((!enableDelayedPrivatization || dsp) && |
| "expected valid DataSharingProcessor"); |
| |
| if (!clauseOps.allocateVars.empty()) { |
| llvm::DenseMap<const semantics::Symbol *, int64_t> privateSlots; |
| int64_t privateSlot = 0; |
| auto addPrivateSlot = [&](const semantics::Symbol &symbol) { |
| if (!privateSlots.try_emplace(&symbol.GetUltimate(), privateSlot).second) |
| fir::emitFatalError( |
| loc, "symbol with multiple private storage slots on one construct"); |
| ++privateSlot; |
| }; |
| for (const Object &object : args.priv.objects) { |
| const semantics::Symbol *symbol = object.sym(); |
| if (!symbol) |
| fir::emitFatalError(loc, "private item without a semantic symbol"); |
| // A privatized common block contributes one private operand per member, |
| // so slot numbering must follow the same expansion. |
| if (const auto *commonDetails = |
| symbol->detailsIf<semantics::CommonBlockDetails>()) { |
| for (const auto &member : commonDetails->objects()) |
| addPrivateSlot(*member); |
| } else { |
| addPrivateSlot(*symbol); |
| } |
| } |
| |
| llvm::DenseSet<const semantics::Symbol *> allocateSymbols; |
| for (const Clause &clause : item->clauses) { |
| if (clause.id != llvm::omp::Clause::OMPC_allocate) |
| continue; |
| const auto &allocate = std::get<clause::Allocate>(clause.u); |
| const auto &objects = std::get<ObjectList>(allocate.t); |
| for (const Object &object : objects) { |
| const semantics::Symbol *symbol = object.sym(); |
| if (!symbol) |
| fir::emitFatalError(loc, |
| "ALLOCATE clause item without a semantic symbol"); |
| const semantics::Symbol *ultimate = &symbol->GetUltimate(); |
| if (!allocateSymbols.insert(ultimate).second) |
| TODO(loc, "ALLOCATE clause item appears more than once"); |
| |
| auto privateSlot = privateSlots.find(ultimate); |
| if (privateSlot == privateSlots.end()) |
| fir::emitFatalError( |
| loc, "ALLOCATE clause item without private storage slot"); |
| |
| auto type = evaluate::DynamicType::From(*ultimate); |
| bool supportedDataSharing = |
| symbol->test(semantics::Symbol::Flag::OmpPrivate) || |
| symbol->test(semantics::Symbol::Flag::OmpFirstPrivate); |
| bool supportedType = |
| ultimate->Rank() == 0 && |
| !semantics::IsAllocatableOrPointer(*ultimate) && type && |
| type->category() != common::TypeCategory::Derived && |
| !type->RequiresDescriptor() && |
| !type->HasDeferredOrAssumedTypeParameter(); |
| if (!supportedDataSharing || !supportedType) |
| TODO(loc, |
| "ALLOCATE clause currently supports only fixed-size intrinsic " |
| "scalar PRIVATE or FIRSTPRIVATE items"); |
| |
| clauseOps.allocatePrivateIndices.push_back(privateSlot->second); |
| } |
| } |
| |
| if (clauseOps.allocatePrivateIndices.size() != |
| clauseOps.allocateVars.size()) |
| fir::emitFatalError(loc, |
| "incomplete ALLOCATE clause private storage mapping"); |
| } |
| |
| OpWithBodyGenInfo genInfo = |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_parallel) |
| .setClauses(&item->clauses) |
| .setEntryBlockArgs(&args) |
| .setGenSkeletonOnly(isComposite) |
| .setDataSharingProcessor(dsp); |
| |
| auto parallelOp = |
| genOpWithBody<mlir::omp::ParallelOp>(genInfo, queue, item, clauseOps); |
| parallelOp.setComposite(isComposite); |
| return parallelOp; |
| } |
| |
| static mlir::omp::ScanOp |
| genScanOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::ScanOperands clauseOps; |
| genScanClauses(converter, semaCtx, item->clauses, loc, clauseOps); |
| return mlir::omp::ScanOp::create(converter.getFirOpBuilder(), |
| converter.getCurrentLocation(), clauseOps); |
| } |
| |
| /// Walk up the parent-op chain from the current insertion point and return |
| /// the nearest enclosing \c omp::ParallelOp, or \c nullptr if none exists |
| /// (i.e. the construct is orphaned). The walk handles intervening ops such |
| /// as \c fir::IfOp that may appear between the worksharing construct and its |
| /// enclosing parallel region. |
| static mlir::omp::ParallelOp |
| findEnclosingParallelOp(fir::FirOpBuilder &builder) { |
| for (auto *op = builder.getInsertionBlock()->getParentOp(); op; |
| op = op->getParentOp()) { |
| if (auto parallelOp = mlir::dyn_cast<mlir::omp::ParallelOp>(op)) |
| return parallelOp; |
| } |
| return {}; |
| } |
| |
| static mlir::omp::SectionsOp |
| genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| const parser::OpenMPSectionsConstruct *sectionsConstruct = |
| getSectionsConstructStackTop(converter); |
| assert(sectionsConstruct && "Missing additional parsing information"); |
| |
| const auto §ionBlocks = |
| std::get<std::list<parser::OpenMPConstruct>>(sectionsConstruct->t); |
| mlir::omp::SectionsOperands clauseOps; |
| llvm::SmallVector<Object> reductionObjects; |
| genSectionsClauses(converter, semaCtx, item->clauses, loc, clauseOps, |
| reductionObjects); |
| |
| auto &builder = converter.getFirOpBuilder(); |
| |
| // Insert privatizations before SECTIONS |
| lower::SymMapScope scope(symTable); |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| lower::omp::isLastItemInQueue(item, queue), |
| /*useDelayedPrivatization=*/false, symTable); |
| dsp.processStep1(); |
| |
| // Detect conditional lastprivate symbols for sections. |
| auto &condLpSyms = dsp.getConditionalLastprivateSymbols(); |
| fir::RecordType lpType; |
| mlir::Value lpAlloca; |
| if (!condLpSyms.empty()) { |
| lpType = buildConditionalLpType(converter, condLpSyms, loc); |
| mlir::omp::DeclareReductionOp declRedOp = |
| buildConditionalLastPrivateReduction(converter, lpType, condLpSyms); |
| |
| // Create the struct alloca outside the parent parallel (if any). |
| // In the orphaned case (no enclosing ParallelOp), use a |
| // module-scope global so that all threads share one reduction target. |
| auto enclosingParallel = findEnclosingParallelOp(builder); |
| bool isOrphaned = !enclosingParallel; |
| |
| // Guard against nested parallelism in the orphaned case. |
| // Emit this BEFORE touching the global to avoid racing on it. |
| if (isOrphaned) |
| emitNestedParallelGuardForCondLp(converter, loc); |
| |
| if (enclosingParallel) { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPoint(enclosingParallel); |
| lpAlloca = builder.createTemporary(loc, lpType); |
| initConditionalLpStruct(converter, loc, lpType, lpAlloca, condLpSyms); |
| } else { |
| lpAlloca = getOrCreateConditionalLpGlobal(converter, loc, lpType); |
| // The global is shared across all threads. Use omp.single (which |
| // has an implicit barrier at exit) so that exactly one thread |
| // initialises and all threads wait before entering the construct. |
| mlir::omp::SingleOperands initSingleOps; |
| auto singleOp = mlir::omp::SingleOp::create(builder, loc, initSingleOps); |
| mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion()); |
| builder.setInsertionPointToStart(singleBlock); |
| initConditionalLpStruct(converter, loc, lpType, lpAlloca, condLpSyms); |
| mlir::omp::TerminatorOp::create(builder, loc); |
| builder.setInsertionPointAfter(singleOp); |
| } |
| |
| clauseOps.reductionVars.push_back(lpAlloca); |
| clauseOps.reductionByref.push_back(true); |
| clauseOps.reductionSyms.push_back( |
| mlir::SymbolRefAttr::get(builder.getContext(), declRedOp.getSymName())); |
| reductionObjects.push_back(Object{{nullptr, std::nullopt}}); |
| } |
| |
| List<Clause> nonDsaClauses; |
| List<const clause::Lastprivate *> lastprivates; |
| |
| for (const Clause &clause : item->clauses) { |
| if (clause.id == llvm::omp::Clause::OMPC_lastprivate) { |
| auto &lastp = std::get<clause::Lastprivate>(clause.u); |
| lastprivates.push_back(&lastp); |
| } else { |
| switch (clause.id) { |
| case llvm::omp::Clause::OMPC_firstprivate: |
| case llvm::omp::Clause::OMPC_private: |
| case llvm::omp::Clause::OMPC_shared: |
| break; |
| default: |
| nonDsaClauses.push_back(clause); |
| } |
| } |
| } |
| |
| // SECTIONS construct. |
| auto sectionsOp = mlir::omp::SectionsOp::create(builder, loc, clauseOps); |
| |
| // Create entry block with reduction variables as arguments. |
| ObjectEntryBlockArgs args; |
| // TODO: Add private syms and vars. |
| args.reduction.objects = reductionObjects; |
| args.reduction.vars = clauseOps.reductionVars; |
| |
| genEntryBlock(builder, args.asEntryBlockArgs(), sectionsOp.getRegion()); |
| mlir::Operation *terminator = |
| lower::genOpenMPTerminator(builder, sectionsOp, loc); |
| |
| // Save address-to-name mapping for conditional LP symbols before section |
| // bodies are lowered (binding will overwrite them inside each section's |
| // callback). The addresses are needed for the post-reduction copy-back. |
| llvm::MapVector<mlir::Value, std::string> condLpOrigAddrs; |
| for (const auto *sym : condLpSyms) { |
| mlir::Value addr = converter.getSymbolAddress(*sym); |
| if (addr) |
| condLpOrigAddrs[addr] = sym->name().ToString(); |
| } |
| |
| // Generate nested SECTION constructs. |
| // This is done here rather than in genOMP([...], OmpSectionDirective ) |
| // because we need to run genReductionVars on each omp.section so that the |
| // reduction variable gets mapped to the private version. |
| // |
| // When conditional lastprivate symbols are present, a custom region entry |
| // callback binds them to the section's struct value-field addresses before |
| // the body is lowered, so that lowering naturally uses the struct fields. |
| llvm::SmallVector<llvm::MapVector<mlir::Value, std::string>> |
| perSectionValAddrs; |
| for (auto [construct, nestedEval] : |
| llvm::zip(sectionBlocks, eval.getNestedEvaluations())) { |
| const auto *sectionConstruct = |
| std::get_if<parser::OmpSectionDirective>(&construct.u); |
| if (!sectionConstruct) { |
| assert(false && |
| "unexpected construct nested inside of SECTIONS construct"); |
| continue; |
| } |
| |
| ConstructQueue sectionQueue{buildConstructQueue( |
| converter.getFirOpBuilder().getModule(), semaCtx, nestedEval, |
| sectionConstruct->source, llvm::omp::Directive::OMPD_section, {})}; |
| |
| builder.setInsertionPoint(terminator); |
| |
| if (condLpSyms.empty()) { |
| genOpWithBody<mlir::omp::SectionOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, nestedEval, |
| llvm::omp::Directive::OMPD_section) |
| .setClauses(§ionQueue.begin()->clauses) |
| .setDataSharingProcessor(&dsp) |
| .setEntryBlockArgs(&args), |
| sectionQueue, sectionQueue.begin()); |
| } else { |
| llvm::MapVector<mlir::Value, std::string> sectionValAddrs; |
| auto sectionRegionEntryCb = [&](mlir::Operation *op) |
| -> llvm::SmallVector<const semantics::Symbol *> { |
| genEntryBlock(builder, args.asEntryBlockArgs(), op->getRegion(0)); |
| auto blockArgIface = |
| mlir::cast<mlir::omp::BlockArgOpenMPOpInterface>(*op); |
| bindEntryBlockArgs(converter, blockArgIface, args); |
| mlir::Value structArg = blockArgIface.getReductionBlockArgs().back(); |
| sectionValAddrs = bindCondLpSymsToStructFields(converter, loc, lpType, |
| structArg, condLpSyms); |
| return args.getSyms(); |
| }; |
| genOpWithBody<mlir::omp::SectionOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, nestedEval, |
| llvm::omp::Directive::OMPD_section) |
| .setClauses(§ionQueue.begin()->clauses) |
| .setDataSharingProcessor(&dsp) |
| .setGenRegionEntryCb(sectionRegionEntryCb), |
| sectionQueue, sectionQueue.begin()); |
| perSectionValAddrs.push_back(std::move(sectionValAddrs)); |
| } |
| } |
| |
| // Inject index stores after each assignment to a conditional LP value field |
| // inside every section. |
| if (!condLpSyms.empty()) { |
| unsigned sectionIdx = 0; |
| for (mlir::Operation &op : sectionsOp.getRegion().front()) { |
| auto sectionOp = mlir::dyn_cast<mlir::omp::SectionOp>(op); |
| if (!sectionOp) |
| continue; |
| |
| auto sectionArgIface = |
| mlir::cast<mlir::omp::BlockArgOpenMPOpInterface>(*sectionOp); |
| mlir::Value sectionStructArg = |
| sectionArgIface.getReductionBlockArgs().back(); |
| |
| unsigned idx = sectionIdx; |
| injectCondLpIndexStores( |
| builder, loc, lpType, sectionStructArg, sectionOp.getRegion(), |
| perSectionValAddrs[sectionIdx], |
| [idx](fir::FirOpBuilder &b, mlir::Location l) -> mlir::Value { |
| return b.createIntegerConstant(l, b.getI64Type(), idx); |
| }); |
| ++sectionIdx; |
| } |
| } |
| |
| // Collect conditional LP symbol names so we can skip them in the normal |
| // lastprivate copy-back (they are handled by the reduction path). |
| llvm::SmallDenseSet<const semantics::Symbol *> condLpSymSet( |
| condLpSyms.begin(), condLpSyms.end()); |
| |
| // Track whether any non-conditional lastprivate copy-backs were emitted. |
| bool hasNonCondLastprivate = false; |
| |
| if (!lastprivates.empty()) { |
| mlir::Region §ionsBody = sectionsOp.getRegion(); |
| assert(sectionsBody.hasOneBlock()); |
| mlir::Block &body = sectionsBody.front(); |
| |
| auto lastSectionOp = llvm::find_if( |
| llvm::reverse(body.getOperations()), [](const mlir::Operation &op) { |
| return llvm::isa<mlir::omp::SectionOp>(op); |
| }); |
| assert(lastSectionOp != body.rend()); |
| |
| for (const clause::Lastprivate *lastp : lastprivates) { |
| builder.setInsertionPoint( |
| lastSectionOp->getRegion(0).back().getTerminator()); |
| mlir::OpBuilder::InsertPoint insp = builder.saveInsertionPoint(); |
| const auto &objList = std::get<ObjectList>(lastp->t); |
| for (const Object &object : objList) { |
| semantics::Symbol *sym = object.sym(); |
| // Skip conditional LP symbols — handled by the reduction path. |
| if (condLpSymSet.count(sym)) |
| continue; |
| hasNonCondLastprivate = true; |
| if (const auto *common = |
| sym->detailsIf<semantics::CommonBlockDetails>()) { |
| for (const auto &obj : common->objects()) |
| converter.copyHostAssociateVar(*obj, &insp, /*hostIsSource=*/false); |
| } else { |
| converter.copyHostAssociateVar(*sym, &insp, /*hostIsSource=*/false); |
| } |
| } |
| } |
| } |
| |
| // Perform DataSharingProcessor's step2 out of SECTIONS |
| builder.setInsertionPointAfter(sectionsOp.getOperation()); |
| dsp.processStep2(sectionsOp, false); |
| // Emit barrier when nowait is present and there are lastprivate copy-backs |
| // (either non-conditional or conditional). The barrier ensures all threads |
| // have completed their work before lastprivate values are read/copied. |
| // |
| // NOTE: The LLVM OpenMP runtime currently imposes an implicit barrier |
| // inside __kmpc_reduce for tree reductions. If the runtime were modified |
| // to release losing threads early when nowait is specified, we could use |
| // the return value from the tree reduction (case 1 = winner) to let the |
| // winner thread perform the copy-back without a separate barrier. |
| if (clauseOps.nowait && (hasNonCondLastprivate || !condLpSyms.empty())) |
| mlir::omp::BarrierOp::create(builder, loc); |
| |
| // Copy-back: copy winning values from the shared reduction struct to the |
| // original variables. When nowait is absent, the worksharing construct's |
| // implicit end-barrier guarantees all reductions are combined before we |
| // reach this point. When nowait is present, the barrier above ensures |
| // the reduction is fully finalized before reading the struct. Wrapped in |
| // omp.single so exactly one thread performs the stores, at the sections |
| // construct's barrier (the semantically correct finalization point) inside |
| // the enclosing parallel. Because this copy-back adds a second |
| // immediately-nested construct to the parallel, the parallel is not marked |
| // omp.combined (see the combined-marking logic in genOMPDispatch). |
| if (!condLpSyms.empty()) { |
| mlir::omp::SingleOperands singleClauseOps; |
| auto singleOp = mlir::omp::SingleOp::create(builder, loc, singleClauseOps); |
| mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion()); |
| builder.setInsertionPointToStart(singleBlock); |
| |
| for (auto &[origAddr, symName] : condLpOrigAddrs) { |
| unsigned valFieldIdx = lpType.getFieldIndex(symName); |
| mlir::Type valType = lpType.getType(valFieldIdx); |
| |
| fir::IntOrValue valFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx); |
| mlir::Value fieldAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(valType), lpAlloca, |
| llvm::SmallVector<fir::IntOrValue, 1>{valFIdx}); |
| mlir::Value val = fir::LoadOp::create(builder, loc, fieldAddr); |
| |
| // Only copy back if some iteration actually assigned to this variable |
| // (index >= 0). Otherwise the original must not be overwritten. |
| unsigned idxFieldIdx = lpType.getFieldIndex("$" + symName); |
| fir::IntOrValue idxFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), idxFieldIdx); |
| mlir::Value idxAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(builder.getI64Type()), lpAlloca, |
| llvm::SmallVector<fir::IntOrValue, 1>{idxFIdx}); |
| mlir::Value idxVal = fir::LoadOp::create(builder, loc, idxAddr); |
| mlir::Value zero = |
| builder.createIntegerConstant(loc, builder.getI64Type(), 0); |
| mlir::Value cond = mlir::arith::CmpIOp::create( |
| builder, loc, mlir::arith::CmpIPredicate::sge, idxVal, zero); |
| auto ifOp = |
| fir::IfOp::create(builder, loc, cond, /*withElseRegion=*/false); |
| builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| fir::StoreOp::create(builder, loc, val, origAddr); |
| builder.setInsertionPointAfter(ifOp); |
| } |
| mlir::omp::TerminatorOp::create(builder, loc); |
| } |
| |
| return sectionsOp; |
| } |
| |
| static mlir::omp::ScopeOp |
| genScopeOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| lower::SymMapScope scope(symTable); |
| mlir::omp::ScopeOperands clauseOps; |
| llvm::SmallVector<Object> reductionObjects; |
| genScopeClauses(converter, semaCtx, item->clauses, loc, clauseOps, |
| reductionObjects); |
| |
| std::optional<DataSharingProcessor> dsp; |
| if (enableDelayedPrivatization) { |
| dsp.emplace(converter, semaCtx, item->clauses, eval, |
| lower::omp::isLastItemInQueue(item, queue), |
| /*useDelayedPrivatization=*/true, symTable); |
| dsp->processStep1(&clauseOps); |
| } |
| |
| ObjectEntryBlockArgs args; |
| if (dsp) |
| args.priv.objects = makeObjects(dsp->getDelayedPrivSymbols()); |
| args.priv.vars = clauseOps.privateVars; |
| args.reduction.objects = reductionObjects; |
| args.reduction.vars = clauseOps.reductionVars; |
| |
| return genOpWithBody<mlir::omp::ScopeOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_scope) |
| .setClauses(&item->clauses) |
| .setEntryBlockArgs(&args) |
| .setDataSharingProcessor(enableDelayedPrivatization ? &dsp.value() |
| : nullptr), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::SingleOp |
| genSingleOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::SingleOperands clauseOps; |
| genSingleClauses(converter, semaCtx, item->clauses, loc, clauseOps); |
| |
| return genOpWithBody<mlir::omp::SingleOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_single) |
| .setClauses(&item->clauses), |
| queue, item, clauseOps); |
| } |
| |
| static bool isDuplicateMappedSymbol( |
| const semantics::Symbol &sym, |
| const llvm::SetVector<const semantics::Symbol *> &privatizedSyms, |
| llvm::ArrayRef<Object> hasDevObjects, llvm::ArrayRef<Object> mappedObjects, |
| llvm::ArrayRef<Object> isDevicePtrObjects) { |
| llvm::SmallVector<const semantics::Symbol *> concatSyms; |
| concatSyms.reserve(privatizedSyms.size() + hasDevObjects.size() + |
| mappedObjects.size() + isDevicePtrObjects.size()); |
| concatSyms.append(privatizedSyms.begin(), privatizedSyms.end()); |
| llvm::transform(hasDevObjects, std::back_inserter(concatSyms), |
| [](const Object &object) { return object.sym(); }); |
| llvm::transform(mappedObjects, std::back_inserter(concatSyms), |
| [](const Object &object) { return object.sym(); }); |
| llvm::transform(isDevicePtrObjects, std::back_inserter(concatSyms), |
| [](const Object &object) { return object.sym(); }); |
| |
| auto checkSymbol = [&](const semantics::Symbol &checkSym) { |
| return std::any_of(concatSyms.begin(), concatSyms.end(), |
| [&](auto v) { return v->GetUltimate() == checkSym; }); |
| }; |
| |
| if (checkSymbol(sym)) |
| return true; |
| |
| const auto *hostAssoc{sym.detailsIf<semantics::HostAssocDetails>()}; |
| if (hostAssoc && checkSymbol(hostAssoc->symbol())) |
| return true; |
| |
| return checkSymbol(sym.GetUltimate()); |
| } |
| |
| // Visitor to collect symbols that have dynamic substring accesses |
| struct DynamicSubstringVisitor { |
| llvm::SmallPtrSet<const semantics::Symbol *, 8> symbolsWithDynamicSubstring; |
| semantics::SemanticsContext &semaCtx; |
| |
| explicit DynamicSubstringVisitor(semantics::SemanticsContext &ctx) |
| : semaCtx(ctx) {} |
| |
| template <typename T> |
| bool Pre(const T &) { |
| return true; |
| } |
| template <typename T> |
| void Post(const T &) {} |
| |
| // Check each expression for substring access |
| void Post(const parser::Expr &expr) { |
| if (const auto *typedExpr = semantics::GetExpr(semaCtx, expr)) { |
| // Try to extract a substring from this expression |
| if (auto substring = Fortran::evaluate::ExtractSubstring(*typedExpr)) { |
| // Check if the substring has non-constant (dynamic) indices |
| bool hasDynamicIndex = false; |
| |
| // Check if explicit lower bound exists and is non-constant |
| if (const auto *lowerExpr = substring->GetLower()) { |
| if (!Fortran::evaluate::ToInt64(*lowerExpr)) |
| hasDynamicIndex = true; |
| } |
| |
| // Check if explicit upper bound exists and is non-constant |
| if (const auto *upperExpr = substring->GetUpper()) { |
| if (!Fortran::evaluate::ToInt64(*upperExpr)) |
| hasDynamicIndex = true; |
| } |
| |
| // If we have dynamic indices, extract the base symbol |
| if (hasDynamicIndex) { |
| if (auto dataRef = |
| Fortran::evaluate::ExtractSubstringBase(*substring)) { |
| if (const auto *symRef = |
| std::get_if<Fortran::evaluate::SymbolRef>(&dataRef->u)) { |
| symbolsWithDynamicSubstring.insert(&symRef->get()); |
| } |
| } |
| } |
| } |
| } |
| } |
| }; |
| |
| // Collect symbols that have dynamic substring accesses in the target region |
| static void collectSymbolsWithDynamicSubstring( |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| llvm::SmallPtrSet<const semantics::Symbol *, 8> |
| &symbolsWithDynamicSubstring) { |
| DynamicSubstringVisitor visitor(semaCtx); |
| eval.visit([&](const auto &node) { parser::Walk(node, visitor); }); |
| symbolsWithDynamicSubstring = visitor.symbolsWithDynamicSubstring; |
| } |
| |
| static mlir::omp::TargetOp |
| genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| bool isTargetDevice = |
| llvm::cast<mlir::omp::OffloadModuleInterface>(*converter.getModuleOp()) |
| .getIsTargetDevice(); |
| |
| // Introduce a new host_eval information structure for this target region. |
| if (!isTargetDevice) |
| converter.getStateStack().stackPush<HostEvalInfoStackFrame>(); |
| |
| mlir::omp::TargetExtOperands clauseOps; |
| DefaultMapsTy defaultMaps; |
| llvm::SmallVector<Object> mapObjects, hasDeviceAddrObjects, |
| inReductionObjects, isDevicePtrObjects; |
| genTargetClauses(converter, semaCtx, symTable, stmtCtx, eval, item->clauses, |
| loc, clauseOps, defaultMaps, hasDeviceAddrObjects, |
| inReductionObjects, isDevicePtrObjects, mapObjects); |
| |
| KernelTypeVisitor visitor(semaCtx, converter.getModuleOp()); |
| clauseOps.kernelType = mlir::omp::TargetExecModeAttr::get( |
| &converter.getMLIRContext(), visitor.getKernelType(eval)); |
| |
| if (!isDevicePtrObjects.empty()) { |
| // is_device_ptr maps get duplicated so the clause and synthesized |
| // has_device_addr entry each own a unique MapInfoOp user, keeping |
| // MapInfoFinalization happy while still wiring the symbol into |
| // has_device_addr when the user didn’t spell it explicitly. |
| auto insertionPt = firOpBuilder.saveInsertionPoint(); |
| auto alreadyPresent = [&](const semantics::Symbol *sym) { |
| return llvm::any_of(hasDeviceAddrObjects, [&](const Object &object) { |
| const semantics::Symbol *objectSym = object.sym(); |
| return objectSym && sym && |
| objectSym->GetUltimate() == sym->GetUltimate(); |
| }); |
| }; |
| |
| for (auto [idx, object] : llvm::enumerate(isDevicePtrObjects)) { |
| const semantics::Symbol *sym = object.sym(); |
| mlir::Value mapVal = clauseOps.isDevicePtrVars[idx]; |
| assert(sym && "expected symbol for is_device_ptr"); |
| assert(mapVal && "expected map value for is_device_ptr"); |
| auto mapInfo = mapVal.getDefiningOp<mlir::omp::MapInfoOp>(); |
| assert(mapInfo && "expected map info op"); |
| |
| if (!alreadyPresent(sym)) { |
| clauseOps.hasDeviceAddrVars.push_back(mapVal); |
| hasDeviceAddrObjects.push_back(object); |
| } |
| |
| firOpBuilder.setInsertionPointAfter(mapInfo); |
| mlir::Operation *clonedOp = firOpBuilder.clone(*mapInfo.getOperation()); |
| auto clonedMapInfo = mlir::cast<mlir::omp::MapInfoOp>(clonedOp); |
| clauseOps.isDevicePtrVars[idx] = clonedMapInfo.getResult(); |
| } |
| firOpBuilder.restoreInsertionPoint(insertionPt); |
| } |
| |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/ |
| lower::omp::isLastItemInQueue(item, queue), |
| /*useDelayedPrivatization=*/true, symTable, |
| /*isTargetPrivitization=*/true); |
| dsp.processStep1(&clauseOps); |
| |
| // The host lowering of `target in_reduction` requires each list item to be |
| // implicitly mapped as an address-preserving entry whose map argument the |
| // redirect rewrites to the runtime reduction-private storage. When the same |
| // variable is also privatized on the target (implicitly, as with a |
| // host-associated scalar that defaults to firstprivate, or explicitly with |
| // `firstprivate(i) in_reduction(+:i)`), the force-map is suppressed because |
| // the symbol is already in the privatization set, and the firstprivate copy |
| // region then has no mold value for it, aborting translation. Reject until |
| // in_reduction can be reconciled with target privatization. |
| for (const Object &object : inReductionObjects) |
| if (const semantics::Symbol *sym = object.sym()) { |
| const semantics::Symbol &ultimate = sym->GetUltimate(); |
| if (llvm::any_of(dsp.getAllSymbolsToPrivatize(), |
| [&](const semantics::Symbol *priv) { |
| return priv->GetUltimate() == ultimate; |
| })) |
| TODO(loc, |
| "TARGET construct with IN_REDUCTION of a privatized variable"); |
| } |
| |
| // Collect symbols that have dynamic substring accesses |
| llvm::SmallPtrSet<const semantics::Symbol *, 8> symbolsWithDynamicSubstring; |
| collectSymbolsWithDynamicSubstring(semaCtx, eval, |
| symbolsWithDynamicSubstring); |
| |
| // 5.8.1 Implicit Data-Mapping Attribute Rules |
| // The following code follows the implicit data-mapping rules to map all the |
| // symbols used inside the region that do not have explicit data-environment |
| // attribute clauses (neither data-sharing; e.g. `private`, nor `map` |
| // clauses). |
| // |
| // When `forceAddressPreserving` is set, the symbol is force-mapped as an |
| // address-preserving `capture(ByRef)` with implicit `tofrom` flags, |
| // bypassing the scalar default capture rules. This is used for `target |
| // in_reduction` list items, whose mapped pointer is passed as the `orig` |
| // argument of `__kmpc_task_reduction_get_th_data`; a ByCopy scalar capture |
| // would break the runtime lookup against the enclosing taskgroup's |
| // task_reduction descriptor. |
| auto captureImplicitMap = [&](const semantics::Symbol &sym, |
| bool forceAddressPreserving = false) { |
| // Structure component symbols don't have bindings, and can only be |
| // explicitly mapped individually. If a member is captured implicitly |
| // we map the entirety of the derived type when we find its symbol. |
| if (sym.owner().IsDerivedType()) |
| return; |
| |
| // if the symbol is part of an already mapped common block, do not make a |
| // map for it. |
| if (!forceAddressPreserving) { |
| if (const Fortran::semantics::Symbol *common = |
| Fortran::semantics::FindCommonBlockContaining(sym.GetUltimate())) |
| if (llvm::any_of(mapObjects, [=](const Object &object) { |
| return object.sym() == common; |
| })) |
| return; |
| } |
| |
| // If we come across a symbol without a symbol address, we |
| // return as we cannot process it, this is intended as a |
| // catch all early exit for symbols that do not have a |
| // corresponding extended value. Such as subroutines, |
| // interfaces and named blocks. |
| if (!converter.getSymbolAddress(sym)) |
| return; |
| |
| // Skip scalar parameters/constants as they do not need to be mapped. |
| // However, parameter arrays must be mapped as they may be accessed with |
| // dynamic indices on the device (e.g., const_array(runtime_index)). |
| // Also, character scalar parameters must be mapped if they have dynamic |
| // substring access. |
| if (semantics::IsNamedConstant(sym) && sym.Rank() == 0 && |
| !symbolsWithDynamicSubstring.contains(&sym.GetUltimate())) |
| return; |
| |
| // Skip groupprivate symbols - they don't need to be mapped because |
| // groupprivate creates its own storage. |
| if (sym.GetUltimate().test(semantics::Symbol::Flag::OmpGroupPrivate)) |
| return; |
| |
| if (!isDuplicateMappedSymbol(sym, dsp.getAllSymbolsToPrivatize(), |
| hasDeviceAddrObjects, mapObjects, |
| isDevicePtrObjects)) { |
| if (const auto *details = |
| sym.template detailsIf<semantics::HostAssocDetails>()) |
| converter.copySymbolBinding(details->symbol(), sym); |
| std::stringstream name; |
| fir::ExtendedValue dataExv = converter.getSymbolExtendedValue(sym); |
| name << sym.name().ToString(); |
| |
| fir::factory::AddrAndBoundsInfo info = |
| Fortran::lower::getDataOperandBaseAddr( |
| converter, firOpBuilder, sym.GetUltimate(), |
| converter.getCurrentLocation()); |
| llvm::SmallVector<mlir::Value> bounds = |
| fir::factory::genImplicitBoundsOps<mlir::omp::MapBoundsOp, |
| mlir::omp::MapBoundsType>( |
| firOpBuilder, info, dataExv, |
| semantics::IsAssumedSizeArray(sym.GetUltimate()), |
| converter.getCurrentLocation()); |
| mlir::Value baseOp = info.rawInput; |
| mlir::Type eleType = baseOp.getType(); |
| if (auto refType = mlir::dyn_cast<fir::ReferenceType>(baseOp.getType())) |
| eleType = refType.getElementType(); |
| |
| std::pair<mlir::omp::ClauseMapFlags, mlir::omp::VariableCaptureKind> |
| mapFlagAndKind; |
| if (forceAddressPreserving) |
| mapFlagAndKind = {mlir::omp::ClauseMapFlags::implicit | |
| mlir::omp::ClauseMapFlags::to | |
| mlir::omp::ClauseMapFlags::from, |
| mlir::omp::VariableCaptureKind::ByRef}; |
| else |
| mapFlagAndKind = getImplicitMapTypeAndKind( |
| firOpBuilder, converter, defaultMaps, eleType, loc, sym); |
| |
| mlir::FlatSymbolRefAttr mapperId; |
| auto defaultmapBehaviour = getDefaultmapIfPresent(defaultMaps, eleType); |
| if (!forceAddressPreserving && |
| defaultmapBehaviour == |
| clause::Defaultmap::ImplicitBehavior::Default) { |
| const semantics::DerivedTypeSpec *typeSpec = |
| sym.GetType() ? sym.GetType()->AsDerived() : nullptr; |
| if (typeSpec) { |
| std::string mapperIdName = |
| typeSpec->name().ToString() + llvm::omp::OmpDefaultMapperName; |
| if (auto *mapperSym = |
| converter.getCurrentScope().FindSymbol(mapperIdName)) |
| mapperIdName = converter.mangleName( |
| mapperIdName, mapperSym->GetUltimate().owner()); |
| else |
| mapperIdName = |
| converter.mangleName(mapperIdName, *typeSpec->GetScope()); |
| |
| if (!mapperIdName.empty()) { |
| bool isPointer = semantics::IsPointer(sym); |
| bool isAllocatable = semantics::IsAllocatable(sym); |
| bool hasDefaultMapper = |
| converter.getModuleOp().lookupSymbol(mapperIdName); |
| // Avoid attaching implicit default mappers to pointer captures. |
| // For large pointer-based derived aggregates this can over-map |
| // nested payloads and conflict with explicit enter/exit maps. |
| // |
| // For an allocatable capture, only synthesize an implicit default |
| // mapper when the type requires one; a flat record does not. |
| if (!isPointer && |
| (hasDefaultMapper || |
| (isAllocatable && |
| requiresImplicitDefaultDeclareMapper(*typeSpec)))) { |
| if (!hasDefaultMapper) { |
| if (auto recordType = mlir::dyn_cast_or_null<fir::RecordType>( |
| converter.genType(*typeSpec))) |
| mapperId = getOrGenImplicitDefaultDeclareMapper( |
| converter.getFirOpBuilder(), loc, recordType, |
| mapperIdName, |
| [&](std::string &mapperIdName, |
| llvm::StringRef memberName) { |
| defaultMangler(converter, mapperIdName, memberName); |
| }); |
| } else { |
| mapperId = mlir::FlatSymbolRefAttr::get( |
| &converter.getMLIRContext(), mapperIdName); |
| } |
| } |
| } |
| } |
| } |
| |
| mlir::Value mapOp = createMapInfoOp( |
| firOpBuilder, converter.getCurrentLocation(), baseOp, |
| /*varPtrPtr=*/mlir::Value{}, name.str(), bounds, /*members=*/{}, |
| /*membersIndex=*/mlir::ArrayAttr{}, std::get<0>(mapFlagAndKind), |
| std::get<1>(mapFlagAndKind), baseOp.getType(), |
| /*partialMap=*/false, mapperId); |
| |
| clauseOps.mapVars.push_back(mapOp); |
| mapObjects.push_back( |
| Object{const_cast<semantics::Symbol *>(&sym), std::nullopt}); |
| } |
| }; |
| // OpenMP requires `in_reduction` list items on `target` to be implicitly |
| // data-mapped. Force-map them as address-preserving captures before the |
| // generic implicit-map walk so that walk treats the symbols as already |
| // mapped via `isDuplicateMappedSymbol` and does not downgrade them to |
| // ByCopy. |
| for (const Object &object : inReductionObjects) |
| if (const semantics::Symbol *sym = object.sym()) |
| captureImplicitMap(*sym, /*forceAddressPreserving=*/true); |
| |
| lower::pft::visitAllSymbols(eval, captureImplicitMap); |
| |
| auto targetOp = mlir::omp::TargetOp::create(firOpBuilder, loc, clauseOps); |
| |
| llvm::SmallVector<mlir::Value> hasDeviceAddrBaseValues, mapBaseValues; |
| extractMappedBaseValues(clauseOps.hasDeviceAddrVars, hasDeviceAddrBaseValues); |
| extractMappedBaseValues(clauseOps.mapVars, mapBaseValues); |
| |
| ObjectEntryBlockArgs args; |
| args.hasDeviceAddr.objects = hasDeviceAddrObjects; |
| args.hasDeviceAddr.vars = hasDeviceAddrBaseValues; |
| args.hostEvalVars = clauseOps.hostEvalVars; |
| // `in_reduction` list items do not get their own entry block argument on |
| // `omp.target`; they are implicitly mapped (see the force-map above) and the |
| // target body accesses them through their `map_entries` block argument. The |
| // `in_reduction` operands remain on the op as host-side metadata. |
| args.map.objects = mapObjects; |
| args.map.vars = mapBaseValues; |
| args.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| args.priv.vars = clauseOps.privateVars; |
| |
| genBodyOfTargetOp(converter, symTable, semaCtx, eval, targetOp, args, loc, |
| queue, item, dsp); |
| |
| // Remove the host_eval information structure created for this target region. |
| if (!isTargetDevice) |
| converter.getStateStack().stackPop(); |
| return targetOp; |
| } |
| |
| static mlir::omp::TargetDataOp genTargetDataOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::TargetDataOperands clauseOps; |
| llvm::SmallVector<Object> useDeviceAddrObjects, useDevicePtrObjects; |
| genTargetDataClauses(converter, semaCtx, stmtCtx, item->clauses, loc, |
| clauseOps, useDeviceAddrObjects, useDevicePtrObjects); |
| |
| auto targetDataOp = mlir::omp::TargetDataOp::create( |
| converter.getFirOpBuilder(), loc, clauseOps); |
| |
| llvm::SmallVector<mlir::Value> useDeviceAddrBaseValues, |
| useDevicePtrBaseValues; |
| extractMappedBaseValues(clauseOps.useDeviceAddrVars, useDeviceAddrBaseValues); |
| extractMappedBaseValues(clauseOps.useDevicePtrVars, useDevicePtrBaseValues); |
| |
| ObjectEntryBlockArgs args; |
| args.useDeviceAddr.objects = useDeviceAddrObjects; |
| args.useDeviceAddr.vars = useDeviceAddrBaseValues; |
| args.useDevicePtr.objects = useDevicePtrObjects; |
| args.useDevicePtr.vars = useDevicePtrBaseValues; |
| |
| genBodyOfTargetDataOp(converter, symTable, semaCtx, eval, targetDataOp, args, |
| loc, queue, item); |
| return targetDataOp; |
| } |
| |
| template <typename OpTy> |
| static OpTy genTargetEnterExitUpdateDataOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| // GCC 9.3.0 emits a (probably) bogus warning about an unused variable. |
| [[maybe_unused]] llvm::omp::Directive directive; |
| if constexpr (std::is_same_v<OpTy, mlir::omp::TargetEnterDataOp>) { |
| directive = llvm::omp::Directive::OMPD_target_enter_data; |
| } else if constexpr (std::is_same_v<OpTy, mlir::omp::TargetExitDataOp>) { |
| directive = llvm::omp::Directive::OMPD_target_exit_data; |
| } else if constexpr (std::is_same_v<OpTy, mlir::omp::TargetUpdateOp>) { |
| directive = llvm::omp::Directive::OMPD_target_update; |
| } else { |
| llvm_unreachable("Unexpected TARGET DATA construct"); |
| } |
| |
| mlir::omp::TargetEnterExitUpdateDataOperands clauseOps; |
| genTargetEnterExitUpdateDataClauses(converter, semaCtx, symTable, stmtCtx, |
| item->clauses, loc, directive, clauseOps); |
| |
| return OpTy::create(firOpBuilder, loc, clauseOps); |
| } |
| |
| static mlir::omp::TaskOp |
| genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::TaskOperands clauseOps; |
| llvm::SmallVector<Object> inReductionObjects; |
| genTaskClauses(converter, semaCtx, symTable, stmtCtx, item->clauses, loc, |
| clauseOps, inReductionObjects); |
| |
| if (!enableDelayedPrivatization) |
| return genOpWithBody<mlir::omp::TaskOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_task) |
| .setClauses(&item->clauses), |
| queue, item, clauseOps); |
| |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| lower::omp::isLastItemInQueue(item, queue), |
| /*useDelayedPrivatization=*/true, symTable); |
| dsp.processStep1(&clauseOps); |
| |
| if (hasPrivatizedArrayElementReduction(inReductionObjects, |
| dsp.getAllSymbolsToPrivatize())) |
| TODO(loc, "TASK construct with IN_REDUCTION of an array element whose " |
| "base array is privatized"); |
| |
| ObjectEntryBlockArgs taskArgs; |
| taskArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| taskArgs.priv.vars = clauseOps.privateVars; |
| taskArgs.inReduction.objects = inReductionObjects; |
| taskArgs.inReduction.vars = clauseOps.inReductionVars; |
| |
| return genOpWithBody<mlir::omp::TaskOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_task) |
| .setClauses(&item->clauses) |
| .setDataSharingProcessor(&dsp) |
| .setEntryBlockArgs(&taskArgs), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::TaskgroupOp |
| genTaskgroupOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::TaskgroupOperands clauseOps; |
| llvm::SmallVector<Object> taskReductionObjects; |
| genTaskgroupClauses(converter, semaCtx, item->clauses, loc, clauseOps, |
| taskReductionObjects); |
| |
| ObjectEntryBlockArgs taskgroupArgs; |
| taskgroupArgs.taskReduction.objects = taskReductionObjects; |
| taskgroupArgs.taskReduction.vars = clauseOps.taskReductionVars; |
| |
| return genOpWithBody<mlir::omp::TaskgroupOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_taskgroup) |
| .setClauses(&item->clauses) |
| .setEntryBlockArgs(&taskgroupArgs), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::TaskwaitOp |
| genTaskwaitOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::TaskwaitOperands clauseOps; |
| genTaskwaitClauses(converter, semaCtx, item->clauses, loc, clauseOps); |
| return mlir::omp::TaskwaitOp::create(converter.getFirOpBuilder(), loc, |
| clauseOps); |
| } |
| |
| static mlir::omp::TaskyieldOp |
| genTaskyieldOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| return mlir::omp::TaskyieldOp::create(converter.getFirOpBuilder(), loc); |
| } |
| |
| static mlir::omp::WorkshareOp genWorkshareOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::WorkshareOperands clauseOps; |
| genWorkshareClauses(converter, semaCtx, stmtCtx, item->clauses, loc, |
| clauseOps); |
| |
| return genOpWithBody<mlir::omp::WorkshareOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_workshare) |
| .setClauses(&item->clauses), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::TeamsOp |
| genTeamsOp(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| lower::SymMapScope scope(symTable); |
| mlir::omp::TeamsOperands clauseOps; |
| llvm::SmallVector<Object> reductionObjects; |
| genTeamsClauses(converter, semaCtx, stmtCtx, item->clauses, loc, clauseOps, |
| reductionObjects); |
| |
| ObjectEntryBlockArgs args; |
| // TODO: Add private syms and vars. |
| args.reduction.objects = reductionObjects; |
| args.reduction.vars = clauseOps.reductionVars; |
| return genOpWithBody<mlir::omp::TeamsOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_teams) |
| .setClauses(&item->clauses) |
| .setEntryBlockArgs(&args), |
| queue, item, clauseOps); |
| } |
| |
| static mlir::omp::WorkdistributeOp genWorkdistributeOp( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| mlir::Location loc, const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| return genOpWithBody<mlir::omp::WorkdistributeOp>( |
| OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval, |
| llvm::omp::Directive::OMPD_workdistribute), |
| queue, item); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Code generation functions for the standalone version of constructs that can |
| // also be a leaf of a composite construct |
| //===----------------------------------------------------------------------===// |
| |
| static mlir::omp::DistributeOp genStandaloneDistribute( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::DistributeOperands distributeClauseOps; |
| genDistributeClauses(converter, semaCtx, stmtCtx, item->clauses, loc, |
| distributeClauseOps); |
| |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| enableDelayedPrivatization, symTable); |
| // Dynamic private arrays cannot safely be allocated in GPU scratch when the |
| // descriptor is captured through the distribute callback. |
| dsp.setForceHeapAllocationForPrivateDynamicArrays(); |
| dsp.processStep1(&distributeClauseOps); |
| |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc, |
| loopNestClauseOps, iv); |
| |
| ObjectEntryBlockArgs distributeArgs; |
| distributeArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| distributeArgs.priv.vars = distributeClauseOps.privateVars; |
| auto distributeOp = genWrapperOp<mlir::omp::DistributeOp>( |
| converter, loc, distributeClauseOps, distributeArgs); |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, item, |
| loopNestClauseOps, iv, {{distributeOp, distributeArgs}}, |
| llvm::omp::Directive::OMPD_distribute, dsp); |
| return distributeOp; |
| } |
| |
| /// Zero-initialize the value fields and set index fields to -1 in a |
| /// conditional-lastprivate reduction struct. |
| /// |
| /// The struct groups all value fields first, then all index fields: |
| /// {val_0, val_1, ..., idx_0, idx_1, ...} |
| /// so fields [0, numVars) are value fields and [numVars, 2*numVars) are |
| /// the corresponding iteration index fields. |
| /// |
| /// The -1 sentinel on index fields ensures the combiner's "sequentially |
| /// last" comparison treats the slot as "no iteration has written yet" |
| /// (any real canonical loop IV >= 0 beats -1). |
| static void initConditionalLpStructDefault(fir::FirOpBuilder &builder, |
| mlir::Location loc, |
| fir::RecordType lpCondType, |
| mlir::Value structRef) { |
| llvm::ArrayRef<std::pair<std::string, mlir::Type>> fields = |
| lpCondType.getTypeList(); |
| unsigned numVars = fields.size() / 2; |
| for (unsigned i = 0, e = fields.size(); i < e; ++i) { |
| mlir::Type fieldTy = fields[i].second; |
| fir::IntOrValue idx = mlir::IntegerAttr::get(builder.getI32Type(), i); |
| mlir::Value fieldAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(fieldTy), structRef, |
| llvm::SmallVector<fir::IntOrValue, 1>{idx}); |
| mlir::Value initVal; |
| if (i >= numVars) // index field (second half) |
| initVal = builder.createIntegerConstant(loc, fieldTy, -1); |
| else if (fir::isa_trivial(fieldTy)) |
| initVal = fir::factory::createZeroValue(builder, loc, fieldTy); |
| else // derived type or other non-trivial: use all-bits-zero |
| initVal = fir::ZeroOp::create(builder, loc, fieldTy); |
| fir::StoreOp::create(builder, loc, initVal, fieldAddr); |
| } |
| } |
| |
| /// Initialize the shared reduction seed struct for conditional lastprivate. |
| /// |
| /// First applies the identity initialization: every index field is set to -1 |
| /// and every value field to 0. Then, for any list item that is also |
| /// firstprivate, that value field's 0 is overwritten with the original |
| /// variable's incoming value. The per-thread reduction init region copies |
| /// each value field from this seed struct, so overwriting a firstprivate |
| /// item's field here is what makes it observe its initial value inside the |
| /// loop. |
| static void initConditionalLpStruct( |
| lower::AbstractConverter &converter, mlir::Location loc, |
| fir::RecordType lpCondType, mlir::Value structRef, |
| const llvm::SetVector<const semantics::Symbol *> &condLpSyms) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| initConditionalLpStructDefault(builder, loc, lpCondType, structRef); |
| |
| fir::RecordType lpType = lpCondType; // non-const copy for getFieldIndex |
| for (const semantics::Symbol *sym : condLpSyms) { |
| if (!sym->test(semantics::Symbol::Flag::OmpFirstPrivate)) |
| continue; |
| mlir::Value origAddr = converter.getSymbolAddress(*sym); |
| assert(origAddr && |
| "firstprivate conditional-lastprivate symbol must have an address"); |
| if (!origAddr) |
| continue; |
| unsigned valIdx = lpType.getFieldIndex(sym->name().ToString()); |
| mlir::Type fieldTy = lpType.getType(valIdx); |
| fir::IntOrValue idx = mlir::IntegerAttr::get(builder.getI32Type(), valIdx); |
| mlir::Value fieldAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(fieldTy), structRef, |
| llvm::SmallVector<fir::IntOrValue, 1>{idx}); |
| mlir::Value origVal = fir::LoadOp::create(builder, loc, origAddr); |
| fir::StoreOp::create(builder, loc, origVal, fieldAddr); |
| } |
| } |
| |
| /// Emit a runtime guard for orphaned conditional-lastprivate worksharing |
| /// constructs. The module-scope global used for the reduction struct is |
| /// shared across all teams, so concurrent nested teams would race on it. |
| /// Clang has a similar limitation for conditional lastprivate due to its |
| /// use of a shared global variable. |
| /// |
| /// Emits: if (omp_get_level() > 1) ERROR STOP "<message>" |
| static void |
| emitNestedParallelGuardForCondLp(lower::AbstractConverter &converter, |
| mlir::Location loc) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| mlir::MLIRContext *ctx = builder.getContext(); |
| mlir::Type i32Ty = builder.getI32Type(); |
| |
| // Declare omp_get_level_() -> i32 if not already present. |
| auto funcTy = mlir::FunctionType::get(ctx, {}, {i32Ty}); |
| if (!builder.getNamedFunction("omp_get_level_")) |
| builder.createFunction(loc, "omp_get_level_", funcTy); |
| |
| mlir::Value level = |
| fir::CallOp::create(builder, loc, |
| builder.getNamedFunction("omp_get_level_"), |
| mlir::ValueRange{}) |
| .getResult(0); |
| mlir::Value one = builder.createIntegerConstant(loc, i32Ty, 1); |
| mlir::Value isNested = mlir::arith::CmpIOp::create( |
| builder, loc, mlir::arith::CmpIPredicate::sgt, level, one); |
| |
| auto ifOp = fir::IfOp::create(builder, loc, /*resultTypes=*/{}, isNested, |
| /*withElse=*/false); |
| builder.setInsertionPoint(ifOp.getThenRegion().front().getTerminator()); |
| |
| // Build a global string constant for the error message. |
| llvm::StringRef msg = |
| "orphaned worksharing construct with lastprivate(conditional:) " |
| "is not supported in nested parallelism"; |
| std::string globalName = "_lp_cond_nested_msg"; |
| size_t msgLen = msg.size(); |
| auto charTy = fir::CharacterType::get(ctx, 1, msgLen); |
| if (!builder.getNamedGlobal(globalName)) { |
| fir::GlobalOp global = builder.createGlobal( |
| loc, charTy, globalName, builder.createInternalLinkage(), |
| /*value=*/mlir::Attribute{}, /*isConst=*/true); |
| mlir::Region ®ion = global.getRegion(); |
| mlir::Block *block = builder.createBlock(®ion); |
| builder.setInsertionPointToStart(block); |
| mlir::Value val = fir::StringLitOp::create(builder, loc, charTy, msg); |
| fir::HasValueOp::create(builder, loc, val); |
| builder.setInsertionPoint(ifOp.getThenRegion().front().getTerminator()); |
| } |
| |
| // Declare _FortranAStopStatementText if not already present. |
| mlir::Type i64Ty = builder.getI64Type(); |
| mlir::Type i1Ty = builder.getI1Type(); |
| mlir::Type ptrTy = builder.getRefType(builder.getIntegerType(8)); |
| auto stopTy = mlir::FunctionType::get(ctx, {ptrTy, i64Ty, i1Ty, i1Ty}, {}); |
| if (!builder.getNamedFunction("_FortranAStopStatementText")) |
| builder.createFunction(loc, "_FortranAStopStatementText", stopTy); |
| |
| mlir::Value msgAddr = |
| fir::AddrOfOp::create(builder, loc, builder.getRefType(charTy), |
| builder.getSymbolRefAttr(globalName)); |
| mlir::Value msgPtr = builder.createConvert(loc, ptrTy, msgAddr); |
| mlir::Value len = builder.createIntegerConstant(loc, i64Ty, msgLen); |
| mlir::Value trueVal = builder.createIntegerConstant(loc, i1Ty, 1); |
| mlir::Value falseVal = builder.createIntegerConstant(loc, i1Ty, 0); |
| fir::CallOp::create(builder, loc, |
| builder.getNamedFunction("_FortranAStopStatementText"), |
| mlir::ValueRange{msgPtr, len, trueVal, falseVal}); |
| |
| builder.setInsertionPointAfter(ifOp); |
| } |
| |
| /// Return the address of a module-scope global for the conditional-lastprivate |
| /// reduction struct. This is used in the *orphaned* worksharing case (sections |
| /// or wsloop inside a subroutine called from a parallel region) where the |
| /// parent op is a FuncOp, not a ParallelOp. |
| /// |
| /// Because there is no enclosing omp.parallel in the same function, a stack |
| /// alloca would give every thread its own private copy and the cross-thread |
| /// reduction combine would never merge results. A global provides a single |
| /// shared address that all threads in the team can reduce into — the same |
| /// semantics a dummy argument provides for ordinary user REDUCTION variables. |
| /// |
| /// Nested parallelism (concurrent teams executing the same orphaned construct) |
| /// would race on this global; a runtime guard emitted by |
| /// emitNestedParallelGuardForCondLp() aborts in that case. |
| /// |
| /// Note: the guard only catches nested parallelism (omp_get_level() > 1). Two |
| /// independent top-level parallel regions invoking this routine concurrently |
| /// (both at level 1) would still race on the global and are not diagnosed. |
| /// Clang's host lowering shares this class of limitation: it tracks the |
| /// conditional-last value/index in named internal globals (updated under a |
| /// critical region), which are likewise shared across concurrent invocations. |
| static mlir::Value |
| getOrCreateConditionalLpGlobal(lower::AbstractConverter &converter, |
| mlir::Location loc, fir::RecordType lpType) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| |
| // Derive a unique global name from the RecordType name. |
| // Type name is "_lp_cond_t.lN.M", global becomes "_lp_cond_global.lN.M". |
| llvm::StringRef typeName = lpType.getName(); |
| assert(typeName.starts_with("_lp_cond_t") && |
| "unexpected conditional LP type name prefix"); |
| std::string globalName = |
| "_lp_cond_global" + |
| typeName.substr(llvm::StringRef("_lp_cond_t").size()).str(); |
| |
| // Create the global if it does not already exist. |
| // The global is re-initialized by initConditionalLpStruct before each |
| // worksharing construct invocation (to reset values from prior calls), |
| // so a simple zero-init suffices here. |
| fir::GlobalOp global = builder.getNamedGlobal(globalName); |
| if (!global) { |
| builder.createGlobal(loc, lpType, globalName, |
| builder.createInternalLinkage()); |
| global = builder.getNamedGlobal(globalName); |
| } |
| assert(global && "global should have been created"); |
| return fir::AddrOfOp::create(builder, loc, global.resultType(), |
| global.getSymbol()); |
| } |
| |
| static mlir::omp::WsloopOp genStandaloneDo( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::WsloopOperands wsloopClauseOps; |
| llvm::SmallVector<Object> wsloopReductionObjects; |
| genWsloopClauses(converter, semaCtx, stmtCtx, item->clauses, loc, |
| wsloopClauseOps, wsloopReductionObjects); |
| |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| enableDelayedPrivatization, symTable); |
| // Worksharing loops use the private-copy lowering for conditional lastprivate |
| // (each list item gets an ordinary private copy + a reduction accumulator), |
| // which is correct under any schedule including nonmonotonic. |
| dsp.setConditionalLpUsesPrivateCopy(true); |
| dsp.processStep1(&wsloopClauseOps); |
| |
| // Conditional lastprivate: build struct type, declare_reduction, and |
| // inject a synthetic reduction variable into the wsloop. |
| auto &condLpSyms = dsp.getConditionalLastprivateSymbols(); |
| fir::RecordType lpType; // hoisted for post-loop rewrite pass |
| mlir::Value lpAlloca; // hoisted for post-reduction copy-back |
| if (!condLpSyms.empty()) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| // lastprivate(conditional:) is correct under any schedule (including |
| // nonmonotonic): each list item has an ordinary private copy for its |
| // in-loop working value, and a guarded commit (see injectCondLpIndexStores) |
| // keeps the value from the highest canonical index in the reduction |
| // accumulator, so out-of-order chunk execution cannot corrupt the result. |
| lpType = buildConditionalLpType(converter, condLpSyms, loc); |
| mlir::omp::DeclareReductionOp declRedOp = |
| buildConditionalLastPrivateReduction(converter, lpType, condLpSyms); |
| |
| // Create the struct alloca OUTSIDE the parent omp.parallel (if any), |
| // so the reduction result persists after the parallel region ends. |
| // In the orphaned case (no enclosing ParallelOp), use a |
| // module-scope global so that all threads share one reduction target. |
| auto enclosingParallel = findEnclosingParallelOp(builder); |
| bool isOrphaned = !enclosingParallel; |
| |
| // Guard against nested parallelism in the orphaned case. |
| // Emit this BEFORE touching the global to avoid racing on it. |
| if (isOrphaned) |
| emitNestedParallelGuardForCondLp(converter, loc); |
| |
| if (enclosingParallel) { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPoint(enclosingParallel); |
| lpAlloca = builder.createTemporary(loc, lpType); |
| // Index fields are initialised to -1 so the combiner's "sequentially |
| // last" comparison treats them as "no iteration has written yet" |
| // (any real canonical loop IV >= 0 beats -1). |
| initConditionalLpStructDefault(builder, loc, lpType, lpAlloca); |
| } else { |
| lpAlloca = getOrCreateConditionalLpGlobal(converter, loc, lpType); |
| // The global is shared across all threads. Use omp.single (which |
| // has an implicit barrier at exit) so that exactly one thread |
| // initialises and all threads wait before entering the construct. |
| mlir::omp::SingleOperands initSingleOps; |
| auto singleOp = mlir::omp::SingleOp::create(builder, loc, initSingleOps); |
| mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion()); |
| builder.setInsertionPointToStart(singleBlock); |
| initConditionalLpStructDefault(builder, loc, lpType, lpAlloca); |
| mlir::omp::TerminatorOp::create(builder, loc); |
| builder.setInsertionPointAfter(singleOp); |
| } |
| |
| // Append to wsloop clause operands. |
| wsloopClauseOps.reductionVars.push_back(lpAlloca); |
| wsloopClauseOps.reductionByref.push_back(true); |
| wsloopClauseOps.reductionSyms.push_back( |
| mlir::SymbolRefAttr::get(builder.getContext(), declRedOp.getSymName())); |
| |
| // Use a null-symbol Object as a sentinel — bindPrivateLike will skip it. |
| wsloopReductionObjects.push_back(Object{{nullptr, std::nullopt}}); |
| } |
| |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc, |
| loopNestClauseOps, iv); |
| |
| ObjectEntryBlockArgs wsloopArgs; |
| wsloopArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| wsloopArgs.priv.vars = wsloopClauseOps.privateVars; |
| wsloopArgs.reduction.objects = wsloopReductionObjects; |
| wsloopArgs.reduction.vars = wsloopClauseOps.reductionVars; |
| auto wsloopOp = genWrapperOp<mlir::omp::WsloopOp>( |
| converter, loc, wsloopClauseOps, wsloopArgs); |
| |
| // Save address-to-name mapping for conditional LP symbols before scoped |
| // binding overwrites them — needed for the post-reduction copy-back. |
| llvm::MapVector<mlir::Value, std::string> condLpOrigAddrs; |
| for (const auto *sym : condLpSyms) { |
| mlir::Value addr = converter.getSymbolAddress(*sym); |
| if (addr) |
| condLpOrigAddrs[addr] = sym->name().ToString(); |
| } |
| |
| // Conditional LP symbols are bound to their ordinary private copies by normal |
| // privatization. Capture each private-copy address (while the bindings are |
| // live) so the injection pass can find writes to it and commit them into the |
| // reduction accumulator struct. |
| llvm::MapVector<mlir::Value, std::string> condLpValAddrs; |
| auto loopPostIvCb = [&](mlir::Operation *) { |
| if (condLpSyms.empty()) |
| return; |
| for (const auto *sym : condLpSyms) |
| condLpValAddrs[converter.getSymbolAddress(*sym)] = sym->name().ToString(); |
| }; |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, item, |
| loopNestClauseOps, iv, {{wsloopOp, wsloopArgs}}, |
| llvm::omp::Directive::OMPD_do, dsp, loopPostIvCb); |
| |
| // Inject index stores after each assignment to a conditional LP value field. |
| if (!condLpSyms.empty()) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| auto blockArgIface = |
| mlir::cast<mlir::omp::BlockArgOpenMPOpInterface>(*wsloopOp); |
| mlir::Value structArg = blockArgIface.getReductionBlockArgs().back(); |
| auto loopNestOp = |
| mlir::cast<mlir::omp::LoopNestOp>(wsloopOp.getWrappedLoop()); |
| injectCondLpIndexStores( |
| builder, loc, lpType, structArg, loopNestOp.getRegion(), condLpValAddrs, |
| [&](fir::FirOpBuilder &b, mlir::Location l) -> mlir::Value { |
| return computeFlattenedCanonicalIV(b, l, loopNestOp); |
| }); |
| } |
| |
| // Post-reduction copy-back. When nowait is absent, the wsloop's implicit |
| // end-barrier guarantees all reductions are combined. When nowait is |
| // present, an explicit barrier is needed before reading the struct. |
| // Wrapped in omp.single so exactly one thread performs the stores, at the |
| // worksharing construct's barrier (the semantically correct finalization |
| // point) inside the enclosing parallel. Because this copy-back adds a second |
| // immediately-nested construct to the parallel, the parallel is not marked |
| // omp.combined (see the combined-marking logic in genOMPDispatch). |
| if (!condLpSyms.empty()) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| |
| // Insert right after the wsloop, still inside the parallel body. |
| builder.setInsertionPointAfter(wsloopOp); |
| |
| if (wsloopClauseOps.nowait) |
| mlir::omp::BarrierOp::create(builder, loc); |
| |
| mlir::omp::SingleOperands singleClauseOps; |
| auto singleOp = mlir::omp::SingleOp::create(builder, loc, singleClauseOps); |
| mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion()); |
| builder.setInsertionPointToStart(singleBlock); |
| |
| for (auto &[origAddr, symName] : condLpOrigAddrs) { |
| unsigned valFieldIdx = lpType.getFieldIndex(symName); |
| mlir::Type valType = lpType.getType(valFieldIdx); |
| |
| fir::IntOrValue valFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx); |
| mlir::Value fieldAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(valType), lpAlloca, |
| llvm::SmallVector<fir::IntOrValue, 1>{valFIdx}); |
| mlir::Value val = fir::LoadOp::create(builder, loc, fieldAddr); |
| |
| // Only copy back if some iteration actually assigned to this variable |
| // (index >= 0). Otherwise the original must not be overwritten. |
| unsigned idxFieldIdx = lpType.getFieldIndex("$" + symName); |
| fir::IntOrValue idxFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), idxFieldIdx); |
| mlir::Value idxAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(builder.getI64Type()), lpAlloca, |
| llvm::SmallVector<fir::IntOrValue, 1>{idxFIdx}); |
| mlir::Value idxVal = fir::LoadOp::create(builder, loc, idxAddr); |
| mlir::Value zero = |
| builder.createIntegerConstant(loc, builder.getI64Type(), 0); |
| mlir::Value cond = mlir::arith::CmpIOp::create( |
| builder, loc, mlir::arith::CmpIPredicate::sge, idxVal, zero); |
| auto ifOp = |
| fir::IfOp::create(builder, loc, cond, /*withElseRegion=*/false); |
| builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| fir::StoreOp::create(builder, loc, val, origAddr); |
| builder.setInsertionPointAfter(ifOp); |
| } |
| mlir::omp::TerminatorOp::create(builder, loc); |
| } |
| |
| return wsloopOp; |
| } |
| |
| static mlir::omp::ParallelOp genStandaloneParallel( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| lower::SymMapScope scope(symTable); |
| mlir::omp::ParallelOperands parallelClauseOps; |
| llvm::SmallVector<Object> parallelReductionObjects; |
| genParallelClauses(converter, semaCtx, stmtCtx, item->clauses, loc, |
| parallelClauseOps, parallelReductionObjects); |
| |
| std::optional<DataSharingProcessor> dsp; |
| if (enableDelayedPrivatization) { |
| dsp.emplace(converter, semaCtx, item->clauses, eval, |
| lower::omp::isLastItemInQueue(item, queue), |
| /*useDelayedPrivatization=*/true, symTable); |
| dsp->processStep1(¶llelClauseOps); |
| } |
| |
| ObjectEntryBlockArgs parallelArgs; |
| if (dsp) |
| parallelArgs.priv.objects = makeObjects(dsp->getDelayedPrivSymbols()); |
| parallelArgs.priv.vars = parallelClauseOps.privateVars; |
| parallelArgs.reduction.objects = parallelReductionObjects; |
| parallelArgs.reduction.vars = parallelClauseOps.reductionVars; |
| return genParallelOp(converter, symTable, semaCtx, eval, loc, queue, item, |
| parallelClauseOps, parallelArgs, |
| enableDelayedPrivatization ? &dsp.value() : nullptr); |
| } |
| |
| static mlir::omp::SimdOp |
| genStandaloneSimd(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| mlir::omp::SimdOperands simdClauseOps; |
| llvm::SmallVector<Object> simdReductionObjects; |
| genSimdClauses(converter, semaCtx, item->clauses, loc, simdClauseOps, |
| simdReductionObjects); |
| |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| enableDelayedPrivatization, symTable); |
| dsp.processStep1(&simdClauseOps); |
| |
| if (!dsp.getConditionalLastprivateSymbols().empty()) |
| TODO(loc, "lastprivate(conditional:) on simd construct"); |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc, |
| loopNestClauseOps, iv); |
| genSimdImplicitLinear(converter, semaCtx, simdClauseOps, loopNestClauseOps, |
| iv); |
| |
| ObjectEntryBlockArgs simdArgs; |
| simdArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| simdArgs.priv.vars = simdClauseOps.privateVars; |
| simdArgs.reduction.objects = simdReductionObjects; |
| simdArgs.reduction.vars = simdClauseOps.reductionVars; |
| auto simdOp = |
| genWrapperOp<mlir::omp::SimdOp>(converter, loc, simdClauseOps, simdArgs); |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, item, |
| loopNestClauseOps, iv, {{simdOp, simdArgs}}, |
| llvm::omp::Directive::OMPD_simd, dsp); |
| return simdOp; |
| } |
| |
| static mlir::omp::TaskloopContextOp genStandaloneTaskloop( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| mlir::omp::TaskloopContextOperands taskloopClauseOps; |
| llvm::SmallVector<Object> reductionObjects; |
| llvm::SmallVector<Object> inReductionObjects; |
| |
| genTaskloopClauses(converter, semaCtx, stmtCtx, item->clauses, loc, |
| taskloopClauseOps, reductionObjects, inReductionObjects); |
| DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| enableDelayedPrivatization, symTable); |
| dsp.processStep1(&taskloopClauseOps); |
| |
| if (hasPrivatizedArrayElementReduction(inReductionObjects, |
| dsp.getAllSymbolsToPrivatize())) |
| TODO(loc, "TASKLOOP construct with IN_REDUCTION of an array element whose " |
| "base array is privatized"); |
| if (hasPrivatizedArrayElementReduction(reductionObjects, |
| dsp.getAllSymbolsToPrivatize())) |
| TODO(loc, "TASKLOOP construct with REDUCTION of an array element whose " |
| "base array is privatized"); |
| |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc, |
| loopNestClauseOps, iv); |
| |
| ObjectEntryBlockArgs taskloopArgs; |
| taskloopArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| taskloopArgs.priv.vars = taskloopClauseOps.privateVars; |
| taskloopArgs.reduction.objects = reductionObjects; |
| taskloopArgs.reduction.vars = taskloopClauseOps.reductionVars; |
| taskloopArgs.inReduction.objects = inReductionObjects; |
| taskloopArgs.inReduction.vars = taskloopClauseOps.inReductionVars; |
| |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| auto taskLoopContextOp = mlir::omp::TaskloopContextOp::create( |
| firOpBuilder, loc, taskloopClauseOps); |
| // Create entry block with arguments. |
| genEntryBlock(firOpBuilder, taskloopArgs.asEntryBlockArgs(), |
| taskLoopContextOp.getRegion()); |
| |
| mlir::OpBuilder::InsertionGuard guard(firOpBuilder); |
| firOpBuilder.setInsertionPointToStart(&taskLoopContextOp.getRegion().front()); |
| mlir::omp::TaskloopWrapperOperands wrapperClauseOps; |
| ObjectEntryBlockArgs wrapperEntryBlockArgs; |
| auto taskLoopWrapperOp = genWrapperOp<mlir::omp::TaskloopWrapperOp>( |
| converter, loc, wrapperClauseOps, wrapperEntryBlockArgs); |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, item, |
| loopNestClauseOps, iv, {{taskLoopContextOp, taskloopArgs}}, |
| llvm::omp::Directive::OMPD_taskloop, dsp); |
| |
| firOpBuilder.setInsertionPointAfter(taskLoopWrapperOp); |
| mlir::omp::TerminatorOp::create(firOpBuilder, loc); |
| return taskLoopContextOp; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Code generation functions for composite constructs |
| //===----------------------------------------------------------------------===// |
| |
| static mlir::omp::DistributeOp genCompositeDistributeParallelDo( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| assert(std::distance(item, queue.end()) == 3 && "Invalid leaf constructs"); |
| ConstructQueue::const_iterator distributeItem = item; |
| ConstructQueue::const_iterator parallelItem = std::next(distributeItem); |
| ConstructQueue::const_iterator doItem = std::next(parallelItem); |
| |
| // Create parent omp.parallel first. |
| mlir::omp::ParallelOperands parallelClauseOps; |
| llvm::SmallVector<Object> parallelReductionObjects; |
| genParallelClauses(converter, semaCtx, stmtCtx, parallelItem->clauses, loc, |
| parallelClauseOps, parallelReductionObjects); |
| |
| DataSharingProcessor dsp(converter, semaCtx, doItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| /*useDelayedPrivatization=*/true, symTable); |
| dsp.setForceHeapAllocationForPrivateDynamicArrays(); |
| dsp.processStep1(¶llelClauseOps); |
| |
| ObjectEntryBlockArgs parallelArgs; |
| parallelArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols()); |
| parallelArgs.priv.vars = parallelClauseOps.privateVars; |
| parallelArgs.reduction.objects = parallelReductionObjects; |
| parallelArgs.reduction.vars = parallelClauseOps.reductionVars; |
| genParallelOp(converter, symTable, semaCtx, eval, loc, queue, parallelItem, |
| parallelClauseOps, parallelArgs, &dsp, /*isComposite=*/true); |
| |
| // Clause processing. |
| mlir::omp::DistributeOperands distributeClauseOps; |
| genDistributeClauses(converter, semaCtx, stmtCtx, distributeItem->clauses, |
| loc, distributeClauseOps); |
| |
| mlir::omp::WsloopOperands wsloopClauseOps; |
| llvm::SmallVector<Object> wsloopReductionObjects; |
| genWsloopClauses(converter, semaCtx, stmtCtx, doItem->clauses, loc, |
| wsloopClauseOps, wsloopReductionObjects); |
| |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, doItem->clauses, loc, |
| loopNestClauseOps, iv); |
| |
| // Operation creation. |
| ObjectEntryBlockArgs distributeArgs; |
| // TODO: Add private syms and vars. |
| auto distributeOp = genWrapperOp<mlir::omp::DistributeOp>( |
| converter, loc, distributeClauseOps, distributeArgs); |
| distributeOp.setComposite(/*val=*/true); |
| |
| ObjectEntryBlockArgs wsloopArgs; |
| // TODO: Add private syms and vars. |
| wsloopArgs.reduction.objects = wsloopReductionObjects; |
| wsloopArgs.reduction.vars = wsloopClauseOps.reductionVars; |
| auto wsloopOp = genWrapperOp<mlir::omp::WsloopOp>( |
| converter, loc, wsloopClauseOps, wsloopArgs); |
| wsloopOp.setComposite(/*val=*/true); |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, doItem, |
| loopNestClauseOps, iv, |
| {{distributeOp, distributeArgs}, {wsloopOp, wsloopArgs}}, |
| llvm::omp::Directive::OMPD_distribute_parallel_do, dsp); |
| return distributeOp; |
| } |
| |
| static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| assert(std::distance(item, queue.end()) == 4 && "Invalid leaf constructs"); |
| ConstructQueue::const_iterator distributeItem = item; |
| ConstructQueue::const_iterator parallelItem = std::next(distributeItem); |
| ConstructQueue::const_iterator doItem = std::next(parallelItem); |
| ConstructQueue::const_iterator simdItem = std::next(doItem); |
| |
| // Create parent omp.parallel first. |
| mlir::omp::ParallelOperands parallelClauseOps; |
| llvm::SmallVector<Object> parallelReductionObjects; |
| genParallelClauses(converter, semaCtx, stmtCtx, parallelItem->clauses, loc, |
| parallelClauseOps, parallelReductionObjects); |
| |
| DataSharingProcessor parallelItemDSP( |
| converter, semaCtx, parallelItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/false, |
| /*useDelayedPrivatization=*/true, symTable); |
| parallelItemDSP.processStep1(¶llelClauseOps); |
| |
| ObjectEntryBlockArgs parallelArgs; |
| parallelArgs.priv.objects = |
| makeObjects(parallelItemDSP.getDelayedPrivSymbols()); |
| parallelArgs.priv.vars = parallelClauseOps.privateVars; |
| parallelArgs.reduction.objects = parallelReductionObjects; |
| parallelArgs.reduction.vars = parallelClauseOps.reductionVars; |
| genParallelOp(converter, symTable, semaCtx, eval, loc, queue, parallelItem, |
| parallelClauseOps, parallelArgs, ¶llelItemDSP, |
| /*isComposite=*/true); |
| |
| // Clause processing. |
| // Use a shared cache so that both wsloop and simd produce the same SSA |
| // values for array/box reduction variables. See genCompositeDoSimd. |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> reductionVarCache; |
| |
| mlir::omp::DistributeOperands distributeClauseOps; |
| genDistributeClauses(converter, semaCtx, stmtCtx, distributeItem->clauses, |
| loc, distributeClauseOps); |
| |
| mlir::omp::WsloopOperands wsloopClauseOps; |
| llvm::SmallVector<Object> wsloopReductionObjects; |
| genWsloopClauses(converter, semaCtx, stmtCtx, doItem->clauses, loc, |
| wsloopClauseOps, wsloopReductionObjects, &reductionVarCache); |
| |
| mlir::omp::SimdOperands simdClauseOps; |
| llvm::SmallVector<Object> simdReductionObjects; |
| genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps, |
| simdReductionObjects, &reductionVarCache); |
| |
| // Same as genCompositeDoSimd. |
| if (!simdClauseOps.linearVars.empty()) { |
| wsloopClauseOps.linearVars = std::move(simdClauseOps.linearVars); |
| wsloopClauseOps.linearStepVars = std::move(simdClauseOps.linearStepVars); |
| wsloopClauseOps.linearVarTypes = simdClauseOps.linearVarTypes; |
| wsloopClauseOps.linearModifiers = simdClauseOps.linearModifiers; |
| simdClauseOps.linearVars.clear(); |
| simdClauseOps.linearStepVars.clear(); |
| simdClauseOps.linearVarTypes = nullptr; |
| simdClauseOps.linearModifiers = nullptr; |
| } |
| DataSharingProcessor simdItemDSP(converter, semaCtx, simdItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| /*useDelayedPrivatization=*/true, symTable); |
| simdItemDSP.processStep1(&simdClauseOps); |
| |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, simdItem->clauses, loc, |
| loopNestClauseOps, iv); |
| genSimdImplicitLinear(converter, semaCtx, simdClauseOps, loopNestClauseOps, |
| iv); |
| |
| // Operation creation. |
| ObjectEntryBlockArgs distributeArgs; |
| // TODO: Add private syms and vars. |
| auto distributeOp = genWrapperOp<mlir::omp::DistributeOp>( |
| converter, loc, distributeClauseOps, distributeArgs); |
| distributeOp.setComposite(/*val=*/true); |
| |
| ObjectEntryBlockArgs wsloopArgs; |
| // TODO: Add private syms and vars. |
| wsloopArgs.reduction.objects = wsloopReductionObjects; |
| wsloopArgs.reduction.vars = wsloopClauseOps.reductionVars; |
| auto wsloopOp = genWrapperOp<mlir::omp::WsloopOp>( |
| converter, loc, wsloopClauseOps, wsloopArgs); |
| wsloopOp.setComposite(/*val=*/true); |
| |
| ObjectEntryBlockArgs simdArgs; |
| simdArgs.priv.objects = makeObjects(simdItemDSP.getDelayedPrivSymbols()); |
| simdArgs.priv.vars = simdClauseOps.privateVars; |
| simdArgs.reduction.objects = simdReductionObjects; |
| simdArgs.reduction.vars = simdClauseOps.reductionVars; |
| auto simdOp = |
| genWrapperOp<mlir::omp::SimdOp>(converter, loc, simdClauseOps, simdArgs); |
| simdOp.setComposite(/*val=*/true); |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, simdItem, |
| loopNestClauseOps, iv, |
| {{distributeOp, distributeArgs}, |
| {wsloopOp, wsloopArgs}, |
| {simdOp, simdArgs}}, |
| llvm::omp::Directive::OMPD_distribute_parallel_do_simd, |
| simdItemDSP); |
| return distributeOp; |
| } |
| |
| static mlir::omp::DistributeOp genCompositeDistributeSimd( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| assert(std::distance(item, queue.end()) == 2 && "Invalid leaf constructs"); |
| ConstructQueue::const_iterator distributeItem = item; |
| ConstructQueue::const_iterator simdItem = std::next(distributeItem); |
| |
| // Clause processing. |
| mlir::omp::DistributeOperands distributeClauseOps; |
| genDistributeClauses(converter, semaCtx, stmtCtx, distributeItem->clauses, |
| loc, distributeClauseOps); |
| |
| mlir::omp::SimdOperands simdClauseOps; |
| llvm::SmallVector<Object> simdReductionObjects; |
| genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps, |
| simdReductionObjects); |
| |
| DataSharingProcessor distributeItemDSP( |
| converter, semaCtx, distributeItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/false, |
| /*useDelayedPrivatization=*/true, symTable); |
| distributeItemDSP.processStep1(&distributeClauseOps); |
| |
| DataSharingProcessor simdItemDSP(converter, semaCtx, simdItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| /*useDelayedPrivatization=*/true, symTable); |
| simdItemDSP.processStep1(&simdClauseOps); |
| |
| // Pass the innermost leaf construct's clauses because that's where COLLAPSE |
| // is placed by construct decomposition. |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, simdItem->clauses, loc, |
| loopNestClauseOps, iv); |
| genSimdImplicitLinear(converter, semaCtx, simdClauseOps, loopNestClauseOps, |
| iv); |
| |
| // Operation creation. |
| ObjectEntryBlockArgs distributeArgs; |
| distributeArgs.priv.objects = |
| makeObjects(distributeItemDSP.getDelayedPrivSymbols()); |
| distributeArgs.priv.vars = distributeClauseOps.privateVars; |
| auto distributeOp = genWrapperOp<mlir::omp::DistributeOp>( |
| converter, loc, distributeClauseOps, distributeArgs); |
| distributeOp.setComposite(/*val=*/true); |
| |
| ObjectEntryBlockArgs simdArgs; |
| simdArgs.priv.objects = makeObjects(simdItemDSP.getDelayedPrivSymbols()); |
| simdArgs.priv.vars = simdClauseOps.privateVars; |
| simdArgs.reduction.objects = simdReductionObjects; |
| simdArgs.reduction.vars = simdClauseOps.reductionVars; |
| auto simdOp = |
| genWrapperOp<mlir::omp::SimdOp>(converter, loc, simdClauseOps, simdArgs); |
| simdOp.setComposite(/*val=*/true); |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, simdItem, |
| loopNestClauseOps, iv, |
| {{distributeOp, distributeArgs}, {simdOp, simdArgs}}, |
| llvm::omp::Directive::OMPD_distribute_simd, simdItemDSP); |
| return distributeOp; |
| } |
| |
| static mlir::omp::WsloopOp genCompositeDoSimd( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| assert(std::distance(item, queue.end()) == 2 && "Invalid leaf constructs"); |
| ConstructQueue::const_iterator doItem = item; |
| ConstructQueue::const_iterator simdItem = std::next(doItem); |
| |
| // Clause processing. |
| // Use a shared cache so that both wsloop and simd produce the same SSA |
| // values for array/box reduction variables, enabling genLoopVars()'s |
| // IRMapping to correctly chain the inner wrapper's operands to the outer |
| // wrapper's block arguments. |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> reductionVarCache; |
| |
| mlir::omp::WsloopOperands wsloopClauseOps; |
| llvm::SmallVector<Object> wsloopReductionObjects; |
| genWsloopClauses(converter, semaCtx, stmtCtx, doItem->clauses, loc, |
| wsloopClauseOps, wsloopReductionObjects, &reductionVarCache); |
| |
| mlir::omp::SimdOperands simdClauseOps; |
| llvm::SmallVector<Object> simdReductionObjects; |
| genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps, |
| simdReductionObjects, &reductionVarCache); |
| |
| // omp.simd writes back linear vars unconditionally, causing a race when |
| // inside a parallel region. Move them to wsloop which has proper last-iter |
| // write-back guarded by a barrier. |
| if (!simdClauseOps.linearVars.empty()) { |
| wsloopClauseOps.linearVars = std::move(simdClauseOps.linearVars); |
| wsloopClauseOps.linearStepVars = std::move(simdClauseOps.linearStepVars); |
| wsloopClauseOps.linearVarTypes = simdClauseOps.linearVarTypes; |
| wsloopClauseOps.linearModifiers = simdClauseOps.linearModifiers; |
| simdClauseOps.linearVars.clear(); |
| simdClauseOps.linearStepVars.clear(); |
| simdClauseOps.linearVarTypes = nullptr; |
| simdClauseOps.linearModifiers = nullptr; |
| } |
| DataSharingProcessor wsloopItemDSP( |
| converter, semaCtx, doItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/false, |
| /*useDelayedPrivatization=*/true, symTable); |
| wsloopItemDSP.processStep1(&wsloopClauseOps); |
| if (!wsloopItemDSP.getConditionalLastprivateSymbols().empty()) |
| TODO(loc, "lastprivate(conditional:) on do simd composite construct"); |
| |
| DataSharingProcessor simdItemDSP(converter, semaCtx, simdItem->clauses, eval, |
| /*shouldCollectPreDeterminedSymbols=*/true, |
| /*useDelayedPrivatization=*/true, symTable); |
| simdItemDSP.processStep1(&simdClauseOps, simdItem->id); |
| |
| // Pass the innermost leaf construct's clauses because that's where COLLAPSE |
| // is placed by construct decomposition. |
| mlir::omp::LoopNestOperands loopNestClauseOps; |
| llvm::SmallVector<const semantics::Symbol *> iv; |
| genLoopNestClauses(converter, semaCtx, eval, simdItem->clauses, loc, |
| loopNestClauseOps, iv); |
| genSimdImplicitLinear(converter, semaCtx, simdClauseOps, loopNestClauseOps, |
| iv); |
| |
| // Operation creation. |
| ObjectEntryBlockArgs wsloopArgs; |
| wsloopArgs.priv.objects = makeObjects(wsloopItemDSP.getDelayedPrivSymbols()); |
| wsloopArgs.priv.vars = wsloopClauseOps.privateVars; |
| wsloopArgs.reduction.objects = wsloopReductionObjects; |
| wsloopArgs.reduction.vars = wsloopClauseOps.reductionVars; |
| auto wsloopOp = genWrapperOp<mlir::omp::WsloopOp>( |
| converter, loc, wsloopClauseOps, wsloopArgs); |
| wsloopOp.setComposite(/*val=*/true); |
| |
| ObjectEntryBlockArgs simdArgs; |
| simdArgs.priv.objects = makeObjects(simdItemDSP.getDelayedPrivSymbols()); |
| simdArgs.priv.vars = simdClauseOps.privateVars; |
| simdArgs.reduction.objects = simdReductionObjects; |
| simdArgs.reduction.vars = simdClauseOps.reductionVars; |
| auto simdOp = |
| genWrapperOp<mlir::omp::SimdOp>(converter, loc, simdClauseOps, simdArgs); |
| simdOp.setComposite(/*val=*/true); |
| |
| genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, simdItem, |
| loopNestClauseOps, iv, |
| {{wsloopOp, wsloopArgs}, {simdOp, simdArgs}}, |
| llvm::omp::Directive::OMPD_do_simd, simdItemDSP); |
| return wsloopOp; |
| } |
| |
| static mlir::omp::TaskloopWrapperOp genCompositeTaskloopSimd( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item) { |
| assert(std::distance(item, queue.end()) == 2 && "Invalid leaf constructs"); |
| if (!semaCtx.langOptions().OpenMPSimd) |
| TODO(loc, "Composite TASKLOOP SIMD"); |
| return nullptr; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Dispatch |
| //===----------------------------------------------------------------------===// |
| |
| static bool genOMPCompositeDispatch( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, ConstructQueue::const_iterator item, |
| mlir::Operation *&newOp) { |
| using llvm::omp::Directive; |
| using lower::omp::matchLeafSequence; |
| |
| // TODO: Privatization for composite constructs is currently only done based |
| // on the clauses for their last leaf construct, which may not always be |
| // correct. Consider per-leaf privatization of composite constructs once |
| // delayed privatization is supported by all participating ops. |
| if (matchLeafSequence(item, queue, Directive::OMPD_distribute_parallel_do)) |
| newOp = genCompositeDistributeParallelDo(converter, symTable, stmtCtx, |
| semaCtx, eval, loc, queue, item); |
| else if (matchLeafSequence(item, queue, |
| Directive::OMPD_distribute_parallel_do_simd)) |
| newOp = genCompositeDistributeParallelDoSimd( |
| converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item); |
| else if (matchLeafSequence(item, queue, Directive::OMPD_distribute_simd)) |
| newOp = genCompositeDistributeSimd(converter, symTable, stmtCtx, semaCtx, |
| eval, loc, queue, item); |
| else if (matchLeafSequence(item, queue, Directive::OMPD_do_simd)) |
| newOp = genCompositeDoSimd(converter, symTable, stmtCtx, semaCtx, eval, loc, |
| queue, item); |
| else if (matchLeafSequence(item, queue, Directive::OMPD_taskloop_simd)) |
| newOp = genCompositeTaskloopSimd(converter, symTable, stmtCtx, semaCtx, |
| eval, loc, queue, item); |
| else |
| return false; |
| |
| return true; |
| } |
| |
| static void genOMPDispatch(lower::AbstractConverter &converter, |
| lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, mlir::Location loc, |
| const ConstructQueue &queue, |
| ConstructQueue::const_iterator item) { |
| assert(item != queue.end()); |
| |
| lower::StatementContext stmtCtx; |
| mlir::Operation *newOp = nullptr; |
| |
| // Generate cleanup code for the stmtCtx after newOp |
| auto finalizeStmtCtx = [&]() { |
| if (newOp) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| fir::FirOpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPointAfter(newOp); |
| stmtCtx.finalizeAndPop(); |
| } |
| }; |
| |
| bool loopLeaf = llvm::omp::getDirectiveAssociation(item->id) == |
| llvm::omp::Association::LoopNest; |
| if (loopLeaf) { |
| symTable.pushScope(); |
| if (genOMPCompositeDispatch(converter, symTable, stmtCtx, semaCtx, eval, |
| loc, queue, item, newOp)) { |
| symTable.popScope(); |
| finalizeStmtCtx(); |
| return; |
| } |
| } |
| |
| llvm::omp::Directive dir = item->id; |
| switch (dir) { |
| case llvm::omp::Directive::OMPD_barrier: |
| newOp = genBarrierOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_distribute: |
| newOp = genStandaloneDistribute(converter, symTable, stmtCtx, semaCtx, eval, |
| loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_do: |
| newOp = genStandaloneDo(converter, symTable, stmtCtx, semaCtx, eval, loc, |
| queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_loop: |
| newOp = genLoopOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_masked: |
| newOp = genMaskedOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_master: |
| newOp = genMasterOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_ordered_blockassoc: |
| // Block-associated "ordered" construct. |
| newOp = genOrderedRegionOp(converter, symTable, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_parallel: |
| newOp = genStandaloneParallel(converter, symTable, stmtCtx, semaCtx, eval, |
| loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_scan: |
| newOp = genScanOp(converter, symTable, semaCtx, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_section: |
| llvm_unreachable("genOMPDispatch: OMPD_section"); |
| // Lowered in the enclosing genSectionsOp. |
| break; |
| case llvm::omp::Directive::OMPD_sections: |
| newOp = genSectionsOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_simd: |
| newOp = |
| genStandaloneSimd(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_scope: |
| newOp = genScopeOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_single: |
| newOp = genSingleOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_target: |
| newOp = genTargetOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_target_data: |
| newOp = genTargetDataOp(converter, symTable, stmtCtx, semaCtx, eval, loc, |
| queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_target_enter_data: |
| newOp = genTargetEnterExitUpdateDataOp<mlir::omp::TargetEnterDataOp>( |
| converter, symTable, stmtCtx, semaCtx, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_target_exit_data: |
| newOp = genTargetEnterExitUpdateDataOp<mlir::omp::TargetExitDataOp>( |
| converter, symTable, stmtCtx, semaCtx, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_target_update: |
| newOp = genTargetEnterExitUpdateDataOp<mlir::omp::TargetUpdateOp>( |
| converter, symTable, stmtCtx, semaCtx, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_task: |
| newOp = genTaskOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_taskgroup: |
| newOp = |
| genTaskgroupOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_taskloop: |
| newOp = genStandaloneTaskloop(converter, symTable, stmtCtx, semaCtx, eval, |
| loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_taskwait: |
| newOp = genTaskwaitOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_taskyield: |
| newOp = |
| genTaskyieldOp(converter, symTable, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_teams: |
| newOp = genTeamsOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_interchange: |
| genInterchangeOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_tile: |
| genTileOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_fuse: |
| genFuseOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_unroll: |
| genUnrollOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item); |
| break; |
| case llvm::omp::Directive::OMPD_workdistribute: |
| newOp = genWorkdistributeOp(converter, symTable, semaCtx, eval, loc, queue, |
| item); |
| break; |
| case llvm::omp::Directive::OMPD_workshare: |
| newOp = genWorkshareOp(converter, symTable, stmtCtx, semaCtx, eval, loc, |
| queue, item); |
| break; |
| default: |
| // Combined and composite constructs should have been split into a sequence |
| // of leaf constructs when building the construct queue. |
| assert(!llvm::omp::isLeafConstruct(dir) && |
| "Unexpected compound construct."); |
| break; |
| } |
| |
| finalizeStmtCtx(); |
| if (loopLeaf) |
| symTable.popScope(); |
| |
| // Add the omp.combined attribute to eligible ops, including non-innermost |
| // leafs of a combined construct and immediately nested block-associated |
| // combinable constructs. SECTIONS, WORKSHARE and WORKDISTRIBUTE are skipped |
| // due to only being able to appear as an innermost combined construct. |
| if (!loopLeaf && |
| llvm::isa_and_present<mlir::omp::ComposableOpInterface>(newOp) && |
| !llvm::isa<mlir::omp::SectionsOp, mlir::omp::WorkshareOp, |
| mlir::omp::WorkdistributeOp>(newOp)) { |
| bool isCombined = false; |
| if (std::next(item) != queue.end()) { |
| // Non-innermost leafs of a combined construct must always hold the |
| // attribute. |
| isCombined = true; |
| } else if (lower::pft::Evaluation *nestedEval = |
| extractOnlyOmpNestedEval(eval)) { |
| // Combinable constructs that are immediately nested with no other |
| // statements or directives preventing them from being combined need the |
| // attribute as well. Disallow block constructs that can only be outermost |
| // leafs and loop transformation constructs. |
| llvm::omp::DirectiveSet combinableDirs = |
| (llvm::omp::blockConstructSet & |
| ~llvm::omp::DirectiveSet{ |
| llvm::omp::Directive::OMPD_ordered_blockassoc, |
| llvm::omp::Directive::OMPD_scope, |
| llvm::omp::Directive::OMPD_taskgroup}) | |
| (llvm::omp::loopConstructSet & ~llvm::omp::loopTransformationSet); |
| const auto &ompEval = nestedEval->get<parser::OpenMPConstruct>(); |
| llvm::omp::Directive nestedDir = |
| parser::omp::GetOmpDirectiveName(ompEval).v; |
| llvm::omp::Directive firstLeafDir = |
| llvm::omp::getLeafConstructsOrSelf(nestedDir).front(); |
| |
| if (combinableDirs.test(firstLeafDir)) |
| isCombined = true; |
| } |
| // Conditional-lastprivate lowering emits an auxiliary omp.single copy-back |
| // as a sibling of the worksharing op inside the same region. That makes |
| // the region an immediate nesting of two constructs rather than one, so it |
| // no longer qualifies as combined. Detect this by counting eligible nested |
| // constructs and clear the combined status when there is more than one. |
| if (isCombined) { |
| int eligibleNested = 0; |
| for (mlir::Operation &nested : newOp->getRegion(0).getOps()) |
| if (llvm::isa<mlir::omp::ComposableOpInterface, |
| mlir::omp::LoopWrapperInterface>(nested)) |
| ++eligibleNested; |
| if (eligibleNested > 1) |
| isCombined = false; |
| } |
| if (isCombined) |
| llvm::cast<mlir::omp::ComposableOpInterface>(newOp).setCombined(true); |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // OpenMPDeclarativeConstruct visitors |
| //===----------------------------------------------------------------------===// |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpUtilityDirective &); |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpAllocateDirective &allocate) { |
| lower::StatementContext stmtCtx; |
| ObjectList objects = makeObjects((allocate.BeginDir().Arguments()), semaCtx); |
| const auto &clauseList = (allocate.BeginDir().Clauses()); |
| List<Clause> clauses = makeClauses(clauseList, semaCtx); |
| mlir::Location loc = converter.genLocation(allocate.source); |
| |
| ConstructQueue queue{buildConstructQueue( |
| converter.getFirOpBuilder().getModule(), semaCtx, eval, allocate.source, |
| llvm::omp::Directive::OMPD_allocate, clauses)}; |
| |
| genAllocateDirOp(converter, semaCtx, stmtCtx, eval, loc, objects, queue, |
| queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpAssumesDirective &assumesConstruct) { |
| // Assumption clauses are hints with no representation in the OpenMP dialect, |
| // so this declarative directive is a no-op. |
| if (!semaCtx.langOptions().OpenMPSimd) |
| TODO(converter.getCurrentLocation(), |
| "assumption clauses on the assumes directive"); |
| } |
| |
| static void |
| genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| const parser::OmpDeclareVariantDirective &declareVariantDirective) { |
| // No lowering for the declarative directive itself; the recorded variants are |
| // resolved at call sites (see resolveDeclareVariantCallee in CallInterface). |
| } |
| |
| static ReductionProcessor::GenCombinerCBTy processReductionCombiner( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, const StylizedInstance &combinerInst, |
| const parser::OmpStylizedInstance &parserInst) { |
| // Extract the typed assignment from the parser-level instance, if |
| // the combiner is an assignment statement (as opposed to a call). |
| const evaluate::Assignment *assign = nullptr; |
| const auto &instance = |
| std::get<parser::OmpStylizedInstance::Instance>(parserInst.t); |
| if (const auto *assignStmt = |
| std::get_if<parser::AssignmentStmt>(&instance.u)) { |
| if (auto *wrapper = assignStmt->typedAssignment.get()) |
| if (wrapper->v) |
| assign = &*wrapper->v; |
| } |
| ReductionProcessor::GenCombinerCBTy genCombinerCB; |
| // combinerInst is the combiner instance for the type currently being lowered |
| // (one per listed type, in typeNameList order): a multi-type declare |
| // reduction carries a distinct stylized instance per type so omp_out/omp_in |
| // are re-typed for each. Single-type is index 0 of one. |
| semantics::SomeExpr evalExpr = |
| std::get<StylizedInstance::Instance>(combinerInst.t); |
| |
| genCombinerCB = [&, evalExpr, assign](fir::FirOpBuilder &builder, |
| mlir::Location loc, mlir::Type type, |
| mlir::Value lhs, mlir::Value rhs, |
| bool isByRef) { |
| lower::SymMapScope scope(symTable); |
| mlir::Value ompOutVar; |
| for (const Object &object : |
| std::get<StylizedInstance::Variables>(combinerInst.t)) { |
| mlir::Value addr = lhs; |
| mlir::Type type = lhs.getType(); |
| std::string name = object.sym()->name().ToString(); |
| bool isRhs = name == "omp_in"; |
| if (isRhs) { |
| addr = rhs; |
| type = rhs.getType(); |
| } |
| |
| if (!fir::conformsWithPassByRef(type)) { |
| addr = builder.createTemporary(loc, type); |
| fir::StoreOp::create(builder, loc, isRhs ? rhs : lhs, addr); |
| } |
| fir::FortranVariableFlagsEnum extraFlags = {}; |
| fir::FortranVariableFlagsAttr attributes = |
| Fortran::lower::translateSymbolAttributes(builder.getContext(), |
| *object.sym(), extraFlags); |
| // For character types, we need to provide the length parameter |
| llvm::SmallVector<mlir::Value> typeParams; |
| if (hlfir::isFortranEntity(addr)) { |
| hlfir::genLengthParameters(loc, builder, hlfir::Entity{addr}, |
| typeParams); |
| } |
| auto declareOp = |
| hlfir::DeclareOp::create(builder, loc, addr, name, nullptr, |
| typeParams, nullptr, nullptr, 0, attributes); |
| if (name == "omp_out") |
| ompOutVar = declareOp.getResult(0); |
| symTable.addVariableDefinition(*object.sym(), declareOp); |
| } |
| |
| // For derived types with a typed assignment available, use |
| // hlfir::AssignOp or user-defined assignment directly instead of |
| // trying to convert the expression to a value (which doesn't work |
| // for record types). Only take this path when the assignment RHS |
| // itself is a derived type -- i.e. the combiner assigns to the whole |
| // derived-type variable (e.g. omp_out = mycombine(omp_out, omp_in)). |
| // When the combiner assigns to a component (e.g. omp_out%x = ...), |
| // the RHS is a scalar intrinsic type and the existing convertExprToValue |
| // path handles it correctly. |
| bool rhsIsDerived = |
| assign && assign->rhs.GetType() && |
| assign->rhs.GetType()->category() == common::TypeCategory::Derived; |
| if (rhsIsDerived && isByRef && |
| mlir::isa<fir::RecordType>(fir::unwrapRefType(lhs.getType()))) { |
| lower::StatementContext stmtCtx; |
| hlfir::Entity lhsEntity{ompOutVar}; |
| hlfir::Entity rhsEntity = lower::convertExprToHLFIR( |
| loc, converter, assign->rhs, symTable, stmtCtx); |
| common::visit( |
| common::visitors{ |
| [&](const evaluate::Assignment::Intrinsic &) { |
| hlfir::AssignOp::create(builder, loc, rhsEntity, lhsEntity); |
| }, |
| [&](const evaluate::ProcedureRef &procRef) { |
| lower::convertUserDefinedAssignmentToHLFIR( |
| loc, converter, procRef, lhsEntity, rhsEntity, symTable); |
| }, |
| [&](const auto &) { |
| llvm_unreachable( |
| "Unexpected assignment type in reduction combiner"); |
| }, |
| }, |
| assign->u); |
| stmtCtx.finalizeAndPop(); |
| mlir::omp::YieldOp::create(builder, loc, lhs); |
| return; |
| } |
| |
| lower::StatementContext stmtCtx; |
| mlir::Value result = common::visit( |
| common::visitors{ |
| [&](const evaluate::ProcedureRef &procRef) -> mlir::Value { |
| convertCallToHLFIR(loc, converter, procRef, std::nullopt, |
| symTable, stmtCtx); |
| auto outVal = fir::LoadOp::create(builder, loc, ompOutVar); |
| if (isByRef) { |
| fir::StoreOp::create(builder, loc, outVal, lhs); |
| return mlir::Value{}; |
| } |
| return outVal; |
| }, |
| [&](const auto &expr) -> mlir::Value { |
| mlir::Value exprResult = fir::getBase(convertExprToValue( |
| loc, converter, evalExpr, symTable, stmtCtx)); |
| // Optional load may be generated if we get a reference to the |
| // reduction type. |
| if (auto refType = llvm::dyn_cast<fir::ReferenceType>( |
| exprResult.getType())) { |
| mlir::Type expectedType = |
| isByRef ? fir::unwrapRefType(lhs.getType()) : lhs.getType(); |
| if (expectedType == refType.getElementType()) |
| exprResult = fir::LoadOp::create(builder, loc, exprResult); |
| } |
| // For component-level derived-type combiners (e.g. |
| // omp_out%x = omp_out%x + omp_in%x), the assignment was |
| // not performed during expression lowering since |
| // convertExprToValue only evaluates the RHS value. |
| // The result type won't match the reduction variable type. |
| // Use the typed assignment LHS to store to the correct |
| // component, then skip the whole-variable store. |
| if (isByRef && |
| exprResult.getType() != fir::unwrapRefType(lhs.getType())) { |
| if (assign) { |
| lower::StatementContext assignCtx; |
| hlfir::Entity lhsEntity = lower::convertExprToHLFIR( |
| loc, converter, assign->lhs, symTable, assignCtx); |
| hlfir::AssignOp::create(builder, loc, exprResult, lhsEntity); |
| assignCtx.finalizeAndPop(); |
| } else { |
| fir::StoreOp::create(builder, loc, exprResult, ompOutVar); |
| } |
| return mlir::Value{}; |
| } |
| if (isByRef) { |
| fir::StoreOp::create(builder, loc, exprResult, lhs); |
| return mlir::Value{}; |
| } |
| return exprResult; |
| }}, |
| evalExpr.u); |
| stmtCtx.finalizeAndPop(); |
| if (isByRef) { |
| mlir::omp::YieldOp::create(builder, loc, lhs); |
| } else { |
| mlir::omp::YieldOp::create(builder, loc, result); |
| } |
| }; |
| return genCombinerCB; |
| } |
| |
| // Checks that the reduction type is either a trivial type, a fixed-length |
| // character type, or a derived type composed of such types. |
| static bool isSimpleReductionType(mlir::Type reductionType) { |
| if (fir::isa_trivial(reductionType)) |
| return true; |
| // Fixed-length CHARACTER is not trivial but can be zero-initialized. |
| // Reject dynamic-length CHARACTER (len == unknownLen()). |
| if (auto charTy = mlir::dyn_cast<fir::CharacterType>(reductionType)) |
| return charTy.getLen() != fir::CharacterType::unknownLen(); |
| if (auto recordTy = mlir::dyn_cast<fir::RecordType>(reductionType)) { |
| for (auto [_, fieldType] : recordTy.getTypeList()) { |
| if (!isSimpleReductionType(fieldType)) |
| return false; |
| } |
| return true; |
| } |
| // Reject array and descriptor-based types. |
| return false; |
| } |
| |
| // Compute the reduction's element type from the given combiner instance's |
| // stylized declaration symbol (omp_out), without checking whether lowering |
| // supports it. A multi-type declare reduction carries one combiner instance per |
| // listed type (typeNameList order), so the caller passes the instance for the |
| // type being lowered. Shared by getReductionType (same-file, enforces support |
| // with a TODO) and materializeUserReduction (skips an unsupported requested |
| // type via isSimpleReductionType instead of aborting the consumer). |
| // Getting the type from that symbol compared to the declared type-list (a |
| // DeclarationTypeSpec) is simpler since we do not need to consider derived vs |
| // intrinsic types. Semantics is guaranteed to generate these symbols. |
| static mlir::Type |
| computeReductionType(lower::AbstractConverter &converter, |
| const parser::OmpStylizedInstance &combinerInstance) { |
| const std::list<parser::OmpStylizedDeclaration> &declList = |
| std::get<std::list<parser::OmpStylizedDeclaration>>(combinerInstance.t); |
| const parser::OmpStylizedDeclaration &decl = declList.front(); |
| const auto &name = std::get<parser::ObjectName>(decl.var.t); |
| const auto &symbol = semantics::SymbolRef(*name.symbol); |
| return converter.genType(symbol); |
| } |
| |
| // Return the reduction's element type, emitting a TODO if lowering does not |
| // support it. |
| static mlir::Type |
| getReductionType(lower::AbstractConverter &converter, |
| const parser::OmpStylizedInstance &combinerInstance) { |
| mlir::Type reductionType = computeReductionType(converter, combinerInstance); |
| if (!isSimpleReductionType(reductionType)) |
| TODO(converter.getCurrentLocation(), |
| "declare reduction currently only supports trivial types, " |
| "fixed-length CHARACTER, or derived types containing them"); |
| return reductionType; |
| } |
| |
| /// Compute a flattened canonical (0-based, always ascending) iteration number |
| /// from all loop IVs. For a single loop, this is simply (IV - LB) / step. |
| /// For collapsed loops with dimensions d0..dN, the flattened index is: |
| /// c0 * (N1*N2*...*Nk) + c1 * (N2*...*Nk) + ... + ck |
| /// where ci = (IVi - LBi) / stepi and Ni = (UBi - LBi) / stepi + 1. |
| /// This yields a unique monotonic index regardless of loop direction, |
| /// which is essential for the combiner's `sgt` comparison to correctly |
| /// identify the sequentially last iteration. |
| static mlir::Value |
| computeFlattenedCanonicalIV(fir::FirOpBuilder &builder, mlir::Location loc, |
| mlir::omp::LoopNestOp loopNestOp) { |
| mlir::Region ®ion = loopNestOp.getRegion(); |
| auto lbs = loopNestOp.getLoopLowerBounds(); |
| auto ubs = loopNestOp.getLoopUpperBounds(); |
| auto steps = loopNestOp.getLoopSteps(); |
| unsigned numDims = lbs.size(); |
| |
| // Use i64 for the flattened index to avoid overflow. |
| mlir::Type i64Ty = builder.getI64Type(); |
| |
| // Compute canonical IV and trip count for each dimension. |
| llvm::SmallVector<mlir::Value> canonIVs(numDims); |
| llvm::SmallVector<mlir::Value> tripCounts(numDims); |
| for (unsigned d = 0; d < numDims; ++d) { |
| // Widen the IV, bounds and step to i64 BEFORE any subtraction/division so |
| // that the intermediate arithmetic cannot overflow the (possibly narrower) |
| // loop-variable type. E.g. for an i32 loop with lb=INT_MIN, ub=INT_MAX, |
| // computing (ub - lb) in i32 would wrap; doing it in i64 is exact. |
| mlir::Value iv = fir::ConvertOp::create(builder, loc, i64Ty, |
| region.front().getArgument(d)); |
| mlir::Value lb = fir::ConvertOp::create(builder, loc, i64Ty, lbs[d]); |
| mlir::Value ub = fir::ConvertOp::create(builder, loc, i64Ty, ubs[d]); |
| mlir::Value step = fir::ConvertOp::create(builder, loc, i64Ty, steps[d]); |
| |
| mlir::Value diff = mlir::arith::SubIOp::create(builder, loc, iv, lb); |
| canonIVs[d] = mlir::arith::DivSIOp::create(builder, loc, diff, step); |
| |
| // Trip count: (UB - LB) / step + 1 (loop bounds are inclusive). |
| mlir::Value range = mlir::arith::SubIOp::create(builder, loc, ub, lb); |
| mlir::Value trips = mlir::arith::DivSIOp::create(builder, loc, range, step); |
| mlir::Value one = builder.createIntegerConstant(loc, i64Ty, 1); |
| tripCounts[d] = mlir::arith::AddIOp::create(builder, loc, trips, one); |
| } |
| |
| // Flatten: result = c0*N1*N2*...*Nk + c1*N2*...*Nk + ... + ck |
| mlir::Value flatIdx = canonIVs[0]; |
| for (unsigned d = 1; d < numDims; ++d) { |
| flatIdx = mlir::arith::MulIOp::create(builder, loc, flatIdx, tripCounts[d]); |
| flatIdx = mlir::arith::AddIOp::create(builder, loc, flatIdx, canonIVs[d]); |
| } |
| return flatIdx; |
| } |
| |
| /// Bind conditional lastprivate symbols to their value fields inside the |
| /// reduction struct. This must be called \b before body lowering so that all |
| /// references to the LP symbols resolve to struct field addresses directly, |
| /// avoiding the need for a post-hoc address-replacement rewrite. |
| /// |
| /// Returns a map from the newly-created struct-field addresses to symbol names |
| /// so that \c injectCondLpIndexStores can later locate writes to these fields. |
| static llvm::MapVector<mlir::Value, std::string> bindCondLpSymsToStructFields( |
| lower::AbstractConverter &converter, mlir::Location loc, |
| fir::RecordType lpType, mlir::Value structArg, |
| const llvm::SetVector<const semantics::Symbol *> &condLpSyms) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| llvm::MapVector<mlir::Value, std::string> valAddrToSymName; |
| for (const auto *sym : condLpSyms) { |
| std::string symName = sym->name().ToString(); |
| unsigned valFieldIdx = lpType.getFieldIndex(symName); |
| mlir::Type valType = lpType.getType(valFieldIdx); |
| |
| fir::IntOrValue valFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx); |
| mlir::Value valAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(valType), structArg, |
| llvm::SmallVector<fir::IntOrValue, 1>{valFIdx}); |
| |
| converter.bindSymbol(*sym, valAddr); |
| valAddrToSymName[valAddr] = symName; |
| } |
| return valAddrToSymName; |
| } |
| |
| /// Walk the given region to find writes to a conditional-lastprivate item -- |
| /// hlfir.assign / fir.store and OpenMP atomic write/update/capture -- whose |
| /// target address is one of those tracked in \p valAddrToSymName (a worksharing |
| /// loop's ordinary private copy, or a section's struct value field). After |
| /// each such write, inject a guarded commit (using \p genIndexVal for the |
| /// canonical index) that stores the value and index into the reduction struct's |
| /// paired fields when that index is the highest seen so far. |
| static void injectCondLpIndexStores( |
| fir::FirOpBuilder &builder, mlir::Location loc, fir::RecordType lpType, |
| mlir::Value structArg, mlir::Region ®ion, |
| const llvm::MapVector<mlir::Value, std::string> &valAddrToSymName, |
| llvm::function_ref<mlir::Value(fir::FirOpBuilder &, mlir::Location)> |
| genIndexVal) { |
| // Look through hlfir.declare to find the underlying struct field address. |
| // When symbols are bound via bindCondLpSymsToStructFields, the lowering |
| // wraps the fir.coordinate_of result in hlfir.declare, so the actual write |
| // target is the declare result rather than the raw coordinate_of. |
| auto lookThroughDeclare = [](mlir::Value v) -> mlir::Value { |
| if (auto declOp = v.getDefiningOp<hlfir::DeclareOp>()) |
| return declOp.getMemref(); |
| return v; |
| }; |
| // valAddrToSymName maps each conditional-lastprivate item's tracked address |
| // (a worksharing loop's ordinary private copy, or a section's struct value |
| // field) to its name. A write may target the hlfir.declare result or its |
| // memref, so match either form. |
| auto matchName = [&](mlir::Value v) -> const std::string * { |
| auto it = valAddrToSymName.find(v); |
| if (it != valAddrToSymName.end()) |
| return &it->second; |
| it = valAddrToSymName.find(lookThroughDeclare(v)); |
| if (it != valAddrToSymName.end()) |
| return &it->second; |
| return nullptr; |
| }; |
| |
| llvm::SmallVector<std::pair<mlir::Operation *, mlir::Value>> writes; |
| region.walk([&](hlfir::AssignOp assignOp) { |
| if (matchName(assignOp.getLhs())) |
| writes.push_back({assignOp, assignOp.getLhs()}); |
| }); |
| region.walk([&](fir::StoreOp storeOp) { |
| if (matchName(storeOp.getMemref())) |
| writes.push_back({storeOp, storeOp.getMemref()}); |
| }); |
| // An OpenMP atomic write/update/capture of a conditional-lastprivate item |
| // also assigns it (clang tracks this too); instrument the same guarded commit |
| // after the atomic operation. (An atomic on a per-thread-private item is |
| // redundant but legal.) A standalone atomic write/update is committed right |
| // after it. For an atomic.capture the write/update lives inside the capture |
| // op's region -- injecting there would violate the capture verifier -- so |
| // those nested ops are skipped and the commit is emitted after the whole |
| // capture op, reading the (already updated) item. |
| auto notInCapture = [](mlir::Operation *op) { |
| return !op->getParentOfType<mlir::omp::AtomicCaptureOp>(); |
| }; |
| region.walk([&](mlir::omp::AtomicWriteOp atomicOp) { |
| if (matchName(atomicOp.getX()) && notInCapture(atomicOp)) |
| writes.push_back({atomicOp, atomicOp.getX()}); |
| }); |
| region.walk([&](mlir::omp::AtomicUpdateOp atomicOp) { |
| if (matchName(atomicOp.getX()) && notInCapture(atomicOp)) |
| writes.push_back({atomicOp, atomicOp.getX()}); |
| }); |
| region.walk([&](mlir::omp::AtomicCaptureOp captureOp) { |
| // A capture assigns two locations: the write/update target (getX) and the |
| // read destination (getV, which captures the item's value). Either may be |
| // a conditional-lastprivate item, so record every match and commit each |
| // after the whole capture op (reading the item's post-capture value). |
| captureOp.walk([&](mlir::Operation *inner) { |
| if (auto w = mlir::dyn_cast<mlir::omp::AtomicWriteOp>(inner)) { |
| if (matchName(w.getX())) |
| writes.push_back({captureOp, w.getX()}); |
| } else if (auto u = mlir::dyn_cast<mlir::omp::AtomicUpdateOp>(inner)) { |
| if (matchName(u.getX())) |
| writes.push_back({captureOp, u.getX()}); |
| } else if (auto r = mlir::dyn_cast<mlir::omp::AtomicReadOp>(inner)) { |
| if (matchName(r.getV())) |
| writes.push_back({captureOp, r.getV()}); |
| } |
| }); |
| }); |
| |
| // Compute the canonical index once at the region entry so that it dominates |
| // all write sites (which may be inside nested fir.if blocks). |
| mlir::Value indexVal; |
| if (!writes.empty()) { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPointToStart(®ion.front()); |
| indexVal = genIndexVal(builder, loc); |
| if (indexVal.getType() != builder.getI64Type()) |
| indexVal = |
| fir::ConvertOp::create(builder, loc, builder.getI64Type(), indexVal); |
| } |
| |
| for (auto &[writeOp, writeAddr] : writes) { |
| const std::string *namePtr = matchName(writeAddr); |
| assert(namePtr && |
| "collected write site must map to a conditional-LP symbol"); |
| const std::string &symName = *namePtr; |
| unsigned valFieldIdx = lpType.getFieldIndex(symName); |
| unsigned idxFieldIdx = lpType.getFieldIndex("$" + symName); |
| mlir::Type valType = lpType.getType(valFieldIdx); |
| mlir::Type idxType = lpType.getType(idxFieldIdx); |
| |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPointAfter(writeOp); |
| |
| // The value just written to the private copy of the list item. |
| mlir::Value curVal = fir::LoadOp::create(builder, loc, writeAddr); |
| |
| fir::IntOrValue valFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx); |
| mlir::Value valAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(valType), structArg, |
| llvm::SmallVector<fir::IntOrValue, 1>{valFIdx}); |
| fir::IntOrValue idxFIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), idxFieldIdx); |
| mlir::Value idxAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(idxType), structArg, |
| llvm::SmallVector<fir::IntOrValue, 1>{idxFIdx}); |
| |
| // Guarded commit into the accumulator: keep the value from the highest |
| // canonical index seen so far. Correct under any schedule (including |
| // nonmonotonic): a lower-index assignment executed out of order cannot |
| // overwrite a higher-index one. |
| mlir::Value curIdx = fir::LoadOp::create(builder, loc, idxAddr); |
| mlir::Value cmp = mlir::arith::CmpIOp::create( |
| builder, loc, mlir::arith::CmpIPredicate::sge, indexVal, curIdx); |
| auto ifOp = fir::IfOp::create(builder, loc, cmp, /*withElseRegion=*/false); |
| builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| fir::StoreOp::create(builder, loc, curVal, valAddr); |
| fir::StoreOp::create(builder, loc, indexVal, idxAddr); |
| } |
| } |
| |
| static mlir::omp::DeclareReductionOp buildConditionalLastPrivateReduction( |
| lower::AbstractConverter &converter, fir::RecordType lpCondType, |
| const llvm::SetVector<const semantics::Symbol *> &condLpSyms) { |
| |
| // Determine, per value field, whether the corresponding list item is also |
| // firstprivate. Value fields are ordered the same as condLpSyms. |
| llvm::SmallVector<bool> valIsFirstprivate; |
| for (const semantics::Symbol *sym : condLpSyms) |
| valIsFirstprivate.push_back( |
| sym->test(semantics::Symbol::Flag::OmpFirstPrivate)); |
| |
| // Init callback: initialize all fields of each thread's private copy. |
| // Value fields get 0 and index fields get -1 (identity). For a list item |
| // that is also firstprivate, the value field is instead copied from the seed |
| // (ompOrig). This matters for the sections path, where the list item is |
| // bound directly to the struct value field and the caller seeds ompOrig with |
| // the original value, so firstprivate initialization is observed per thread. |
| // For worksharing loops ompOrig is only identity-seeded (the firstprivate |
| // value lives in the item's ordinary private copy, not the accumulator), so |
| // this copy is a harmless no-op there. |
| // |
| // Returns a null mlir::Value to signal that initialization has already |
| // been performed directly on ompPriv. The reduction infrastructure |
| // (populateByRefInitAndCleanupRegions → initAndCleanupUnboxedDerivedType) |
| // checks for a non-null scalarInitValue before emitting a store, so |
| // returning null here safely skips the redundant store. |
| auto genInitValueCB = [lpCondType, valIsFirstprivate]( |
| fir::FirOpBuilder &builder, mlir::Location loc, |
| mlir::Type type, mlir::Value ompOrig, |
| mlir::Value ompPriv) -> mlir::Value { |
| initConditionalLpStructDefault(builder, loc, lpCondType, ompPriv); |
| fir::RecordType lpType = lpCondType; |
| llvm::ArrayRef<std::pair<std::string, mlir::Type>> fields = |
| lpType.getTypeList(); |
| unsigned numVars = fields.size() / 2; |
| for (unsigned i = 0; i < numVars; ++i) { |
| if (!valIsFirstprivate[i]) |
| continue; |
| mlir::Type fieldTy = fields[i].second; |
| fir::IntOrValue idx = mlir::IntegerAttr::get(builder.getI32Type(), i); |
| mlir::Value privAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(fieldTy), ompPriv, |
| llvm::SmallVector<fir::IntOrValue, 1>{idx}); |
| mlir::Value origAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(fieldTy), ompOrig, |
| llvm::SmallVector<fir::IntOrValue, 1>{idx}); |
| mlir::Value origVal = fir::LoadOp::create(builder, loc, origAddr); |
| fir::StoreOp::create(builder, loc, origVal, privAddr); |
| } |
| return mlir::Value{}; |
| }; |
| |
| // Combiner callback: for each (value, index) pair, pick the later iteration. |
| // Fields are arranged as: {val_0, ..., val_{N-1}, idx_0, ..., idx_{N-1}} |
| // where idx field names are "$" + val field name. |
| // If rhs.idx > lhs.idx, copy rhs value and index into lhs. |
| auto genCombinerCB = [lpCondType](fir::FirOpBuilder &builder, |
| mlir::Location loc, mlir::Type type, |
| mlir::Value lhs, mlir::Value rhs, |
| bool isByRef) { |
| fir::RecordType lpType = lpCondType; // non-const copy for getFieldIndex |
| llvm::ArrayRef<std::pair<std::string, mlir::Type>> fields = |
| lpType.getTypeList(); |
| unsigned numVars = fields.size() / 2; |
| |
| // Walk the first half (value fields). Index field name = "$" + |
| // value name. The "$" character is invalid in Fortran identifiers, |
| // so the prefix cannot collide with any user variable name. |
| for (unsigned i = 0; i < numVars; ++i) { |
| auto [valName, valType] = fields[i]; |
| std::string idxName = "$" + valName; |
| unsigned valIdx = lpType.getFieldIndex(valName); |
| unsigned idxIdx = lpType.getFieldIndex(idxName); |
| mlir::Type idxType = lpType.getType(idxIdx); |
| |
| // Get addresses of LHS and RHS index fields |
| fir::IntOrValue idxFieldIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), idxIdx); |
| mlir::Value lhsIdxAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(idxType), lhs, |
| llvm::SmallVector<fir::IntOrValue, 1>{idxFieldIdx}); |
| mlir::Value rhsIdxAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(idxType), rhs, |
| llvm::SmallVector<fir::IntOrValue, 1>{idxFieldIdx}); |
| |
| mlir::Value lhsIdx = fir::LoadOp::create(builder, loc, lhsIdxAddr); |
| mlir::Value rhsIdx = fir::LoadOp::create(builder, loc, rhsIdxAddr); |
| |
| // Compare: rhs index > lhs index (signed, iteration indices) |
| mlir::Value cmp = mlir::arith::CmpIOp::create( |
| builder, loc, mlir::arith::CmpIPredicate::sgt, rhsIdx, lhsIdx); |
| |
| // If RHS comes from a later iteration, copy its value and index to LHS |
| auto ifOp = fir::IfOp::create(builder, loc, cmp, /*else*/ false); |
| builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| |
| // Copy value field: rhs.val_s → lhs.val_s |
| fir::IntOrValue valFieldIdx = |
| mlir::IntegerAttr::get(builder.getI32Type(), valIdx); |
| mlir::Value rhsValAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(valType), rhs, |
| llvm::SmallVector<fir::IntOrValue, 1>{valFieldIdx}); |
| mlir::Value lhsValAddr = fir::CoordinateOp::create( |
| builder, loc, builder.getRefType(valType), lhs, |
| llvm::SmallVector<fir::IntOrValue, 1>{valFieldIdx}); |
| mlir::Value rhsVal = fir::LoadOp::create(builder, loc, rhsValAddr); |
| fir::StoreOp::create(builder, loc, rhsVal, lhsValAddr); |
| |
| // Copy index field: rhs.idx_s → lhs.idx_s |
| fir::StoreOp::create(builder, loc, rhsIdx, lhsIdxAddr); |
| |
| builder.setInsertionPointAfter(ifOp); |
| } |
| |
| // By-ref: yield the accumulator (LHS) |
| mlir::omp::YieldOp::create(builder, loc, lhs); |
| }; |
| |
| // RecordType is always by-ref |
| bool isByRef = true; |
| mlir::Location loc = converter.getCurrentLocation(); |
| mlir::Type redType = fir::ReferenceType::get(lpCondType); |
| std::string reductionName = ReductionProcessor::getReductionName( |
| "lp_cond", converter.getKindMap(), redType, isByRef); |
| |
| return ReductionProcessor::createDeclareReductionHelper< |
| mlir::omp::DeclareReductionOp>(converter, reductionName, redType, loc, |
| isByRef, genCombinerCB, genInitValueCB); |
| } |
| |
| /// Build a FIR RecordType for conditional lastprivate reduction. |
| /// For symbols {x, y}, creates: |
| /// !fir.type<_lp_cond_t.lN.M{x:T_x, y:T_y, kx:i64, ky:i64}> |
| /// where N is the source line number and M is a monotonic counter. |
| static fir::RecordType buildConditionalLpType( |
| lower::AbstractConverter &converter, |
| const llvm::SetVector<const semantics::Symbol *> &condLpSyms, |
| mlir::Location loc) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| mlir::MLIRContext *context = builder.getContext(); |
| |
| // Derive a unique suffix from the source location and a monotonic counter. |
| // The line number makes names traceable to source; the counter prevents |
| // collisions when INCLUDE files place directives on identical line numbers. |
| // Use atomic for thread-safety in case flang ever lowers in parallel. |
| static std::atomic<unsigned> counter{0}; |
| unsigned line = 0; |
| if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(loc)) |
| line = fileLoc.getLine(); |
| else if (auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(loc)) { |
| for (mlir::Location sub : fusedLoc.getLocations()) { |
| if (auto fileSub = mlir::dyn_cast<mlir::FileLineColLoc>(sub)) { |
| line = fileSub.getLine(); |
| break; |
| } |
| } |
| } |
| std::string typeName = |
| "_lp_cond_t.l" + std::to_string(line) + "." + std::to_string(counter++); |
| |
| // The counter in typeName makes each call produce a fresh, unfinalized |
| // record type, so there is no existing type to reuse; always build it. |
| auto lpCondType = fir::RecordType::get(context, typeName); |
| |
| // Build field list: first all value fields, then all index fields. |
| // Grouping values before indices (rather than interleaving value/index |
| // pairs) can reduce padding holes when value types differ from i64. |
| llvm::SmallVector<std::pair<std::string, mlir::Type>> fields; |
| |
| // Value fields first. Semantics has already restricted the list items to |
| // whole scalar variables of intrinsic numeric or logical type; assert that |
| // invariant here to catch any semantic regression in assertions builds. |
| for (const auto *sym : condLpSyms) { |
| const semantics::Symbol &ultimate = sym->GetUltimate(); |
| [[maybe_unused]] const semantics::DeclTypeSpec *type = ultimate.GetType(); |
| assert(ultimate.Rank() == 0 && type && |
| (type->category() == semantics::DeclTypeSpec::Category::Numeric || |
| type->category() == semantics::DeclTypeSpec::Category::Logical) && |
| !semantics::IsAllocatableOrPointer(ultimate) && |
| "conditional lastprivate requires a scalar intrinsic " |
| "numeric/logical, non-pointer/allocatable variable"); |
| std::string symName = sym->name().ToString(); |
| mlir::Type symType = converter.genType(*sym); |
| fields.push_back({symName, symType}); |
| } |
| |
| // Then index fields (i64). |
| for (const auto *sym : condLpSyms) { |
| std::string indexName = "$" + sym->name().ToString(); |
| fields.push_back({indexName, builder.getI64Type()}); |
| } |
| |
| // Finalize the type with the field list |
| lpCondType.finalize({}, fields); |
| |
| return lpCondType; |
| } |
| |
| // Represent the reduction combiner as a clause, return reference to it. |
| // If there is a "combiner" clause already present, do nothing. Otherwise |
| // manufacture a combiner clause from the combiner expression on the reduction |
| // specifier and append it to the list of clauses. |
| static const clause::Combiner & |
| appendCombiner(const parser::OmpDeclareReductionDirective &construct, |
| List<Clause> &clauses, semantics::SemanticsContext &semaCtx) { |
| for (const Clause &clause : clauses) { |
| if (clause.id == llvm::omp::Clause::OMPC_combiner) |
| return std::get<clause::Combiner>(clause.u); |
| } |
| |
| using namespace parser::omp; |
| const parser::OmpDirectiveSpecification &dirSpec = construct.v; |
| auto *specifier = GetFirstArgument<parser::OmpReductionSpecifier>(dirSpec); |
| assert(specifier && "Expecting reduction specifier"); |
| if (auto *expr = GetCombinerExpr(*specifier)) { |
| clause::Combiner combiner; |
| for (const parser::OmpStylizedInstance &sinst : expr->v) |
| combiner.v.push_back(makeStylizedInstance(sinst, semaCtx)); |
| clauses.push_back(makeClause(llvm::omp::Clause::OMPC_combiner, |
| std::move(combiner), expr->source)); |
| return std::get<clause::Combiner>(clauses.back().u); |
| } |
| |
| llvm_unreachable("Expecting reduction combiner"); |
| } |
| |
| // Lower a single declare-reduction directive. Serves both same-file lowering |
| // (symOpt null, no filter) and separate-compilation materialization of an |
| // imported reduction (symOpt is the source symbol, with a fresh SymMap). |
| // When \p requestedOpName is non-empty (the lazy clause-driven path), only the |
| // per-type instance whose scoped op name equals it is emitted, and lowerability |
| // is checked per-type rather than for the whole declaration; same-file callers |
| // pass an empty filter and lower every listed type as before. Mirrors |
| // genOpenMPDeclareMapperImpl. |
| static void genOpenMPDeclareReductionImpl( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| const parser::OmpDeclareReductionDirective &construct, |
| const semantics::Symbol *symOpt = nullptr, |
| llvm::StringRef requestedOpName = {}) { |
| if (semaCtx.langOptions().OpenMPSimd) |
| return; |
| |
| const auto &specifier = |
| DEREF(parser::omp::GetFirstArgument<parser::OmpReductionSpecifier>( |
| construct.v)); |
| const auto &typeNameList = std::get<parser::OmpTypeNameList>(specifier.t); |
| List<Clause> clauses = makeClauses(construct.v.Clauses(), semaCtx); |
| const clause::Combiner &combiner = |
| appendCombiner(construct, clauses, semaCtx); |
| const auto &identifier = |
| std::get<parser::OmpReductionIdentifier>(specifier.t); |
| |
| // Convert the parser-level reduction identifier to the clause-level |
| // representation, then use ReductionProcessor to derive the canonical name. |
| clause::ReductionOperator redOp = |
| clause::makeReductionOperator(identifier, semaCtx); |
| |
| // Get the parser-level combiner expression so we can pass each |
| // parser::OmpStylizedInstance to processReductionCombiner. |
| // The combiner expression's instances correspond 1:1 to typeNameList entries. |
| const auto *combinerExpr = parser::omp::GetCombinerExpr(specifier); |
| assert(combinerExpr && "Expecting combiner expression"); |
| auto parserInstIt = combinerExpr->v.begin(); |
| |
| // Get the parser-level initializer expression (if present) so we can |
| // pass each parser::OmpStylizedInstance to processInitializer. |
| const parser::OmpInitializerExpression *initExpr = nullptr; |
| for (const auto &clause : construct.v.Clauses().v) { |
| initExpr = parser::omp::GetInitializerExpr(clause); |
| if (initExpr) |
| break; |
| } |
| auto parserInitInstIt = |
| initExpr ? initExpr->v.begin() |
| : std::list<parser::OmpStylizedInstance>::const_iterator{}; |
| |
| // Index of the type currently being lowered within the parallel per-type |
| // lists: typeNameList.v, the clause-level combiner instances (combiner.v), |
| // and the clause-level initializer instances. Single-type is index 0 of one. |
| unsigned instanceIdx = 0; |
| for (const auto &typeSpec : typeNameList.v) { |
| (void)typeSpec; // Currently unused |
| |
| assert(parserInstIt != combinerExpr->v.end() && |
| "Mismatched combiner instance count"); |
| const parser::OmpStylizedInstance &parserInst = *parserInstIt++; |
| |
| mlir::Type reductionType; |
| if (requestedOpName.empty()) { |
| // Same-file path: lower every listed type, emitting a TODO on an |
| // unsupported one exactly as before. |
| reductionType = getReductionType(converter, parserInst); |
| } else { |
| // Lazy per-type path: check lowerability for THIS type alone (not the |
| // whole-declaration gate), so a multi-type declaration with an |
| // unsupported sibling still materializes the requested supported type. |
| // An unsupported requested type is left unmaterialized (the clause then |
| // keeps its TODO). |
| reductionType = computeReductionType(converter, parserInst); |
| if (!isSimpleReductionType(reductionType)) { |
| if (initExpr) |
| ++parserInitInstIt; |
| ++instanceIdx; |
| continue; |
| } |
| } |
| bool isByRef = ReductionProcessor::doReductionByRef(reductionType); |
| // Compute the canonical reduction name the same way |
| // processReductionArguments does. |
| std::string reductionNameStr = common::visit( |
| common::visitors{ |
| [&](const clause::DefinedOperator &defOp) -> std::string { |
| return common::visit( |
| common::visitors{ |
| [&](const clause::DefinedOperator::IntrinsicOperator |
| &intrOp) -> std::string { |
| // Directive side of the intrinsic-operator USER |
| // reduction naming contract (clause side: |
| // ReductionProcessor::processReductionArguments site |
| // 2). A user `declare reduction(+:t)` is scoped by its |
| // owning scope exactly like the defined-operator and |
| // named paths, so two declarations for the same |
| // (operator, type) in different scopes get distinct ops |
| // instead of colliding on the one global builtin name. |
| // Only user reductions reach here (builtins are created |
| // directly by createDeclareReduction), so this always |
| // scopes. The intrinsic-operator parse node carries no |
| // reduction symbol: symOpt supplies it for separate |
| // compilation; same-file it is resolved from the |
| // current (declaration) scope under the operator's |
| // mangled name (MakeNameFromOperator), which resolves |
| // to the same ultimate the clause side finds, so both |
| // name the op identically. |
| const semantics::Symbol *redSymPtr = symOpt; |
| if (!redSymPtr) { |
| parser::CharBlock mangledOpName = semantics::omp:: |
| MangledIntrinsicOperatorReductionName( |
| ReductionProcessor::toParserIntrinsicOperator( |
| intrOp), |
| semaCtx); |
| redSymPtr = semantics::omp::FindUserReductionSymbol( |
| converter.getCurrentScope(), mangledOpName); |
| } |
| if (!redSymPtr) |
| TODO(converter.getCurrentLocation(), |
| "OpenMP user-defined intrinsic-operator declare " |
| "reduction symbol was not found for lowering"); |
| const semantics::Symbol &redSym = |
| redSymPtr->GetUltimate(); |
| return ReductionProcessor::getScopedUserReductionName( |
| converter, redSym, reductionType, isByRef); |
| }, |
| [&](const clause::DefinedOperator::DefinedOpName &opName) |
| -> std::string { |
| // Directive side of the user-defined operator reduction |
| // naming contract (the clause side is in |
| // ReductionProcessor::processReductionArguments). Name |
| // the op via getScopedUserReductionName from the |
| // symbol's ultimate (name, owner) plus the per-type |
| // suffix, byte-identical to the clause reference. |
| // symOpt supplies the source symbol for separate |
| // compilation, else opName.v.sym(). Runs once per |
| // listed type (the enclosing loop walks typeNameList in |
| // lockstep with the per-type combiner/initializer |
| // instances), emitting one op per (operator, type); |
| // single-type is N=1. |
| const semantics::Symbol &redSym = |
| symOpt ? symOpt->GetUltimate() |
| : opName.v.sym()->GetUltimate(); |
| const auto *userDetails = |
| redSym.detailsIf<semantics::UserReductionDetails>(); |
| if (!userDetails) |
| TODO(converter.getCurrentLocation(), |
| "OpenMP user-defined operator declare reduction " |
| "without user reduction details"); |
| return ReductionProcessor::getScopedUserReductionName( |
| converter, redSym, reductionType, isByRef); |
| }, |
| }, |
| defOp.u); |
| }, |
| [&](const clause::ProcedureDesignator &pd) -> std::string { |
| // Directive side of the named-reduction naming contract (clause |
| // side in ReductionProcessor::processReductionArguments). Name |
| // the op via getScopedUserReductionName from the source symbol's |
| // ultimate (name, owner) plus the per-type suffix, byte-identical |
| // to the clause reference. symOpt supplies the source symbol for |
| // separate compilation, else pd.v.sym(); GetUltimate keeps the |
| // name stable across a plain `use`. reductionType is the declared |
| // element type (already canonical), so unlike the clause side it |
| // needs no namingType normalization. |
| const semantics::Symbol &redSym = |
| (symOpt ? *symOpt : *pd.v.sym()).GetUltimate(); |
| return ReductionProcessor::getScopedUserReductionName( |
| converter, redSym, reductionType, isByRef); |
| }, |
| }, |
| redOp.u); |
| |
| // Lazy path: emit only the op the clause requested, skipping this |
| // declaration's other listed types (keeping the per-type iterators in |
| // lockstep). Single-type is the N==1 case where the one name matches. |
| if (!requestedOpName.empty() && reductionNameStr != requestedOpName) { |
| if (initExpr) |
| ++parserInitInstIt; |
| ++instanceIdx; |
| continue; |
| } |
| |
| assert(instanceIdx < combiner.v.size() && |
| "Mismatched combiner instance count"); |
| ReductionProcessor::GenCombinerCBTy genCombinerCB = |
| processReductionCombiner(converter, symTable, semaCtx, |
| combiner.v[instanceIdx], parserInst); |
| const parser::OmpStylizedInstance *parserInitInst = nullptr; |
| if (initExpr) { |
| assert(parserInitInstIt != initExpr->v.end() && |
| "Mismatched initializer instance count"); |
| parserInitInst = &*parserInitInstIt++; |
| } |
| |
| // Get the omp_out symbol from the combiner. Used for finalization checks |
| // in populateByRefInitAndCleanupRegions and for generating default |
| // initialization via genScalarDefaultInitializerValue. |
| const semantics::Symbol *reductionSym = nullptr; |
| const auto &declList = |
| std::get<std::list<parser::OmpStylizedDeclaration>>(parserInst.t); |
| for (const auto &decl : declList) { |
| const auto &name = std::get<parser::ObjectName>(decl.var.t); |
| if (name.ToString() == "omp_out") { |
| reductionSym = name.symbol; |
| break; |
| } |
| } |
| |
| ReductionProcessor::GenInitValueCBTy genInitValueCB; |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| if (!cp.processInitializer(symTable, genInitValueCB, parserInitInst, |
| instanceIdx)) { |
| // No initializer clause provided. Per OpenMP, initialize as |
| // default-initialized using the shared inline init helper. |
| const semantics::DerivedTypeSpec *derivedTypeSpec = nullptr; |
| if (const semantics::DeclTypeSpec *declTypeSpec = typeSpec.declTypeSpec) |
| derivedTypeSpec = declTypeSpec->AsDerived(); |
| |
| mlir::Type unwrappedType = fir::unwrapRefType(reductionType); |
| if (fir::isa_trivial(unwrappedType)) { |
| // Trivial types return the zero value directly (by-value init). |
| genInitValueCB = [](fir::FirOpBuilder &builder, mlir::Location loc, |
| mlir::Type type, mlir::Value, |
| mlir::Value) -> mlir::Value { |
| mlir::Type ty = fir::unwrapRefType(type); |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) |
| ty = seqTy.getEleTy(); |
| else if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(ty)) { |
| auto eleTy = fir::unwrapRefType(boxTy.getEleTy()); |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy)) |
| ty = seqTy.getEleTy(); |
| else |
| ty = eleTy; |
| } |
| return fir::ZeroOp::create(builder, loc, ty); |
| }; |
| } else if (mlir::isa<fir::CharacterType>(unwrappedType) || |
| fir::isa_derived(unwrappedType)) { |
| // CHARACTER and derived types use by-ref init via the shared helper. |
| genInitValueCB = [&converter, derivedTypeSpec, reductionSym]( |
| fir::FirOpBuilder &builder, mlir::Location loc, |
| mlir::Type type, mlir::Value, |
| mlir::Value) -> mlir::Value { |
| mlir::Block *initBlock = builder.getInsertionBlock(); |
| mlir::Value privVar = initBlock->getArgument(1); |
| lower::genInlineTypeDefaultInit(converter, builder, loc, type, |
| privVar, derivedTypeSpec, |
| reductionSym); |
| return mlir::Value{}; |
| }; |
| } else { |
| llvm_unreachable( |
| "unhandled type in declare reduction without initializer"); |
| } |
| } |
| mlir::Type redType = |
| isByRef |
| ? static_cast<mlir::Type>(fir::ReferenceType::get(reductionType)) |
| : reductionType; |
| |
| ReductionProcessor::createDeclareReductionHelper< |
| mlir::omp::DeclareReductionOp>( |
| converter, reductionNameStr, redType, converter.getCurrentLocation(), |
| isByRef, genCombinerCB, genInitValueCB, reductionSym); |
| ++instanceIdx; |
| } |
| } |
| |
| // Same-file delegator for a declare-reduction directive. Mirrors the |
| // declare-mapper delegator: it forwards the enclosing SymMap so the combiner/ |
| // initializer callbacks share the current scope. Separate-compilation |
| // materialization instead calls genOpenMPDeclareReductionImpl directly with a |
| // fresh SymMap and the source reduction symbol. |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpDeclareReductionDirective &construct) { |
| genOpenMPDeclareReductionImpl(converter, symTable, semaCtx, construct); |
| } |
| |
| // Lazily materialize the omp.declare_reduction op the reduction clause resolved |
| // but did not find, for separate compilation. The clause side has already |
| // resolved the exact imported reduction symbol and computed the per-type scoped |
| // op name it needs; this emits exactly that one op (idempotently) instead of an |
| // eager pre-pass materializing every accessible reduction. Called from the |
| // clause bind sites in ReductionProcessor.cpp when the op is absent and the |
| // reduction's defining module is a mod file. |
| void Fortran::lower::materializeUserReduction( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| const semantics::Symbol &resolvedReduction, llvm::StringRef requestedOpName, |
| mlir::Type requestedType, bool isByRef) { |
| // Per-type lowerability: an unsupported requested element type is not |
| // materialized, so the clause keeps its TODO. Checked here (not the |
| // all-types isLowerableUserReductionDecl gate) so a multi-type declaration |
| // with an unsupported sibling still materializes the requested supported |
| // type. The combiner-in-clause form is skipped per-declaration below. |
| (void)isByRef; |
| if (!isSimpleReductionType(requestedType)) |
| return; |
| const semantics::Symbol &ultimate = resolvedReduction.GetUltimate(); |
| const auto *userDetails = |
| ultimate.detailsIf<semantics::UserReductionDetails>(); |
| if (!userDetails) |
| return; |
| for (const auto *decl : userDetails->GetDeclList()) { |
| const auto *reductionDecl = |
| std::get_if<parser::OmpDeclareReductionDirective>(&decl->u); |
| if (!reductionDecl) |
| continue; |
| // The combiner-in-clause form (OpenMP 6.0 `declare reduction(id:type) |
| // combiner(...)`) is not lowered even in the same-file path; skip it so an |
| // imported one does not abort. A referenced one still gets the clause TODO. |
| const auto &specifier = |
| DEREF(parser::omp::GetFirstArgument<parser::OmpReductionSpecifier>( |
| reductionDecl->v)); |
| const auto &combinerExpr = |
| std::get<std::optional<parser::OmpCombinerExpression>>(specifier.t); |
| if (!combinerExpr.has_value()) |
| continue; |
| // Mod-file reading runs only ResolveNames, so an imported combiner and |
| // initializer carry bound names but null typedExpr/typedCall (the same-file |
| // path fills these later in PerformStatementSemantics). Lowering reads the |
| // typed forms, so run expression analysis over this imported directive now; |
| // the genOpenMPDeclareReductionImpl -> createDeclareReductionHelper |
| // lookupSymbol cache makes a repeat call idempotent. |
| semantics::ExprChecker checker{semaCtx}; |
| parser::Walk(*reductionDecl, checker); |
| // Fresh, materialization-local SymMap: the combiner/initializer callbacks |
| // created inside the impl capture it by reference and run synchronously |
| // during createDeclareReductionHelper, so it must outlive the impl call. |
| lower::SymMap materializeSymTable; |
| genOpenMPDeclareReductionImpl(converter, materializeSymTable, semaCtx, |
| *reductionDecl, &ultimate, requestedOpName); |
| } |
| } |
| |
| static void |
| genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| const parser::OmpDeclareSimdDirective &declareSimdConstruct) { |
| mlir::Location loc = converter.getCurrentLocation(); |
| const parser::OmpDirectiveSpecification &beginSpec = declareSimdConstruct.v; |
| |
| // A `declare simd` directive may appear in the specification part of an |
| // interface body. In that case the PFT records the directive as an |
| // evaluation of the enclosing program unit rather than of the interface |
| // body's subprogram, and the clause operands (linear/aligned/uniform) |
| // reference dummy arguments that are local to the interface body and |
| // therefore have no address in the enclosing scope. Detect this by |
| // comparing the program unit lexically containing the directive with the |
| // procedure currently being lowered; if they differ, this evaluation is |
| // for a different procedure (the interface-body subprogram) and emitting |
| // an `omp.declare_simd` op here would create it with null operands. Skip |
| // emission: lowering for `declare simd` on an external procedure declared |
| // only via an interface body is not handled by this op-based form. |
| const semantics::Scope &progUnitScope = |
| semantics::GetProgramUnitContaining(semaCtx.FindScope(beginSpec.source)); |
| lower::pft::FunctionLikeUnit *owningProc = eval.getOwningProcedure(); |
| const semantics::Symbol *owningSym = |
| (owningProc && !owningProc->isMainProgram()) |
| ? &owningProc->getSubprogramSymbol() |
| : (owningProc ? owningProc->getMainProgramSymbol() : nullptr); |
| if (progUnitScope.symbol() != owningSym) |
| return; |
| |
| List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx); |
| |
| mlir::omp::DeclareSimdOperands clauseOps; |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processAligned(clauseOps); |
| cp.processInbranch(clauseOps); |
| cp.processLinear(clauseOps, /*isDeclareSimd=*/true); |
| cp.processNotinbranch(clauseOps); |
| cp.processSimdlen(clauseOps); |
| cp.processUniform(clauseOps); |
| |
| mlir::omp::DeclareSimdOp::create(converter.getFirOpBuilder(), loc, clauseOps); |
| } |
| |
| static void |
| genOpenMPDeclareMapperImpl(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const parser::OmpDeclareMapperDirective &construct, |
| const semantics::Symbol *mapperSymOpt = nullptr) { |
| mlir::Location loc = converter.genLocation(construct.source); |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| const parser::OmpArgumentList &args = construct.v.Arguments(); |
| assert(args.v.size() == 1 && "Expecting single argument"); |
| lower::StatementContext stmtCtx; |
| const auto *spec = std::get_if<parser::OmpMapperSpecifier>(&args.v.front().u); |
| assert(spec && "Expecting mapper specifier"); |
| const auto &mapperName{std::get<std::string>(spec->t)}; |
| const auto &varType{std::get<parser::TypeSpec>(spec->t)}; |
| const auto &varName{std::get<parser::Name>(spec->t)}; |
| assert(varType.declTypeSpec->category() == |
| semantics::DeclTypeSpec::Category::TypeDerived && |
| "Expected derived type"); |
| |
| std::string mapperNameStr = mapperName; |
| if (mapperSymOpt && mapperNameStr != "default") { |
| mapperNameStr = converter.mangleName(mapperNameStr, mapperSymOpt->owner()); |
| } else if (auto *sym = |
| converter.getCurrentScope().FindSymbol(mapperNameStr)) { |
| mapperNameStr = converter.mangleName(mapperNameStr, sym->owner()); |
| } |
| |
| // If the mapper op already exists (e.g., created by regular lowering or by |
| // materialization of imported mappers), do not recreate it. |
| if (converter.getModuleOp().lookupSymbol(mapperNameStr)) |
| return; |
| |
| // Save current insertion point before moving to the module scope to create |
| // the DeclareMapperOp |
| mlir::OpBuilder::InsertionGuard guard(firOpBuilder); |
| |
| firOpBuilder.setInsertionPointToStart(converter.getModuleOp().getBody()); |
| auto mlirType = converter.genType(varType.declTypeSpec->derivedTypeSpec()); |
| auto declMapperOp = mlir::omp::DeclareMapperOp::create( |
| firOpBuilder, loc, mapperNameStr, mlirType); |
| auto ®ion = declMapperOp.getRegion(); |
| firOpBuilder.createBlock(®ion); |
| auto varVal = region.addArgument(firOpBuilder.getRefType(mlirType), loc); |
| converter.bindSymbol(*varName.symbol, varVal); |
| |
| // Populate the declareMapper region with the map information. |
| mlir::omp::DeclareMapperInfoOperands clauseOps; |
| List<Clause> clauses = makeClauses(construct.v.Clauses(), semaCtx); |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processMap(loc, stmtCtx, clauseOps); |
| mlir::omp::DeclareMapperInfoOp::create(firOpBuilder, loc, clauseOps); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpDeclareMapperDirective &construct) { |
| genOpenMPDeclareMapperImpl(converter, semaCtx, construct); |
| } |
| |
| static void |
| genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| const parser::OmpDeclareTargetDirective &declareTargetConstruct) { |
| mlir::omp::DeclareTargetOperands clauseOps; |
| llvm::SmallVector<DeclareTargetCaptureInfo> symbolAndClause; |
| mlir::ModuleOp mod = converter.getFirOpBuilder().getModule(); |
| getDeclareTargetInfo(converter, semaCtx, eval, declareTargetConstruct, |
| clauseOps, symbolAndClause); |
| |
| for (const DeclareTargetCaptureInfo &symClause : symbolAndClause) { |
| mlir::Operation *op = |
| mod.lookupSymbol(converter.mangleName(symClause.symbol)); |
| |
| // Some symbols are deferred until later in the module, these are handled |
| // upon finalization of the module for OpenMP inside of Bridge, so we simply |
| // skip for now. |
| if (!op) |
| continue; |
| |
| markDeclareTarget(op, converter, symClause.clause, clauseOps.deviceType, |
| symClause.automap); |
| } |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpGroupprivateDirective &directive) { |
| // The semantic analysis sets the flag and device_type on the |
| // symbols; omp.groupprivate is materialised by groupprivatizeVars. |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpRequiresDirective &requiresConstruct) { |
| // Requires directives are gathered and processed in semantics and |
| // then combined in the lowering bridge before triggering codegen |
| // just once. Hence, there is no need to lower each individual |
| // occurrence here. |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpThreadprivateDirective &threadprivate) { |
| // The directive is lowered when instantiating the variable to |
| // support the case of threadprivate variable declared in module. |
| } |
| |
| namespace { |
| struct MetadirectiveCandidate { |
| MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec, |
| llvm::omp::VariantMatchInfo vmi, bool isExplicit, |
| std::optional<semantics::omp::DynamicUserCondition> |
| dynamicCond = std::nullopt, |
| bool conditionShouldBeTrue = true) |
| : spec(spec), vmi(vmi), isExplicit(isExplicit), dynamicCond(dynamicCond), |
| conditionShouldBeTrue(conditionShouldBeTrue) {} |
| |
| const parser::OmpDirectiveSpecification *spec = nullptr; |
| llvm::omp::VariantMatchInfo vmi; |
| bool isExplicit = false; |
| std::optional<semantics::omp::DynamicUserCondition> dynamicCond; |
| bool conditionShouldBeTrue = true; |
| }; |
| } // namespace |
| |
| static void genMetadirective(lower::AbstractConverter &converter, |
| lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpClauseList &clauseList) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| |
| llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits; |
| collectEnclosingConstructTraits(builder.getInsertionBlock()->getParentOp(), |
| constructTraits); |
| semantics::omp::OmpVariantMatchContext ompCtx = |
| makeVariantMatchContext(builder.getModule(), constructTraits); |
| |
| llvm::SmallVector<MetadirectiveCandidate, 4> candidates; |
| // A null directive specification represents either the implicit `nothing` |
| // variant or the absence of an explicit otherwise/default clause. |
| const parser::OmpDirectiveSpecification *fallback = nullptr; |
| |
| // Extract the context-selector that controls whether a WHEN variant is |
| // applicable. Modifier validation requires exactly one selector per clause. |
| auto getContextSelector = [](const parser::OmpClause::When &whenClause) |
| -> const parser::modifier::OmpContextSelector & { |
| const auto &modifiers = std::get<0>(whenClause.v.t); |
| assert(modifiers && modifiers->size() == 1 && |
| "WHEN clause should contain one context-selector"); |
| return std::get<parser::modifier::OmpContextSelector>(modifiers->front().u); |
| }; |
| |
| // Extract the directive variant spec from a when clause. |
| // Returns {spec_ptr, isExplicit}. A null spec means "nothing". |
| auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause) |
| -> std::pair<const parser::OmpDirectiveSpecification *, bool> { |
| const auto &opt = std::get<1>(whenClause.v.t); |
| if (!opt) |
| return {nullptr, false}; |
| if (opt->value().DirId() == llvm::omp::Directive::OMPD_nothing) |
| return {nullptr, true}; |
| return {&opt->value(), true}; |
| }; |
| |
| // Return the directive spec pointer, or nullptr for "nothing". |
| auto getFallbackVariant = [](const parser::OmpDirectiveSpecification &spec) |
| -> const parser::OmpDirectiveSpecification * { |
| if (spec.DirId() == llvm::omp::Directive::OMPD_nothing) |
| return nullptr; |
| return &spec; |
| }; |
| |
| for (const auto &clause : clauseList.v) { |
| if (const auto *whenClause = |
| std::get_if<parser::OmpClause::When>(&clause.u)) { |
| const auto &ctxSel = getContextSelector(*whenClause); |
| auto [spec, isExplicit] = getDirectiveVariant(*whenClause); |
| |
| // METADIRECTIVE cannot yet honour some selector features that are |
| // otherwise accepted; reject them before building the match info. |
| switch (semantics::omp::FindUnsupportedSelectorFeature(ctxSel, semaCtx)) { |
| case semantics::omp::UnsupportedSelectorFeature::TargetDevice: |
| TODO(converter.genLocation(clause.source), |
| "target_device selector in METADIRECTIVE"); |
| break; |
| case semantics::omp::UnsupportedSelectorFeature:: |
| ClauseOrExtensionProperty: |
| TODO(converter.genLocation(clause.source), |
| "clause or extension trait matching in METADIRECTIVE"); |
| break; |
| case semantics::omp::UnsupportedSelectorFeature::None: |
| break; |
| } |
| |
| llvm::omp::VariantMatchInfo rawVMI; |
| std::optional<semantics::omp::DynamicUserCondition> dynamicCond = |
| semantics::omp::MakeVariantMatchInfo(rawVMI, ctxSel, semaCtx); |
| |
| if (dynamicCond) { |
| constexpr llvm::omp::TraitProperty dynamicConditionTrait = |
| llvm::omp::TraitProperty::user_condition_unknown; |
| constexpr llvm::omp::TraitProperty matchAnyTrait = |
| llvm::omp::TraitProperty::implementation_extension_match_any; |
| constexpr llvm::omp::TraitProperty matchNoneTrait = |
| llvm::omp::TraitProperty::implementation_extension_match_none; |
| |
| // Static applicability must only use traits known at lowering time. |
| // For example, in |
| // when(implementation={vendor(llvm)}, |
| // user={condition(score(5): flag)}: barrier) |
| // vendor(llvm) can be checked now, but flag cannot. Drop the |
| // runtime-only user_condition_unknown for applicability, while keeping |
| // score(5) so ranking can still honor the user-condition selector. |
| llvm::omp::VariantMatchInfo staticVMI = rawVMI; |
| std::optional<llvm::APInt> conditionScore; |
| auto scoreIt = staticVMI.ScoreMap.find(dynamicConditionTrait); |
| if (scoreIt != staticVMI.ScoreMap.end()) { |
| conditionScore = scoreIt->second; |
| staticVMI.ScoreMap.erase(scoreIt); |
| } |
| staticVMI.RequiredTraits.reset(unsigned(dynamicConditionTrait)); |
| llvm::APInt *conditionScorePtr = |
| conditionScore ? &*conditionScore : nullptr; |
| |
| bool hasMatchAny = rawVMI.RequiredTraits.test(unsigned(matchAnyTrait)); |
| bool hasMatchNone = |
| rawVMI.RequiredTraits.test(unsigned(matchNoneTrait)); |
| bool isStaticVMIApplicable = |
| llvm::omp::isVariantApplicableInContext(staticVMI, ompCtx); |
| // If staticVMI does not match, only match_any can still apply. Check |
| // conditionTrueVMI because the runtime condition may satisfy match_any. |
| if (!isStaticVMIApplicable) { |
| if (!hasMatchAny || staticVMI.RequiredTraits.test( |
| unsigned(llvm::omp::TraitProperty::invalid))) |
| continue; |
| |
| llvm::omp::VariantMatchInfo conditionTrueVMI = staticVMI; |
| conditionTrueVMI.addTrait( |
| llvm::omp::TraitProperty::user_condition_true, "<condition>", |
| conditionScorePtr); |
| if (!llvm::omp::isVariantApplicableInContext(conditionTrueVMI, |
| ompCtx)) |
| continue; |
| } |
| |
| auto addConditionTraitForRanking = |
| [&](llvm::omp::VariantMatchInfo &rankingVMI) { |
| rankingVMI.addTrait( |
| hasMatchNone ? dynamicConditionTrait |
| : llvm::omp::TraitProperty::user_condition_true, |
| "<condition>", conditionScorePtr); |
| }; |
| |
| if (hasMatchAny && isStaticVMIApplicable) { |
| // A statically matched match_any selector needs two candidates: a |
| // guarded candidate with the user condition and score, and an |
| // unguarded candidate with only the statically matched traits. If the |
| // when clause omits its directive, only add the unguarded candidate. |
| if (isExplicit) { |
| llvm::omp::VariantMatchInfo conditionTrueVMI = staticVMI; |
| addConditionTraitForRanking(conditionTrueVMI); |
| candidates.emplace_back(spec, conditionTrueVMI, isExplicit, |
| dynamicCond); |
| } |
| candidates.emplace_back(spec, staticVMI, isExplicit); |
| continue; |
| } |
| |
| llvm::omp::VariantMatchInfo rankingVMI = staticVMI; |
| // An omitted directive is implicit nothing, so do not let the runtime |
| // condition raise its rank. Explicit `nothing` is still a variant. |
| if (!isExplicit && hasMatchAny && !isStaticVMIApplicable) |
| rankingVMI = llvm::omp::VariantMatchInfo(); |
| else if (isExplicit) |
| addConditionTraitForRanking(rankingVMI); |
| candidates.emplace_back(spec, rankingVMI, isExplicit, dynamicCond, |
| /*conditionShouldBeTrue=*/!hasMatchNone); |
| continue; |
| } |
| |
| if (!llvm::omp::isVariantApplicableInContext(rawVMI, ompCtx)) |
| continue; |
| |
| candidates.emplace_back(spec, rawVMI, isExplicit); |
| } else if (const auto *otherwiseClause = |
| std::get_if<parser::OmpClause::Otherwise>(&clause.u)) { |
| if (otherwiseClause->v && otherwiseClause->v->v) |
| fallback = getFallbackVariant(otherwiseClause->v->v->value()); |
| } else if (const auto *defaultVariantClause = |
| std::get_if<parser::OmpClause::DefaultVariant>(&clause.u)) { |
| const auto &dirSpec = defaultVariantClause->v.v; |
| fallback = getFallbackVariant(dirSpec.value()); |
| } |
| } |
| |
| // Lower a single resolved candidate. |
| auto genVariant = [&](const parser::OmpDirectiveSpecification *spec) { |
| if (!spec) { |
| genNestedEvaluations(converter, eval); |
| return; |
| } |
| List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx); |
| mlir::Location variantLoc = converter.genLocation(spec->source); |
| ConstructQueue queue{ |
| buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx, |
| eval, spec->source, spec->DirId(), variantClauses)}; |
| |
| if (llvm::any_of(queue, [](const auto &item) { |
| return llvm::omp::getDirectiveAssociation(item.id) == |
| llvm::omp::Association::LoopNest; |
| })) { |
| TODO(variantLoc, "loop-associated METADIRECTIVE variant"); |
| } |
| |
| if (llvm::any_of(queue, [](const auto &item) { |
| return llvm::omp::getDirectiveAssociation(item.id) == |
| llvm::omp::Association::Declaration || |
| llvm::omp::getDirectiveCategory(item.id) == |
| llvm::omp::Category::Declarative; |
| })) { |
| TODO(variantLoc, "declarative METADIRECTIVE variant"); |
| } |
| |
| genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue, |
| queue.begin()); |
| }; |
| |
| auto selectBestCandidate = |
| [](llvm::ArrayRef<unsigned> candidateIndices, |
| llvm::ArrayRef<MetadirectiveCandidate> candidates, |
| const semantics::omp::OmpVariantMatchContext &ompCtx) |
| -> std::optional<unsigned> { |
| if (candidateIndices.empty()) |
| return std::nullopt; |
| if (candidateIndices.size() == 1) |
| return candidateIndices.front(); |
| |
| // The OpenMP context scorer preserves input order for tied candidates. |
| // Put explicit variants first so they take precedence over implicit |
| // `nothing`, as required by metadirective selection. |
| llvm::SmallVector<unsigned, 4> candidateOrder; |
| candidateOrder.reserve(candidateIndices.size()); |
| for (unsigned idx : candidateIndices) |
| if (candidates[idx].isExplicit) |
| candidateOrder.push_back(idx); |
| for (unsigned idx : candidateIndices) |
| if (!candidates[idx].isExplicit) |
| candidateOrder.push_back(idx); |
| |
| llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs; |
| orderedVMIs.reserve(candidateOrder.size()); |
| for (unsigned idx : candidateOrder) |
| orderedVMIs.push_back(candidates[idx].vmi); |
| |
| int bestIdx = llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx); |
| if (bestIdx >= 0) { |
| assert(static_cast<size_t>(bestIdx) < candidateOrder.size() && |
| "best variant index out of range"); |
| return candidateOrder[bestIdx]; |
| } |
| return std::nullopt; |
| }; |
| |
| llvm::SmallVector<unsigned, 4> remainingCandidates; |
| remainingCandidates.reserve(candidates.size()); |
| for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx) |
| remainingCandidates.push_back(idx); |
| |
| lower::StatementContext stmtCtx; |
| |
| // Candidates that reach this loop passed static filtering. Runtime user |
| // conditions are lowered as a ranked if/else cascade: |
| // |
| // when(user={condition(a)}: barrier) |
| // when(user={condition(b)}: taskwait) |
| // otherwise(nothing) |
| // |
| // becomes: |
| // |
| // if (a) barrier |
| // else if (b) taskwait |
| // else nothing |
| // |
| // If the else path selects the same unguarded directive, lower it directly. |
| // Stop when selection reaches an unguarded candidate or the fallback. |
| while (!remainingCandidates.empty()) { |
| std::optional<unsigned> selected = |
| selectBestCandidate(remainingCandidates, candidates, ompCtx); |
| if (!selected) { |
| genVariant(fallback); |
| return; |
| } |
| |
| const MetadirectiveCandidate &candidate = candidates[*selected]; |
| if (!candidate.dynamicCond) { |
| genVariant(candidate.spec); |
| return; |
| } |
| |
| llvm::SmallVector<unsigned, 4> elsePathCandidates(remainingCandidates); |
| auto *remainingIt = llvm::find(elsePathCandidates, *selected); |
| assert(remainingIt != elsePathCandidates.end() && |
| "selected candidate missing from remaining candidates"); |
| elsePathCandidates.erase(remainingIt); |
| |
| // match_any may create a guarded condition-true candidate and an unguarded |
| // static candidate for the same directive. If the else path picks the |
| // unguarded one then fold it: |
| // |
| // if (flag) barrier into just barrier |
| // else barrier |
| if (std::optional<unsigned> selectedInElse = |
| selectBestCandidate(elsePathCandidates, candidates, ompCtx)) { |
| const MetadirectiveCandidate &candidateInElse = |
| candidates[*selectedInElse]; |
| if (!candidateInElse.dynamicCond && |
| candidateInElse.spec == candidate.spec) { |
| genVariant(candidate.spec); |
| return; |
| } |
| } |
| |
| mlir::Location condLoc = |
| converter.genLocation(candidate.dynamicCond->source); |
| const auto *condExpr = |
| semantics::GetExpr(semaCtx, *candidate.dynamicCond->expr); |
| assert(condExpr && "missing expression for user condition"); |
| mlir::Value condVal = |
| fir::getBase(converter.genExprValue(*condExpr, stmtCtx, &condLoc)); |
| |
| if (condVal.getType() != builder.getI1Type()) |
| condVal = builder.createConvert(condLoc, builder.getI1Type(), condVal); |
| if (!candidate.conditionShouldBeTrue) { |
| mlir::Value trueVal = |
| builder.createIntegerConstant(condLoc, builder.getI1Type(), 1); |
| condVal = mlir::arith::XOrIOp::create(builder, condLoc, condVal, trueVal); |
| } |
| |
| stmtCtx.finalizeAndReset(); |
| auto ifOp = fir::IfOp::create(builder, condLoc, condVal, |
| /*withElseRegion=*/true); |
| builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); |
| genVariant(candidate.spec); |
| |
| builder.setInsertionPointToStart(&ifOp.getElseRegion().front()); |
| remainingCandidates = std::move(elsePathCandidates); |
| } |
| genVariant(fallback); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpMetadirectiveDirective &meta) { |
| genMetadirective(converter, symTable, semaCtx, eval, meta.v.Clauses()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPDeclarativeConstruct &ompDeclConstruct) { |
| Fortran::common::visit( |
| [&](auto &&s) { return genOMP(converter, symTable, semaCtx, eval, s); }, |
| ompDeclConstruct.u); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // OpenMPStandaloneConstruct visitors |
| //===----------------------------------------------------------------------===// |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPSimpleStandaloneConstruct &construct) { |
| const auto &directive = std::get<parser::OmpDirectiveName>(construct.v.t); |
| List<Clause> clauses = makeClauses(construct.v.Clauses(), semaCtx); |
| mlir::Location currentLocation = converter.genLocation(directive.source); |
| |
| ConstructQueue queue{ |
| buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx, |
| eval, directive.source, directive.v, clauses)}; |
| if (directive.v == llvm::omp::Directive::OMPD_ordered_standalone) { |
| // Standalone "ordered" directive. |
| genOrderedOp(converter, symTable, semaCtx, eval, currentLocation, queue, |
| queue.begin()); |
| } else { |
| // Dispatch handles the "block-associated" variant of "ordered". |
| genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue, |
| queue.begin()); |
| } |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPFlushConstruct &construct) { |
| const auto &argumentList = construct.v.Arguments(); |
| const auto &clauseList = construct.v.Clauses(); |
| ObjectList objects = makeObjects(argumentList, semaCtx); |
| List<Clause> clauses = |
| makeList(clauseList.v, [&](auto &&s) { return makeClause(s, semaCtx); }); |
| mlir::Location currentLocation = converter.genLocation(construct.source); |
| |
| ConstructQueue queue{buildConstructQueue( |
| converter.getFirOpBuilder().getModule(), semaCtx, eval, construct.source, |
| llvm::omp::Directive::OMPD_flush, clauses)}; |
| genFlushOp(converter, symTable, semaCtx, eval, currentLocation, objects, |
| queue, queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPCancelConstruct &cancelConstruct) { |
| List<Clause> clauses = makeList(cancelConstruct.v.Clauses().v, [&](auto &&s) { |
| return makeClause(s, semaCtx); |
| }); |
| mlir::Location loc = converter.genLocation(cancelConstruct.source); |
| |
| ConstructQueue queue{buildConstructQueue( |
| converter.getFirOpBuilder().getModule(), semaCtx, eval, |
| cancelConstruct.source, llvm::omp::Directive::OMPD_cancel, clauses)}; |
| genCancelOp(converter, semaCtx, eval, loc, queue, queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPCancellationPointConstruct |
| &cancellationPointConstruct) { |
| List<Clause> clauses = |
| makeList(cancellationPointConstruct.v.Clauses().v, |
| [&](auto &&s) { return makeClause(s, semaCtx); }); |
| mlir::Location loc = converter.genLocation(cancellationPointConstruct.source); |
| |
| ConstructQueue queue{ |
| buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx, |
| eval, cancellationPointConstruct.source, |
| llvm::omp::Directive::OMPD_cancel, clauses)}; |
| genCancellationPointOp(converter, semaCtx, eval, loc, queue, queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPDepobjConstruct &construct) { |
| // These values will be ignored until the construct itself is implemented, |
| // but run them anyway for the sake of testing (via a Todo test). |
| ObjectList objects = makeObjects(construct.v.Arguments(), semaCtx); |
| assert(objects.size() == 1); |
| List<Clause> clauses = makeClauses(construct.v.Clauses(), semaCtx); |
| assert(clauses.size() == 1); |
| (void)objects; |
| (void)clauses; |
| |
| if (!semaCtx.langOptions().OpenMPSimd) |
| TODO(converter.getCurrentLocation(), "OpenMPDepobjConstruct"); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPInteropConstruct &interopConstruct) { |
| if (semaCtx.langOptions().OpenMPSimd) |
| return; |
| |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::Location loc = converter.genLocation(interopConstruct.source); |
| mlir::MLIRContext *context = firOpBuilder.getContext(); |
| |
| List<Clause> clauses = makeClauses(interopConstruct.v.Clauses(), semaCtx); |
| |
| // Process shared clauses (depend, nowait, device) that apply to all |
| // action clauses. |
| lower::StatementContext stmtCtx; |
| mlir::omp::DependClauseOps dependOps; |
| mlir::omp::NowaitClauseOps nowaitOps; |
| mlir::Value deviceValue; |
| |
| ClauseProcessor cp(converter, semaCtx, clauses); |
| cp.processDepend(symTable, stmtCtx, dependOps); |
| cp.processNowait(nowaitOps); |
| |
| // Process device clause manually. |
| for (auto &clause : clauses) { |
| if (auto *deviceClause = std::get_if<clause::Device>(&clause.u)) { |
| const auto &deviceExpr = std::get<SomeExpr>(deviceClause->t); |
| deviceValue = fir::getBase(converter.genExprValue(deviceExpr, stmtCtx)); |
| } |
| } |
| |
| // Helper to get the address of an interop variable from an Object. A |
| // designator such as arr(1) or rec%obj must lower through genExprAddr so we |
| // obtain the address of the actual scalar element/component with the correct |
| // type, rather than the base symbol address (which would be the whole array, |
| // or null for a component defined inside a derived type). |
| auto getInteropVarAddr = [&](const Object &object) -> mlir::Value { |
| if (const auto &designator = object.ref()) { |
| fir::ExtendedValue exv = |
| converter.genExprAddr(*designator, stmtCtx, &loc); |
| return fir::getBase(exv); |
| } |
| const semantics::Symbol *sym = object.sym(); |
| assert(sym && "interop variable must have a symbol"); |
| mlir::Value addr = converter.getSymbolAddress(*sym); |
| assert(addr && "interop variable must have a valid address"); |
| return addr; |
| }; |
| |
| // Process action clauses: init, destroy, use. |
| for (auto &clause : clauses) { |
| if (auto *initClause = std::get_if<clause::Init>(&clause.u)) { |
| auto &interopVar = std::get<clause::Init::InteropVar>(initClause->t); |
| auto &interopTypes = std::get<clause::Init::InteropTypes>(initClause->t); |
| auto &interopPref = |
| std::get<std::optional<clause::Init::InteropPreference>>( |
| initClause->t); |
| |
| mlir::Value interopVarAddr = getInteropVarAddr(interopVar); |
| |
| // Convert interop types to MLIR attributes. A single init clause may |
| // list both `target` and `targetsync`; keep them on one op and let the |
| // LLVM-IR translation collapse them into a single runtime init call, |
| // matching Clang. |
| llvm::SmallVector<mlir::Attribute> interopTypeAttrs; |
| for (auto itype : interopTypes) { |
| switch (itype) { |
| case clause::Init::InteropType::Target: |
| interopTypeAttrs.push_back(mlir::omp::InteropTypeAttr::get( |
| context, mlir::omp::InteropType::target)); |
| break; |
| case clause::Init::InteropType::Targetsync: |
| interopTypeAttrs.push_back(mlir::omp::InteropTypeAttr::get( |
| context, mlir::omp::InteropType::targetsync)); |
| break; |
| } |
| } |
| auto interopTypesAttr = mlir::ArrayAttr::get(context, interopTypeAttrs); |
| |
| // Convert prefer_type to I64ArrayAttr if present. |
| mlir::ArrayAttr preferTypeAttr; |
| if (interopPref) { |
| llvm::SmallVector<int64_t> prefValues; |
| for (auto &pref : *interopPref) { |
| // Try to evaluate as a constant integer (e.g., omp_ifr_cuda). |
| if (auto constVal = evaluate::ToInt64(pref)) { |
| prefValues.push_back(*constVal); |
| } else if (auto str = |
| evaluate::GetScalarConstantValue<evaluate::Ascii>( |
| pref)) { |
| // Map standard OpenMP foreign-runtime identifier strings to |
| // their well-known integer values (OpenMP 5.1, Table 22.2). |
| auto frId = llvm::StringSwitch<std::optional<int64_t>>( |
| llvm::StringRef(*str).lower()) |
| .Case("cuda", 1) |
| .Case("cuda_driver", 2) |
| .Case("opencl", 3) |
| .Case("sycl", 4) |
| .Case("hip", 5) |
| .Case("level_zero", 6) |
| .Default(std::nullopt); |
| if (frId) |
| prefValues.push_back(*frId); |
| else |
| TODO(loc, "unknown foreign-runtime identifier in " |
| "prefer_type"); |
| } else { |
| TODO(loc, "non-constant prefer_type value in interop init"); |
| } |
| } |
| if (!prefValues.empty()) |
| preferTypeAttr = firOpBuilder.getI64ArrayAttr(prefValues); |
| } |
| |
| // Emit a single InteropInitOp carrying all interop-types from this init |
| // clause. The translation to LLVM IR collapses them into one runtime |
| // call, matching Clang (one init clause -> one __tgt_interop_init). |
| mlir::omp::InteropInitOp::create( |
| firOpBuilder, loc, interopVarAddr, interopTypesAttr, preferTypeAttr, |
| deviceValue, |
| dependOps.dependKinds.empty() |
| ? nullptr |
| : firOpBuilder.getArrayAttr(dependOps.dependKinds), |
| dependOps.dependVars, |
| /*depend_iterated_kinds=*/nullptr, |
| /*depend_iterated=*/mlir::ValueRange{}, nowaitOps.nowait); |
| |
| } else if (auto *destroyClause = std::get_if<clause::Destroy>(&clause.u)) { |
| assert(destroyClause->v && |
| "destroy clause must have an interop variable"); |
| if (destroyClause->v) { |
| mlir::Value interopVarAddr = getInteropVarAddr(*destroyClause->v); |
| mlir::omp::InteropDestroyOp::create( |
| firOpBuilder, loc, interopVarAddr, deviceValue, |
| dependOps.dependKinds.empty() |
| ? nullptr |
| : firOpBuilder.getArrayAttr(dependOps.dependKinds), |
| dependOps.dependVars, |
| /*depend_iterated_kinds=*/nullptr, |
| /*depend_iterated=*/mlir::ValueRange{}, nowaitOps.nowait); |
| } |
| |
| } else if (auto *useClause = std::get_if<clause::Use>(&clause.u)) { |
| mlir::Value interopVarAddr = getInteropVarAddr(useClause->v); |
| mlir::omp::InteropUseOp::create( |
| firOpBuilder, loc, interopVarAddr, deviceValue, |
| dependOps.dependKinds.empty() |
| ? nullptr |
| : firOpBuilder.getArrayAttr(dependOps.dependKinds), |
| dependOps.dependVars, |
| /*depend_iterated_kinds=*/nullptr, |
| /*depend_iterated=*/mlir::ValueRange{}, nowaitOps.nowait); |
| } |
| } |
| } |
| |
| static void |
| genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| const parser::OpenMPStandaloneConstruct &standaloneConstruct) { |
| Fortran::common::visit( |
| [&](auto &&s) { return genOMP(converter, symTable, semaCtx, eval, s); }, |
| standaloneConstruct.u); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPAllocatorsConstruct &allocsConstruct) { |
| if (!semaCtx.langOptions().OpenMPSimd) |
| TODO(converter.getCurrentLocation(), "OpenMPAllocatorsConstruct"); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // OpenMPConstruct visitors |
| //===----------------------------------------------------------------------===// |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPAtomicConstruct &construct) { |
| lowerAtomic(converter, symTable, semaCtx, eval, construct); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpDelimitedMetadirectiveDirective &meta) { |
| genMetadirective(converter, symTable, semaCtx, eval, |
| meta.BeginDir().Clauses()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpBlockConstruct &blockConstruct) { |
| const parser::OmpDirectiveSpecification &beginSpec = |
| blockConstruct.BeginDir(); |
| List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx); |
| if (auto &endSpec = blockConstruct.EndDir()) |
| clauses.append(makeClauses(endSpec->Clauses(), semaCtx)); |
| |
| llvm::omp::Directive directive = beginSpec.DirId(); |
| assert(llvm::omp::blockConstructSet.test(directive) && |
| "Expected block construct"); |
| mlir::Location currentLocation = converter.genLocation(beginSpec.source); |
| |
| for (const Clause &clause : clauses) { |
| mlir::Location clauseLocation = converter.genLocation(clause.source); |
| if (!std::holds_alternative<clause::Affinity>(clause.u) && |
| !std::holds_alternative<clause::Allocate>(clause.u) && |
| !std::holds_alternative<clause::Copyin>(clause.u) && |
| !std::holds_alternative<clause::Copyprivate>(clause.u) && |
| !std::holds_alternative<clause::Default>(clause.u) && |
| !std::holds_alternative<clause::Defaultmap>(clause.u) && |
| !std::holds_alternative<clause::Depend>(clause.u) && |
| !std::holds_alternative<clause::Filter>(clause.u) && |
| !std::holds_alternative<clause::Final>(clause.u) && |
| !std::holds_alternative<clause::Firstprivate>(clause.u) && |
| !std::holds_alternative<clause::HasDeviceAddr>(clause.u) && |
| !std::holds_alternative<clause::If>(clause.u) && |
| !std::holds_alternative<clause::IsDevicePtr>(clause.u) && |
| !std::holds_alternative<clause::Map>(clause.u) && |
| !std::holds_alternative<clause::Nowait>(clause.u) && |
| !std::holds_alternative<clause::NumTeams>(clause.u) && |
| !std::holds_alternative<clause::NumThreads>(clause.u) && |
| !std::holds_alternative<clause::OmpxBare>(clause.u) && |
| !std::holds_alternative<clause::Priority>(clause.u) && |
| !std::holds_alternative<clause::Private>(clause.u) && |
| !std::holds_alternative<clause::ProcBind>(clause.u) && |
| !std::holds_alternative<clause::Reduction>(clause.u) && |
| !std::holds_alternative<clause::Shared>(clause.u) && |
| !std::holds_alternative<clause::Simd>(clause.u) && |
| !std::holds_alternative<clause::ThreadLimit>(clause.u) && |
| !std::holds_alternative<clause::Threads>(clause.u) && |
| !std::holds_alternative<clause::UseDeviceAddr>(clause.u) && |
| !std::holds_alternative<clause::UseDevicePtr>(clause.u) && |
| !std::holds_alternative<clause::InReduction>(clause.u) && |
| !std::holds_alternative<clause::Mergeable>(clause.u) && |
| !std::holds_alternative<clause::Untied>(clause.u) && |
| !std::holds_alternative<clause::TaskReduction>(clause.u) && |
| !std::holds_alternative<clause::Detach>(clause.u) && |
| !std::holds_alternative<clause::Device>(clause.u) && |
| !std::holds_alternative<clause::DynGroupprivate>(clause.u)) { |
| const common::LangOptions &options = semaCtx.langOptions(); |
| if (!options.OpenMPSimd) { |
| std::string name = |
| parser::omp::GetUpperName(clause.id, options.OpenMPVersion); |
| TODO(clauseLocation, name + " clause is not implemented yet"); |
| } |
| } |
| } |
| |
| ConstructQueue queue{ |
| buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx, |
| eval, beginSpec.source, directive, clauses)}; |
| genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue, |
| queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpAssumeDirective &assumeConstruct) { |
| if (!semaCtx.langOptions().OpenMPSimd) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| lower::StatementContext stmtCtx; |
| |
| const parser::OmpDirectiveSpecification &beginSpec = |
| assumeConstruct.BeginDir(); |
| for (const parser::OmpClause &clause : beginSpec.Clauses().v) { |
| mlir::Location clauseLoc = converter.genLocation(clause.source); |
| const auto *holds = std::get_if<parser::OmpClause::Holds>(&clause.u); |
| if (!holds) { |
| TODO(clauseLoc, "assumption clause is not implemented yet"); |
| } |
| const parser::Expr &parserExpr = holds->v.v.value(); |
| const semantics::SomeExpr *expr = semantics::GetExpr(semaCtx, parserExpr); |
| assert(expr && "Expecting analyzed expression for holds clause"); |
| |
| mlir::Value cond = |
| fir::getBase(converter.genExprValue(*expr, stmtCtx, &clauseLoc)); |
| cond = |
| firOpBuilder.createConvert(clauseLoc, firOpBuilder.getI1Type(), cond); |
| mlir::LLVM::AssumeOp::create(firOpBuilder, clauseLoc, cond); |
| } |
| stmtCtx.finalizeAndPop(); |
| } |
| genNestedEvaluations(converter, eval); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPCriticalConstruct &criticalConstruct) { |
| const parser::OmpDirectiveSpecification &beginSpec = |
| criticalConstruct.BeginDir(); |
| List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx); |
| |
| ConstructQueue queue{buildConstructQueue( |
| converter.getFirOpBuilder().getModule(), semaCtx, eval, beginSpec.source, |
| llvm::omp::Directive::OMPD_critical, clauses)}; |
| |
| std::optional<parser::Name> critName; |
| const parser::OmpArgumentList &args = beginSpec.Arguments(); |
| if (!args.v.empty()) { |
| // All of these things should be guaranteed to exist after semantic checks. |
| auto *object = parser::Unwrap<parser::OmpObject>(args.v.front()); |
| assert(object && "Expecting object as argument"); |
| auto *designator = parser::omp::GetDesignatorFromObj(*object); |
| assert(designator && "Expecting desginator in argument"); |
| auto *name = parser::GetDesignatorNameIfDataRef(*designator); |
| assert(name && "Expecting dataref in designator"); |
| critName = *name; |
| } |
| mlir::Location currentLocation = converter.getCurrentLocation(); |
| genCriticalOp(converter, symTable, semaCtx, eval, currentLocation, queue, |
| queue.begin(), critName); |
| } |
| |
| // Copy the character value `str` into fresh stack memory with an appended NUL |
| // and return a pointer to its first character. Fortran characters are not |
| // NUL-terminated, so the terminator is needed for C runtime entry points (such |
| // as `__kmpc_error`) that expect a NUL-terminated string. |
| static mlir::Value genNullTerminatedString(fir::FirOpBuilder &builder, |
| mlir::Location loc, |
| const fir::ExtendedValue &str) { |
| fir::factory::CharacterExprHelper helper(builder, loc); |
| const mlir::Value addr = fir::getBase(str); |
| const mlir::Value len = fir::getLen(str); |
| const auto charTy = |
| mlir::cast<fir::CharacterType>(fir::unwrapRefType(addr.getType())); |
| mlir::MLIRContext *ctx = builder.getContext(); |
| const mlir::Type idxTy = builder.getIndexType(); |
| const mlir::Value idxLen = builder.createConvert(loc, idxTy, len); |
| const mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1); |
| const mlir::Value lenPlusOne = |
| mlir::arith::AddIOp::create(builder, loc, idxLen, one); |
| |
| // Allocate `len + 1` characters and copy the message into them, leaving room |
| // for the terminating NUL. |
| const fir::CharBoxValue temp = helper.createCharacterTemp(charTy, lenPlusOne); |
| helper.createCopy(temp, fir::CharBoxValue{addr, len}, len); |
| |
| // Address the buffer as an array of single characters so the terminating NUL |
| // can be stored at index `len`. |
| const auto singleTy = fir::CharacterType::get( |
| ctx, charTy.getFKind(), fir::CharacterType::singleton()); |
| const mlir::Type singleRefTy = builder.getRefType(singleTy); |
| const mlir::Type seqRefTy = builder.getRefType(fir::SequenceType::get( |
| {fir::SequenceType::getUnknownExtent()}, singleTy)); |
| const mlir::Value seq = |
| builder.createConvert(loc, seqRefTy, temp.getBuffer()); |
| const mlir::Value nulAddr = |
| fir::CoordinateOp::create(builder, loc, singleRefTy, seq, idxLen); |
| const mlir::Value zero = |
| builder.createIntegerConstant(loc, builder.getI8Type(), 0); |
| const mlir::Value nul = |
| helper.createSingletonFromCode(zero, charTy.getFKind()); |
| fir::StoreOp::create(builder, loc, nul, nulAddr); |
| |
| // Pass a pointer to the first character of the buffer. |
| return builder.createConvert(loc, singleRefTy, temp.getBuffer()); |
| } |
| |
| // Lower an `!$omp error` directive. The `at(compilation)` form is handled |
| // entirely in semantics, so only the `at(execution)` form reaches lowering, |
| // where it becomes an `omp.error` operation. |
| static void genErrorDirective(lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, |
| const parser::OmpErrorDirective &errDir) { |
| const semantics::omp::OmpErrorArgs args{ |
| semantics::omp::GetErrorDirectiveArgs(errDir)}; |
| |
| if (args.at != parser::OmpAtClause::ActionTime::Execution || |
| semaCtx.langOptions().OpenMPSimd) |
| return; |
| |
| std::optional<std::string> message; |
| MaybeExpr messageExpr; |
| if (args.message) { |
| if (auto expr = semantics::omp::GetEvaluateExpr(*args.message)) { |
| if (auto val = evaluate::GetScalarConstantValue<evaluate::Ascii>(*expr)) |
| message = *val; |
| else |
| messageExpr = expr; |
| } |
| } |
| |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| const mlir::Location loc = converter.getCurrentLocation(); |
| const mlir::omp::ClauseSeverity sev = |
| args.severity == parser::OmpSeverityClause::SevLevel::Warning |
| ? mlir::omp::ClauseSeverity::warning |
| : mlir::omp::ClauseSeverity::fatal; |
| |
| // A compile-time-constant message is stored directly on the operation as an |
| // attribute. A non-constant message is lowered to a null-terminated string in |
| // memory and passed as the `message_expr` operand. |
| mlir::StringAttr msgAttr; |
| mlir::Value msgExprVal; |
| if (message) { |
| msgAttr = builder.getStringAttr(*message); |
| } else if (messageExpr) { |
| lower::StatementContext stmtCtx; |
| fir::ExtendedValue str = converter.genExprAddr(loc, *messageExpr, stmtCtx); |
| msgExprVal = genNullTerminatedString(builder, loc, str); |
| } |
| |
| mlir::omp::ErrorOp::create(builder, loc, sev, msgAttr, msgExprVal); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpUtilityDirective &dir) { |
| common::visit(common::visitors{ |
| [&](const parser::OmpNothingDirective &) { |
| // nothing-directive is a no-op (OpenMP 5.2 [8.4]) |
| }, |
| [&](const parser::OmpErrorDirective &errDir) { |
| genErrorDirective(converter, semaCtx, errDir); |
| }, |
| }, |
| dir.u); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPDispatchConstruct &) { |
| if (!semaCtx.langOptions().OpenMPSimd) |
| TODO(converter.getCurrentLocation(), "OpenMPDispatchConstruct"); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPLoopConstruct &loopConstruct) { |
| const parser::OmpDirectiveSpecification &beginSpec = loopConstruct.BeginDir(); |
| List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx); |
| if (auto &endSpec = loopConstruct.EndDir()) |
| clauses.append(makeClauses(endSpec->Clauses(), semaCtx)); |
| |
| mlir::Location currentLocation = converter.genLocation(beginSpec.source); |
| |
| for (auto &construct : std::get<parser::Block>(loopConstruct.t)) { |
| if (const parser::OpenMPLoopConstruct *ompNestedLoopCons = |
| parser::omp::GetOmpLoop(construct)) { |
| llvm::omp::Directive nestedDirective = |
| parser::omp::GetOmpDirectiveName(*ompNestedLoopCons).v; |
| switch (nestedDirective) { |
| case llvm::omp::Directive::OMPD_tile: |
| // Skip OMPD_tile since the tile sizes will be retrieved when |
| // generating the omp.loop_nest op. |
| break; |
| default: { |
| unsigned version = semaCtx.langOptions().OpenMPVersion; |
| TODO(currentLocation, |
| "Applying a loop-associated on the loop generated by the " + |
| llvm::omp::getOpenMPDirectiveName(nestedDirective, version) + |
| " construct"); |
| } |
| } |
| } |
| } |
| |
| const parser::OmpDirectiveName &beginName = beginSpec.DirName(); |
| ConstructQueue queue{ |
| buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx, |
| eval, beginName.source, beginName.v, clauses)}; |
| genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue, |
| queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OmpSectionDirective §ionConstruct) { |
| // Do nothing here. SECTION is lowered inside of the lowering for Sections |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPSectionsConstruct &construct) { |
| const parser::OmpDirectiveSpecification &beginSpec{construct.BeginDir()}; |
| List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx); |
| const auto &endSpec{construct.EndDir()}; |
| assert(endSpec && |
| "Missing end section directive should have been handled in semantics"); |
| clauses.append(makeClauses(endSpec->Clauses(), semaCtx)); |
| mlir::Location currentLocation = converter.getCurrentLocation(); |
| |
| const parser::OmpDirectiveName &beginName{beginSpec.DirName()}; |
| ConstructQueue queue{ |
| buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx, |
| eval, beginName.source, beginName.v, clauses)}; |
| |
| mlir::SaveStateStack<SectionsConstructStackFrame> saveStateStack{ |
| converter.getStateStack(), construct}; |
| genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue, |
| queue.begin()); |
| } |
| |
| static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPConstruct &ompConstruct) { |
| Fortran::common::visit( |
| [&](auto &&s) { return genOMP(converter, symTable, semaCtx, eval, s); }, |
| ompConstruct.u); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Public functions |
| //===----------------------------------------------------------------------===// |
| |
| mlir::Operation *Fortran::lower::genOpenMPTerminator(fir::FirOpBuilder &builder, |
| mlir::Operation *op, |
| mlir::Location loc) { |
| if (mlir::isa<mlir::omp::AtomicUpdateOp, mlir::omp::DeclareReductionOp, |
| mlir::omp::LoopNestOp>(op)) |
| return mlir::omp::YieldOp::create(builder, loc); |
| return mlir::omp::TerminatorOp::create(builder, loc); |
| } |
| |
| void Fortran::lower::genOpenMPConstruct(lower::AbstractConverter &converter, |
| lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPConstruct &omp) { |
| lower::SymMapScope scope(symTable); |
| genOMP(converter, symTable, semaCtx, eval, omp); |
| } |
| |
| void Fortran::lower::genOpenMPDeclarativeConstruct( |
| lower::AbstractConverter &converter, lower::SymMap &symTable, |
| semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval, |
| const parser::OpenMPDeclarativeConstruct &omp) { |
| genOMP(converter, symTable, semaCtx, eval, omp); |
| genNestedEvaluations(converter, eval); |
| } |
| |
| void Fortran::lower::genOpenMPSymbolProperties( |
| lower::AbstractConverter &converter, const lower::pft::Variable &var) { |
| assert(var.hasSymbol() && "Expecting Symbol"); |
| const semantics::Symbol &sym = var.getSymbol(); |
| |
| if (sym.test(semantics::Symbol::Flag::OmpGroupPrivate)) |
| lower::genGroupprivateOp(converter, var); |
| |
| if (sym.test(semantics::Symbol::Flag::OmpThreadprivate)) |
| lower::genThreadprivateOp(converter, var); |
| |
| if (sym.test(semantics::Symbol::Flag::OmpDeclareTarget)) |
| lower::genDeclareTargetIntGlobal(converter, var); |
| } |
| |
| void Fortran::lower::genGroupprivateOp(lower::AbstractConverter &converter, |
| const lower::pft::Variable &var) { |
| const semantics::Symbol &sym = var.getSymbol(); |
| |
| // For common block members, the groupprivate op is generated for the entire |
| // common block in groupprivatizeVars, not for individual members here. |
| // The common block already has a global, so nothing to do here. |
| if (semantics::FindCommonBlockContaining(sym.GetUltimate())) |
| return; |
| |
| // Handle non-global variables: local variables with the SAVE attribute can |
| // appear in a groupprivate directive. Promote them to fir.global so that |
| // omp.groupprivate can reference them by symbol name. |
| if (!var.isGlobal()) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::Location currentLocation = converter.getCurrentLocation(); |
| auto module = converter.getModuleOp(); |
| std::string globalName = converter.mangleName(sym); |
| if (!module.lookupSymbol<fir::GlobalOp>(globalName)) |
| globalInitialization(converter, firOpBuilder, sym, var, currentLocation); |
| } |
| |
| // The actual omp.groupprivate operations are created by groupprivatizeVars. |
| } |
| |
| void Fortran::lower::genThreadprivateOp(lower::AbstractConverter &converter, |
| const lower::pft::Variable &var) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::Location currentLocation = converter.getCurrentLocation(); |
| |
| const semantics::Symbol &sym = var.getSymbol(); |
| mlir::Value symThreadprivateValue; |
| if (const semantics::Symbol *common = |
| semantics::FindCommonBlockContaining(sym.GetUltimate())) { |
| mlir::Value commonValue = converter.getSymbolAddress(*common); |
| if (mlir::isa<mlir::omp::ThreadprivateOp>(commonValue.getDefiningOp())) { |
| // Generate ThreadprivateOp for a common block instead of its members and |
| // only do it once for a common block. |
| return; |
| } |
| // Generate ThreadprivateOp and rebind the common block. |
| mlir::Value commonThreadprivateValue = mlir::omp::ThreadprivateOp::create( |
| firOpBuilder, currentLocation, commonValue.getType(), commonValue); |
| converter.bindSymbol(*common, commonThreadprivateValue); |
| // Generate the threadprivate value for the common block member. |
| symThreadprivateValue = |
| genCommonBlockMember(converter, currentLocation, sym, |
| commonThreadprivateValue, common->size()); |
| } else if (!var.isGlobal()) { |
| // Non-global variable which can be in threadprivate directive must be one |
| // variable in main program, and it has implicit SAVE attribute. Take it as |
| // with SAVE attribute, so to create GlobalOp for it to simplify the |
| // translation to LLVM IR. |
| // Avoids performing multiple globalInitializations. |
| fir::GlobalOp global; |
| auto module = converter.getModuleOp(); |
| std::string globalName = converter.mangleName(sym); |
| if (module.lookupSymbol<fir::GlobalOp>(globalName)) |
| global = module.lookupSymbol<fir::GlobalOp>(globalName); |
| else |
| global = globalInitialization(converter, firOpBuilder, sym, var, |
| currentLocation); |
| |
| mlir::Value symValue = fir::AddrOfOp::create( |
| firOpBuilder, currentLocation, global.resultType(), global.getSymbol()); |
| symThreadprivateValue = mlir::omp::ThreadprivateOp::create( |
| firOpBuilder, currentLocation, symValue.getType(), symValue); |
| } else { |
| mlir::Value symValue = converter.getSymbolAddress(sym); |
| |
| // The symbol may be use-associated multiple times, and nothing needs to be |
| // done after the original symbol is mapped to the threadprivatized value |
| // for the first time. Use the threadprivatized value directly. |
| mlir::Operation *op; |
| if (auto declOp = symValue.getDefiningOp<hlfir::DeclareOp>()) |
| op = declOp.getMemref().getDefiningOp(); |
| else |
| op = symValue.getDefiningOp(); |
| if (mlir::isa<mlir::omp::ThreadprivateOp>(op)) |
| return; |
| |
| symThreadprivateValue = mlir::omp::ThreadprivateOp::create( |
| firOpBuilder, currentLocation, symValue.getType(), symValue); |
| } |
| |
| fir::ExtendedValue sexv = converter.getSymbolExtendedValue(sym); |
| fir::ExtendedValue symThreadprivateExv = |
| getExtendedValue(sexv, symThreadprivateValue); |
| converter.bindSymbol(sym, symThreadprivateExv); |
| } |
| |
| // This function replicates threadprivate's behaviour of generating |
| // an internal fir.GlobalOp for non-global variables in the main program |
| // that have the implicit SAVE attribute, to simplifiy LLVM-IR and MLIR |
| // generation. |
| void Fortran::lower::genDeclareTargetIntGlobal( |
| lower::AbstractConverter &converter, const lower::pft::Variable &var) { |
| if (!var.isGlobal()) { |
| // A non-global variable which can be in a declare target directive must |
| // be a variable in the main program, and it has the implicit SAVE |
| // attribute. We create a GlobalOp for it to simplify the translation to |
| // LLVM IR. |
| globalInitialization(converter, converter.getFirOpBuilder(), |
| var.getSymbol(), var, converter.getCurrentLocation()); |
| } |
| } |
| |
| bool Fortran::lower::isOpenMPTargetConstruct( |
| const parser::OpenMPConstruct &omp) { |
| llvm::omp::Directive dir = llvm::omp::Directive::OMPD_unknown; |
| if (const auto *block = std::get_if<parser::OmpBlockConstruct>(&omp.u)) { |
| dir = block->BeginDir().DirId(); |
| } else if (const auto *loop = |
| std::get_if<parser::OpenMPLoopConstruct>(&omp.u)) { |
| dir = loop->BeginDir().DirId(); |
| } |
| return llvm::omp::allTargetSet.test(dir); |
| } |
| |
| void Fortran::lower::gatherOpenMPDeferredDeclareTargets( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPDeclarativeConstruct &ompDecl, |
| llvm::SmallVectorImpl<OMPDeferredDeclareTargetInfo> |
| &deferredDeclareTarget) { |
| Fortran::common::visit( |
| common::visitors{ |
| [&](const parser::OmpDeclareTargetDirective &ompReq) { |
| collectDeferredDeclareTargets(converter, semaCtx, eval, ompReq, |
| deferredDeclareTarget); |
| }, |
| [&](const auto &) {}, |
| }, |
| ompDecl.u); |
| } |
| |
| bool Fortran::lower::isOpenMPDeviceDeclareTarget( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::pft::Evaluation &eval, |
| const parser::OpenMPDeclarativeConstruct &ompDecl) { |
| return Fortran::common::visit( |
| common::visitors{ |
| [&](const parser::OmpDeclareTargetDirective &ompReq) { |
| mlir::omp::DeclareTargetDeviceType targetType = |
| getDeclareTargetFunctionDevice(converter, semaCtx, eval, ompReq) |
| .value_or(mlir::omp::DeclareTargetDeviceType::host); |
| return targetType != mlir::omp::DeclareTargetDeviceType::host; |
| }, |
| [&](const auto &) { return false; }, |
| }, |
| ompDecl.u); |
| } |
| |
| // In certain cases such as subroutine or function interfaces which declare |
| // but do not define or directly call the subroutine or function in the same |
| // module, their lowering is delayed until after the declare target construct |
| // itself is processed, so there symbol is not within the table. |
| // |
| // This function will also return true if we encounter any device declare |
| // target cases, to satisfy checking if we require the requires attributes |
| // on the module. |
| bool Fortran::lower::markOpenMPDeferredDeclareTargetFunctions( |
| mlir::Operation *mod, |
| llvm::SmallVectorImpl<OMPDeferredDeclareTargetInfo> &deferredDeclareTargets, |
| AbstractConverter &converter) { |
| bool deviceCodeFound = false; |
| auto modOp = llvm::cast<mlir::ModuleOp>(mod); |
| for (auto declTar : deferredDeclareTargets) { |
| mlir::Operation *op = modOp.lookupSymbol(converter.mangleName(declTar.sym)); |
| |
| // Due to interfaces being optionally emitted on usage in a module, |
| // not finding an operation at this point cannot be a hard error, we |
| // simply ignore it for now. |
| // TODO: Add semantic checks for detecting cases where an erronous |
| // (undefined) symbol has been supplied to a declare target clause |
| if (!op) |
| continue; |
| |
| auto devType = declTar.declareTargetDeviceType; |
| if (!deviceCodeFound && devType != mlir::omp::DeclareTargetDeviceType::host) |
| deviceCodeFound = true; |
| |
| markDeclareTarget(op, converter, declTar.declareTargetCaptureClause, |
| devType, declTar.automap); |
| } |
| |
| return deviceCodeFound; |
| } |
| |
| void Fortran::lower::genOpenMPRequires(mlir::Operation *mod, |
| const semantics::Symbol *symbol) { |
| using MlirRequires = mlir::omp::ClauseRequires; |
| |
| if (auto offloadMod = |
| llvm::dyn_cast<mlir::omp::OffloadModuleInterface>(mod)) { |
| llvm::omp::ClauseSet reqs; |
| if (symbol) { |
| common::visit( |
| [&](const auto &details) { |
| if constexpr (std::is_base_of_v<semantics::WithOmpDeclarative, |
| std::decay_t<decltype(details)>>) { |
| reqs = details.ompRequires(); |
| } |
| }, |
| symbol->details()); |
| } |
| |
| // Use pre-populated omp.requires module attribute if it was set, so that |
| // the "-fopenmp-force-usm" compiler option is honored. |
| MlirRequires mlirFlags = offloadMod.getRequires(); |
| if (reqs.test(llvm::omp::Clause::OMPC_dynamic_allocators)) |
| mlirFlags = mlirFlags | MlirRequires::dynamic_allocators; |
| if (reqs.test(llvm::omp::Clause::OMPC_reverse_offload)) |
| mlirFlags = mlirFlags | MlirRequires::reverse_offload; |
| if (reqs.test(llvm::omp::Clause::OMPC_unified_address)) |
| mlirFlags = mlirFlags | MlirRequires::unified_address; |
| if (reqs.test(llvm::omp::Clause::OMPC_unified_shared_memory)) |
| mlirFlags = mlirFlags | MlirRequires::unified_shared_memory; |
| |
| offloadMod.setRequires(mlirFlags); |
| } |
| } |
| |
| // Walk scopes and materialize omp.declare_mapper ops for mapper declarations |
| // found in imported modules. If \p scope is null, start from the global scope. |
| void Fortran::lower::materializeOpenMPDeclareMappers( |
| Fortran::lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, const semantics::Scope *scope) { |
| const semantics::Scope &root = scope ? *scope : semaCtx.globalScope(); |
| |
| // Recurse into child scopes first (modules, submodules, etc.). |
| for (const semantics::Scope &child : root.children()) |
| materializeOpenMPDeclareMappers(converter, semaCtx, &child); |
| |
| // Only consider module scopes to avoid duplicating local constructs. |
| if (!root.IsModule()) |
| return; |
| |
| // Only materialize for modules coming from mod files to avoid duplicates. |
| if (!root.symbol() || !root.symbol()->test(semantics::Symbol::Flag::ModFile)) |
| return; |
| |
| // Scan symbols in this module scope for MapperDetails. |
| for (auto &it : root) { |
| const semantics::Symbol &sym = *it.second; |
| if (auto *md = sym.detailsIf<semantics::MapperDetails>()) { |
| for (const auto *decl : md->GetDeclList()) { |
| if (const auto *mapperDecl = |
| std::get_if<parser::OmpDeclareMapperDirective>(&decl->u)) { |
| genOpenMPDeclareMapperImpl(converter, semaCtx, *mapperDecl, &sym); |
| } |
| } |
| } |
| } |
| } |
| |
| // Walk scopes and materialize omp.declare_reduction ops for user-defined |
| // operator reductions imported from modules (deleted: replaced by lazy, |
| // clause-driven materialization). |