[Openmp-commits] [flang] [llvm] [mlir] [openmp] [Flang][OpenMP] Add support for interop construct (PR #203959)

Sunil Shrestha via Openmp-commits openmp-commits at lists.llvm.org
Wed Jul 8 09:49:16 PDT 2026


https://github.com/sshrestha-aa updated https://github.com/llvm/llvm-project/pull/203959

>From d355f314c8f325671d739c84b892dc8153b79639 Mon Sep 17 00:00:00 2001
From: Sunil Shrestha <sunil.shrestha at hpe.com>
Date: Mon, 1 Jun 2026 17:48:49 -0500
Subject: [PATCH 1/2] [Flang][OpenMP] Add support for interop construct

This adds support for the OpenMP interop construct, which enables
interoperability with foreign runtime environments (e.g., CUDA, HIP).
The init, use, and destroy action clauses are lowered from the Fortran
parse tree to MLIR interop ops and translated to the appropriate runtime
calls. The omp_lib module is extended with interop query API interfaces,
property constants, and return code constants. A semantics fix relaxes
the depend/targetsync constraint for use/destroy directives where the
interop type was established at a separate init site. Unsupported depend
clauses and prefer_type selector-list forms emit explicit diagnostics
instead of being silently dropped.

Assisted-by: Claude Opus 4.6
---
 flang/lib/Lower/OpenMP/Clauses.cpp            |  50 +++++-
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 142 +++++++++++++++++-
 flang/lib/Semantics/check-omp-structure.cpp   |   7 +-
 .../Lower/OpenMP/Todo/interop-construct.f90   |   9 --
 flang/test/Lower/OpenMP/interop.f90           | 114 ++++++++++++++
 .../mlir/Dialect/OpenMP/OpenMPAttrDefs.td     |  10 ++
 .../mlir/Dialect/OpenMP/OpenMPEnums.td        |  18 +++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 112 ++++++++++++++
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |  40 +++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 101 +++++++++++++
 .../Target/LLVMIR/openmp-interop-depend.mlir  |   7 +
 mlir/test/Target/LLVMIR/openmp-interop.mlir   |  71 +++++++++
 openmp/module/omp_lib.F90.var                 |  94 ++++++++++++
 13 files changed, 761 insertions(+), 14 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/interop-construct.f90
 create mode 100644 flang/test/Lower/OpenMP/interop.f90
 create mode 100644 mlir/test/Target/LLVMIR/openmp-interop-depend.mlir
 create mode 100644 mlir/test/Target/LLVMIR/openmp-interop.mlir

diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index 03852dc9e7d74..49cf6e5556445 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1030,8 +1030,54 @@ Indirect make(const parser::OmpClause::Indirect &inp,
 
 Init make(const parser::OmpClause::Init &inp,
           semantics::SemanticsContext &semaCtx) {
-  // inp -> empty
-  llvm_unreachable("Empty: init");
+  // OmpInitClause has modifiers: OmpPreferType, OmpInteropType,
+  //   OmpDepinfoModifier, and an OmpObject (the interop variable).
+  auto &mods = semantics::OmpGetModifiers(inp.v);
+
+  // Extract interop types (repeatable modifier).
+  Init::InteropTypes interopTypes;
+  for (auto *interopType :
+       semantics::OmpGetRepeatableModifier<parser::OmpInteropType>(mods)) {
+    switch (interopType->v) {
+    case parser::OmpInteropType::Value::Target:
+      interopTypes.push_back(Init::InteropType::Target);
+      break;
+    case parser::OmpInteropType::Value::Targetsync:
+      interopTypes.push_back(Init::InteropType::Targetsync);
+      break;
+    }
+  }
+
+  // Extract prefer_type (optional modifier).
+  std::optional<Init::InteropPreference> interopPreference;
+  if (auto *preferType =
+          semantics::OmpGetUniqueModifier<parser::OmpPreferType>(mods)) {
+    Init::InteropPreference prefs;
+    for (auto &prefSpec : preferType->v) {
+      common::visit(
+          common::visitors{
+              [&](const parser::OmpPreferenceSpecification::
+                      ForeignRuntimeIdentifier &fri) {
+                prefs.push_back(makeExpr(fri.value(), semaCtx));
+              },
+              [&](const std::list<parser::OmpPreferenceSelector> &selectors) {
+                // TODO: Handle preference-selector lists.
+                if (!selectors.empty())
+                  llvm::report_fatal_error(
+                      "not yet implemented: prefer_type with "
+                      "preference-selector lists in interop init clause");
+              },
+          },
+          prefSpec.u);
+    }
+    if (!prefs.empty())
+      interopPreference = std::move(prefs);
+  }
+
+  auto &interopVar = std::get<parser::OmpObject>(inp.v.t);
+  return Init{{/*InteropPreference=*/std::move(interopPreference),
+               /*InteropTypes=*/std::move(interopTypes),
+               /*InteropVar=*/makeObject(interopVar, semaCtx)}};
 }
 
 Initializer make(const parser::OmpClause::Initializer &inp,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 1ed79b077d57a..47a70efeb293a 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -19,6 +19,7 @@
 #include "Utils.h"
 #include "flang/Common/idioms.h"
 #include "flang/Evaluate/expression.h"
+#include "flang/Evaluate/fold.h"
 #include "flang/Evaluate/tools.h"
 #include "flang/Evaluate/type.h"
 #include "flang/Lower/Bridge.h"
@@ -53,6 +54,7 @@
 #include "mlir/Support/StateStack.h"
 #include "mlir/Transforms/RegionUtils.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringSwitch.h"
 
 using namespace Fortran::lower::omp;
 using namespace Fortran::common::openmp;
@@ -5107,8 +5109,144 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
                    const parser::OpenMPInteropConstruct &interopConstruct) {
-  if (!semaCtx.langOptions().OpenMPSimd)
-    TODO(converter.getCurrentLocation(), "OpenMPInteropConstruct");
+  if (semaCtx.langOptions().OpenMPSimd)
+    return;
+
+  fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+  mlir::Location loc = converter.genLocation(interopConstruct.source);
+  mlir::MLIRContext *context = firOpBuilder.getContext();
+
+  List<Clause> clauses = makeClauses(interopConstruct.v.Clauses(), semaCtx);
+
+  // Process shared clauses (depend, nowait, device) that apply to all
+  // action clauses.
+  lower::StatementContext stmtCtx;
+  mlir::omp::DependClauseOps dependOps;
+  mlir::omp::NowaitClauseOps nowaitOps;
+  mlir::Value deviceValue;
+
+  ClauseProcessor cp(converter, semaCtx, clauses);
+  cp.processDepend(symTable, stmtCtx, dependOps);
+  cp.processNowait(nowaitOps);
+
+  // Process device clause manually.
+  for (auto &clause : clauses) {
+    if (auto *deviceClause = std::get_if<clause::Device>(&clause.u)) {
+      const auto &deviceExpr = std::get<SomeExpr>(deviceClause->t);
+      deviceValue = fir::getBase(converter.genExprValue(deviceExpr, stmtCtx));
+    }
+  }
+
+  // Helper to get the address of an interop variable from an Object.
+  auto getInteropVarAddr = [&](const Object &object) -> mlir::Value {
+    const semantics::Symbol *sym = object.sym();
+    assert(sym && "interop variable must have a symbol");
+    mlir::Value addr = converter.getSymbolAddress(*sym);
+    assert(addr && "interop variable must have a valid address");
+    return addr;
+  };
+
+  // Process action clauses: init, destroy, use.
+  for (auto &clause : clauses) {
+    if (auto *initClause = std::get_if<clause::Init>(&clause.u)) {
+      auto &interopVar = std::get<clause::Init::InteropVar>(initClause->t);
+      auto &interopTypes = std::get<clause::Init::InteropTypes>(initClause->t);
+      auto &interopPref =
+          std::get<std::optional<clause::Init::InteropPreference>>(
+              initClause->t);
+
+      mlir::Value interopVarAddr = getInteropVarAddr(interopVar);
+
+      // Convert interop types to MLIR attributes.
+      llvm::SmallVector<mlir::Attribute> interopTypeAttrs;
+      for (auto itype : interopTypes) {
+        mlir::omp::InteropType mlirType;
+        switch (itype) {
+        case clause::Init::InteropType::Target:
+          mlirType = mlir::omp::InteropType::target;
+          break;
+        case clause::Init::InteropType::Targetsync:
+          mlirType = mlir::omp::InteropType::targetsync;
+          break;
+        }
+        interopTypeAttrs.push_back(
+            mlir::omp::InteropTypeAttr::get(context, mlirType));
+      }
+      auto interopTypesAttr = mlir::ArrayAttr::get(context, interopTypeAttrs);
+
+      // Convert prefer_type to I64ArrayAttr if present.
+      mlir::ArrayAttr preferTypeAttr;
+      if (interopPref) {
+        llvm::SmallVector<int64_t> prefValues;
+        for (auto &pref : *interopPref) {
+          // Try to evaluate as a constant integer (e.g., omp_ifr_cuda).
+          if (auto constVal = evaluate::ToInt64(pref)) {
+            prefValues.push_back(*constVal);
+          } else if (auto str =
+                         evaluate::GetScalarConstantValue<evaluate::Ascii>(
+                             pref)) {
+            // Map standard OpenMP foreign-runtime identifier strings to
+            // their well-known integer values (OpenMP 5.1, Table 22.2).
+            auto frId = llvm::StringSwitch<std::optional<int64_t>>(
+                            llvm::StringRef(*str).lower())
+                            .Case("cuda", 1)
+                            .Case("cuda_driver", 2)
+                            .Case("opencl", 3)
+                            .Case("sycl", 4)
+                            .Case("hip", 5)
+                            .Case("level_zero", 6)
+                            .Case("hsa", 7)
+                            .Default(std::nullopt);
+            if (frId)
+              prefValues.push_back(*frId);
+            else
+              TODO(loc, "unknown foreign-runtime identifier in "
+                        "prefer_type");
+          } else {
+            TODO(loc, "non-constant prefer_type value in interop init");
+          }
+        }
+        if (!prefValues.empty())
+          preferTypeAttr = firOpBuilder.getI64ArrayAttr(prefValues);
+      }
+
+      mlir::omp::InteropInitOp::create(
+          firOpBuilder, loc, interopVarAddr, interopTypesAttr, preferTypeAttr,
+          deviceValue,
+          dependOps.dependKinds.empty()
+              ? nullptr
+              : firOpBuilder.getArrayAttr(dependOps.dependKinds),
+          dependOps.dependVars,
+          /*depend_iterated_kinds=*/nullptr,
+          /*depend_iterated=*/mlir::ValueRange{}, nowaitOps.nowait);
+
+    } else if (auto *destroyClause = std::get_if<clause::Destroy>(&clause.u)) {
+      assert(destroyClause->v &&
+             "destroy clause must have an interop variable");
+      if (destroyClause->v) {
+        mlir::Value interopVarAddr = getInteropVarAddr(*destroyClause->v);
+        mlir::omp::InteropDestroyOp::create(
+            firOpBuilder, loc, interopVarAddr, deviceValue,
+            dependOps.dependKinds.empty()
+                ? nullptr
+                : firOpBuilder.getArrayAttr(dependOps.dependKinds),
+            dependOps.dependVars,
+            /*depend_iterated_kinds=*/nullptr,
+            /*depend_iterated=*/mlir::ValueRange{}, nowaitOps.nowait);
+      }
+
+    } else if (auto *useClause = std::get_if<clause::Use>(&clause.u)) {
+      mlir::Value interopVarAddr = getInteropVarAddr(useClause->v);
+      mlir::omp::InteropUseOp::create(
+          firOpBuilder, loc, interopVarAddr, deviceValue,
+          dependOps.dependKinds.empty()
+              ? nullptr
+              : firOpBuilder.getArrayAttr(dependOps.dependKinds),
+          dependOps.dependVars,
+          /*depend_iterated_kinds=*/nullptr,
+          /*depend_iterated=*/mlir::ValueRange{}, nowaitOps.nowait);
+    }
+  }
 }
 
 static void
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 7c531ae0046ae..333a71705b5b7 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5872,7 +5872,12 @@ void OmpStructureChecker::Enter(const parser::OpenMPInteropConstruct &x) {
     context_.Say(GetContext().directiveSource,
         "Each interop-type may be specified at most once."_err_en_US);
   }
-  if (isDependClauseOccurred && !targetSyncCount) {
+  // Only enforce the depend/targetsync constraint when an init clause is
+  // present on this directive. For use/destroy-only directives, the interop
+  // object's type was established at its init site, which may be in a
+  // different statement — we cannot verify it statically here.
+  bool hasInitClause = (targetCount > 0 || targetSyncCount > 0);
+  if (isDependClauseOccurred && hasInitClause && !targetSyncCount) {
     context_.Say(GetContext().directiveSource,
         "A DEPEND clause can only appear on the directive if the interop-type includes TARGETSYNC"_err_en_US);
   }
diff --git a/flang/test/Lower/OpenMP/Todo/interop-construct.f90 b/flang/test/Lower/OpenMP/Todo/interop-construct.f90
deleted file mode 100644
index 4f69667863990..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/interop-construct.f90
+++ /dev/null
@@ -1,9 +0,0 @@
-! REQUIRES: openmp_runtime
-! RUN: %not_todo_cmd %flang_fc1 -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: OpenMPInteropConstruct
-program interop_test
-  use omp_lib
-  integer(omp_interop_kind) :: obj
-  !$omp interop init(targetsync,target: obj)
-end program interop_test
diff --git a/flang/test/Lower/OpenMP/interop.f90 b/flang/test/Lower/OpenMP/interop.f90
new file mode 100644
index 0000000000000..85ccfdbbef19d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/interop.f90
@@ -0,0 +1,114 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+
+!===============================================================================
+! Interop Init — target
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_init(
+!CHECK-SAME:    %[[ARG:.*]]: !fir.ref<i64> {fir.bindc_name = "obj"})
+!CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ARG]]
+!CHECK:         omp.interop.init %[[DECL]]#0 : !fir.ref<i64> interop_types([#omp<interop_type(target)>])
+subroutine test_interop_init(obj)
+  integer(8) :: obj
+  !$omp interop init(target: obj)
+end subroutine
+
+!===============================================================================
+! Interop Init — targetsync
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_init_targetsync(
+!CHECK:         omp.interop.init %{{.*}} : !fir.ref<i64> interop_types([#omp<interop_type(targetsync)>])
+subroutine test_interop_init_targetsync(obj)
+  integer(8) :: obj
+  !$omp interop init(targetsync: obj)
+end subroutine
+
+!===============================================================================
+! Interop Init — targetsync, target (both)
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_init_both(
+!CHECK:         omp.interop.init %{{.*}} : !fir.ref<i64> interop_types([#omp<interop_type(targetsync)>, #omp<interop_type(target)>])
+subroutine test_interop_init_both(obj)
+  integer(8) :: obj
+  !$omp interop init(targetsync, target: obj)
+end subroutine
+
+!===============================================================================
+! Interop Use
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_use(
+!CHECK:         omp.interop.use %{{.*}} : !fir.ref<i64>
+subroutine test_interop_use(obj)
+  integer(8) :: obj
+  !$omp interop use(obj)
+end subroutine
+
+!===============================================================================
+! Interop Destroy
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_destroy(
+!CHECK:         omp.interop.destroy %{{.*}} : !fir.ref<i64>
+subroutine test_interop_destroy(obj)
+  integer(8) :: obj
+  !$omp interop destroy(obj)
+end subroutine
+
+!===============================================================================
+! Interop Init — nowait
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_init_nowait(
+!CHECK:         omp.interop.init %{{.*}} : !fir.ref<i64> interop_types([#omp<interop_type(target)>]) nowait
+subroutine test_interop_init_nowait(obj)
+  integer(8) :: obj
+  !$omp interop init(target: obj) nowait
+end subroutine
+
+!===============================================================================
+! Interop Destroy — nowait
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_destroy_nowait(
+!CHECK:         omp.interop.destroy %{{.*}} : !fir.ref<i64> nowait
+subroutine test_interop_destroy_nowait(obj)
+  integer(8) :: obj
+  !$omp interop destroy(obj) nowait
+end subroutine
+
+!===============================================================================
+! Interop Init — device clause
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_device(
+!CHECK:         %[[DEV:.*]] = fir.load %{{.*}} : !fir.ref<i32>
+!CHECK:         omp.interop.init %{{.*}} : !fir.ref<i64> interop_types([#omp<interop_type(target)>]) device(%[[DEV]] : i32)
+subroutine test_interop_device(obj, dev)
+  integer(8) :: obj
+  integer :: dev
+  !$omp interop device(dev) init(target: obj)
+end subroutine
+!===============================================================================
+! Interop Init — prefer_type with string FR identifiers
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_prefer_str(
+!CHECK:         omp.interop.init %{{.*}} : !fir.ref<i64> interop_types([#omp<interop_type(targetsync)>]) prefer_type([1, 6])
+subroutine test_interop_prefer_str(obj)
+  integer(8) :: obj
+  !$omp interop init(prefer_type("cuda", "level_zero"), targetsync: obj)
+end subroutine
+
+!===============================================================================
+! Interop Init — prefer_type with string FR identifier (hip)
+!===============================================================================
+
+!CHECK-LABEL: func.func @_QPtest_interop_prefer_hip(
+!CHECK:         omp.interop.init %{{.*}} : !fir.ref<i64> interop_types([#omp<interop_type(target)>]) prefer_type([5])
+subroutine test_interop_prefer_hip(obj)
+  integer(8) :: obj
+  !$omp interop init(prefer_type("hip"), target: obj)
+end subroutine
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
index c9e6764e7d634..f6bc8f11ccccb 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
@@ -79,6 +79,16 @@ def TaskDependArrayAttr
   let constBuilderCall = ?;
 }
 
+//===----------------------------------------------------------------------===//
+// InteropTypeArrayAttr
+//===----------------------------------------------------------------------===//
+
+def InteropTypeArrayAttr
+    : TypedArrayAttrBase<InteropTypeAttr,
+                         InteropTypeAttr.summary # " array"> {
+  let constBuilderCall = ?;
+}
+
 //===----------------------------------------------------------------------===//
 // VersionAttr
 //===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td
index 55ffcce956171..c961d3e853680 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td
@@ -373,4 +373,22 @@ def FallbackModifierAttr : OpenMP_EnumAttr<FallbackModifier,
   let assemblyFormat = "`(` $value `)`";
 }
 
+//===----------------------------------------------------------------------===//
+// interop_type for interop construct
+//===----------------------------------------------------------------------===//
+
+def InteropTypeTarget : I32EnumAttrCase<"target", 0>;
+def InteropTypeTargetSync : I32EnumAttrCase<"targetsync", 1>;
+
+def InteropType : OpenMP_I32EnumAttr<
+    "InteropType",
+    "interop type", [
+      InteropTypeTarget,
+      InteropTypeTargetSync
+    ]>;
+
+def InteropTypeAttr : OpenMP_EnumAttr<InteropType, "interop_type"> {
+  let assemblyFormat = "`(` $value `)`";
+}
+
 #endif // OPENMP_ENUMS
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 51e7080db5b29..e5aae8c332a99 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2614,4 +2614,116 @@ def GroupprivateOp : OpenMP_Op<"groupprivate",
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// 14.1 interop construct
+//===----------------------------------------------------------------------===//
+
+def InteropInitOp
+    : OpenMP_Op<"interop.init", traits = [AttrSizedOperandSegments],
+                clauses = [OpenMP_DependClause, OpenMP_NowaitClause]> {
+  let summary = "interop init operation";
+  let description = [{
+    The interop init operation initializes an interop object for
+    foreign runtime interoperability. The `interop_type` attribute specifies
+    whether the interop object provides access to the target execution
+    environment (`target`), a synchronization object (`targetsync`), or both.
+
+    The optional `prefer_type` attribute provides a list of preferred foreign
+    runtime types as integer identifiers.
+
+    The optional `device` operand specifies the device number.
+  }] # clausesDescription;
+
+  let arguments = !con((ins
+      OpenMP_PointerLikeType:$interop_var,
+      InteropTypeArrayAttr:$interop_types,
+      OptionalAttr<I64ArrayAttr>:$prefer_type,
+      Optional<AnyInteger>:$device),
+      clausesArgs);
+
+  let assemblyFormat = [{
+    $interop_var `:` type($interop_var)
+    `interop_types` `(` $interop_types `)`
+    (`prefer_type` `(` $prefer_type^ `)`)?
+    (`device` `(` $device^ `:` type($device) `)`)?
+    oilist(
+      `depend` `(`
+      custom<DependVarList>($depend_vars, type($depend_vars), $depend_kinds,
+                            $depend_iterated, type($depend_iterated),
+                            $depend_iterated_kinds) `)`
+      | `nowait` $nowait
+    )
+    attr-dict
+  }];
+
+  let builders = [
+    OpBuilder<(ins CArg<"const InteropInitOperands &">:$clauses)>
+  ];
+}
+
+def InteropUseOp
+    : OpenMP_Op<"interop.use", traits = [AttrSizedOperandSegments],
+                clauses = [OpenMP_DependClause, OpenMP_NowaitClause]> {
+  let summary = "interop use operation";
+  let description = [{
+    The interop use operation indicates use of an interop object. This is
+    used in combination with depend clauses to synchronize tasks with
+    foreign runtime operations.
+  }] # clausesDescription;
+
+  let arguments = !con((ins
+      OpenMP_PointerLikeType:$interop_var,
+      Optional<AnyInteger>:$device),
+      clausesArgs);
+
+  let assemblyFormat = [{
+    $interop_var `:` type($interop_var)
+    (`device` `(` $device^ `:` type($device) `)`)?
+    oilist(
+      `depend` `(`
+      custom<DependVarList>($depend_vars, type($depend_vars), $depend_kinds,
+                            $depend_iterated, type($depend_iterated),
+                            $depend_iterated_kinds) `)`
+      | `nowait` $nowait
+    )
+    attr-dict
+  }];
+
+  let builders = [
+    OpBuilder<(ins CArg<"const InteropUseOperands &">:$clauses)>
+  ];
+}
+
+def InteropDestroyOp
+    : OpenMP_Op<"interop.destroy", traits = [AttrSizedOperandSegments],
+                clauses = [OpenMP_DependClause, OpenMP_NowaitClause]> {
+  let summary = "interop destroy operation";
+  let description = [{
+    The interop destroy operation releases an interop object and frees
+    any resources associated with it.
+  }] # clausesDescription;
+
+  let arguments = !con((ins
+      OpenMP_PointerLikeType:$interop_var,
+      Optional<AnyInteger>:$device),
+      clausesArgs);
+
+  let assemblyFormat = [{
+    $interop_var `:` type($interop_var)
+    (`device` `(` $device^ `:` type($device) `)`)?
+    oilist(
+      `depend` `(`
+      custom<DependVarList>($depend_vars, type($depend_vars), $depend_kinds,
+                            $depend_iterated, type($depend_iterated),
+                            $depend_iterated_kinds) `)`
+      | `nowait` $nowait
+    )
+    attr-dict
+  }];
+
+  let builders = [
+    OpBuilder<(ins CArg<"const InteropDestroyOperands &">:$clauses)>
+  ];
+}
+
 #endif // OPENMP_OPS
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 7c75791611218..8270d5fa85a1c 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4527,6 +4527,46 @@ void OrderedRegionOp::build(OpBuilder &builder, OperationState &state,
 
 LogicalResult OrderedRegionOp::verify() { return verifyOrderedParent(**this); }
 
+//===----------------------------------------------------------------------===//
+// InteropInitOp
+//===----------------------------------------------------------------------===//
+
+void InteropInitOp::build(OpBuilder &builder, OperationState &state,
+                          const InteropInitOperands &clauses) {
+  // TODO Store clauses in op: dependKinds, dependVars, nowait.
+  InteropInitOp::build(builder, state, /*interop_var=*/nullptr,
+                       /*interop_types=*/nullptr, /*prefer_type=*/nullptr,
+                       /*device=*/nullptr, /*depend_kinds=*/nullptr,
+                       /*depend_vars=*/{}, /*depend_iterated_kinds=*/nullptr,
+                       /*depend_iterated=*/{}, /*nowait=*/nullptr);
+}
+
+//===----------------------------------------------------------------------===//
+// InteropUseOp
+//===----------------------------------------------------------------------===//
+
+void InteropUseOp::build(OpBuilder &builder, OperationState &state,
+                         const InteropUseOperands &clauses) {
+  // TODO Store clauses in op: dependKinds, dependVars, nowait.
+  InteropUseOp::build(builder, state, /*interop_var=*/nullptr,
+                      /*device=*/nullptr, /*depend_kinds=*/nullptr,
+                      /*depend_vars=*/{}, /*depend_iterated_kinds=*/nullptr,
+                      /*depend_iterated=*/{}, /*nowait=*/nullptr);
+}
+
+//===----------------------------------------------------------------------===//
+// InteropDestroyOp
+//===----------------------------------------------------------------------===//
+
+void InteropDestroyOp::build(OpBuilder &builder, OperationState &state,
+                             const InteropDestroyOperands &clauses) {
+  // TODO Store clauses in op: dependKinds, dependVars, nowait.
+  InteropDestroyOp::build(builder, state, /*interop_var=*/nullptr,
+                          /*device=*/nullptr, /*depend_kinds=*/nullptr,
+                          /*depend_vars=*/{}, /*depend_iterated_kinds=*/nullptr,
+                          /*depend_iterated=*/{}, /*nowait=*/nullptr);
+}
+
 //===----------------------------------------------------------------------===//
 // TaskwaitOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index d35e8612e158b..9b42e93a55c5a 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -3711,6 +3711,98 @@ convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
   return success();
 }
 
+static LogicalResult
+convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
+                        LLVM::ModuleTranslation &moduleTranslation) {
+  if (!initOp.getDependVars().empty() || initOp.getDependKinds() ||
+      !initOp.getDependIterated().empty() || initOp.getDependIteratedKinds())
+    return initOp.emitError()
+           << "not yet implemented: Unhandled clause depend in "
+           << omp::InteropInitOp::getOperationName() << " operation";
+
+  llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+  llvm::Value *interopVar =
+      moduleTranslation.lookupValue(initOp.getInteropVar());
+  llvm::Value *device = initOp.getDevice()
+                            ? moduleTranslation.lookupValue(initOp.getDevice())
+                            : nullptr;
+
+  // TODO: Handle depend clauses when supported.
+  llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
+  llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
+  bool hasNowait = initOp.getNowait();
+
+  // Emit one call per interop type. The runtime expects a single type per call.
+  for (auto typeAttr : initOp.getInteropTypes()) {
+    auto interopTypeEnum = cast<omp::InteropTypeAttr>(typeAttr).getValue();
+    llvm::omp::OMPInteropType interopType;
+    switch (interopTypeEnum) {
+    case omp::InteropType::target:
+      interopType = llvm::omp::OMPInteropType::Target;
+      break;
+    case omp::InteropType::targetsync:
+      interopType = llvm::omp::OMPInteropType::TargetSync;
+      break;
+    }
+    ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
+                                     numDeps, depArray, hasNowait);
+  }
+  return success();
+}
+
+static LogicalResult
+convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp,
+                           llvm::IRBuilderBase &builder,
+                           LLVM::ModuleTranslation &moduleTranslation) {
+  if (!destroyOp.getDependVars().empty() || destroyOp.getDependKinds() ||
+      !destroyOp.getDependIterated().empty() ||
+      destroyOp.getDependIteratedKinds())
+    return destroyOp.emitError()
+           << "not yet implemented: Unhandled clause depend in "
+           << omp::InteropDestroyOp::getOperationName() << " operation";
+
+  llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+  llvm::Value *interopVar =
+      moduleTranslation.lookupValue(destroyOp.getInteropVar());
+  llvm::Value *device =
+      destroyOp.getDevice()
+          ? moduleTranslation.lookupValue(destroyOp.getDevice())
+          : nullptr;
+
+  llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
+  llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
+  bool hasNowait = destroyOp.getNowait();
+
+  ompBuilder->createOMPInteropDestroy(builder, interopVar, device, numDeps,
+                                      depArray, hasNowait);
+  return success();
+}
+
+static LogicalResult
+convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder,
+                       LLVM::ModuleTranslation &moduleTranslation) {
+  if (!useOp.getDependVars().empty() || useOp.getDependKinds() ||
+      !useOp.getDependIterated().empty() || useOp.getDependIteratedKinds())
+    return useOp.emitError()
+           << "not yet implemented: Unhandled clause depend in "
+           << omp::InteropUseOp::getOperationName() << " operation";
+
+  llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+  llvm::Value *interopVar =
+      moduleTranslation.lookupValue(useOp.getInteropVar());
+  llvm::Value *device = useOp.getDevice()
+                            ? moduleTranslation.lookupValue(useOp.getDevice())
+                            : nullptr;
+
+  llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
+  llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
+  bool hasNowait = useOp.getNowait();
+
+  ompBuilder->createOMPInteropUse(builder, interopVar, device, numDeps,
+                                  depArray, hasNowait);
+  return success();
+}
+
 static LogicalResult
 convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
                      LLVM::ModuleTranslation &moduleTranslation) {
@@ -8884,6 +8976,15 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
           .Case([&](omp::TaskwaitOp op) {
             return convertOmpTaskwaitOp(op, builder, moduleTranslation);
           })
+          .Case([&](omp::InteropInitOp op) {
+            return convertOmpInteropInitOp(op, builder, moduleTranslation);
+          })
+          .Case([&](omp::InteropDestroyOp op) {
+            return convertOmpInteropDestroyOp(op, builder, moduleTranslation);
+          })
+          .Case([&](omp::InteropUseOp op) {
+            return convertOmpInteropUseOp(op, builder, moduleTranslation);
+          })
           .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
                 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
                 omp::CriticalDeclareOp>([](auto op) {
diff --git a/mlir/test/Target/LLVMIR/openmp-interop-depend.mlir b/mlir/test/Target/LLVMIR/openmp-interop-depend.mlir
new file mode 100644
index 0000000000000..ebd601558f392
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-interop-depend.mlir
@@ -0,0 +1,7 @@
+// RUN: not mlir-translate --mlir-to-llvmir %s 2>&1 | FileCheck %s
+
+// CHECK: error: not yet implemented: Unhandled clause depend in omp.interop.init operation
+llvm.func @test_interop_init_depend(%interop: !llvm.ptr, %dep: !llvm.ptr) {
+  omp.interop.init %interop : !llvm.ptr interop_types([#omp<interop_type(targetsync)>]) depend(taskdependinout -> %dep : !llvm.ptr)
+  llvm.return
+}
diff --git a/mlir/test/Target/LLVMIR/openmp-interop.mlir b/mlir/test/Target/LLVMIR/openmp-interop.mlir
new file mode 100644
index 0000000000000..9fd1fd583aba8
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-interop.mlir
@@ -0,0 +1,71 @@
+// RUN: mlir-translate --mlir-to-llvmir %s | FileCheck %s
+
+// CHECK-LABEL: define void @test_interop_init(
+// CHECK-SAME:    ptr %[[OBJ:.*]])
+// CHECK:         %[[GTID:.*]] = call i32 @__kmpc_global_thread_num(ptr @{{.*}})
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %[[GTID]], ptr %[[OBJ]], i32 1, i32 -1, i32 0, ptr null, i32 0)
+// CHECK:         ret void
+llvm.func @test_interop_init(%arg0: !llvm.ptr) {
+  %0 = llvm.mlir.constant(1 : i64) : i64
+  omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(target)>])
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_init_targetsync(
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 2, i32 -1, i32 0, ptr null, i32 0)
+llvm.func @test_interop_init_targetsync(%arg0: !llvm.ptr) {
+  omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(targetsync)>])
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_init_both(
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 2, i32 -1, i32 0, ptr null, i32 0)
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 1, i32 -1, i32 0, ptr null, i32 0)
+llvm.func @test_interop_init_both(%arg0: !llvm.ptr) {
+  omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(targetsync)>, #omp<interop_type(target)>])
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_use(
+// CHECK:         %[[GTID:.*]] = call i32 @__kmpc_global_thread_num(ptr @{{.*}})
+// CHECK:         call void @__tgt_interop_use(ptr @{{.*}}, i32 %[[GTID]], ptr %{{.*}}, i32 -1, i32 0, ptr null, i32 0)
+// CHECK:         ret void
+llvm.func @test_interop_use(%arg0: !llvm.ptr) {
+  omp.interop.use %arg0 : !llvm.ptr
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_destroy(
+// CHECK:         %[[GTID:.*]] = call i32 @__kmpc_global_thread_num(ptr @{{.*}})
+// CHECK:         call void @__tgt_interop_destroy(ptr @{{.*}}, i32 %[[GTID]], ptr %{{.*}}, i32 -1, i32 0, ptr null, i32 0)
+// CHECK:         ret void
+llvm.func @test_interop_destroy(%arg0: !llvm.ptr) {
+  omp.interop.destroy %arg0 : !llvm.ptr
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_init_nowait(
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 1, i32 -1, i32 0, ptr null, i32 1)
+llvm.func @test_interop_init_nowait(%arg0: !llvm.ptr) {
+  omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(target)>]) nowait
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_destroy_nowait(
+// CHECK:         call void @__tgt_interop_destroy(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 -1, i32 0, ptr null, i32 1)
+llvm.func @test_interop_destroy_nowait(%arg0: !llvm.ptr) {
+  omp.interop.destroy %arg0 : !llvm.ptr nowait
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_init_device(
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 1, i32 %{{.*}}, i32 0, ptr null, i32 0)
+llvm.func @test_interop_init_device(%arg0: !llvm.ptr, %arg1: i32) {
+  omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(target)>]) device(%arg1 : i32)
+  llvm.return
+}
+
+// CHECK: declare i32 @__kmpc_global_thread_num(ptr)
+// CHECK: declare void @__tgt_interop_init(ptr, i32, ptr, i32, i32, i32, ptr, i32)
+// CHECK: declare void @__tgt_interop_use(ptr, i32, ptr, i32, i32, ptr, i32)
+// CHECK: declare void @__tgt_interop_destroy(ptr, i32, ptr, i32, i32, ptr, i32)
diff --git a/openmp/module/omp_lib.F90.var b/openmp/module/omp_lib.F90.var
index 464056847ab92..9a7db73f97542 100644
--- a/openmp/module/omp_lib.F90.var
+++ b/openmp/module/omp_lib.F90.var
@@ -40,6 +40,8 @@
         integer, parameter, public :: omp_alloctrait_val_kind = c_intptr_t
         integer, parameter, public :: omp_interop_kind = c_intptr_t
         integer, parameter, public :: omp_interop_fr_kind = omp_integer_kind
+        integer, parameter, public :: omp_interop_property_kind = omp_integer_kind
+        integer, parameter, public :: omp_interop_rc_kind = omp_integer_kind
 
         type omp_alloctrait
           integer(kind=omp_alloctrait_key_kind) key
@@ -84,6 +86,8 @@
         public :: omp_alloctrait_val_kind
         public :: omp_interop_kind
         public :: omp_interop_fr_kind
+        public :: omp_interop_property_kind
+        public :: omp_interop_rc_kind
         public :: omp_alloctrait
         public :: omp_pause_resource_kind
         public :: omp_depend_kind
@@ -218,6 +222,28 @@
 
         integer (kind=omp_interop_kind), parameter, public :: omp_interop_none = 0
 
+!       interop property constants
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_fr_id = -1
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_fr_name = -2
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_vendor = -3
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_vendor_name = -4
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_device_num = -5
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_platform = -6
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_device = -7
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_device_context = -8
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_targetsync = -9
+        integer (kind=omp_interop_property_kind), parameter, public :: omp_ipr_first = -9
+
+!       interop return code constants
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_no_value = 1
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_success = 0
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_empty = -1
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_out_of_range = -2
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_type_int = -3
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_type_ptr = -4
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_type_str = -5
+        integer (kind=omp_interop_rc_kind), parameter, public :: omp_irc_other = -6
+
         interface
 
 !         ***
@@ -1061,6 +1087,67 @@
             logical (kind=omp_logical_kind) kmp_get_cancellation_status
           end function kmp_get_cancellation_status
 
+!         ***
+!         *** interop query routines
+!         ***
+
+          function omp_get_num_interop_properties(interop) bind(c)
+            use omp_lib_kinds
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_integer_kind) omp_get_num_interop_properties
+          end function omp_get_num_interop_properties
+
+          function omp_get_interop_int(interop, property_id, err) bind(c)
+            use omp_lib_kinds
+            use, intrinsic :: iso_c_binding, only: c_intptr_t, c_int
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_interop_property_kind), value :: property_id
+            integer (kind=c_int) :: err
+            integer (kind=c_intptr_t) omp_get_interop_int
+          end function omp_get_interop_int
+
+          function omp_get_interop_ptr(interop, property_id, err) bind(c)
+            use omp_lib_kinds
+            use, intrinsic :: iso_c_binding, only: c_ptr, c_int
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_interop_property_kind), value :: property_id
+            integer (kind=c_int) :: err
+            type(c_ptr) omp_get_interop_ptr
+          end function omp_get_interop_ptr
+
+          function omp_get_interop_str(interop, property_id, err) bind(c)
+            use omp_lib_kinds
+            use, intrinsic :: iso_c_binding, only: c_ptr, c_int
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_interop_property_kind), value :: property_id
+            integer (kind=c_int) :: err
+            type(c_ptr) omp_get_interop_str
+          end function omp_get_interop_str
+
+          function omp_get_interop_name(interop, property_id) bind(c)
+            use omp_lib_kinds
+            use, intrinsic :: iso_c_binding, only: c_ptr
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_interop_property_kind), value :: property_id
+            type(c_ptr) omp_get_interop_name
+          end function omp_get_interop_name
+
+          function omp_get_interop_type_desc(interop, property_id) bind(c)
+            use omp_lib_kinds
+            use, intrinsic :: iso_c_binding, only: c_ptr
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_interop_property_kind), value :: property_id
+            type(c_ptr) omp_get_interop_type_desc
+          end function omp_get_interop_type_desc
+
+          function omp_get_interop_rc_desc(interop, ret_code) bind(c)
+            use omp_lib_kinds
+            use, intrinsic :: iso_c_binding, only: c_ptr
+            integer (kind=omp_interop_kind), value :: interop
+            integer (kind=omp_interop_rc_kind), value :: ret_code
+            type(c_ptr) omp_get_interop_rc_desc
+          end function omp_get_interop_rc_desc
+
         end interface
 
         ! make the above routine definitions public
@@ -1182,5 +1269,12 @@
         public :: kmp_set_warnings_on
         public :: kmp_set_warnings_off
         public :: kmp_get_cancellation_status
+        public :: omp_get_num_interop_properties
+        public :: omp_get_interop_int
+        public :: omp_get_interop_ptr
+        public :: omp_get_interop_str
+        public :: omp_get_interop_name
+        public :: omp_get_interop_type_desc
+        public :: omp_get_interop_rc_desc
 
       end module omp_lib

>From 336e7bb81cc71d2192599cf170c6575e216265ca Mon Sep 17 00:00:00 2001
From: Sunil Shrestha <sunil.shrestha at hpe.com>
Date: Mon, 6 Jul 2026 16:44:38 -0500
Subject: [PATCH 2/2] Address feedback

  - track presence of the init clause instead of counting target/targetsync
  - collapse interop types into a single interop_init call
  - cast device id to match the runtime's expected type
  - ensure parity between the omp_lib module and header
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  17 ++-
 flang/lib/Semantics/check-omp-structure.cpp   |   3 +-
 .../OpenMP/interop-construct-v60.f90          |  24 ++++
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     |   6 +
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  24 ++--
 mlir/test/Target/LLVMIR/openmp-interop.mlir   |  26 ++++-
 openmp/module/omp_lib.h.var                   | 105 ++++++++++++++++++
 7 files changed, 188 insertions(+), 17 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/interop-construct-v60.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 47a70efeb293a..fc6847b2104ce 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -5157,20 +5157,22 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 
       mlir::Value interopVarAddr = getInteropVarAddr(interopVar);
 
-      // Convert interop types to MLIR attributes.
+      // Convert interop types to MLIR attributes. A single init clause may
+      // list both `target` and `targetsync`; keep them on one op and let the
+      // LLVM-IR translation collapse them into a single runtime init call,
+      // matching Clang.
       llvm::SmallVector<mlir::Attribute> interopTypeAttrs;
       for (auto itype : interopTypes) {
-        mlir::omp::InteropType mlirType;
         switch (itype) {
         case clause::Init::InteropType::Target:
-          mlirType = mlir::omp::InteropType::target;
+          interopTypeAttrs.push_back(mlir::omp::InteropTypeAttr::get(
+              context, mlir::omp::InteropType::target));
           break;
         case clause::Init::InteropType::Targetsync:
-          mlirType = mlir::omp::InteropType::targetsync;
+          interopTypeAttrs.push_back(mlir::omp::InteropTypeAttr::get(
+              context, mlir::omp::InteropType::targetsync));
           break;
         }
-        interopTypeAttrs.push_back(
-            mlir::omp::InteropTypeAttr::get(context, mlirType));
       }
       auto interopTypesAttr = mlir::ArrayAttr::get(context, interopTypeAttrs);
 
@@ -5210,6 +5212,9 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
           preferTypeAttr = firOpBuilder.getI64ArrayAttr(prefValues);
       }
 
+      // Emit a single InteropInitOp carrying all interop-types from this init
+      // clause. The translation to LLVM IR collapses them into one runtime
+      // call, matching Clang (one init clause -> one __tgt_interop_init).
       mlir::omp::InteropInitOp::create(
           firOpBuilder, loc, interopVarAddr, interopTypesAttr, preferTypeAttr,
           deviceValue,
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 333a71705b5b7..0ba3d8659d516 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5787,6 +5787,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause::ThreadLimit &x) {
 
 void OmpStructureChecker::Enter(const parser::OpenMPInteropConstruct &x) {
   bool isDependClauseOccurred{false};
+  bool hasInitClause{false};
   int targetCount{0}, targetSyncCount{0};
   const auto &dir{std::get<parser::OmpDirectiveName>(x.v.t)};
   std::set<const Symbol *> objectSymbolList;
@@ -5796,6 +5797,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPInteropConstruct &x) {
     common::visit(
         common::visitors{
             [&](const parser::OmpClause::Init &initClause) {
+              hasInitClause = true;
               if (OmpVerifyModifiers(initClause.v, llvm::omp::OMPC_init,
                       GetContext().directiveSource, context_)) {
 
@@ -5876,7 +5878,6 @@ void OmpStructureChecker::Enter(const parser::OpenMPInteropConstruct &x) {
   // present on this directive. For use/destroy-only directives, the interop
   // object's type was established at its init site, which may be in a
   // different statement — we cannot verify it statically here.
-  bool hasInitClause = (targetCount > 0 || targetSyncCount > 0);
   if (isDependClauseOccurred && hasInitClause && !targetSyncCount) {
     context_.Say(GetContext().directiveSource,
         "A DEPEND clause can only appear on the directive if the interop-type includes TARGETSYNC"_err_en_US);
diff --git a/flang/test/Semantics/OpenMP/interop-construct-v60.f90 b/flang/test/Semantics/OpenMP/interop-construct-v60.f90
new file mode 100644
index 0000000000000..78a226f8a6b1f
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/interop-construct-v60.f90
@@ -0,0 +1,24 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %python %S/../test_errors.py %s %flang %openmp_flags -fopenmp-version=60
+! OpenMP Version 6.0
+! 14.1 Interop construct
+! In OpenMP 6.0 the interop-type modifier on an INIT clause is optional, so
+! INIT(obj) alone is accepted. A DEPEND clause, however, requires the
+! interop-type to include TARGETSYNC, which an untyped INIT does not provide,
+! so INIT(obj) combined with DEPEND is diagnosed.
+
+SUBROUTINE test_interop_untyped()
+  USE omp_lib
+  INTEGER(OMP_INTEROP_KIND) :: obj
+  !$OMP INTEROP INIT(obj)
+  PRINT *, 'pass'
+END SUBROUTINE test_interop_untyped
+
+SUBROUTINE test_interop_untyped_depend()
+  USE omp_lib
+  INTEGER(OMP_INTEROP_KIND) :: obj
+  !ERROR: A DEPEND clause can only appear on the directive if the interop-type includes TARGETSYNC
+  !$OMP INTEROP INIT(obj) DEPEND(INOUT: obj)
+  PRINT *, 'pass'
+END SUBROUTINE test_interop_untyped_depend
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 92eb7de0d882f..cabfcdf68eacb 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -8048,6 +8048,8 @@ CallInst *OpenMPIRBuilder::createOMPInteropInit(
   Value *ThreadId = getOrCreateThreadID(Ident);
   if (Device == nullptr)
     Device = Constant::getAllOnesValue(Int32);
+  else if (Device->getType() != Int32)
+    Device = Builder.CreateIntCast(Device, Int32, /*isSigned=*/true);
   Constant *InteropTypeVal = ConstantInt::get(Int32, (int)InteropType);
   if (NumDependences == nullptr) {
     NumDependences = ConstantInt::get(Int32, 0);
@@ -8076,6 +8078,8 @@ CallInst *OpenMPIRBuilder::createOMPInteropDestroy(
   Value *ThreadId = getOrCreateThreadID(Ident);
   if (Device == nullptr)
     Device = Constant::getAllOnesValue(Int32);
+  else if (Device->getType() != Int32)
+    Device = Builder.CreateIntCast(Device, Int32, /*isSigned=*/true);
   if (NumDependences == nullptr) {
     NumDependences = ConstantInt::get(Int32, 0);
     PointerType *PointerTypeVar = PointerType::getUnqual(M.getContext());
@@ -8104,6 +8108,8 @@ CallInst *OpenMPIRBuilder::createOMPInteropUse(const LocationDescription &Loc,
   Value *ThreadId = getOrCreateThreadID(Ident);
   if (Device == nullptr)
     Device = Constant::getAllOnesValue(Int32);
+  else if (Device->getType() != Int32)
+    Device = Builder.CreateIntCast(Device, Int32, /*isSigned=*/true);
   if (NumDependences == nullptr) {
     NumDependences = ConstantInt::get(Int32, 0);
     PointerType *PointerTypeVar = PointerType::getUnqual(M.getContext());
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 9b42e93a55c5a..14b0b912d7c1b 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -3732,21 +3732,27 @@ convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
   llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
   bool hasNowait = initOp.getNowait();
 
-  // Emit one call per interop type. The runtime expects a single type per call.
-  for (auto typeAttr : initOp.getInteropTypes()) {
-    auto interopTypeEnum = cast<omp::InteropTypeAttr>(typeAttr).getValue();
-    llvm::omp::OMPInteropType interopType;
-    switch (interopTypeEnum) {
+  // A single `init` clause may list both `target` and `targetsync`, but the
+  // runtime init call takes a single interop-type. Collapse the set to one
+  // value, matching Clang: if `target` is present use Target, otherwise
+  // TargetSync. The offload runtime object model supports only one type per
+  // object; representing both would require a runtime change.
+  bool hasTarget = false, hasTargetSync = false;
+  for (mlir::Attribute typeAttr : initOp.getInteropTypes()) {
+    switch (cast<omp::InteropTypeAttr>(typeAttr).getValue()) {
     case omp::InteropType::target:
-      interopType = llvm::omp::OMPInteropType::Target;
+      hasTarget = true;
       break;
     case omp::InteropType::targetsync:
-      interopType = llvm::omp::OMPInteropType::TargetSync;
+      hasTargetSync = true;
       break;
     }
-    ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
-                                     numDeps, depArray, hasNowait);
   }
+  llvm::omp::OMPInteropType interopType =
+      (!hasTarget && hasTargetSync) ? llvm::omp::OMPInteropType::TargetSync
+                                    : llvm::omp::OMPInteropType::Target;
+  ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
+                                   numDeps, depArray, hasNowait);
   return success();
 }
 
diff --git a/mlir/test/Target/LLVMIR/openmp-interop.mlir b/mlir/test/Target/LLVMIR/openmp-interop.mlir
index 9fd1fd583aba8..85cadd241e23d 100644
--- a/mlir/test/Target/LLVMIR/openmp-interop.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-interop.mlir
@@ -19,8 +19,8 @@ llvm.func @test_interop_init_targetsync(%arg0: !llvm.ptr) {
 }
 
 // CHECK-LABEL: define void @test_interop_init_both(
-// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 2, i32 -1, i32 0, ptr null, i32 0)
 // CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 1, i32 -1, i32 0, ptr null, i32 0)
+// CHECK-NOT:     call void @__tgt_interop_init
 llvm.func @test_interop_init_both(%arg0: !llvm.ptr) {
   omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(targetsync)>, #omp<interop_type(target)>])
   llvm.return
@@ -65,6 +65,30 @@ llvm.func @test_interop_init_device(%arg0: !llvm.ptr, %arg1: i32) {
   llvm.return
 }
 
+// CHECK-LABEL: define void @test_interop_init_device_i64(
+// CHECK:         %[[DEVICE:.*]] = trunc i64 %{{.*}} to i32
+// CHECK:         call void @__tgt_interop_init(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 1, i32 %[[DEVICE]], i32 0, ptr null, i32 0)
+llvm.func @test_interop_init_device_i64(%arg0: !llvm.ptr, %arg1: i64) {
+  omp.interop.init %arg0 : !llvm.ptr interop_types([#omp<interop_type(target)>]) device(%arg1 : i64)
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_destroy_device_i64(
+// CHECK:         %[[DEVICE:.*]] = trunc i64 %{{.*}} to i32
+// CHECK:         call void @__tgt_interop_destroy(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 %[[DEVICE]], i32 0, ptr null, i32 0)
+llvm.func @test_interop_destroy_device_i64(%arg0: !llvm.ptr, %arg1: i64) {
+  omp.interop.destroy %arg0 : !llvm.ptr device(%arg1 : i64)
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_interop_use_device_i64(
+// CHECK:         %[[DEVICE:.*]] = trunc i64 %{{.*}} to i32
+// CHECK:         call void @__tgt_interop_use(ptr @{{.*}}, i32 %{{.*}}, ptr %{{.*}}, i32 %[[DEVICE]], i32 0, ptr null, i32 0)
+llvm.func @test_interop_use_device_i64(%arg0: !llvm.ptr, %arg1: i64) {
+  omp.interop.use %arg0 : !llvm.ptr device(%arg1 : i64)
+  llvm.return
+}
+
 // CHECK: declare i32 @__kmpc_global_thread_num(ptr)
 // CHECK: declare void @__tgt_interop_init(ptr, i32, ptr, i32, i32, i32, ptr, i32)
 // CHECK: declare void @__tgt_interop_use(ptr, i32, ptr, i32, i32, ptr, i32)
diff --git a/openmp/module/omp_lib.h.var b/openmp/module/omp_lib.h.var
index e515c9434f120..5e1996e31fe0a 100644
--- a/openmp/module/omp_lib.h.var
+++ b/openmp/module/omp_lib.h.var
@@ -56,6 +56,10 @@
       parameter(omp_interop_kind=int_ptr_kind())
       integer omp_interop_fr_kind
       parameter(omp_interop_fr_kind=omp_integer_kind)
+      integer omp_interop_property_kind
+      parameter(omp_interop_property_kind=omp_integer_kind)
+      integer omp_interop_rc_kind
+      parameter(omp_interop_rc_kind=omp_integer_kind)
 
       integer(kind=omp_integer_kind)openmp_version
       parameter(openmp_version=@LIBOMP_OMP_YEAR_MONTH@)
@@ -296,6 +300,46 @@
       integer(kind=omp_interop_kind)omp_interop_none
       parameter(omp_interop_none=0)
 
+!     interop property constants
+      integer(kind=omp_interop_property_kind)omp_ipr_fr_id
+      parameter(omp_ipr_fr_id=-1)
+      integer(kind=omp_interop_property_kind)omp_ipr_fr_name
+      parameter(omp_ipr_fr_name=-2)
+      integer(kind=omp_interop_property_kind)omp_ipr_vendor
+      parameter(omp_ipr_vendor=-3)
+      integer(kind=omp_interop_property_kind)omp_ipr_vendor_name
+      parameter(omp_ipr_vendor_name=-4)
+      integer(kind=omp_interop_property_kind)omp_ipr_device_num
+      parameter(omp_ipr_device_num=-5)
+      integer(kind=omp_interop_property_kind)omp_ipr_platform
+      parameter(omp_ipr_platform=-6)
+      integer(kind=omp_interop_property_kind)omp_ipr_device
+      parameter(omp_ipr_device=-7)
+      integer(kind=omp_interop_property_kind)omp_ipr_device_context
+      parameter(omp_ipr_device_context=-8)
+      integer(kind=omp_interop_property_kind)omp_ipr_targetsync
+      parameter(omp_ipr_targetsync=-9)
+      integer(kind=omp_interop_property_kind)omp_ipr_first
+      parameter(omp_ipr_first=-9)
+
+!     interop return code constants
+      integer(kind=omp_interop_rc_kind)omp_irc_no_value
+      parameter(omp_irc_no_value=1)
+      integer(kind=omp_interop_rc_kind)omp_irc_success
+      parameter(omp_irc_success=0)
+      integer(kind=omp_interop_rc_kind)omp_irc_empty
+      parameter(omp_irc_empty=-1)
+      integer(kind=omp_interop_rc_kind)omp_irc_out_of_range
+      parameter(omp_irc_out_of_range=-2)
+      integer(kind=omp_interop_rc_kind)omp_irc_type_int
+      parameter(omp_irc_type_int=-3)
+      integer(kind=omp_interop_rc_kind)omp_irc_type_ptr
+      parameter(omp_irc_type_ptr=-4)
+      integer(kind=omp_interop_rc_kind)omp_irc_type_str
+      parameter(omp_irc_type_str=-5)
+      integer(kind=omp_interop_rc_kind)omp_irc_other
+      parameter(omp_irc_other=-6)
+
       interface
 
 !       ***
@@ -1129,6 +1173,67 @@
 
         subroutine kmp_set_warnings_off() bind(c)
         end subroutine kmp_set_warnings_off
+
+!       ***
+!       *** interop query routines
+!       ***
+
+        function omp_get_num_interop_properties(interop) bind(c)
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_integer_kind) omp_get_num_interop_properties
+        end function omp_get_num_interop_properties
+
+        function omp_get_interop_int(interop, property_id, err) bind(c)
+          use, intrinsic :: iso_c_binding, only: c_intptr_t, c_int
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_interop_property_kind), value :: property_id
+          integer (kind=c_int) :: err
+          integer (kind=c_intptr_t) omp_get_interop_int
+        end function omp_get_interop_int
+
+        function omp_get_interop_ptr(interop, property_id, err) bind(c)
+          use, intrinsic :: iso_c_binding, only: c_ptr, c_int
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_interop_property_kind), value :: property_id
+          integer (kind=c_int) :: err
+          type(c_ptr) omp_get_interop_ptr
+        end function omp_get_interop_ptr
+
+        function omp_get_interop_str(interop, property_id, err) bind(c)
+          use, intrinsic :: iso_c_binding, only: c_ptr, c_int
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_interop_property_kind), value :: property_id
+          integer (kind=c_int) :: err
+          type(c_ptr) omp_get_interop_str
+        end function omp_get_interop_str
+
+        function omp_get_interop_name(interop, property_id) bind(c)
+          use, intrinsic :: iso_c_binding, only: c_ptr
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_interop_property_kind), value :: property_id
+          type(c_ptr) omp_get_interop_name
+        end function omp_get_interop_name
+
+        function omp_get_interop_type_desc(interop, property_id) bind(c)
+          use, intrinsic :: iso_c_binding, only: c_ptr
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_interop_property_kind), value :: property_id
+          type(c_ptr) omp_get_interop_type_desc
+        end function omp_get_interop_type_desc
+
+        function omp_get_interop_rc_desc(interop, ret_code) bind(c)
+          use, intrinsic :: iso_c_binding, only: c_ptr
+          import
+          integer (kind=omp_interop_kind), value :: interop
+          integer (kind=omp_interop_rc_kind), value :: ret_code
+          type(c_ptr) omp_get_interop_rc_desc
+        end function omp_get_interop_rc_desc
       end interface
 
 #ifdef __INTEL_COMPILER



More information about the Openmp-commits mailing list