[Mlir-commits] [clang] [flang] [mlir] [Flang][OpenMP] Lower DECLARE TARGET INDIRECT clause (PR #208387)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Tue Aug 25 15:55:43 PDT 2026


https://github.com/blazie2004 updated https://github.com/llvm/llvm-project/pull/208387

>From 6158565249089bc039d9290d8403f0f6872f4197 Mon Sep 17 00:00:00 2001
From: Jay Satish Kumar Patel <kumarpat at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 8 Jul 2026 04:53:19 -0500
Subject: [PATCH 1/4] [Flang][OpenMP] Lower DECLARE TARGET INDIRECT clause

---
 flang/include/flang/Lower/OpenMP.h            |  1 +
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 21 +++++
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |  1 +
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 40 ++++++---
 flang/lib/Lower/OpenMP/Utils.h                |  1 +
 flang/lib/Semantics/check-omp-structure.cpp   | 15 ++++
 .../Lower/OpenMP/Todo/omp-clause-indirect.f90 | 34 --------
 .../OpenMP/declare-target-indirect-clause.f90 | 30 +++++++
 .../OpenMP/declare-target-indirect-merge.f90  | 47 +++++++++++
 .../Lower/OpenMP/declare-target-indirect.f90  | 39 +++++++++
 flang/test/Semantics/indirect02.f90           |  7 +-
 flang/test/Semantics/indirect03.f90           | 31 +++++++
 .../mlir/Dialect/OpenMP/OpenMPAttrDefs.td     |  3 +-
 .../Dialect/OpenMP/OpenMPClauseOperands.h     | 10 ++-
 .../Dialect/OpenMP/OpenMPOpsInterfaces.td     | 18 +++-
 .../OpenMP/Transforms/MarkDeclareTarget.cpp   |  3 +-
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 83 ++++++++++++++++++-
 .../OpenMP/mark-declare-target-indirect.mlir  | 39 +++++++++
 ...target-declare-target-indirect-device.mlir | 19 +++++
 ...mptarget-declare-target-indirect-host.mlir | 20 +++++
 20 files changed, 405 insertions(+), 57 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/omp-clause-indirect.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-target-indirect-clause.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-target-indirect-merge.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-target-indirect.f90
 create mode 100644 flang/test/Semantics/indirect03.f90
 create mode 100644 mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir
 create mode 100644 mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir
 create mode 100644 mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir

diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index a3f35498b9180..975bbf6a15bb4 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -59,6 +59,7 @@ struct OMPDeferredDeclareTargetInfo {
   mlir::omp::DeclareTargetCaptureClause declareTargetCaptureClause;
   mlir::omp::DeclareTargetDeviceType declareTargetDeviceType;
   bool automap = false;
+  bool indirect = false;
   const Fortran::semantics::Symbol &sym;
 };
 
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index b2f511c2c9cd1..fd1af2f812ea5 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -13,6 +13,8 @@
 #include "ClauseProcessor.h"
 #include "Utils.h"
 
+#include "flang/Evaluate/fold.h"
+#include "flang/Evaluate/tools.h"
 #include "flang/Lower/ConvertCall.h"
 #include "flang/Lower/ConvertExprToHLFIR.h"
 #include "flang/Lower/OpenMP/Clauses.h"
@@ -478,6 +480,25 @@ bool ClauseProcessor::processDeviceType(
   return false;
 }
 
+bool ClauseProcessor::processIndirect(
+    mlir::omp::IndirectClauseOps &result) const {
+  if (auto *clause = findUniqueClause<omp::clause::Indirect>()) {
+    // Case: declare target ... indirect[(scalar-logical-constant)]
+    // An `indirect` clause with no argument defaults to `.true.`.
+    bool isIndirect = true;
+    if (clause->v) {
+      auto foldedExpr = Fortran::evaluate::Fold(
+          semaCtx.foldingContext(), Fortran::common::Clone(*clause->v));
+      if (auto logicalVal = Fortran::evaluate::GetScalarConstantValue<
+              Fortran::evaluate::LogicalResult>(foldedExpr))
+        isIndirect = logicalVal->IsTrue();
+    }
+    result.indirect = isIndirect;
+    return true;
+  }
+  return false;
+}
+
 bool ClauseProcessor::processDistSchedule(
     lower::StatementContext &stmtCtx,
     mlir::omp::DistScheduleClauseOps &result) const {
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index 10d52bcdf89de..05622e5e3ede3 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -146,6 +146,7 @@ class ClauseProcessor {
   processEnter(llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &result) const;
   bool processIf(omp::clause::If::DirectiveNameModifier directiveName,
                  mlir::omp::IfClauseOps &result) const;
+  bool processIndirect(mlir::omp::IndirectClauseOps &result) const;
   bool
   processInReduction(mlir::Location currentLocation,
                      mlir::omp::InReductionClauseOps &result,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 7503d33c8df38..5ea2703ea571c 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1554,9 +1554,13 @@ static void getDeclareTargetInfo(
     cp.processEnter(symbolAndClause);
     cp.processLink(symbolAndClause);
     cp.processTo(symbolAndClause);
+    cp.processIndirect(clauseOps);
 
-    cp.processTODO<clause::Indirect>(converter.getCurrentLocation(),
-                                     llvm::omp::Directive::OMPD_declare_target);
+    // The `indirect` clause applies to the functions named by the directive
+    // (it requires an `enter` or `to` clause). Propagate the directive-level
+    // value to each captured symbol so it reaches the declare target attribute.
+    for (DeclareTargetCaptureInfo &sym : symbolAndClause)
+      sym.indirect = clauseOps.indirect;
   }
 }
 
@@ -1580,7 +1584,8 @@ static void collectDeferredDeclareTargets(
 
     if (!op) {
       deferredDeclareTarget.push_back({symClause.clause, clauseOps.deviceType,
-                                       symClause.automap, symClause.symbol});
+                                       symClause.automap, symClause.indirect,
+                                       symClause.symbol});
     }
   }
 }
@@ -1828,7 +1833,8 @@ getImplicitMapTypeAndKind(fir::FirOpBuilder &firOpBuilder,
 static void
 markDeclareTarget(mlir::Operation *op, lower::AbstractConverter &converter,
                   mlir::omp::DeclareTargetCaptureClause captureClause,
-                  mlir::omp::DeclareTargetDeviceType deviceType, bool automap) {
+                  mlir::omp::DeclareTargetDeviceType deviceType, bool automap,
+                  bool indirect) {
   // TODO: Add support for program local variables with declare target applied
   auto declareTargetOp = llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(op);
   if (!declareTargetOp)
@@ -1838,18 +1844,32 @@ markDeclareTarget(mlir::Operation *op, lower::AbstractConverter &converter,
 
   // 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
+  // function/subroutine), or because it is named by more than one declare
+  // target directive. It should be marked as any if it has been assigned both
+  // host and nohost. The `indirect` modifier is a capability: once any
+  // declaration requests it, it must stay set, so it is merged with logical OR
+  // rather than overwritten (which could drop a previous `indirect = true`).
   if (declareTargetOp.isDeclareTarget()) {
+    bool mergedIndirect =
+        declareTargetOp.getDeclareTargetIndirect() || indirect;
+
     if (declareTargetOp.getDeclareTargetDeviceType() != deviceType)
       declareTargetOp.setDeclareTarget(mlir::omp::DeclareTargetDeviceType::any,
                                        captureClause, automap,
-                                       /*implicit=*/false);
+                                       /*implicit=*/false, mergedIndirect);
+    else if (mergedIndirect != declareTargetOp.getDeclareTargetIndirect())
+      // Same device type, but a later declaration added `indirect`; update it
+      // while preserving the already-established capture clause and automap.
+      declareTargetOp.setDeclareTarget(
+          declareTargetOp.getDeclareTargetDeviceType(),
+          declareTargetOp.getDeclareTargetCaptureClause(),
+          declareTargetOp.getDeclareTargetAutomap(), /*implicit=*/false,
+          mergedIndirect);
     return;
   }
 
   declareTargetOp.setDeclareTarget(deviceType, captureClause, automap,
-                                   /*implicit=*/false);
+                                   /*implicit=*/false, indirect);
 }
 
 //===----------------------------------------------------------------------===//
@@ -6791,7 +6811,7 @@ genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
       continue;
 
     markDeclareTarget(op, converter, symClause.clause, clauseOps.deviceType,
-                      symClause.automap);
+                      symClause.automap, symClause.indirect);
   }
 }
 
@@ -8017,7 +8037,7 @@ bool Fortran::lower::markOpenMPDeferredDeclareTargetFunctions(
       deviceCodeFound = true;
 
     markDeclareTarget(op, converter, declTar.declareTargetCaptureClause,
-                      devType, declTar.automap);
+                      devType, declTar.automap, declTar.indirect);
   }
 
   return deviceCodeFound;
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..db38c2b755be7 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -53,6 +53,7 @@ namespace omp {
 struct DeclareTargetCaptureInfo {
   mlir::omp::DeclareTargetCaptureClause clause;
   bool automap = false;
+  bool indirect = false;
   const semantics::Symbol &symbol;
 
   DeclareTargetCaptureInfo(mlir::omp::DeclareTargetCaptureClause c,
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 1cc5a5d885a0c..5c0c6f4dd4424 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -2711,6 +2711,21 @@ void OmpStructureChecker::Leave(const parser::OmpDeclareTargetDirective &x) {
       context_.Warn(common::UsageWarning::OpenMPUsage, toClause->source,
           "The usage of TO clause on DECLARE TARGET directive has been deprecated. Use ENTER clause instead."_warn_en_US);
     }
+    if (indirectClause) {
+      // The INDIRECT clause is only allowed together with DEVICE_TYPE(ANY) (an
+      // absent DEVICE_TYPE clause also implies ANY). A host- or device-only
+      // procedure cannot be the target of an indirect device invocation.
+      if (const parser::OmpClause *deviceTypeClause{
+              FindClause(llvm::omp::Clause::OMPC_device_type)}) {
+        const auto &deviceType{
+            std::get<parser::OmpClause::DeviceType>(deviceTypeClause->u)};
+        if (deviceType.v.v !=
+            parser::OmpDeviceTypeClause::DeviceTypeDescription::Any) {
+          context_.Say(x.source,
+              "Only the DEVICE_TYPE(ANY) clause is allowed with the INDIRECT clause on the DECLARE TARGET directive"_err_en_US);
+        }
+      }
+    }
   }
 
   bool toClauseFound{false};
diff --git a/flang/test/Lower/OpenMP/Todo/omp-clause-indirect.f90 b/flang/test/Lower/OpenMP/Todo/omp-clause-indirect.f90
deleted file mode 100644
index 82efa8818a83c..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/omp-clause-indirect.f90
+++ /dev/null
@@ -1,34 +0,0 @@
-! This test checks the lowering of OpenMP Indirect Clause when used with the Declare Target directive
-
-! RUN: not %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 %s 2>&1 | FileCheck %s
-
-module functions
-  implicit none
-
-  interface
-    function func() result(i)
-      character(1) :: i
-    end function
-  end interface
-
-contains
-  function func1() result(i)
-    !CHECK: not yet implemented: Unhandled clause INDIRECT in DECLARE TARGET construct
-    !$omp declare target enter(func1) indirect(.true.)
-    character(1) :: i
-    i = 'a'
-    return
-  end function
-end module
-
-program main
-  use functions
-  implicit none
-  procedure (func), pointer :: ptr1=>func1
-  character(1) :: val1
-
-  !$omp target map(from: val1)
-  val1 = ptr1()
-  !$omp end target
-
-end program
diff --git a/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90 b/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90
new file mode 100644
index 0000000000000..ced2535925888
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90
@@ -0,0 +1,30 @@
+!RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+!RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -fopenmp-is-target-device %s -o - | FileCheck %s
+
+! Check that the INDIRECT clause on a DECLARE TARGET directive is lowered to the
+! `indirect` field of the omp.declare_target attribute.
+
+module functions
+  implicit none
+contains
+  ! CHECK: func.func @_QMfunctionsPfunc_true({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  function func_true() result(i)
+    !$omp declare target enter(func_true) indirect(.true.)
+    character(1) :: i
+    i = 'a'
+  end function
+
+  ! CHECK: func.func @_QMfunctionsPfunc_implicit({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  function func_implicit() result(i)
+    !$omp declare target enter(func_implicit) indirect
+    character(1) :: i
+    i = 'b'
+  end function
+
+  ! CHECK: func.func @_QMfunctionsPfunc_false({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = false>}
+  function func_false() result(i)
+    !$omp declare target enter(func_false) indirect(.false.)
+    character(1) :: i
+    i = 'c'
+  end function
+end module
diff --git a/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90 b/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90
new file mode 100644
index 0000000000000..cee898cb84bee
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90
@@ -0,0 +1,47 @@
+! Check that the INDIRECT modifier is preserved (merged with logical OR) when a
+! procedure is named by more than one DECLARE TARGET directive. A prior
+! `indirect = true` must not be dropped, either by the device_type merge to
+! `any` or by an early return when the device_type already matches.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+
+module m1
+  implicit none
+contains
+  ! A later directive adds `indirect` with the same (default) device_type.
+  ! CHECK: func.func @_QMm1Pfoo1() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  function foo1() result(i)
+    !$omp declare target enter(foo1)
+    !$omp declare target enter(foo1) indirect(.true.)
+    integer :: i
+    i = 1
+  end function
+end module
+
+module m2
+  implicit none
+contains
+  ! `indirect` first, plain second: it must stay set.
+  ! CHECK: func.func @_QMm2Pfoo2() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  function foo2() result(i)
+    !$omp declare target enter(foo2) indirect(.true.)
+    !$omp declare target enter(foo2)
+    integer :: i
+    i = 1
+  end function
+end module
+
+module m3
+  implicit none
+contains
+  ! `indirect` (device_type any) followed by a device_type(nohost) declaration:
+  ! the device type merges to `any` and the `indirect = true` must be carried
+  ! over rather than overwritten.
+  ! CHECK: func.func @_QMm3Pfoo3() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  function foo3() result(i)
+    !$omp declare target enter(foo3) indirect(.true.)
+    !$omp declare target enter(foo3) device_type(nohost)
+    integer :: i
+    i = 1
+  end function
+end module
diff --git a/flang/test/Lower/OpenMP/declare-target-indirect.f90 b/flang/test/Lower/OpenMP/declare-target-indirect.f90
new file mode 100644
index 0000000000000..3cd87d9b82575
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-target-indirect.f90
@@ -0,0 +1,39 @@
+! This test checks the lowering of the OpenMP INDIRECT clause when used with the
+! DECLARE TARGET directive, together with an indirect call (through a procedure
+! pointer) from within a target region.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -fopenmp-is-target-device %s -o - | FileCheck %s
+
+module functions
+  implicit none
+
+  interface
+    function func() result(i)
+      character(1) :: i
+    end function
+  end interface
+
+contains
+  ! CHECK: func.func @_QMfunctionsPfunc1({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  function func1() result(i)
+    !$omp declare target enter(func1) indirect(.true.)
+    character(1) :: i
+    i = 'a'
+    return
+  end function
+end module
+
+program main
+  use functions
+  implicit none
+  procedure (func), pointer :: ptr1=>func1
+  character(1) :: val1
+
+  ! CHECK-LABEL: func.func @_QQmain()
+  ! CHECK: omp.target
+  !$omp target map(from: val1)
+  val1 = ptr1()
+  !$omp end target
+
+end program
diff --git a/flang/test/Semantics/indirect02.f90 b/flang/test/Semantics/indirect02.f90
index 3fae39f1c2281..8835919341a9b 100644
--- a/flang/test/Semantics/indirect02.f90
+++ b/flang/test/Semantics/indirect02.f90
@@ -1,7 +1,8 @@
-! This test checks the lowering of OpenMP Indirect Clause when used with the Declare Target directive
+! This test checks that the OpenMP INDIRECT clause on the DECLARE TARGET
+! directive is rejected before OpenMP 5.1 and accepted from 5.1 onwards.
 
 ! RUN: not %flang -fopenmp -fopenmp-version=50 %s 2>&1 | FileCheck %s --check-prefix="CHECK-50"
-! RUN: not %flang -fopenmp -fopenmp-version=52 %s 2>&1 | FileCheck %s --check-prefix="CHECK-52"
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix="CHECK-52"
 
 module functions
   implicit none
@@ -15,7 +16,7 @@ function func() result(i)
 contains
   function func1() result(i)
     !CHECK-50: INDIRECT clause is not allowed on DECLARE TARGET directive in OpenMP v5.0, try -fopenmp-version=51
-    !CHECK-52: not yet implemented: Unhandled clause INDIRECT in DECLARE TARGET construct
+    !CHECK-52: !$OMP DECLARE TARGET ENTER(func1) INDIRECT(.true._4)
     !$omp declare target enter(func1) indirect(.true.)
     character(1) :: i
     i = 'a'
diff --git a/flang/test/Semantics/indirect03.f90 b/flang/test/Semantics/indirect03.f90
new file mode 100644
index 0000000000000..0bab188a9ff51
--- /dev/null
+++ b/flang/test/Semantics/indirect03.f90
@@ -0,0 +1,31 @@
+! This test checks the OpenMP restriction that the INDIRECT clause on a DECLARE
+! TARGET directive is only allowed with DEVICE_TYPE(ANY) (an absent DEVICE_TYPE
+! clause also implies ANY). A host- or device-only procedure cannot be the
+! target of an indirect device invocation.
+
+! RUN: not %flang -fopenmp -fopenmp-version=52 %s 2>&1 | FileCheck %s
+
+module functions
+  implicit none
+contains
+  !CHECK: Only the DEVICE_TYPE(ANY) clause is allowed with the INDIRECT clause on the DECLARE TARGET directive
+  function func_host() result(i)
+    !$omp declare target enter(func_host) device_type(host) indirect(.true.)
+    character(1) :: i
+    i = 'a'
+  end function
+
+  !CHECK: Only the DEVICE_TYPE(ANY) clause is allowed with the INDIRECT clause on the DECLARE TARGET directive
+  function func_nohost() result(i)
+    !$omp declare target enter(func_nohost) device_type(nohost) indirect(.true.)
+    character(1) :: i
+    i = 'b'
+  end function
+
+  ! DEVICE_TYPE(ANY) with INDIRECT is allowed, so no error is expected here.
+  function func_any() result(i)
+    !$omp declare target enter(func_any) device_type(any) indirect(.true.)
+    character(1) :: i
+    i = 'c'
+  end function
+end module
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
index 55895add86dc4..9f0b776870265 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
@@ -46,7 +46,8 @@ def DeclareTargetAttr : OpenMP_Attr<"DeclareTarget", "declaretarget"> {
       (ins OptionalParameter<"DeclareTargetDeviceTypeAttr">:$device_type,
           OptionalParameter<"DeclareTargetCaptureClauseAttr">:$capture_clause,
           DefaultValuedParameter<"bool", "false">:$automap,
-          DefaultValuedParameter<"bool", "false">:$implicit);
+          DefaultValuedParameter<"bool", "false">:$implicit,
+          DefaultValuedParameter<"bool", "false">:$indirect);
 
   let assemblyFormat = "`<` struct(params) `>`";
 }
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauseOperands.h b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauseOperands.h
index 35b58375698f6..fbe5ee576228b 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauseOperands.h
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauseOperands.h
@@ -33,6 +33,12 @@ struct DeviceTypeClauseOps {
   DeclareTargetDeviceType deviceType = DeclareTargetDeviceType::any;
 };
 
+struct IndirectClauseOps {
+  /// Whether the declare target entities may be invoked indirectly (through a
+  /// function pointer) from within a target region.
+  bool indirect = false;
+};
+
 //===----------------------------------------------------------------------===//
 // Extra operation operand structures.
 //===----------------------------------------------------------------------===//
@@ -43,8 +49,8 @@ using HostEvaluatedOperands =
     detail::Clauses<CollapseClauseOps, LoopRelatedClauseOps, NumTeamsClauseOps,
                     NumThreadsClauseOps, ThreadLimitClauseOps>;
 
-// TODO: Add `indirect` clause.
-using DeclareTargetOperands = detail::Clauses<DeviceTypeClauseOps>;
+using DeclareTargetOperands =
+    detail::Clauses<DeviceTypeClauseOps, IndirectClauseOps>;
 
 /// omp.target_enter_data, omp.target_exit_data and omp.target_update take the
 /// same clauses, so we give the structure to be shared by all of them a
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td
index 66730ae52d8ee..4e637753c7132 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td
@@ -376,7 +376,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
       /*methodName=*/"setDeclareTarget",
       (ins "mlir::omp::DeclareTargetDeviceType":$deviceType,
            "mlir::omp::DeclareTargetCaptureClause":$captureClause,
-           "bool":$automap, "bool":$implicit), [{}], [{
+           "bool":$automap, "bool":$implicit, "bool":$indirect), [{}], [{
         $_op->setAttr("omp.declare_target",
                   mlir::omp::DeclareTargetAttr::get(
                       $_op->getContext(),
@@ -384,7 +384,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
                           $_op->getContext(), deviceType),
                       mlir::omp::DeclareTargetCaptureClauseAttr::get(
                           $_op->getContext(), captureClause),
-                      automap, implicit));
+                      automap, implicit, indirect));
       }]>,
       InterfaceMethod<
       /*description=*/[{
@@ -446,6 +446,20 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
           if (auto dAttr = llvm::dyn_cast_or_null<mlir::omp::DeclareTargetAttr>(dTar))
             return dAttr.getImplicit();
          return false;
+      }]>,
+      InterfaceMethod<
+      /*description=*/[{
+        Return true if the DeclareTarget attribute has the INDIRECT modifier,
+        indicating that the entity may be invoked through a function pointer
+        from within a target region.
+      }],
+      /*retTy=*/"bool",
+      /*methodName=*/"getDeclareTargetIndirect",
+      (ins), [{}], [{
+        if (mlir::Attribute dTar = $_op->getAttr("omp.declare_target"))
+          if (auto dAttr = llvm::dyn_cast_or_null<mlir::omp::DeclareTargetAttr>(dTar))
+            return dAttr.getIndirect();
+         return false;
       }]>
   ];
 }
diff --git a/mlir/lib/Dialect/OpenMP/Transforms/MarkDeclareTarget.cpp b/mlir/lib/Dialect/OpenMP/Transforms/MarkDeclareTarget.cpp
index d988752da84b9..519a51e6a18f6 100644
--- a/mlir/lib/Dialect/OpenMP/Transforms/MarkDeclareTarget.cpp
+++ b/mlir/lib/Dialect/OpenMP/Transforms/MarkDeclareTarget.cpp
@@ -282,7 +282,8 @@ class MarkDeclareTargetPass
       // Update the operation and add callees to the worklist to propagate it.
       declareTargetOp.setDeclareTarget(changedDeviceType,
                                        omp::DeclareTargetCaptureClause::to,
-                                       /*automap=*/false, /*implicit=*/true);
+                                       /*automap=*/false, /*implicit=*/true,
+                                       /*indirect=*/false);
 
       for (auto &callee : calls[workItem.first])
         worklist.push_back({callee.getKey(), changedDeviceType});
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 47a8493708d0f..45623a61a42a1 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -9708,6 +9708,69 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
   return success();
 }
 
+/// Register a `declare target ... indirect` function so that the OpenMP runtime
+/// can resolve indirect calls (through a function pointer) to it from within a
+/// target region. This mirrors clang's
+/// `CGOpenMPRuntime::emitDeclareTargetFunction`.
+///
+/// On the host the function itself is registered as an indirect offload entry,
+/// which causes an offloading entry (with the indirect flag) to be emitted at
+/// module finalization. On the target device a new, externally visible global
+/// holding the address of the function is generated (so the runtime can read
+/// the device address while leaving the function's own linkage and visibility
+/// unchanged) and that global is registered instead.
+static void registerIndirectDeclareTargetFunction(
+    FunctionOpInterface funcOp, llvm::OpenMPIRBuilder *ompBuilder,
+    LLVM::ModuleTranslation &moduleTranslation) {
+  llvm::Function *llvmFunc = moduleTranslation.lookupFunction(funcOp.getName());
+  if (!llvmFunc)
+    return;
+
+  // Build the unique offload entry name using the function as the parent name,
+  // e.g. `__omp_offloading_<device>_<file>_<func>_l<line>`.
+  auto loc = funcOp->getLoc()->findInstanceOf<FileLineColLoc>();
+  auto fileInfoCallBack = [&loc]() {
+    std::string filename = "";
+    std::uint64_t lineNo = 0;
+    if (loc) {
+      filename = loc.getFilename().str();
+      lineNo = loc.getLine();
+    }
+    return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
+                                                 lineNo);
+  };
+
+  llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
+  llvm::TargetRegionEntryInfo entryInfo = ompBuilder->getTargetEntryUniqueInfo(
+      fileInfoCallBack, vfs, funcOp.getName());
+  llvm::SmallString<128> name;
+  ompBuilder->OffloadInfoManager.getTargetRegionEntryFnName(name, entryInfo);
+
+  llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
+  const llvm::DataLayout &dl = llvmModule->getDataLayout();
+  // The entry tracks a pointer to the function, so its size is the store size
+  // of a pointer.
+  int64_t varSize = dl.getTypeStoreSize(
+      llvm::PointerType::getUnqual(llvmModule->getContext()));
+
+  llvm::Constant *addr = llvmFunc;
+  if (ompBuilder->Config.isTargetDevice()) {
+    llvm::PointerType *fnPtrTy = llvm::PointerType::get(
+        llvmModule->getContext(), dl.getProgramAddressSpace());
+    auto *addrGlobal = new llvm::GlobalVariable(
+        *llvmModule, fnPtrTy, /*isConstant=*/true,
+        llvm::GlobalValue::ExternalLinkage, llvmFunc, name, nullptr,
+        llvm::GlobalValue::NotThreadLocal, dl.getDefaultGlobalsAddressSpace());
+    addrGlobal->setVisibility(llvm::GlobalValue::ProtectedVisibility);
+    addr = addrGlobal;
+  }
+
+  ompBuilder->OffloadInfoManager.registerDeviceGlobalVarEntryInfo(
+      name, addr, varSize,
+      llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryIndirect,
+      llvm::GlobalValue::WeakODRLinkage);
+}
+
 static LogicalResult
 convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
                          llvm::OpenMPIRBuilder *ompBuilder,
@@ -9722,13 +9785,25 @@ convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
   if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
     if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
             op->getParentOfType<ModuleOp>().getOperation())) {
-      if (!offloadMod.getIsTargetDevice())
-        return success();
-
+      bool isTargetDevice = offloadMod.getIsTargetDevice();
       omp::DeclareTargetDeviceType declareType =
           attribute.getDeviceType().getValue();
+      bool isHostFunc = declareType == omp::DeclareTargetDeviceType::host;
+
+      // A `declare target ... indirect(.true.)` function must be registered so
+      // that indirect calls to it from within a target region can be resolved
+      // by the runtime. This applies to both host and device compilation, but
+      // not to host-only functions that are about to be deleted on the device.
+      mlir::BoolAttr indirectAttr = attribute.getIndirect();
+      if (indirectAttr && indirectAttr.getValue() &&
+          !(isTargetDevice && isHostFunc))
+        registerIndirectDeclareTargetFunction(funcOp, ompBuilder,
+                                              moduleTranslation);
+
+      if (!isTargetDevice)
+        return success();
 
-      if (declareType == omp::DeclareTargetDeviceType::host) {
+      if (isHostFunc) {
         llvm::Function *llvmFunc =
             moduleTranslation.lookupFunction(funcOp.getName());
         llvmFunc->dropAllReferences();
diff --git a/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir b/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir
new file mode 100644
index 0000000000000..3ebdcb66cd017
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir
@@ -0,0 +1,39 @@
+// RUN: mlir-opt -omp-mark-declare-target -split-input-file %s | FileCheck %s
+
+// The `omp-mark-declare-target` pass marks functions that are reachable from
+// explicit target code as implicitly declare target. The `indirect` modifier
+// however is a property of the specific declare target declaration and must NOT
+// be propagated to functions that are only reached through (direct) calls.
+
+// A function explicitly declared `indirect` that directly calls another
+// function: the callee is implicitly captured and must be marked declare target
+// with `indirect = false`, not inherit the parent's `indirect = true`.
+module {
+  // CHECK: func.func @indirect_parent() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  func.func @indirect_parent() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>} {
+    func.call @direct_callee() : () -> ()
+    return
+  }
+
+  // CHECK: func.func @direct_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = false>}
+  func.func @direct_callee() {
+    return
+  }
+}
+
+// -----
+
+// A callee that is itself explicitly declared `indirect` keeps its own value
+// (the pass must not clobber it).
+module {
+  // CHECK: func.func @indirect_parent2() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  func.func @indirect_parent2() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>} {
+    func.call @explicitly_indirect_callee() : () -> ()
+    return
+  }
+
+  // CHECK: func.func @explicitly_indirect_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  func.func @explicitly_indirect_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>} {
+    return
+  }
+}
diff --git a/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir
new file mode 100644
index 0000000000000..ad86bed2770fa
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir
@@ -0,0 +1,19 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Test the device-side lowering of `declare target ... indirect` functions. A
+// new global holding the address of the function is generated with protected
+// visibility so the runtime can access it. A function marked `indirect = false`
+// must not generate such a global.
+
+module attributes {llvm.target_triple = "amdgcn-amd-amdhsa", omp.is_target_device = true} {
+  // CHECK: @[[ENTRY:__omp_offloading_[0-9a-z]+_[0-9a-z]+_indirect_fn_l[0-9]+]] = protected constant ptr @indirect_fn
+  llvm.func @indirect_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = true>} {
+    llvm.return
+  }
+
+  // A function marked `indirect = false` must not produce an indirect global.
+  // CHECK-NOT: plain_fn_l
+  llvm.func @plain_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = false>} {
+    llvm.return
+  }
+}
diff --git a/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir
new file mode 100644
index 0000000000000..f3b098018d196
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir
@@ -0,0 +1,20 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Test the host-side lowering of `declare target ... indirect` functions. The
+// runtime needs an offload entry (flag 8 == OMPTargetGlobalVarEntryIndirect)
+// so that indirect calls within a target region can be resolved. A function
+// marked `indirect = false` must not register an offload entry.
+
+// CHECK-DAG: %struct.__tgt_offload_entry = type { i64, i16, i16, i32, ptr, ptr, i64, i64, ptr }
+module attributes {llvm.target_triple = "x86_64-unknown-linux-gnu", omp.is_target_device = false} {
+  // CHECK: @.offloading.entry.[[ENTRY:__omp_offloading_[0-9a-z]+_[0-9a-z]+_indirect_fn_l[0-9]+]] = weak constant %struct.__tgt_offload_entry { i64 0, i16 1, i16 1, i32 8, ptr @indirect_fn, ptr @{{.*}}, i64 8, i64 0, ptr null }
+  llvm.func @indirect_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = true>} {
+    llvm.return
+  }
+
+  // A function marked `indirect = false` must not produce an offload entry.
+  // CHECK-NOT: plain_fn_l
+  llvm.func @plain_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = false>} {
+    llvm.return
+  }
+}

>From 9daf6f4715adc53634d4a4a6bff21618e24524fc Mon Sep 17 00:00:00 2001
From: Jay Satish Kumar Patel <kumarpat at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 29 Jul 2026 02:19:58 -0500
Subject: [PATCH 2/4]  Rewrite device indirect calls via indirect-call lookup;
 fix CIR build and TO+INDIRECT

---
 clang/lib/CIR/CodeGen/CIRGenOpenMPRuntime.cpp |  3 +-
 .../include/flang/Optimizer/OpenMP/Passes.td  | 11 +++
 flang/lib/Optimizer/OpenMP/CMakeLists.txt     |  1 +
 .../Optimizer/OpenMP/IndirectCallLookup.cpp   | 93 +++++++++++++++++++
 flang/lib/Optimizer/Passes/Pipelines.cpp      |  2 +
 flang/lib/Semantics/check-omp-structure.cpp   |  2 +-
 .../Transforms/omp-indirect-call-lookup.fir   | 63 +++++++++++++
 7 files changed, 173 insertions(+), 2 deletions(-)
 create mode 100644 flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp
 create mode 100644 flang/test/Transforms/omp-indirect-call-lookup.fir

diff --git a/clang/lib/CIR/CodeGen/CIRGenOpenMPRuntime.cpp b/clang/lib/CIR/CodeGen/CIRGenOpenMPRuntime.cpp
index 93ec2248f76eb..88615aac9057b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenOpenMPRuntime.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenOpenMPRuntime.cpp
@@ -190,5 +190,6 @@ void CIRGenOpenMPRuntime::emitDeclareTargetFunction(const FunctionDecl *fd,
       llvm::cast<mlir::omp::DeclareTargetInterface>(funcOp.getOperation());
   declTargetIface.setDeclareTarget(convertDeviceType(attr->getDevType()),
                                    convertCaptureClause(attr->getMapType()),
-                                   /*automap=*/false, /*implicit=*/false);
+                                   /*automap=*/false, /*implicit=*/false,
+                                   /*indirect=*/false);
 }
diff --git a/flang/include/flang/Optimizer/OpenMP/Passes.td b/flang/include/flang/Optimizer/OpenMP/Passes.td
index 08272d24252ee..d917913e8fd92 100644
--- a/flang/include/flang/Optimizer/OpenMP/Passes.td
+++ b/flang/include/flang/Optimizer/OpenMP/Passes.td
@@ -117,6 +117,17 @@ def AutomapToTargetDataPass
   let dependentDialects = ["mlir::omp::OpenMPDialect"];
 }
 
+def IndirectCallLookupPass
+    : Pass<"omp-indirect-call-lookup", "mlir::ModuleOp"> {
+  let summary = "Route device indirect calls through the OpenMP indirect-call "
+                "lookup runtime function.";
+  let dependentDialects = [
+    "mlir::func::FuncDialect",
+    "fir::FIROpsDialect",
+    "mlir::omp::OpenMPDialect"
+  ];
+}
+
 def UnimplementedDeviceCheckPass
     : Pass<"omp-unimplemented-device-check", "::mlir::ModuleOp"> {
   let summary = "Report not-yet-implemented situations on target device code";
diff --git a/flang/lib/Optimizer/OpenMP/CMakeLists.txt b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
index f29ba86a8a28c..31b19f8e18bc4 100644
--- a/flang/lib/Optimizer/OpenMP/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
@@ -4,6 +4,7 @@ add_flang_library(FlangOpenMPTransforms
   AutomapToTargetData.cpp
   DoConcurrentConversion.cpp
   GenericLoopConversion.cpp
+  IndirectCallLookup.cpp
   MapsForPrivatizedSymbols.cpp
   MapInfoFinalization.cpp
   LowerWorkdistribute.cpp
diff --git a/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp b/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp
new file mode 100644
index 0000000000000..c530b823ef703
--- /dev/null
+++ b/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp
@@ -0,0 +1,93 @@
+//===- IndirectCallLookup.cpp ---------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// On a GPU target device, rewrites indirect fir.call ops so the callee (a host
+// function address held in a procedure pointer) is resolved to the device
+// address via the `__llvm_omp_indirect_call_lookup` runtime function.
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/Dialect/FIRDialect.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/OpenMP/Passes.h"
+
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
+#include "mlir/IR/Builders.h"
+#include "llvm/ADT/SmallVector.h"
+
+namespace flangomp {
+#define GEN_PASS_DEF_INDIRECTCALLLOOKUPPASS
+#include "flang/Optimizer/OpenMP/Passes.h.inc"
+} // namespace flangomp
+
+using namespace mlir;
+
+/// Runtime function that maps a host function address to the device address.
+static constexpr llvm::StringRef indirectCallLookupName =
+    "__llvm_omp_indirect_call_lookup";
+
+namespace {
+class IndirectCallLookupPass
+    : public flangomp::impl::IndirectCallLookupPassBase<IndirectCallLookupPass> {
+public:
+  void runOnOperation() override {
+    mlir::ModuleOp module = getOperation();
+    auto offloadMod = mlir::dyn_cast<mlir::omp::OffloadModuleInterface>(
+        module.getOperation());
+
+    // Only a GPU target device needs host-to-device address translation.
+    if (!offloadMod || !offloadMod.getIsTargetDevice() ||
+        !offloadMod.getIsGPU())
+      return;
+
+    // An indirect fir.call has no callee symbol; operand 0 is the callee value.
+    llvm::SmallVector<fir::CallOp> indirectCalls;
+    module.walk([&](fir::CallOp call) {
+      if (!call.getCallee())
+        indirectCalls.push_back(call);
+    });
+    if (indirectCalls.empty())
+      return;
+
+    mlir::MLIRContext *ctx = &getContext();
+    mlir::OpBuilder builder(ctx);
+
+    // A function value lowers to a pointer, so an opaque `() -> ()` type matches
+    // the runtime function's ptr argument and result.
+    auto opaqueFnTy = mlir::FunctionType::get(ctx, {}, {});
+
+    // Declare the runtime lookup function once.
+    auto lookupFn =
+        module.lookupSymbol<mlir::func::FuncOp>(indirectCallLookupName);
+    if (!lookupFn) {
+      builder.setInsertionPointToStart(module.getBody());
+      lookupFn = mlir::func::FuncOp::create(
+          builder, module.getLoc(), indirectCallLookupName,
+          mlir::FunctionType::get(ctx, {opaqueFnTy}, {opaqueFnTy}));
+      lookupFn.setPrivate();
+    }
+
+    for (fir::CallOp call : indirectCalls) {
+      builder.setInsertionPoint(call);
+      mlir::Location loc = call.getLoc();
+      mlir::Value callee = call.getOperand(0);
+
+      // Resolve the host callee to the device address, then call through it.
+      mlir::Value hostAddr =
+          fir::ConvertOp::create(builder, loc, opaqueFnTy, callee);
+      auto lookup = fir::CallOp::create(builder, loc, lookupFn,
+                                        mlir::ValueRange{hostAddr});
+      mlir::Value deviceCallee = fir::ConvertOp::create(
+          builder, loc, callee.getType(), lookup.getResult(0));
+      call.setOperand(0, deviceCallee);
+    }
+  }
+};
+} // namespace
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 232b27a148ab2..4de405745ba0a 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -381,6 +381,8 @@ void createOpenMPFIRPassPipeline(mlir::PassManager &pm,
   pm.addPass(flangomp::createMapInfoFinalizationPass());
 
   pm.addPass(flangomp::createGenericLoopConversionPass());
+  if (opts.isTargetDevice)
+    pm.addPass(flangomp::createIndirectCallLookupPass());
 }
 
 void createDebugPasses(mlir::PassManager &pm,
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 5c0c6f4dd4424..31954fc510f79 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -2702,7 +2702,7 @@ void OmpStructureChecker::Leave(const parser::OmpDeclareTargetDirective &x) {
       context_.Say(x.source,
           "If the DECLARE TARGET directive has a clause, it must contain at least one ENTER clause or LINK clause"_err_en_US);
     }
-    if (indirectClause && !enterClause) {
+    if (indirectClause && !enterClause && !toClause) {
       context_.Say(x.source,
           "The INDIRECT clause cannot be used without the ENTER clause with the DECLARE TARGET directive."_err_en_US);
     }
diff --git a/flang/test/Transforms/omp-indirect-call-lookup.fir b/flang/test/Transforms/omp-indirect-call-lookup.fir
new file mode 100644
index 0000000000000..3b2af2a43f0d1
--- /dev/null
+++ b/flang/test/Transforms/omp-indirect-call-lookup.fir
@@ -0,0 +1,63 @@
+// RUN: fir-opt --split-input-file --omp-indirect-call-lookup %s | FileCheck %s
+
+// On a GPU target device, indirect calls are routed through the lookup so the
+// host callee address is resolved to the device address.
+
+// CHECK: func.func private @__llvm_omp_indirect_call_lookup(() -> ()) -> (() -> ())
+module attributes {omp.is_target_device = true, omp.is_gpu = true} {
+  // CHECK-LABEL: func.func @gpu_device_caller
+  func.func @gpu_device_caller(%arg0: !fir.ref<!fir.boxproc<() -> i32>>) -> i32 {
+    %0 = fir.load %arg0 : !fir.ref<!fir.boxproc<() -> i32>>
+    %1 = fir.box_addr %0 : (!fir.boxproc<() -> i32>) -> (() -> i32)
+    // CHECK: %[[BOXADDR:.*]] = fir.box_addr
+    // CHECK: %[[HOST:.*]] = fir.convert %[[BOXADDR]] : (() -> i32) -> (() -> ())
+    // CHECK: %[[DEV:.*]] = fir.call @__llvm_omp_indirect_call_lookup(%[[HOST]]) : (() -> ()) -> (() -> ())
+    // CHECK: %[[CALLEE:.*]] = fir.convert %[[DEV]] : (() -> ()) -> (() -> i32)
+    // CHECK: fir.call %[[CALLEE]]() : () -> i32
+    %2 = fir.call %1() : () -> i32
+    return %2 : i32
+  }
+}
+
+// -----
+
+// A non-GPU target device shares the host address space, so no lookup is added.
+module attributes {omp.is_target_device = true, omp.is_gpu = false} {
+  // CHECK-LABEL: func.func @nongpu_device_caller
+  // CHECK-NOT: __llvm_omp_indirect_call_lookup
+  func.func @nongpu_device_caller(%arg0: !fir.ref<!fir.boxproc<() -> i32>>) -> i32 {
+    %0 = fir.load %arg0 : !fir.ref<!fir.boxproc<() -> i32>>
+    %1 = fir.box_addr %0 : (!fir.boxproc<() -> i32>) -> (() -> i32)
+    // CHECK: fir.call %{{.*}}() : () -> i32
+    %2 = fir.call %1() : () -> i32
+    return %2 : i32
+  }
+}
+
+// -----
+
+// Host compilation: no lookup is added.
+module attributes {omp.is_target_device = false, omp.is_gpu = false} {
+  // CHECK-LABEL: func.func @host_caller
+  // CHECK-NOT: __llvm_omp_indirect_call_lookup
+  func.func @host_caller(%arg0: !fir.ref<!fir.boxproc<() -> i32>>) -> i32 {
+    %0 = fir.load %arg0 : !fir.ref<!fir.boxproc<() -> i32>>
+    %1 = fir.box_addr %0 : (!fir.boxproc<() -> i32>) -> (() -> i32)
+    %2 = fir.call %1() : () -> i32
+    return %2 : i32
+  }
+}
+
+// -----
+
+// Direct calls are left untouched on a GPU target device.
+module attributes {omp.is_target_device = true, omp.is_gpu = true} {
+  func.func private @callee() -> i32
+  // CHECK-LABEL: func.func @direct_caller
+  // CHECK-NOT: __llvm_omp_indirect_call_lookup
+  func.func @direct_caller() -> i32 {
+    // CHECK: fir.call @callee() : () -> i32
+    %0 = fir.call @callee() : () -> i32
+    return %0 : i32
+  }
+}

>From 380e6f6c7c1dc68f9dd4802f54853f10af1f49bb Mon Sep 17 00:00:00 2001
From: Jay Satish Kumar Patel <kumarpat at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 29 Jul 2026 02:33:04 -0500
Subject: [PATCH 3/4] [Flang][OpenMP] Test INDIRECT with the TO clause on
 DECLARE TARGET

---
 flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp | 7 ++++---
 flang/test/Semantics/indirect02.f90               | 9 +++++++++
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp b/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp
index c530b823ef703..042229ba08690 100644
--- a/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp
+++ b/flang/lib/Optimizer/OpenMP/IndirectCallLookup.cpp
@@ -35,7 +35,8 @@ static constexpr llvm::StringRef indirectCallLookupName =
 
 namespace {
 class IndirectCallLookupPass
-    : public flangomp::impl::IndirectCallLookupPassBase<IndirectCallLookupPass> {
+    : public flangomp::impl::IndirectCallLookupPassBase<
+          IndirectCallLookupPass> {
 public:
   void runOnOperation() override {
     mlir::ModuleOp module = getOperation();
@@ -59,8 +60,8 @@ class IndirectCallLookupPass
     mlir::MLIRContext *ctx = &getContext();
     mlir::OpBuilder builder(ctx);
 
-    // A function value lowers to a pointer, so an opaque `() -> ()` type matches
-    // the runtime function's ptr argument and result.
+    // A function value lowers to a pointer, so an opaque `() -> ()` type
+    // matches the runtime function's ptr argument and result.
     auto opaqueFnTy = mlir::FunctionType::get(ctx, {}, {});
 
     // Declare the runtime lookup function once.
diff --git a/flang/test/Semantics/indirect02.f90 b/flang/test/Semantics/indirect02.f90
index 8835919341a9b..2f04d2efe9d6a 100644
--- a/flang/test/Semantics/indirect02.f90
+++ b/flang/test/Semantics/indirect02.f90
@@ -22,6 +22,15 @@ function func1() result(i)
     i = 'a'
     return
   end function
+
+  ! TO is the pre-5.2 spelling of ENTER, so INDIRECT is accepted with it too.
+  function func2() result(i)
+    !CHECK-52: !$OMP DECLARE TARGET TO(func2) INDIRECT(.true._4)
+    !$omp declare target to(func2) indirect(.true.)
+    character(1) :: i
+    i = 'b'
+    return
+  end function
 end module
 
 program main

>From b28df8185db4ede7ae0518979df9d1104fd9f12a Mon Sep 17 00:00:00 2001
From: Jay Satish Kumar Patel <kumarpat at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 14 Aug 2026 04:17:10 -0500
Subject: [PATCH 4/4] [Flang][OpenMP] Fix INDIRECT clause argument handling in
 semantics and lowering

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    |  6 ++---
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |  2 +-
 flang/lib/Semantics/check-omp-structure.cpp   | 16 ++++++++++---
 .../OpenMP/declare-target-indirect-clause.f90 |  7 +++---
 .../OpenMP/declare-target-indirect-merge.f90  |  6 ++---
 .../Lower/OpenMP/declare-target-indirect.f90  | 23 ++++++++++++++++++-
 flang/test/Semantics/indirect01.f90           |  2 +-
 flang/test/Semantics/indirect03.f90           | 21 +++++++++++++++++
 .../OpenMP/Transforms/FunctionFiltering.cpp   |  3 ++-
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  4 +---
 .../OpenMP/mark-declare-target-indirect.mlir  | 18 +++++++--------
 ...target-declare-target-indirect-device.mlir |  4 ++--
 ...mptarget-declare-target-indirect-host.mlir |  4 ++--
 13 files changed, 84 insertions(+), 32 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index fd1af2f812ea5..3376ec4634ac7 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -23,6 +23,7 @@
 #include "flang/Optimizer/Builder/HLFIRTools.h"
 #include "flang/Optimizer/Dialect/FIRType.h"
 #include "flang/Optimizer/Support/InternalNames.h"
+#include "flang/Semantics/openmp-utils.h"
 #include "flang/Semantics/tools.h"
 #include "flang/Utils/OpenMP.h"
 #include "llvm/Frontend/OpenMP/OMP.h.inc"
@@ -489,9 +490,8 @@ bool ClauseProcessor::processIndirect(
     if (clause->v) {
       auto foldedExpr = Fortran::evaluate::Fold(
           semaCtx.foldingContext(), Fortran::common::Clone(*clause->v));
-      if (auto logicalVal = Fortran::evaluate::GetScalarConstantValue<
-              Fortran::evaluate::LogicalResult>(foldedExpr))
-        isIndirect = logicalVal->IsTrue();
+      // The argument may have any logical kind, not just logical(4).
+      isIndirect = semantics::omp::GetLogicalValue(foldedExpr).value_or(true);
     }
     result.indirect = isIndirect;
     return true;
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index 05622e5e3ede3..9e84881907d1c 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -94,6 +94,7 @@ class ClauseProcessor {
   bool processInbranch(mlir::omp::InbranchClauseOps &result) const;
   bool processInclusive(mlir::Location currentLocation,
                         mlir::omp::InclusiveClauseOps &result) const;
+  bool processIndirect(mlir::omp::IndirectClauseOps &result) const;
   bool processInitializer(
       lower::SymMap &symMap,
       ReductionProcessor::GenInitValueCBTy &genInitValueCB,
@@ -146,7 +147,6 @@ class ClauseProcessor {
   processEnter(llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &result) const;
   bool processIf(omp::clause::If::DirectiveNameModifier directiveName,
                  mlir::omp::IfClauseOps &result) const;
-  bool processIndirect(mlir::omp::IndirectClauseOps &result) const;
   bool
   processInReduction(mlir::Location currentLocation,
                      mlir::omp::InReductionClauseOps &result,
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 31954fc510f79..6f3f12185dcf9 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -2704,7 +2704,7 @@ void OmpStructureChecker::Leave(const parser::OmpDeclareTargetDirective &x) {
     }
     if (indirectClause && !enterClause && !toClause) {
       context_.Say(x.source,
-          "The INDIRECT clause cannot be used without the ENTER clause with the DECLARE TARGET directive."_err_en_US);
+          "The INDIRECT clause cannot be used without the ENTER or TO clause with the DECLARE TARGET directive."_err_en_US);
     }
     unsigned version{context_.langOptions().OpenMPVersion};
     if (toClause && version >= 52) {
@@ -2712,11 +2712,21 @@ void OmpStructureChecker::Leave(const parser::OmpDeclareTargetDirective &x) {
           "The usage of TO clause on DECLARE TARGET directive has been deprecated. Use ENTER clause instead."_warn_en_US);
     }
     if (indirectClause) {
+      // The restriction applies only when the argument evaluates to true; an
+      // absent argument defaults to true.
+      bool isIndirect{true};
+      if (const auto &indirectExpr{
+              std::get<parser::OmpClause::Indirect>(indirectClause->u).v.v}) {
+        const auto &parserExpr{parser::UnwrapRef<parser::Expr>(*indirectExpr)};
+        if (auto &&expr{GetEvaluateExpr(parserExpr)})
+          isIndirect = GetLogicalValue(*expr).value_or(true);
+      }
       // The INDIRECT clause is only allowed together with DEVICE_TYPE(ANY) (an
       // absent DEVICE_TYPE clause also implies ANY). A host- or device-only
       // procedure cannot be the target of an indirect device invocation.
-      if (const parser::OmpClause *deviceTypeClause{
-              FindClause(llvm::omp::Clause::OMPC_device_type)}) {
+      const parser::OmpClause *deviceTypeClause{
+          FindClause(llvm::omp::Clause::OMPC_device_type)};
+      if (isIndirect && deviceTypeClause) {
         const auto &deviceType{
             std::get<parser::OmpClause::DeviceType>(deviceTypeClause->u)};
         if (deviceType.v.v !=
diff --git a/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90 b/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90
index ced2535925888..e4c0a071afd47 100644
--- a/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90
+++ b/flang/test/Lower/OpenMP/declare-target-indirect-clause.f90
@@ -7,21 +7,22 @@
 module functions
   implicit none
 contains
-  ! CHECK: func.func @_QMfunctionsPfunc_true({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  ! CHECK: func.func @_QMfunctionsPfunc_true({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
   function func_true() result(i)
     !$omp declare target enter(func_true) indirect(.true.)
     character(1) :: i
     i = 'a'
   end function
 
-  ! CHECK: func.func @_QMfunctionsPfunc_implicit({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  ! CHECK: func.func @_QMfunctionsPfunc_implicit({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
   function func_implicit() result(i)
     !$omp declare target enter(func_implicit) indirect
     character(1) :: i
     i = 'b'
   end function
 
-  ! CHECK: func.func @_QMfunctionsPfunc_false({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = false>}
+  ! A false value equals the attribute default, so no `indirect` field prints.
+  ! CHECK: func.func @_QMfunctionsPfunc_false({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter)>}
   function func_false() result(i)
     !$omp declare target enter(func_false) indirect(.false.)
     character(1) :: i
diff --git a/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90 b/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90
index cee898cb84bee..c417c88c54ec7 100644
--- a/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90
+++ b/flang/test/Lower/OpenMP/declare-target-indirect-merge.f90
@@ -9,7 +9,7 @@ module m1
   implicit none
 contains
   ! A later directive adds `indirect` with the same (default) device_type.
-  ! CHECK: func.func @_QMm1Pfoo1() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  ! CHECK: func.func @_QMm1Pfoo1() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
   function foo1() result(i)
     !$omp declare target enter(foo1)
     !$omp declare target enter(foo1) indirect(.true.)
@@ -22,7 +22,7 @@ module m2
   implicit none
 contains
   ! `indirect` first, plain second: it must stay set.
-  ! CHECK: func.func @_QMm2Pfoo2() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  ! CHECK: func.func @_QMm2Pfoo2() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
   function foo2() result(i)
     !$omp declare target enter(foo2) indirect(.true.)
     !$omp declare target enter(foo2)
@@ -37,7 +37,7 @@ module m3
   ! `indirect` (device_type any) followed by a device_type(nohost) declaration:
   ! the device type merges to `any` and the `indirect = true` must be carried
   ! over rather than overwritten.
-  ! CHECK: func.func @_QMm3Pfoo3() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  ! CHECK: func.func @_QMm3Pfoo3() -> i32 attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
   function foo3() result(i)
     !$omp declare target enter(foo3) indirect(.true.)
     !$omp declare target enter(foo3) device_type(nohost)
diff --git a/flang/test/Lower/OpenMP/declare-target-indirect.f90 b/flang/test/Lower/OpenMP/declare-target-indirect.f90
index 3cd87d9b82575..ce287b513ab38 100644
--- a/flang/test/Lower/OpenMP/declare-target-indirect.f90
+++ b/flang/test/Lower/OpenMP/declare-target-indirect.f90
@@ -15,13 +15,29 @@ function func() result(i)
   end interface
 
 contains
-  ! CHECK: func.func @_QMfunctionsPfunc1({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
+  ! CHECK: func.func @_QMfunctionsPfunc1({{.*}}attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
   function func1() result(i)
     !$omp declare target enter(func1) indirect(.true.)
     character(1) :: i
     i = 'a'
     return
   end function
+
+  ! The argument may have any logical kind, not just the default one. A false
+  ! value matches the attribute default, so no `indirect` field is printed.
+  ! CHECK: func.func @_QMfunctionsPfunc2({{.*}}capture_clause = (enter)>}
+  function func2() result(i)
+    !$omp declare target enter(func2) indirect(.false._1)
+    character(1) :: i
+    i = 'b'
+  end function
+
+  ! CHECK: func.func @_QMfunctionsPfunc3({{.*}}capture_clause = (enter)>}
+  function func3() result(i)
+    !$omp declare target enter(func3) indirect(.false._8)
+    character(1) :: i
+    i = 'c'
+  end function
 end module
 
 program main
@@ -32,6 +48,11 @@ program main
 
   ! CHECK-LABEL: func.func @_QQmain()
   ! CHECK: omp.target
+  ! The procedure pointer is resolved to a callable address that is then used
+  ! as the callee of an indirect fir.call.
+  ! CHECK: %[[PROC:.*]] = fir.load %{{.*}} : !fir.ref<!fir.boxproc<{{.*}}>>
+  ! CHECK: %[[CALLEE:.*]] = fir.box_addr %[[PROC]] : (!fir.boxproc<{{.*}}>) -> {{.*}}
+  ! CHECK: fir.call %[[CALLEE]](
   !$omp target map(from: val1)
   val1 = ptr1()
   !$omp end target
diff --git a/flang/test/Semantics/indirect01.f90 b/flang/test/Semantics/indirect01.f90
index 81fcfbc94aa3e..67537cc5703d0 100644
--- a/flang/test/Semantics/indirect01.f90
+++ b/flang/test/Semantics/indirect01.f90
@@ -13,7 +13,7 @@ function func() result(i)
 
 contains
   function func1() result(i)
-    !CHECK: The INDIRECT clause cannot be used without the ENTER clause with the DECLARE TARGET directive.
+    !CHECK: The INDIRECT clause cannot be used without the ENTER or TO clause with the DECLARE TARGET directive.
     !$omp declare target indirect(.true.)
     character(1) :: i
     i = 'a'
diff --git a/flang/test/Semantics/indirect03.f90 b/flang/test/Semantics/indirect03.f90
index 0bab188a9ff51..3d47d77a5bef7 100644
--- a/flang/test/Semantics/indirect03.f90
+++ b/flang/test/Semantics/indirect03.f90
@@ -28,4 +28,25 @@ function func_any() result(i)
     character(1) :: i
     i = 'c'
   end function
+
+  ! The restriction only applies when INDIRECT evaluates to true, so a
+  ! device-only procedure is allowed here.
+  function func_false() result(i)
+    !$omp declare target enter(func_false) device_type(nohost) indirect(.false.)
+    character(1) :: i
+    i = 'd'
+  end function
+
+  ! The argument may have any logical kind, not just the default one.
+  function func_false_kind1() result(i)
+    !$omp declare target enter(func_false_kind1) device_type(nohost) indirect(.false._1)
+    character(1) :: i
+    i = 'e'
+  end function
+
+  function func_false_kind8() result(i)
+    !$omp declare target enter(func_false_kind8) device_type(nohost) indirect(.false._8)
+    character(1) :: i
+    i = 'f'
+  end function
 end module
diff --git a/mlir/lib/Dialect/OpenMP/Transforms/FunctionFiltering.cpp b/mlir/lib/Dialect/OpenMP/Transforms/FunctionFiltering.cpp
index 3b236dd69f425..48d571a4c442a 100644
--- a/mlir/lib/Dialect/OpenMP/Transforms/FunctionFiltering.cpp
+++ b/mlir/lib/Dialect/OpenMP/Transforms/FunctionFiltering.cpp
@@ -101,7 +101,8 @@ class FunctionFilteringPass
       if (declareTargetOp && !declareTargetOp.isDeclareTarget())
         declareTargetOp.setDeclareTarget(omp::DeclareTargetDeviceType::host,
                                          omp::DeclareTargetCaptureClause::to,
-                                         /*automap=*/false, /*implicit=*/true);
+                                         /*automap=*/false, /*implicit=*/true,
+                                         /*indirect=*/false);
       return WalkResult::advance();
     });
   }
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 45623a61a42a1..f001385920398 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -9794,9 +9794,7 @@ convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
       // that indirect calls to it from within a target region can be resolved
       // by the runtime. This applies to both host and device compilation, but
       // not to host-only functions that are about to be deleted on the device.
-      mlir::BoolAttr indirectAttr = attribute.getIndirect();
-      if (indirectAttr && indirectAttr.getValue() &&
-          !(isTargetDevice && isHostFunc))
+      if (attribute.getIndirect() && !(isTargetDevice && isHostFunc))
         registerIndirectDeclareTargetFunction(funcOp, ompBuilder,
                                               moduleTranslation);
 
diff --git a/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir b/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir
index 3ebdcb66cd017..97714f81a060d 100644
--- a/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir
+++ b/mlir/test/Dialect/OpenMP/mark-declare-target-indirect.mlir
@@ -6,16 +6,16 @@
 // be propagated to functions that are only reached through (direct) calls.
 
 // A function explicitly declared `indirect` that directly calls another
-// function: the callee is implicitly captured and must be marked declare target
-// with `indirect = false`, not inherit the parent's `indirect = true`.
+// function: the callee is implicitly captured (marked with `implicit = true`)
+// and must not inherit the parent's `indirect = true`.
 module {
-  // CHECK: func.func @indirect_parent() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
-  func.func @indirect_parent() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>} {
+  // CHECK: func.func @indirect_parent() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
+  func.func @indirect_parent() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>} {
     func.call @direct_callee() : () -> ()
     return
   }
 
-  // CHECK: func.func @direct_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = false>}
+  // CHECK: func.func @direct_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (to), implicit = true>}
   func.func @direct_callee() {
     return
   }
@@ -26,14 +26,14 @@ module {
 // A callee that is itself explicitly declared `indirect` keeps its own value
 // (the pass must not clobber it).
 module {
-  // CHECK: func.func @indirect_parent2() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
-  func.func @indirect_parent2() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>} {
+  // CHECK: func.func @indirect_parent2() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
+  func.func @indirect_parent2() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>} {
     func.call @explicitly_indirect_callee() : () -> ()
     return
   }
 
-  // CHECK: func.func @explicitly_indirect_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>}
-  func.func @explicitly_indirect_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false, indirect = true>} {
+  // CHECK: func.func @explicitly_indirect_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>}
+  func.func @explicitly_indirect_callee() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), indirect = true>} {
     return
   }
 }
diff --git a/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir
index ad86bed2770fa..07242b603613e 100644
--- a/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir
+++ b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-device.mlir
@@ -7,13 +7,13 @@
 
 module attributes {llvm.target_triple = "amdgcn-amd-amdhsa", omp.is_target_device = true} {
   // CHECK: @[[ENTRY:__omp_offloading_[0-9a-z]+_[0-9a-z]+_indirect_fn_l[0-9]+]] = protected constant ptr @indirect_fn
-  llvm.func @indirect_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = true>} {
+  llvm.func @indirect_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), indirect = true>} {
     llvm.return
   }
 
   // A function marked `indirect = false` must not produce an indirect global.
   // CHECK-NOT: plain_fn_l
-  llvm.func @plain_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = false>} {
+  llvm.func @plain_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter)>} {
     llvm.return
   }
 }
diff --git a/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir
index f3b098018d196..aa9d88b3f0f64 100644
--- a/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir
+++ b/mlir/test/Target/LLVMIR/omptarget-declare-target-indirect-host.mlir
@@ -8,13 +8,13 @@
 // CHECK-DAG: %struct.__tgt_offload_entry = type { i64, i16, i16, i32, ptr, ptr, i64, i64, ptr }
 module attributes {llvm.target_triple = "x86_64-unknown-linux-gnu", omp.is_target_device = false} {
   // CHECK: @.offloading.entry.[[ENTRY:__omp_offloading_[0-9a-z]+_[0-9a-z]+_indirect_fn_l[0-9]+]] = weak constant %struct.__tgt_offload_entry { i64 0, i16 1, i16 1, i32 8, ptr @indirect_fn, ptr @{{.*}}, i64 8, i64 0, ptr null }
-  llvm.func @indirect_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = true>} {
+  llvm.func @indirect_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), indirect = true>} {
     llvm.return
   }
 
   // A function marked `indirect = false` must not produce an offload entry.
   // CHECK-NOT: plain_fn_l
-  llvm.func @plain_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false, indirect = false>} {
+  llvm.func @plain_fn() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter)>} {
     llvm.return
   }
 }



More information about the Mlir-commits mailing list