[flang-commits] [flang] [flang][OpenMP] Support for nocontext and novariants clauses for dispatch construct (PR #227954)

via flang-commits flang-commits at lists.llvm.org
Wed Sep 30 21:57:23 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-semantics

Author: SunilKuravinakop

<details>
<summary>Changes</summary>

Support for nocontext and novariants clauses for dispatch construct in flang. 

Earlier PRs were for Support in mlir and basic support for dispatch construct in flang.

---

Patch is 61.53 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/227954.diff


15 Files Affected:

- (modified) flang/include/flang/Lower/OpenMP.h (+22-2) 
- (modified) flang/lib/Lower/CallInterface.cpp (+7-5) 
- (modified) flang/lib/Lower/ConvertCall.cpp (+75-24) 
- (modified) flang/lib/Lower/OpenMP/ClauseProcessor.cpp (+34) 
- (modified) flang/lib/Lower/OpenMP/ClauseProcessor.h (+4) 
- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+92-3) 
- (modified) flang/lib/Lower/OpenMP/Utils.cpp (+36-3) 
- (modified) flang/lib/Lower/OpenMP/Utils.h (+5-2) 
- (modified) flang/lib/Semantics/rewrite-parse-tree.cpp (+5) 
- (added) flang/test/Fir/OpenMP/dispatch.fir (+145) 
- (added) flang/test/Integration/OpenMP/dispatch.f90 (+132) 
- (modified) flang/test/Lower/OpenMP/Todo/dispatch.f90 (+23-7) 
- (added) flang/test/Lower/OpenMP/dispatch.f90 (+354) 
- (modified) flang/test/Semantics/OpenMP/dispatch.f90 (+50) 
- (modified) flang/test/Semantics/OpenMP/simd-only.f90 (+12) 


``````````diff
diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 863a4ebc897d4..6dfc96b6ecc28 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -22,6 +22,8 @@ namespace mlir {
 class Operation;
 class Location;
 class Type;
+class Value;
+class OpBuilder;
 namespace omp {
 enum class DeclareTargetDeviceType : uint32_t;
 enum class DeclareTargetCaptureClause : uint32_t;
@@ -33,6 +35,10 @@ class FirOpBuilder;
 } // namespace fir
 
 namespace Fortran {
+namespace evaluate {
+class ProcedureRef;
+} // namespace evaluate
+
 namespace parser {
 struct OpenMPConstruct;
 struct OpenMPDeclarativeConstruct;
@@ -113,12 +119,26 @@ void markOpenMPImportedDeclareTargets(
     semantics::SemanticsContext &semaCtx);
 
 namespace omp {
+bool isDispatchTargetCall(const Fortran::evaluate::ProcedureRef &proc,
+                          AbstractConverter &converter);
+
 /// If \p base carries OpenMP DECLARE VARIANT entries, return the variant symbol
 /// that best matches the enclosing OpenMP context, or nullptr if none matches.
-/// \p base is expected to have variant entries.
+/// \p base is expected to have variant entries. When \p excludeDispatchContext
+/// is set, the innermost enclosing `dispatch` construct is removed from the
+/// context before matching, implementing the `nocontext` clause.
 const Fortran::semantics::Symbol *
 resolveDeclareVariantCallee(const Fortran::semantics::Symbol &base,
-                            AbstractConverter &converter);
+                            AbstractConverter &converter,
+                            bool excludeDispatchContext = false);
+
+/// Return the i1 `novariants` value of an enclosing `omp.dispatch`, or a null
+/// Value if there is none.
+mlir::Value getEnclosingDispatchNovariants(mlir::OpBuilder &builder);
+
+/// Return the i1 `nocontext` value of an enclosing `omp.dispatch`, or a null
+/// Value if there is none.
+mlir::Value getEnclosingDispatchNocontext(mlir::OpBuilder &builder);
 } // namespace omp
 
 // Materialize (idempotently) the omp.declare_reduction op for one already-
diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp
index 9885345bed3a5..e2db53f938c77 100644
--- a/flang/lib/Lower/CallInterface.cpp
+++ b/flang/lib/Lower/CallInterface.cpp
@@ -69,9 +69,9 @@ bool Fortran::lower::CallerInterface::hasAlternateReturns() const {
 /// nullptr when \p proc is not such a base call, or when no variant matches the
 /// context, so the base procedure is used as usual.
 static const Fortran::semantics::Symbol *
-getOmpDeclareVariantCallee(const Fortran::evaluate::ProcedureDesignator &proc,
+getOmpDeclareVariantCallee(const Fortran::evaluate::ProcedureRef &procRef,
                            Fortran::lower::AbstractConverter &converter) {
-  const Fortran::semantics::Symbol *symbol = proc.GetSymbol();
+  const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol();
   if (!symbol)
     return nullptr;
   const Fortran::semantics::Symbol &ultimate{symbol->GetUltimate()};
@@ -81,7 +81,9 @@ getOmpDeclareVariantCallee(const Fortran::evaluate::ProcedureDesignator &proc,
       ultimate.detailsIf<Fortran::semantics::SubprogramDetails>();
   if (!details || details->ompDeclareVariants().empty())
     return nullptr;
-  return Fortran::lower::omp::resolveDeclareVariantCallee(ultimate, converter);
+  return Fortran::lower::omp::resolveDeclareVariantCallee(
+      ultimate, converter,
+      !Fortran::lower::omp::isDispatchTargetCall(procRef, converter));
 }
 
 /// Return the binding label (from BIND(C...)) or the mangled name of the
@@ -103,7 +105,7 @@ std::string Fortran::lower::CallerInterface::getMangledName() const {
   // actual argument or to associate it with a procedure pointer) go through
   // getProcMangledName and are intentionally not redirected to the variant.
   if (const Fortran::semantics::Symbol *variant =
-          getOmpDeclareVariantCallee(procRef.proc(), converter))
+          getOmpDeclareVariantCallee(procRef, converter))
     return converter.mangleName(*variant);
   return getProcMangledName(procRef.proc(), converter);
 }
@@ -112,7 +114,7 @@ const Fortran::semantics::Symbol *
 Fortran::lower::CallerInterface::getProcedureSymbol() const {
   // A matching OpenMP DECLARE VARIANT call targets the variant procedure.
   if (const Fortran::semantics::Symbol *variant =
-          getOmpDeclareVariantCallee(procRef.proc(), converter))
+          getOmpDeclareVariantCallee(procRef, converter))
     return variant;
   return procRef.proc().GetSymbol();
 }
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 8c70c8145b820..47a5ab5c1430b 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -18,6 +18,7 @@
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/CustomIntrinsicCall.h"
 #include "flang/Lower/HlfirIntrinsics.h"
+#include "flang/Lower/OpenMP.h"
 #include "flang/Lower/PFTBuilder.h"
 #include "flang/Lower/StatementContext.h"
 #include "flang/Lower/SymbolMap.h"
@@ -35,6 +36,7 @@
 #include "flang/Optimizer/Dialect/CUF/CUFOps.h"
 #include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/IR/IRMapping.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/CommandLine.h"
@@ -512,6 +514,8 @@ Fortran::lower::genCallOpAndResult(
   // arguments which can happen in legal program if it was passed as a dummy
   // procedure argument earlier with no further type information.
   mlir::SymbolRefAttr funcSymbolAttr;
+  mlir::FunctionType funcType = callSiteType;
+  bool mustCastFunc = false;
   bool addHostAssociations = false;
   if (!funcPointer) {
     mlir::FunctionType funcOpType = caller.getFuncOp().getFunctionType();
@@ -532,12 +536,11 @@ Fortran::lower::genCallOpAndResult(
     // mismatch due to the extra argument, but the interface is otherwise
     // explicit and safe), handle interface mismatch due to F77 implicit
     // interface "abuse" with a function address cast if needed.
-    if (!addHostAssociations &&
-        mustCastFuncOpToCopeWithImplicitInterfaceMismatch(
-            loc, converter, callSiteType, funcOpType))
-      funcPointer = fir::AddrOfOp::create(builder, loc, funcOpType, symbolAttr);
-    else
-      funcSymbolAttr = symbolAttr;
+    mustCastFunc = !addHostAssociations &&
+                   mustCastFuncOpToCopeWithImplicitInterfaceMismatch(
+                       loc, converter, callSiteType, funcOpType);
+    funcType = mustCastFunc ? callSiteType : funcOpType;
+    funcSymbolAttr = symbolAttr;
 
     // Issue a warning if the procedure name conflicts with
     // a runtime function name a call to which has been already
@@ -553,31 +556,79 @@ Fortran::lower::genCallOpAndResult(
                           "Flang - this may lead to undefined behavior")));
   }
 
-  mlir::FunctionType funcType =
-      funcPointer ? callSiteType : caller.getFuncOp().getFunctionType();
-
   // If we have any ignore_tkr(c) dummy args, adjust the function type to
   // have these args match the caller.
   if (auto modifiedFuncType =
           getTypeWithIgnoreTkrC(funcType, caller, builder.getContext())) {
-    // Note: funcPointer would only be non-null here, if we are already
-    // processing indirect function call. In such case we can re-use the same
-    // funcPointer and we'll cast it below the the modified funcType.
-    if (!funcPointer) {
-      // We want to cast the function to a different type, in order to avoid
-      // changing/casting some of the args. The cast will generate a new
-      // function pointer, so that we would make a function call not through
-      // the original function symbol, but through the new function pointer
-      // (an indirect function call).
-      mlir::SymbolRefAttr symbolAttr =
-          builder.getSymbolRefAttr(caller.getMangledName());
-      // Create pointer to original function. This pointer will be cast later.
-      funcPointer = fir::AddrOfOp::create(builder, loc, funcType, symbolAttr);
-      funcSymbolAttr = {}; // This marks it as indirect call
-    }
     funcType = *modifiedFuncType;
+    mustCastFunc = true;
+  }
+
+  // OpenMP dispatch `novariants`/`nocontext`: at runtime pick the right target
+  // via an indirect call, evaluating arguments once. All candidate procedures
+  // share one signature; revisit if declare-variant `adjust_args`/`append_args`
+  // land.
+  if (funcSymbolAttr && Fortran::lower::omp::isDispatchTargetCall(
+                            caller.getCallDescription(), converter)) {
+    mlir::Value novariantsCond =
+        Fortran::lower::omp::getEnclosingDispatchNovariants(builder);
+    mlir::Value nocontextCond =
+        Fortran::lower::omp::getEnclosingDispatchNocontext(builder);
+    const Fortran::semantics::Symbol *baseSym =
+        caller.getCallDescription().proc().GetSymbol();
+    const Fortran::semantics::Symbol *selectedSym = caller.getProcedureSymbol();
+    // A runtime choice is only needed when a variant was actually selected for
+    // the enclosing dispatch context (otherwise the base is already the call
+    // target and dropping the dispatch construct cannot introduce a variant).
+    if ((novariantsCond || nocontextCond) && baseSym && selectedSym &&
+        &baseSym->GetUltimate() != &selectedSym->GetUltimate()) {
+      const Fortran::semantics::Symbol &baseUlt = baseSym->GetUltimate();
+      const Fortran::semantics::Symbol &selectedUlt =
+          selectedSym->GetUltimate();
+
+      auto addrOfSym =
+          [&](const Fortran::semantics::Symbol &sym) -> mlir::Value {
+        mlir::func::FuncOp func = Fortran::lower::getOrDeclareFunction(
+            Fortran::evaluate::ProcedureDesignator{sym}, converter);
+        mlir::Value address =
+            fir::AddrOfOp::create(builder, loc, func.getFunctionType(),
+                                  builder.getSymbolRefAttr(func.getSymName()));
+        return builder.createConvert(loc, funcType, address);
+      };
+
+      // Start from the variant selected with the dispatch construct in context.
+        mlir::Value target = addrOfSym(selectedUlt);
+
+      // `nocontext(true)`: re-select the variant with the dispatch construct
+      // removed from the OpenMP context. That may resolve to a different
+      // variant (e.g. one matching `device={kind(host)}`) or to the base
+      // procedure.
+      if (nocontextCond) {
+        const Fortran::semantics::Symbol *nocontextSym =
+            Fortran::lower::omp::resolveDeclareVariantCallee(
+                baseUlt, converter, /*excludeDispatchContext=*/true);
+        const Fortran::semantics::Symbol &nocontextUlt =
+            nocontextSym ? nocontextSym->GetUltimate() : baseUlt;
+        if (&nocontextUlt != &selectedUlt)
+          target = mlir::arith::SelectOp::create(
+              builder, loc, nocontextCond, addrOfSym(nocontextUlt), target);
+      }
+
+      // `novariants(true)` takes final precedence: always call the base.
+      if (novariantsCond)
+        target = mlir::arith::SelectOp::create(builder, loc, novariantsCond,
+                                               addrOfSym(baseUlt), target);
+
+      funcPointer = target;
+    }
   }
 
+  if (!funcPointer && mustCastFunc)
+    funcPointer = fir::AddrOfOp::create(
+        builder, loc, caller.getFuncOp().getFunctionType(), funcSymbolAttr);
+  if (funcPointer)
+    funcSymbolAttr = {};
+
   llvm::SmallVector<mlir::Value> operands;
   // First operand of indirect call is the function pointer. Cast it to
   // required function type for the call to handle procedures that have a
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index b871f630d3dae..e5a5a479cf6c9 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -717,6 +717,40 @@ bool ClauseProcessor::processNogroup(
   return markClauseOccurrence<omp::clause::Nogroup>(result.nogroup);
 }
 
+bool ClauseProcessor::processNocontext(
+    lower::StatementContext &stmtCtx,
+    mlir::omp::NocontextClauseOps &result) const {
+  const parser::CharBlock *source = nullptr;
+  if (auto *clause = findUniqueClause<omp::clause::Nocontext>(&source)) {
+    fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+    mlir::Location clauseLocation = converter.genLocation(*source);
+
+    mlir::Value nocontextVal =
+        fir::getBase(converter.genExprValue(clause->v, stmtCtx));
+    result.nocontext = firOpBuilder.createConvert(
+        clauseLocation, firOpBuilder.getI1Type(), nocontextVal);
+    return true;
+  }
+  return false;
+}
+
+bool ClauseProcessor::processNovariants(
+    lower::StatementContext &stmtCtx,
+    mlir::omp::NovariantsClauseOps &result) const {
+  const parser::CharBlock *source = nullptr;
+  if (auto *clause = findUniqueClause<omp::clause::Novariants>(&source)) {
+    fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+    mlir::Location clauseLocation = converter.genLocation(*source);
+
+    mlir::Value novariantsVal =
+        fir::getBase(converter.genExprValue(clause->v, stmtCtx));
+    result.novariants = firOpBuilder.createConvert(
+        clauseLocation, firOpBuilder.getI1Type(), novariantsVal);
+    return true;
+  }
+  return false;
+}
+
 bool ClauseProcessor::processNowait(mlir::omp::NowaitClauseOps &result) const {
   return markClauseOccurrence<omp::clause::Nowait>(result.nowait);
 }
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index 6b0e0cf606f48..6cfcc85024dc0 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -100,8 +100,12 @@ class ClauseProcessor {
       const parser::OmpStylizedInstance *parserInitInstance = nullptr,
       unsigned instanceIdx = 0) const;
   bool processMergeable(mlir::omp::MergeableClauseOps &result) const;
+  bool processNocontext(lower::StatementContext &stmtCtx,
+                        mlir::omp::NocontextClauseOps &result) const;
   bool processNogroup(mlir::omp::NogroupClauseOps &result) const;
   bool processNotinbranch(mlir::omp::NotinbranchClauseOps &result) const;
+  bool processNovariants(lower::StatementContext &stmtCtx,
+                         mlir::omp::NovariantsClauseOps &result) const;
   bool processNowait(mlir::omp::NowaitClauseOps &result) const;
   bool processNumTasks(lower::StatementContext &stmtCtx,
                        mlir::omp::NumTasksClauseOps &result) const;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index df14d13b76a4b..c50f64e4af05f 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -22,6 +22,7 @@
 #include "flang/Evaluate/expression.h"
 #include "flang/Evaluate/fold.h"
 #include "flang/Evaluate/tools.h"
+#include "flang/Evaluate/traverse.h"
 #include "flang/Evaluate/type.h"
 #include "flang/Lower/Bridge.h"
 #include "flang/Lower/ConvertCall.h"
@@ -845,6 +846,24 @@ getSectionsConstructStackTop(lower::AbstractConverter &converter) {
   return frame ? &frame->sectionsConstruct : nullptr;
 }
 
+class DispatchTargetStackFrame
+    : public mlir::StateStackFrameBase<DispatchTargetStackFrame> {
+public:
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(DispatchTargetStackFrame)
+
+  explicit DispatchTargetStackFrame(const evaluate::ProcedureRef *target)
+      : target{target} {}
+
+  const evaluate::ProcedureRef *target;
+};
+
+bool Fortran::lower::omp::isDispatchTargetCall(
+    const evaluate::ProcedureRef &proc, lower::AbstractConverter &converter) {
+  auto *frame =
+      converter.getStateStack().getStackTop<DispatchTargetStackFrame>();
+  return frame && frame->target == &proc;
+}
+
 /// Bind objects to their corresponding entry block arguments.
 ///
 /// The binding will be performed inside of the current block, which does not
@@ -2408,6 +2427,19 @@ static void genDistributeClauses(lower::AbstractConverter &converter,
   cp.processOrder(clauseOps);
 }
 
+static void genDispatchClauses(lower::AbstractConverter &converter,
+                               semantics::SemanticsContext &semaCtx,
+                               lower::StatementContext &stmtCtx,
+                               const List<Clause> &clauses, mlir::Location loc,
+                               mlir::omp::DispatchOperands &clauseOps) {
+  ClauseProcessor cp(converter, semaCtx, clauses);
+  cp.processNocontext(stmtCtx, clauseOps);
+  cp.processNovariants(stmtCtx, clauseOps);
+  cp.processNowait(clauseOps);
+  cp.processTODO<clause::Depend, clause::Device, clause::IsDevicePtr>(
+      loc, llvm::omp::Directive::OMPD_dispatch);
+}
+
 static void genFlushClauses(lower::AbstractConverter &converter,
                             semantics::SemanticsContext &semaCtx,
                             const ObjectList &objects,
@@ -2981,6 +3013,47 @@ genCriticalOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
       queue, item, nameAttr);
 }
 
+class DispatchTargetFinder
+    : public evaluate::AnyTraverse<DispatchTargetFinder,
+                                   const evaluate::ProcedureRef *> {
+public:
+  using Base = evaluate::AnyTraverse<DispatchTargetFinder,
+                                     const evaluate::ProcedureRef *>;
+  DispatchTargetFinder() : Base{*this} {}
+  using Base::operator();
+
+  const evaluate::ProcedureRef *
+  operator()(const evaluate::ProcedureRef &proc) const {
+    return &proc;
+  }
+};
+
+static mlir::omp::DispatchOp genDispatchOp(
+    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::DispatchOperands clauseOps;
+  genDispatchClauses(converter, semaCtx, stmtCtx, item->clauses, loc,
+                     clauseOps);
+
+  const evaluate::ProcedureRef *target = nullptr;
+  for (lower::pft::Evaluation &nested : eval.getNestedEvaluations())
+    nested.visit(common::visitors{
+        [&](const parser::CallStmt &stmt) { target = &*stmt.typedCall; },
+        [&](const parser::AssignmentStmt &stmt) {
+          target = DispatchTargetFinder{}(stmt.typedAssignment->v->rhs);
+        },
+        [](const auto &) {}});
+  mlir::SaveStateStack<DispatchTargetStackFrame> saveStateStack{
+      converter.getStateStack(), target};
+
+  return genOpWithBody<mlir::omp::DispatchOp>(
+      OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
+                        llvm::omp::Directive::OMPD_dispatch),
+      queue, item, clauseOps);
+}
+
 static mlir::omp::FlushOp
 genFlushOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
            semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
@@ -5731,6 +5804,10 @@ genOMPDispatch(lower::AbstractConverter &converter, lower::SymMap &symTable,
   case llvm::omp::Directive::OMPD_barrier:
     newOp = genBarrierOp(converter, symTable, semaCtx, eval, loc, queue, item);
     break;
+  case llvm::omp::Directive::OMPD_dispatch:
+    newOp = genDispatchOp(converter, symTable, stmtCtx, semaCtx, eval, loc,
+                          queue, item);
+    break;
   case llvm::omp::Directive::OMPD_distribute:
     newOp = genStandaloneDistribute(converter, symTable, stmtCtx, semaCtx, eval,
                                     loc, queue, item);
@@ -8385,9 +8462,21 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 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");
+                   const parser::OpenMPDispatchConstruct &dispatchConstruct) {
+  const parser::OmpDirectiveSpecification &beginSpec =
+      dispatchConstruct.BeginDir();
+  List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx);
+  if (auto &endSpec = dispatchConstruct.EndDir())
+    clauses.append(makeClauses(endSpec->Clauses(), semaCtx));
+
+  llvm::omp::Directive directive = beginSpec.DirId();
+  mlir::Location currentLocat...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/227954


More information about the flang-commits mailing list