[flang-commits] [flang] 5b9fa24 - [Flang][MLIR][OpenMP] Move host op filtering to the omp dialect (#208189)

via flang-commits flang-commits at lists.llvm.org
Thu Jul 9 03:56:54 PDT 2026


Author: Sergio Afonso
Date: 2026-07-09T11:56:50+01:00
New Revision: 5b9fa24dbd7b504b99143ebd2f5f12f8560c6331

URL: https://github.com/llvm/llvm-project/commit/5b9fa24dbd7b504b99143ebd2f5f12f8560c6331
DIFF: https://github.com/llvm/llvm-project/commit/5b9fa24dbd7b504b99143ebd2f5f12f8560c6331.diff

LOG: [Flang][MLIR][OpenMP] Move host op filtering to the omp dialect (#208189)

The MLIR pass that removes operations exclusively intended for the host
from OpenMP target offload modules is currently defined as part of
Flang. However, this is a feature that would benefit from being reusable
by other frontends, as removing such operations is a requirement for all
OpenMP target device modules prior to LLVM IR translation.

By moving the `omp-host-op-filtering` pass out of Flang, it had to be
updated to work on a lower-level LLVM dialect-based representation,
rather than FIR. This simplified some of the existing edge cases, such
as `fir.declare` ops and `fir.boxchar` type handling. In addition, new
function arguments are introduced as placeholders and return values from
host-only functions are removed, producing a cleaner result and
simplifying the pass as compared to previously.

As a result of a later execution of this pass, dynamic dispatch of host
functions via dispatch table using `fir.dispatch`, `fir.type_info` and
`fir.dt_entry` ops would break due to the removal of `fir.dt_entry`
operations pointing to deleted host functions, while `fir.dispatch` ops
pointing to the same functions would remain. The
`omp-function-filtering` pass is updated to prevent `fir.dt_entry` from
being deleted and let them point to an undefined symbol. This lets FIR
lowering to LLVM work well enough to get to the host op filtering stage,
where any resulting operations for this host dynamic dispatch are
cleaned up.

Added: 
    flang/test/Lower/OpenMP/function-filtering-4.f90
    mlir/lib/Dialect/OpenMP/Transforms/HostOpFiltering.cpp
    mlir/test/Dialect/OpenMP/host-op-filtering.mlir

Modified: 
    flang/include/flang/Optimizer/OpenMP/Passes.td
    flang/lib/Optimizer/OpenMP/CMakeLists.txt
    flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
    flang/lib/Optimizer/Passes/Pipelines.cpp
    flang/test/Fir/basic-program.fir
    flang/test/Lower/OpenMP/host-eval.f90
    mlir/include/mlir/Dialect/OpenMP/Transforms/Passes.td
    mlir/lib/Dialect/OpenMP/Transforms/CMakeLists.txt

Removed: 
    flang/lib/Optimizer/OpenMP/HostOpFiltering.cpp
    flang/test/Transforms/OpenMP/function-filtering-host-ops.mlir


################################################################################
diff  --git a/flang/include/flang/Optimizer/OpenMP/Passes.td b/flang/include/flang/Optimizer/OpenMP/Passes.td
index b5d9372e356f1..9ec159e1ba1e0 100644
--- a/flang/include/flang/Optimizer/OpenMP/Passes.td
+++ b/flang/include/flang/Optimizer/OpenMP/Passes.td
@@ -57,28 +57,6 @@ def FunctionFilteringPass : Pass<"omp-function-filtering"> {
   ];
 }
 
-def HostOpFilteringPass : Pass<"omp-host-op-filtering"> {
-  let summary =
-      "Removes host-only operations from host functions retained by the "
-      "function filtering pass when compiling for a target device.";
-  let description = [{
-    Removes operations from host functions that are not placed inside of an
-    `omp.target` region when compiling for the target device.
-    
-    It must run after any passes that might introduce new host operations,
-    especially if they belong to the 'omp' dialect, like `LowerWorkdistribute`.
-    This ensures all potentially problematic host operations are accounted for
-    prior to LLVM IR translation.
-
-    This pass works at the FIR level of abstraction.
-  }];
-  let dependentDialects = [
-    "mlir::arith::ArithDialect",
-    "mlir::func::FuncDialect",
-    "fir::FIROpsDialect"
-  ];
-}
-
 def DoConcurrentConversionPass : Pass<"omp-do-concurrent-conversion", "mlir::ModuleOp"> {
   let summary = "Map `DO CONCURRENT` loops to OpenMP worksharing loops.";
 

diff  --git a/flang/lib/Optimizer/OpenMP/CMakeLists.txt b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
index f25e95f041d20..db29e93b71dad 100644
--- a/flang/lib/Optimizer/OpenMP/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
@@ -5,7 +5,6 @@ add_flang_library(FlangOpenMPTransforms
   DoConcurrentConversion.cpp
   FunctionFiltering.cpp
   GenericLoopConversion.cpp
-  HostOpFiltering.cpp
   MapsForPrivatizedSymbols.cpp
   MapInfoFinalization.cpp
   DeleteUnreachableTargets.cpp

diff  --git a/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp b/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
index e8bcc0c7fa062..ee61dd8881fd0 100644
--- a/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
+++ b/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
@@ -48,8 +48,7 @@ checkDeviceImplementationStatus(omp::OffloadModuleInterface offloadModule) {
     if (!redOp.getByrefElementType())
       return WalkResult::advance();
 
-    auto seqTy =
-        mlir::dyn_cast<fir::SequenceType>(*redOp.getByrefElementType());
+    auto seqTy = dyn_cast<fir::SequenceType>(*redOp.getByrefElementType());
 
     bool isByRefReductionSupported =
         !seqTy || !fir::sequenceWithNonConstantShape(seqTy);
@@ -104,7 +103,7 @@ class FunctionFilteringPass
         SymbolTable::UseRange funcUses = *funcOp.getSymbolUses(op);
         for (SymbolTable::SymbolUse use : funcUses) {
           Operation *callOp = use.getUser();
-          if (auto internalFunc = mlir::dyn_cast<func::FuncOp>(callOp)) {
+          if (auto internalFunc = dyn_cast<func::FuncOp>(callOp)) {
             // Do not delete internal procedures holding the symbol of their
             // Fortran host procedure as attribute.
             internalFunc->removeAttr(fir::getHostSymbolAttrName());
@@ -114,6 +113,15 @@ class FunctionFilteringPass
             internalFunc.setVisibility(mlir::SymbolTable::Visibility::Public);
             continue;
           }
+          // Prevent dispatch table entries pointing to deleted functions
+          // from being removed. This prevents the lowering of any
+          // corresponding fir.dispatch ops from triggering errors. These
+          // fir.dt_entry ops will point to an undefined symbol as a result,
+          // which currently doesn't cause an issue, as fir.dispatch-related ops
+          // are later removed by the host op filtering pass.
+          if (isa<fir::DTEntryOp>(callOp))
+            continue;
+
           // If the callOp has users then replace them with Undef values.
           if (!callOp->use_empty()) {
             SmallVector<Value> undefResults;

diff  --git a/flang/lib/Optimizer/OpenMP/HostOpFiltering.cpp b/flang/lib/Optimizer/OpenMP/HostOpFiltering.cpp
deleted file mode 100644
index 05ea0bc08c4ab..0000000000000
--- a/flang/lib/Optimizer/OpenMP/HostOpFiltering.cpp
+++ /dev/null
@@ -1,422 +0,0 @@
-//===- HostOpFiltering.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
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements transforms to filter out host-only operations from
-// any remaining host functions when compiling for a target device.
-//
-//===----------------------------------------------------------------------===//
-
-#include "flang/Optimizer/Dialect/FIRDialect.h"
-#include "flang/Optimizer/Dialect/FIROps.h"
-#include "flang/Optimizer/OpenMP/Passes.h"
-
-#include "mlir/Dialect/Arith/IR/Arith.h"
-#include "mlir/Dialect/Func/IR/FuncOps.h"
-#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
-#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
-#include "llvm/ADT/SetVector.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/TypeSwitch.h"
-
-namespace flangomp {
-#define GEN_PASS_DEF_HOSTOPFILTERINGPASS
-#include "flang/Optimizer/OpenMP/Passes.h.inc"
-} // namespace flangomp
-
-using namespace mlir;
-
-/// Add an operation to one of the output sets to be later rewritten.
-template <typename OpTy>
-static void collectRewrite(OpTy op, llvm::SetVector<OpTy> &rewrites) {
-  rewrites.insert(op);
-}
-
-/// Add an \c omp.map.info operation and all its members recursively to the
-/// output set to be later rewritten.
-///
-/// Dependencies across \c omp.map.info are maintained by ensuring dependencies
-/// are added to the output sets before operations based on them.
-template <>
-void collectRewrite(omp::MapInfoOp mapOp,
-                    llvm::SetVector<omp::MapInfoOp> &rewrites) {
-  for (Value member : mapOp.getMembers())
-    collectRewrite(cast<omp::MapInfoOp>(member.getDefiningOp()), rewrites);
-
-  rewrites.insert(mapOp);
-}
-
-/// Add the given value to a sorted set if it should be replaced by a
-/// placeholder when used as an operand that must remain for the device.
-///
-/// Values that are block arguments of \c func.func operations are skipped,
-/// since they will still be available after all rewrites are completed.
-static void collectRewrite(Value value, llvm::SetVector<Value> &rewrites) {
-  if ((isa<BlockArgument>(value) &&
-       isa<func::FuncOp>(
-           cast<BlockArgument>(value).getOwner()->getParentOp())) ||
-      rewrites.contains(value))
-    return;
-
-  rewrites.insert(value);
-}
-
-/// Provide the `device_type` of an `omp.declare_target` attribute, if defined.
-static std::optional<omp::DeclareTargetDeviceType>
-getDeclareTargetDevice(Operation &op) {
-  auto declareTargetOp = dyn_cast<omp::DeclareTargetInterface>(op);
-  if (declareTargetOp && declareTargetOp.isDeclareTarget())
-    return declareTargetOp.getDeclareTargetDeviceType();
-  return std::nullopt;
-}
-
-namespace {
-class HostOpFilteringPass
-    : public flangomp::impl::HostOpFilteringPassBase<HostOpFilteringPass> {
-public:
-  HostOpFilteringPass() = default;
-
-  void runOnOperation() override {
-    auto op = dyn_cast<omp::OffloadModuleInterface>(getOperation());
-    if (!op || !op.getIsTargetDevice())
-      return;
-
-    op->walk<WalkOrder::PreOrder>([&](func::FuncOp funcOp) {
-      omp::DeclareTargetDeviceType declareType =
-          getDeclareTargetDevice(*funcOp.getOperation())
-              .value_or(omp::DeclareTargetDeviceType::host);
-
-      // Only process host function definitions.
-      if (funcOp.isExternal() ||
-          declareType != omp::DeclareTargetDeviceType::host)
-        return WalkResult::advance();
-
-      if (failed(rewriteHostFunction(funcOp))) {
-        funcOp.emitOpError() << "could not filter host-only operations";
-        return WalkResult::interrupt();
-      }
-      return WalkResult::advance();
-    });
-
-    // Make non-declare target globals internal for the device. They cannot be
-    // deleted, because they are needed in order to properly lower map clauses.
-    // However, no uses will remain in the device module, so we make them
-    // internal to prevent link time issues.
-    op->walk([&](fir::GlobalOp globalOp) {
-      if (!getDeclareTargetDevice(*globalOp.getOperation()).has_value())
-        globalOp.setLinkName("internal");
-    });
-  }
-
-private:
-  /// Rewrite the given host device function containing \c omp.target
-  /// operations, to remove host-only operations that are not used by device
-  /// codegen.
-  ///
-  /// It is based on the expected form of the MLIR module as produced by Flang
-  /// lowering, after HLFIR to FIR lowering, and it performs the following
-  /// mutations:
-  ///   - Replace all values returned by the function with \c fir.undefined.
-  ///   - \c omp.target operations are moved to the end of the function. If they
-  ///     are nested inside of any other operations, they are hoisted out of
-  ///     them.
-  ///   - \c depend, \c device and \c if clauses are removed from these target
-  ///     functions. Values used to initialize other clauses are replaced by
-  ///     placeholders as follows:
-  ///     - Values defined by block arguments are replaced by placeholders only
-  ///       if they are not attached to the parent \c func.func operation. In
-  ///       that case, they are passed unmodified.
-  ///     - \c arith.constant and \c fir.address_of ops are maintained.
-  ///     - Values of type \c fir.boxchar are replaced with a combination of
-  ///       \c fir.alloca for a single bit and a \c fir.emboxchar.
-  ///     - Other values are replaced by a combination of an \c fir.alloca for a
-  ///       single bit and an \c fir.convert to the original type of the value.
-  ///       This can be done because the code eventually generated for these
-  ///       operations will be discarded, as they aren't runnable by the target
-  ///       device.
-  ///   - \c omp.map.info operations associated to these target regions are
-  ///     preserved. These are moved above all \c omp.target and sorted to
-  ///     satisfy dependencies among them.
-  ///   - \c bounds arguments are removed from \c omp.map.info operations.
-  ///   - \c var_ptr and \c var_ptr_ptr arguments of \c omp.map.info are
-  ///     handled as follows:
-  ///     - \c var_ptr_ptr is expected to be defined by a \c fir.box_offset
-  ///       operation which is preserved. Otherwise, the pass will fail.
-  ///     - \c var_ptr can be defined by a \c fir.declare which is also
-  ///       preserved. Its \c memref argument is replaced by a placeholder or
-  ///       maintained, similarly to non-map clauses of target operations
-  ///       described above. If it has \c shape or \c typeparams arguments, they
-  ///       are replaced by applicable constants. \c dummy_scope arguments
-  ///       are discarded.
-  ///   - Every other operation not located inside of an \c omp.target is
-  ///     removed.
-  LogicalResult rewriteHostFunction(func::FuncOp funcOp) {
-    Region &region = funcOp.getRegion();
-
-    // Collect target operations inside of the function.
-    llvm::SmallVector<omp::TargetOp> targetOps;
-    region.walk<WalkOrder::PreOrder>([&](Operation *op) {
-      // Skip the inside of omp.target regions, since these contain device code.
-      if (auto targetOp = dyn_cast<omp::TargetOp>(op)) {
-        targetOps.push_back(targetOp);
-        return WalkResult::skip();
-      }
-
-      // Replace omp.target_data entry block argument uses with the value used
-      // to initialize the associated omp.map.info operation. This way,
-      // references are still valid once the omp.target operation has been
-      // extracted out of the omp.target_data region.
-      if (auto targetDataOp = dyn_cast<omp::TargetDataOp>(op)) {
-        llvm::SmallVector<std::pair<Value, BlockArgument>> argPairs;
-        cast<omp::BlockArgOpenMPOpInterface>(*targetDataOp)
-            .getBlockArgsPairs(argPairs);
-        for (auto [operand, blockArg] : argPairs) {
-          auto mapInfo = cast<omp::MapInfoOp>(operand.getDefiningOp());
-          Value varPtr = mapInfo.getVarPtr();
-
-          // If the var_ptr operand of the omp.map.info op defining this entry
-          // block argument is a fir.declare, the uses of all users of that
-          // entry block argument that are themselves fir.declare are replaced
-          // by the value produced by the outer one.
-          //
-          // This prevents this pass from producing chains of fir.declare of the
-          // type:
-          // %0 = ...
-          // %1 = fir.declare %0
-          // %2 = fir.declare %1...
-          // %3 = omp.map.info var_ptr(%2 ...
-          if (auto outerDeclare = varPtr.getDefiningOp<fir::DeclareOp>())
-            for (Operation *user : blockArg.getUsers())
-              if (isa<fir::DeclareOp>(user))
-                user->replaceAllUsesWith(outerDeclare);
-
-          // All remaining uses of the entry block argument are replaced with
-          // the var_ptr initialization value.
-          blockArg.replaceAllUsesWith(varPtr);
-        }
-      }
-      return WalkResult::advance();
-    });
-
-    // Make a temporary clone of the parent operation with an empty region,
-    // and update all references to entry block arguments to those of the new
-    // region. Users will later either be moved to the new region or deleted
-    // when the original region is replaced by the new.
-    OpBuilder builder(&getContext());
-    builder.setInsertionPointAfter(funcOp);
-    Operation *newOp = builder.cloneWithoutRegions(funcOp);
-    Block &block = newOp->getRegion(0).emplaceBlock();
-
-    llvm::SmallVector<Location> locs;
-    locs.reserve(region.getNumArguments());
-    llvm::transform(region.getArguments(), std::back_inserter(locs),
-                    [](const BlockArgument &arg) { return arg.getLoc(); });
-    block.addArguments(region.getArgumentTypes(), locs);
-
-    for (auto [oldArg, newArg] :
-         llvm::zip_equal(region.getArguments(), block.getArguments()))
-      oldArg.replaceAllUsesWith(newArg);
-
-    // Collect omp.map.info ops while satisfying interdependencies and remove
-    // operands that aren't used by target device codegen.
-    //
-    // This logic must be updated whenever operands to omp.target change.
-    llvm::SetVector<Value> rewriteValues;
-    llvm::SetVector<omp::MapInfoOp> mapInfos;
-    for (omp::TargetOp targetOp : targetOps) {
-      assert(targetOp.getHostEvalVars().empty() &&
-             "unexpected host_eval in target device module");
-
-      // Variables unused by the device.
-      targetOp.getDependVarsMutable().clear();
-      targetOp.setDependKindsAttr(nullptr);
-      targetOp.getDependIteratedMutable().clear();
-      targetOp.setDependIteratedKindsAttr(nullptr);
-      targetOp.getDeviceMutable().clear();
-      targetOp.getDynGroupprivateSizeMutable().clear();
-      targetOp.getIfExprMutable().clear();
-
-      // TODO: Clear some of these operands rather than rewriting them,
-      // depending on whether they are needed by device codegen once support for
-      // them is fully implemented.
-      for (Value allocVar : targetOp.getAllocateVars())
-        collectRewrite(allocVar, rewriteValues);
-      for (Value allocVar : targetOp.getAllocatorVars())
-        collectRewrite(allocVar, rewriteValues);
-      for (Value inReduction : targetOp.getInReductionVars())
-        collectRewrite(inReduction, rewriteValues);
-      for (Value isDevPtr : targetOp.getIsDevicePtrVars())
-        collectRewrite(isDevPtr, rewriteValues);
-      for (Value mapVar : targetOp.getHasDeviceAddrVars())
-        collectRewrite(cast<omp::MapInfoOp>(mapVar.getDefiningOp()), mapInfos);
-      for (Value mapVar : targetOp.getMapVars())
-        collectRewrite(cast<omp::MapInfoOp>(mapVar.getDefiningOp()), mapInfos);
-      for (Value privateVar : targetOp.getPrivateVars())
-        collectRewrite(privateVar, rewriteValues);
-      for (Value threadLimit : targetOp.getThreadLimitVars())
-        collectRewrite(threadLimit, rewriteValues);
-    }
-
-    // Move omp.map.info ops to the new block and collect dependencies.
-    llvm::SetVector<fir::DeclareOp> declareOps;
-    llvm::SetVector<fir::BoxOffsetOp> boxOffsets;
-    for (omp::MapInfoOp mapOp : mapInfos) {
-      if (auto declareOp = dyn_cast_if_present<fir::DeclareOp>(
-              mapOp.getVarPtr().getDefiningOp()))
-        collectRewrite(declareOp, declareOps);
-      else
-        collectRewrite(mapOp.getVarPtr(), rewriteValues);
-
-      if (Value varPtrPtr = mapOp.getVarPtrPtr()) {
-        if (auto boxOffset = llvm::dyn_cast_if_present<fir::BoxOffsetOp>(
-                varPtrPtr.getDefiningOp()))
-          collectRewrite(boxOffset, boxOffsets);
-        else
-          return mapOp->emitOpError() << "var_ptr_ptr rewrite only supported "
-                                         "if defined by fir.box_offset";
-      }
-
-      // Bounds are not used during target device codegen.
-      mapOp.getBoundsMutable().clear();
-      mapOp->moveBefore(&block, block.end());
-    }
-
-    // Create a temporary marker to simplify the op moving process below.
-    builder.setInsertionPointToStart(&block);
-    auto marker = fir::UndefOp::create(builder, builder.getUnknownLoc(),
-                                       builder.getNoneType());
-    builder.setInsertionPoint(marker);
-
-    // Handle dependencies of fir.declare ops.
-    for (fir::DeclareOp declareOp : declareOps) {
-      collectRewrite(declareOp.getMemref(), rewriteValues);
-
-      if (declareOp.getStorage())
-        collectRewrite(declareOp.getStorage(), rewriteValues);
-
-      // Shape and typeparams aren't needed for target device codegen, but
-      // removing them would break verifiers.
-      Value zero;
-      if (declareOp.getShape() || !declareOp.getTypeparams().empty())
-        zero = arith::ConstantOp::create(builder, declareOp.getLoc(),
-                                         builder.getI64IntegerAttr(0));
-
-      if (auto shape = declareOp.getShape()) {
-        // The pre-cg rewrite pass requires the shape to be defined by one of
-        // fir.shape, fir.shapeshift or fir.shift, so we need to make sure it's
-        // still defined by one of these after this pass.
-        Operation *shapeOp = shape.getDefiningOp();
-        llvm::SmallVector<Value> extents(shapeOp->getNumOperands(), zero);
-        Value newShape =
-            llvm::TypeSwitch<Operation *, Value>(shapeOp)
-                .Case([&](fir::ShapeOp op) {
-                  return fir::ShapeOp::create(builder, op.getLoc(), extents);
-                })
-                .Case([&](fir::ShapeShiftOp op) {
-                  auto type = fir::ShapeShiftType::get(op.getContext(),
-                                                       extents.size() / 2);
-                  return fir::ShapeShiftOp::create(builder, op.getLoc(), type,
-                                                   extents);
-                })
-                .Case([&](fir::ShiftOp op) {
-                  auto type =
-                      fir::ShiftType::get(op.getContext(), extents.size());
-                  return fir::ShiftOp::create(builder, op.getLoc(), type,
-                                              extents);
-                })
-                .Default([](Operation *op) {
-                  op->emitOpError()
-                      << "fir.declare shape expected to be one of: "
-                         "fir.shape, fir.shapeshift or fir.shift";
-                  return nullptr;
-                });
-
-        if (!newShape)
-          return failure();
-
-        declareOp.getShapeMutable().assign(newShape);
-      }
-
-      for (OpOperand &typeParam : declareOp.getTypeparamsMutable())
-        typeParam.assign(zero);
-
-      declareOp.getDummyScopeMutable().clear();
-    }
-
-    // We don't actually need the proper initialization, but rather just
-    // maintain the basic form of these operands. Generally, we create 1-bit
-    // placeholder allocas that we "typecast" to the expected type and replace
-    // all uses. Using fir.undefined here instead is not possible because these
-    // variables cannot be constants, as that would trigger 
diff erent codegen
-    // for target regions.
-    for (Value value : rewriteValues) {
-      Location loc = value.getLoc();
-      Value rewriteValue;
-      if (isa_and_present<arith::ConstantOp, fir::AddrOfOp>(
-              value.getDefiningOp())) {
-        // If it's defined by fir.address_of, then we need to keep that op as
-        // well because it might be pointing to a 'declare target' global.
-        // Constants can also trigger 
diff erent codegen paths, so we keep them
-        // as well.
-        rewriteValue = builder.clone(*value.getDefiningOp())->getResult(0);
-      } else if (auto boxCharType =
-                     dyn_cast<fir::BoxCharType>(value.getType())) {
-        // !fir.boxchar types cannot be directly obtained by converting a
-        // !fir.ref<i1>, as they aren't reference types. Since they can appear
-        // representing some `target firstprivate` clauses, we need to create
-        // a special case here based on creating a placeholder fir.emboxchar op.
-        MLIRContext *ctx = &getContext();
-        fir::KindTy kind = boxCharType.getKind();
-        auto placeholder = fir::AllocaOp::create(
-            builder, loc, fir::CharacterType::getSingleton(ctx, kind));
-        auto one = arith::ConstantOp::create(builder, loc, builder.getI32Type(),
-                                             builder.getI32IntegerAttr(1));
-        rewriteValue = fir::EmboxCharOp::create(builder, loc, boxCharType,
-                                                placeholder, one);
-      } else {
-        Value placeholder =
-            fir::AllocaOp::create(builder, loc, builder.getI1Type());
-        rewriteValue =
-            fir::ConvertOp::create(builder, loc, value.getType(), placeholder);
-      }
-      value.replaceAllUsesWith(rewriteValue);
-    }
-
-    // Move omp.map.info dependencies.
-    for (fir::DeclareOp declareOp : declareOps)
-      declareOp->moveBefore(marker);
-
-    // The box_ref argument of fir.box_offset is expected to be the same value
-    // that was passed as var_ptr to the corresponding omp.map.info, so we don't
-    // need to handle its defining op here.
-    for (fir::BoxOffsetOp boxOffset : boxOffsets)
-      boxOffset->moveBefore(marker);
-
-    marker->erase();
-
-    // Move target operations to the end of the new block.
-    for (omp::TargetOp targetOp : targetOps)
-      targetOp->moveBefore(&block, block.end());
-
-    // Add terminator to the new block.
-    builder.setInsertionPointToEnd(&block);
-    llvm::SmallVector<Value> returnValues;
-    returnValues.reserve(funcOp.getNumResults());
-    for (auto type : funcOp.getResultTypes())
-      returnValues.push_back(
-          fir::UndefOp::create(builder, funcOp.getLoc(), type));
-
-    func::ReturnOp::create(builder, funcOp.getLoc(), returnValues);
-
-    // Replace old region (now missing ops) with the new one and remove the
-    // temporary operation clone.
-    region.takeBody(newOp->getRegion(0));
-    newOp->erase();
-    return success();
-  }
-};
-} // namespace

diff  --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index a8e598ab95ac8..5fa70096e0e41 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -352,7 +352,6 @@ void createHLFIRToFIRPassPipeline(mlir::PassManager &pm,
   if (enableOpenMP != EnableOpenMP::None) {
     pm.addPass(flangomp::createLowerWorkshare());
     pm.addPass(flangomp::createLowerWorkdistribute());
-    pm.addPass(flangomp::createHostOpFilteringPass());
   }
   if (enableOpenMP == EnableOpenMP::Simd)
     pm.addPass(flangomp::createSimdOnlyPass());
@@ -462,11 +461,17 @@ void createDefaultFIRCodeGenPassPipeline(mlir::PassManager &pm,
 
   fir::addFIRToLLVMPass(pm, config);
 
-  // Convert applicable OpenMP stack allocations to shared memory allocations
-  // for GPU targets. This pass must run after any alloca-generating passes to
-  // ensure all are adequately accounted for.
-  if (config.EnableOpenMP && !config.EnableOpenMPSimd)
+  if (config.EnableOpenMP && !config.EnableOpenMPSimd) {
+    // Remove all non target-related operations from host functions still
+    // remaining at this point, if compiling for an OpenMP target device. This
+    // is required before translating 'omp' dialect operations to LLVM IR.
+    pm.addPass(mlir::omp::createHostOpFilteringPass());
+
+    // Convert applicable OpenMP stack allocations to shared memory allocations
+    // for GPU targets. This pass must run after any alloca-generating passes to
+    // ensure all are adequately accounted for.
     pm.addPass(mlir::omp::createStackToSharedPass());
+  }
 }
 
 /// Create a pass pipeline for lowering from MLIR to LLVM IR

diff  --git a/flang/test/Fir/basic-program.fir b/flang/test/Fir/basic-program.fir
index cbf45b216a911..471e5a89046eb 100644
--- a/flang/test/Fir/basic-program.fir
+++ b/flang/test/Fir/basic-program.fir
@@ -85,7 +85,6 @@ func.func @_QQmain() {
 // PASSES-NEXT:   ConvertHLFIRtoFIR
 // PASSES-NEXT:   LowerWorkshare
 // PASSES-NEXT:   LowerWorkdistribute
-// PASSES-NEXT:   HostOpFilteringPass
 // PASSES-NEXT:   CSE
 // PASSES-NEXT:   (S) 0 num-cse'd - Number of operations CSE'd
 // PASSES-NEXT:   (S) 0 num-dce'd - Number of operations DCE'd
@@ -185,6 +184,7 @@ func.func @_QQmain() {
 // PASSES-NEXT:  LowerNontemporalPass
 // PASSES-NEXT: FIRToLLVMLowering
 // PASSES-NEXT: ReconcileUnrealizedCasts
+// PASSES-NEXT:  HostOpFilteringPass
 // PASSES-NEXT: 'llvm.func' Pipeline
 // PASSES-NEXT:  StackToSharedPass
 // PASSES-NEXT: PrepareForOMPOffloadPrivatizationPass

diff  --git a/flang/test/Lower/OpenMP/function-filtering-4.f90 b/flang/test/Lower/OpenMP/function-filtering-4.f90
new file mode 100644
index 0000000000000..de8a26c789dc5
--- /dev/null
+++ b/flang/test/Lower/OpenMP/function-filtering-4.f90
@@ -0,0 +1,63 @@
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-llvm %s -o - | FileCheck --check-prefixes=LLVM-HOST,LLVM-ALL %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-hlfir %s -o - | FileCheck --check-prefixes=MLIR-HOST,MLIR-ALL %s
+! RUN: %if amdgpu-registered-target %{ %flang_fc1 -triple amdgcn-amd-amdhsa -fopenmp -fopenmp-version=52 -fopenmp-is-target-device -emit-llvm %s -o - | FileCheck --check-prefixes=LLVM-DEVICE,LLVM-ALL %s %}
+! RUN: %if amdgpu-registered-target %{ %flang_fc1 -triple amdgcn-amd-amdhsa -fopenmp -fopenmp-version=52 -fopenmp-is-target-device -emit-hlfir %s -o - | FileCheck --check-prefixes=MLIR-DEVICE,MLIR-ALL %s %}
+! RUN: bbc -fopenmp -fopenmp-version=52 -emit-hlfir %s -o - | FileCheck --check-prefixes=MLIR-HOST,MLIR-ALL %s
+! RUN: %if amdgpu-registered-target %{ bbc -target amdgcn-amd-amdhsa -fopenmp -fopenmp-version=52 -fopenmp-is-target-device -emit-hlfir %s -o - | FileCheck --check-prefixes=MLIR-DEVICE,MLIR-ALL %s %}
+
+! Check that the correct LLVM IR operations are kept for the host and device
+! after running the whole set of translation and transformation passes from
+! Fortran for a type-bound procedure.
+
+module mymodule
+  implicit none
+
+  type :: myclass
+  contains
+    procedure :: myfunc => myfunc
+  end type myclass
+
+contains
+  ! MLIR-HOST: func.func @_QMmymodulePmyfunc
+  ! MLIR-DEVICE-NOT: func.func @_QMmymodulePmyfunc
+
+  ! LLVM-HOST: define void @_QMmymodulePmyfunc
+  ! LLVM-DEVICE-NOT: define void @_QMmymodulePmyfunc
+  subroutine myfunc(self)
+    class(myclass) :: self
+    call foo()
+  end subroutine
+end module
+
+! MLIR-ALL: func.func @_QPmain
+
+! LLVM-HOST: define void @main_
+! LLVM-DEVICE-NOT: define void @main_
+subroutine main(x)
+  use mymodule
+  implicit none
+
+  integer, intent(inout) :: x
+  class(myclass), allocatable :: myobj
+  allocate(myobj)
+
+  ! MLIR-ALL: fir.dispatch "myfunc"
+
+  ! LLVM-HOST: %[[MYFUNC_PTR:.*]] = inttoptr i64 %{{.*}} to ptr
+  ! LLVM-HOST: call void %[[MYFUNC_PTR]](ptr %{{.*}})
+  ! LLVM-DEVICE-NOT: call void %{{.*}}(ptr %{{.*}})
+  call myobj%myfunc()
+
+  !$omp target map(tofrom: x)
+  x = x + 1
+  !$omp end target
+
+  deallocate(myobj)
+
+  ! LLVM-HOST: ret void
+end subroutine main
+
+! LLVM-ALL: define {{.*}}void @__omp_offloading{{.*}}main_{{.*}}
+
+! MLIR-ALL: fir.type_info @_QMmymoduleTmyclass {{.*}} dispatch_table
+! MLIR-ALL-NEXT: fir.dt_entry "myfunc", @_QMmymodulePmyfunc

diff  --git a/flang/test/Lower/OpenMP/host-eval.f90 b/flang/test/Lower/OpenMP/host-eval.f90
index 3bd873009530c..35749a6ac5050 100644
--- a/flang/test/Lower/OpenMP/host-eval.f90
+++ b/flang/test/Lower/OpenMP/host-eval.f90
@@ -22,10 +22,8 @@ subroutine teams()
 
   !$omp end target
 
-  ! HOST: omp.teams
-  ! HOST-SAME: num_teams({{.*}}) thread_limit({{.*}}) {
-
-  ! DEVICE-NOT: omp.teams
+  ! BOTH: omp.teams
+  ! BOTH-SAME: num_teams({{.*}}) thread_limit({{.*}}) {
   !$omp teams num_teams(1) thread_limit(2)
   call foo()
   !$omp end teams
@@ -78,18 +76,13 @@ subroutine distribute_parallel_do()
   !$omp end distribute parallel do
   !$omp end target teams
 
-  ! HOST: omp.teams
-  ! DEVICE-NOT: omp.teams
+  ! BOTH: omp.teams
   !$omp teams
 
-  ! HOST: omp.parallel
-  ! HOST-SAME: num_threads({{.*}})
-  ! HOST: omp.distribute
-  ! HOST-NEXT: omp.wsloop
-
-  ! DEVICE-NOT: omp.parallel
-  ! DEVICE-NOT: omp.distribute
-  ! DEVICE-NOT: omp.wsloop
+  ! BOTH: omp.parallel
+  ! BOTH-SAME: num_threads({{.*}})
+  ! BOTH: omp.distribute
+  ! BOTH-NEXT: omp.wsloop
   !$omp distribute parallel do num_threads(1)
   do i=1,10
     call foo()
@@ -147,15 +140,14 @@ subroutine distribute_parallel_do_simd()
   !$omp end distribute parallel do simd
   !$omp end target teams
 
-  ! HOST: omp.teams
-  ! DEVICE-NOT: omp.teams
+  ! BOTH: omp.teams
   !$omp teams
 
-  ! HOST: omp.parallel
-  ! HOST-SAME: num_threads({{.*}})
-  ! HOST: omp.distribute
-  ! HOST-NEXT: omp.wsloop
-  ! HOST-NEXT: omp.simd
+  ! BOTH: omp.parallel
+  ! BOTH-SAME: num_threads({{.*}})
+  ! BOTH: omp.distribute
+  ! BOTH-NEXT: omp.wsloop
+  ! BOTH-NEXT: omp.simd
 
   ! DEVICE-NOT: omp.parallel
   ! DEVICE-NOT: omp.distribute
@@ -207,12 +199,10 @@ subroutine distribute()
   !$omp end distribute
   !$omp end target teams
 
-  ! HOST: omp.teams
-  ! DEVICE-NOT: omp.teams
+  ! BOTH: omp.teams
   !$omp teams
 
-  ! HOST: omp.distribute
-  ! DEVICE-NOT: omp.distribute
+  ! BOTH: omp.distribute
   !$omp distribute
   do i=1,10
     call foo()
@@ -261,15 +251,11 @@ subroutine distribute_simd()
   !$omp end distribute simd
   !$omp end target teams
 
-  ! HOST: omp.teams
-  ! DEVICE-NOT: omp.teams
+  ! BOTH: omp.teams
   !$omp teams
 
-  ! HOST: omp.distribute
-  ! HOST-NEXT: omp.simd
-
-  ! DEVICE-NOT: omp.distribute
-  ! DEVICE-NOT: omp.simd
+  ! BOTH: omp.distribute
+  ! BOTH-NEXT: omp.simd
   !$omp distribute simd
   do i=1,10
     call foo()

diff  --git a/flang/test/Transforms/OpenMP/function-filtering-host-ops.mlir b/flang/test/Transforms/OpenMP/function-filtering-host-ops.mlir
deleted file mode 100644
index 3acdfda876812..0000000000000
--- a/flang/test/Transforms/OpenMP/function-filtering-host-ops.mlir
+++ /dev/null
@@ -1,560 +0,0 @@
-// RUN: fir-opt --omp-host-op-filtering %s | FileCheck %s
-
-module attributes {omp.is_target_device = true} {
-  // CHECK-LABEL: func.func private @external
-  // CHECK-NOT: return
-  func.func private @external()
-
-  // CHECK-LABEL: func.func @basic_checks
-  // CHECK-SAME: (%[[ARG:.*]]: !fir.ref<i32>) -> (i32, f32)
-  func.func @basic_checks(%arg: !fir.ref<i32>) -> (i32, f32) {
-    // CHECK-NEXT: %[[PLACEHOLDER:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[ALLOC:.*]] = fir.convert %[[PLACEHOLDER]] : (!fir.ref<i1>) -> !fir.ref<i32>
-    // CHECK-NEXT: %[[GLOBAL:.*]] = fir.address_of(@global_scalar) : !fir.ref<i32>
-    %r0 = arith.constant 10 : i32
-    %r1 = arith.constant 2.5 : f32
-
-    func.call @foo() : () -> ()
-
-    %0 = fir.declare %arg {uniq_name = "arg"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %global = fir.address_of(@global_scalar) : !fir.ref<i32>
-    %1 = fir.declare %global {uniq_name = "global_scalar"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %alloc = fir.alloca i32
-    %2 = fir.declare %alloc {uniq_name = "alloc"} : (!fir.ref<i32>) -> !fir.ref<i32>
-
-    // CHECK-NEXT: %[[ALLOC_DECL:.*]] = fir.declare %[[ALLOC]] {uniq_name = "alloc"}
-    // CHECK-NEXT: %[[ARG_DECL:.*]] = fir.declare %[[ARG]] {uniq_name = "arg"}
-    // CHECK-NEXT: %[[GLOBAL_DECL:.*]] = fir.declare %[[GLOBAL]] {uniq_name = "global_scalar"}
-
-    // CHECK-NEXT: %[[MAP2:.*]] = omp.map.info var_ptr(%[[ALLOC_DECL]]{{.*}})
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[ARG_DECL]]{{.*}})
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[GLOBAL_DECL]]{{.*}})
-    // CHECK-NEXT: %[[MAP3:.*]] = omp.map.info var_ptr(%[[ALLOC]]{{.*}})
-    %m0 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m1 = omp.map.info var_ptr(%1 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m2 = omp.map.info var_ptr(%2 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m3 = omp.map.info var_ptr(%alloc : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-
-    // CHECK-NEXT: omp.target kernel_type(generic) has_device_addr(%[[MAP2]] -> {{.*}} : {{.*}}) map_entries(%[[MAP0]] -> {{.*}}, %[[MAP1]] -> {{.*}}, %[[MAP3]] -> {{.*}} : {{.*}})
-    omp.target kernel_type(generic) has_device_addr(%m2 -> %arg0 : !fir.ref<i32>) map_entries(%m0 -> %arg1, %m1 -> %arg2, %m3 -> %arg3 : !fir.ref<i32>, !fir.ref<i32>, !fir.ref<i32>) {
-      // CHECK-NEXT: func.call
-      func.call @foo() : () -> ()
-      omp.terminator
-    }
-
-    // CHECK-NOT: omp.parallel
-    // CHECK-NOT: func.call
-    // CHECK-NOT: omp.map.info
-    omp.parallel {
-      func.call @foo() : () -> ()
-      omp.terminator
-    }
-
-    // CHECK-NOT: omp.map.info
-    %m4 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m5 = omp.map.info var_ptr(%1 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m6 = omp.map.info var_ptr(%2 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m7 = omp.map.info var_ptr(%alloc : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-
-    // CHECK-NOT: omp.target_data
-    omp.target_data map_entries(%m4, %m5, %m6, %m7 : !fir.ref<i32>, !fir.ref<i32>, !fir.ref<i32>, !fir.ref<i32>) {
-      // CHECK-NOT: func.call
-      func.call @foo() : () -> ()
-      omp.terminator
-    }
-
-    // CHECK-NOT: omp.target_enter_data
-    // CHECK-NOT: omp.target_exit_data
-    // CHECK-NOT: omp.target_update
-    %m8 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(to) capture(ByRef) -> !fir.ref<i32>
-    omp.target_enter_data map_entries(%m8 : !fir.ref<i32>)
-
-    %m9 = omp.map.info var_ptr(%1 : !fir.ref<i32>, i32) map_clauses(from) capture(ByRef) -> !fir.ref<i32>
-    omp.target_exit_data map_entries(%m9 : !fir.ref<i32>)
-
-    %m10 = omp.map.info var_ptr(%2 : !fir.ref<i32>, !fir.ref<i32>) map_clauses(to) capture(ByRef) -> !fir.ref<i32>
-    omp.target_update map_entries(%m10 : !fir.ref<i32>)
-
-    // CHECK-NOT: func.call
-    func.call @foo() : () -> ()
-
-    // CHECK:      %[[RETURN0:.*]] = fir.undefined i32
-    // CHECK-NEXT: %[[RETURN1:.*]] = fir.undefined f32
-    // CHECK-NEXT: return %[[RETURN0]], %[[RETURN1]]
-    return %r0, %r1 : i32, f32
-  }
-
-  // CHECK-LABEL: func.func @allocatable_array
-  // CHECK-SAME: (%[[ALLOCATABLE:.*]]: [[ALLOCATABLE_TYPE:.*]], %[[ARRAY:.*]]: [[ARRAY_TYPE:[^)]*]])
-  func.func @allocatable_array(%allocatable: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, %array: !fir.ref<!fir.array<9xi32>>) {
-    // CHECK-NEXT: %[[ZERO:.*]] = arith.constant 0 : i64
-    // CHECK-NEXT: %[[SHAPE:.*]] = fir.shape %[[ZERO]] : (i64) -> !fir.shape<1>
-    // CHECK-NEXT: %[[ALLOCATABLE_DECL:.*]] = fir.declare %[[ALLOCATABLE]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "allocatable"} : ([[ALLOCATABLE_TYPE]]) -> [[ALLOCATABLE_TYPE]]
-    // CHECK-NEXT: %[[ARRAY_DECL:.*]] = fir.declare %[[ARRAY]](%[[SHAPE]]) {uniq_name = "array"} : ([[ARRAY_TYPE]], !fir.shape<1>) -> [[ARRAY_TYPE]]
-    // CHECK-NEXT: %[[VAR_PTR_PTR:.*]] = fir.box_offset %[[ALLOCATABLE_DECL]] base_addr : ([[ALLOCATABLE_TYPE]]) -> [[VAR_PTR_PTR_TYPE:.*]]
-    // CHECK-NEXT: %[[MAP_ALLOCATABLE:.*]] = omp.map.info var_ptr(%[[ALLOCATABLE_DECL]] : [[ALLOCATABLE_TYPE]], !fir.box<!fir.heap<!fir.array<?xf32>>>) map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%[[VAR_PTR_PTR]] : [[VAR_PTR_PTR_TYPE]], f32) -> [[VAR_PTR_PTR_TYPE]]
-    // CHECK-NEXT: %[[MAP_ARRAY:.*]] = omp.map.info var_ptr(%[[ARRAY_DECL]] : [[ARRAY_TYPE]], !fir.array<9xi32>) map_clauses(tofrom) capture(ByRef) -> [[ARRAY_TYPE]]
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP_ALLOCATABLE]] -> %{{.*}}, %[[MAP_ARRAY]] -> %{{.*}} : [[VAR_PTR_PTR_TYPE]], [[ARRAY_TYPE]])
-    %c0 = arith.constant 0 : index
-    %c1 = arith.constant 1 : index
-    %c8 = arith.constant 8 : index
-    %c9 = arith.constant 9 : index
-
-    %0 = fir.declare %allocatable {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "allocatable"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
-    %1 = omp.map.bounds lower_bound(%c0 : index) upper_bound(%c8 : index) extent(%c9 : index) stride(%c1 : index) start_idx(%c1 : index)
-    %2 = fir.box_offset %0 base_addr : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>
-    %m0 = omp.map.info var_ptr(%0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.box<!fir.heap<!fir.array<?xf32>>>) map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%2 : !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>, f32) bounds(%1) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>
-
-    %3 = fir.shape %c9 : (index) -> !fir.shape<1>
-    %4 = fir.declare %array(%3) {uniq_name = "array"} : (!fir.ref<!fir.array<9xi32>>, !fir.shape<1>) -> !fir.ref<!fir.array<9xi32>>
-    %5 = omp.map.bounds lower_bound(%c0 : index) upper_bound(%c8 : index) extent(%c9 : index) stride(%c1 : index) start_idx(%c1 : index)
-    %6 = omp.map.info var_ptr(%4 : !fir.ref<!fir.array<9xi32>>, !fir.array<9xi32>) map_clauses(tofrom) capture(ByRef) bounds(%5) -> !fir.ref<!fir.array<9xi32>>
-
-    omp.target kernel_type(generic) map_entries(%m0 -> %arg0, %6 -> %arg1 : !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>, !fir.ref<!fir.array<9xi32>>) {
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @character
-  // CHECK-SAME: (%[[X:.*]]: [[X_TYPE:[^)]*]])
-  func.func @character(%x: !fir.ref<!fir.char<1>>) {
-    // CHECK-NEXT: %[[ZERO]] = arith.constant 0 : i64
-    %0 = fir.dummy_scope : !fir.dscope
-    %c1 = arith.constant 1 : index
-    // CHECK-NEXT: %[[X_DECL:.*]] = fir.declare %[[X]] typeparams %[[ZERO]] {uniq_name = "x"} : ([[X_TYPE]], i64) -> [[X_TYPE]]
-    %3 = fir.declare %x typeparams %c1 dummy_scope %0 {uniq_name = "x"} : (!fir.ref<!fir.char<1>>, index, !fir.dscope) -> !fir.ref<!fir.char<1>>
-    // CHECK-NEXT: %[[MAP:.*]] = omp.map.info var_ptr(%[[X_DECL]] : [[X_TYPE]], !fir.char<1>) map_clauses(tofrom) capture(ByRef) -> [[X_TYPE]]
-    %map = omp.map.info var_ptr(%3 : !fir.ref<!fir.char<1>>, !fir.char<1>) map_clauses(tofrom) capture(ByRef) -> !fir.ref<!fir.char<1>>
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP]] -> %{{.*}})
-    omp.target kernel_type(generic) map_entries(%map -> %arg0 : !fir.ref<!fir.char<1>>) {
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @assumed_rank
-  // CHECK-SAME: (%[[X:.*]]: [[X_TYPE:[^)]*]])
-  func.func @assumed_rank(%x: !fir.box<!fir.array<*:f32>>) {
-    // CHECK-NEXT: %[[PLACEHOLDER:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[ALLOCA:.*]] = fir.convert %[[PLACEHOLDER]] : (!fir.ref<i1>) -> !fir.ref<[[X_TYPE]]>
-    %0 = fir.alloca !fir.box<!fir.array<*:f32>>
-    %1 = fir.dummy_scope : !fir.dscope
-    %2 = fir.declare %x dummy_scope %1 {uniq_name = "x"} : (!fir.box<!fir.array<*:f32>>, !fir.dscope) -> !fir.box<!fir.array<*:f32>>
-    %3 = fir.rebox_assumed_rank %2 lbs ones : (!fir.box<!fir.array<*:f32>>) -> !fir.box<!fir.array<*:f32>>
-    %4 = fir.box_addr %3 : (!fir.box<!fir.array<*:f32>>) -> !fir.ref<!fir.array<*:f32>>
-    fir.store %3 to %0 : !fir.ref<!fir.box<!fir.array<*:f32>>>
-    // CHECK-NEXT: %[[VAR_PTR_PTR:.*]] = fir.box_offset %[[ALLOCA]] base_addr : (!fir.ref<[[X_TYPE]]>) -> [[VAR_PTR_PTR_TYPE:.*]]
-    %5 = fir.box_offset %0 base_addr : (!fir.ref<!fir.box<!fir.array<*:f32>>>) -> !fir.llvm_ptr<!fir.ref<!fir.array<*:f32>>>
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[ALLOCA]] : !fir.ref<[[X_TYPE]]>, !fir.box<!fir.array<*:f32>>) {{.*}} var_ptr_ptr(%[[VAR_PTR_PTR]] : [[VAR_PTR_PTR_TYPE]], !fir.array<*:f32>) -> [[VAR_PTR_PTR_TYPE]]
-    %6 = omp.map.info var_ptr(%0 : !fir.ref<!fir.box<!fir.array<*:f32>>>, !fir.box<!fir.array<*:f32>>) map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%5 : !fir.llvm_ptr<!fir.ref<!fir.array<*:f32>>>, !fir.array<*:f32>) -> !fir.llvm_ptr<!fir.ref<!fir.array<*:f32>>>
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[ALLOCA]] : !fir.ref<[[X_TYPE]]>, !fir.box<!fir.array<*:f32>>) {{.*}} members(%[[MAP0]] : [0] : [[VAR_PTR_PTR_TYPE]]) -> !fir.ref<!fir.array<*:f32>>
-    %7 = omp.map.info var_ptr(%0 : !fir.ref<!fir.box<!fir.array<*:f32>>>, !fir.box<!fir.array<*:f32>>) map_clauses(to) capture(ByRef) members(%6 : [0] : !fir.llvm_ptr<!fir.ref<!fir.array<*:f32>>>) -> !fir.ref<!fir.array<*:f32>>
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> %{{.*}}, %[[MAP0]] -> {{.*}})
-    omp.target kernel_type(generic) map_entries(%7 -> %arg1, %6 -> %arg2 : !fir.ref<!fir.array<*:f32>>, !fir.llvm_ptr<!fir.ref<!fir.array<*:f32>>>) {
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @box_ptr
-  // CHECK-SAME: (%[[X:.*]]: [[X_TYPE:[^)]*]])
-  func.func @box_ptr(%x: !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>) {
-    // CHECK-NEXT: %[[ZERO:.*]] = arith.constant 0 : i64
-    // CHECK-NEXT: %[[SHAPE:.*]] = fir.shape_shift %[[ZERO]], %[[ZERO]] : (i64, i64) -> !fir.shapeshift<1>
-    // CHECK-NEXT: %[[PLACEHOLDER_X:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[ALLOCA_X:.*]] = fir.convert %[[PLACEHOLDER_X]] : (!fir.ref<i1>) -> [[X_TYPE]]
-    %0 = fir.alloca !fir.box<!fir.ptr<!fir.array<?xi32>>>
-    %1 = fir.dummy_scope : !fir.dscope
-    %2 = fir.declare %x dummy_scope %1 {fortran_attrs = #fir.var_attrs<contiguous, pointer>, uniq_name = "x"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>, !fir.dscope) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
-    %3 = fir.load %2 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
-    fir.store %3 to %0 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
-
-    // CHECK-NEXT: %[[PLACEHOLDER_Y:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[ALLOCA_Y:.*]] = fir.convert %[[PLACEHOLDER_Y]] : (!fir.ref<i1>) -> [[Y_TYPE:.*]]
-    %c0 = arith.constant 0 : index
-    %4:3 = fir.box_dims %3, %c0 : (!fir.box<!fir.ptr<!fir.array<?xi32>>>, index) -> (index, index, index)
-    %c1 = arith.constant 1 : index
-    %c0_0 = arith.constant 0 : index
-    %5:3 = fir.box_dims %3, %c0_0 : (!fir.box<!fir.ptr<!fir.array<?xi32>>>, index) -> (index, index, index)
-    %c0_1 = arith.constant 0 : index
-    %6 = arith.subi %5#1, %c1 : index
-    %7 = omp.map.bounds lower_bound(%c0_1 : index) upper_bound(%6 : index) extent(%5#1 : index) stride(%5#2 : index) start_idx(%4#0 : index) {stride_in_bytes = true}
-    %8 = fir.box_addr %3 : (!fir.box<!fir.ptr<!fir.array<?xi32>>>) -> !fir.ptr<!fir.array<?xi32>>
-    %c0_2 = arith.constant 0 : index
-    %9:3 = fir.box_dims %3, %c0_2 : (!fir.box<!fir.ptr<!fir.array<?xi32>>>, index) -> (index, index, index)
-    %10 = fir.shape_shift %9#0, %9#1 : (index, index) -> !fir.shapeshift<1>
-    
-    // CHECK-NEXT: %[[Y_DECL:.*]] = fir.declare %[[ALLOCA_Y]](%[[SHAPE]]) {fortran_attrs = #fir.var_attrs<target>, uniq_name = "y"} : ([[Y_TYPE]], !fir.shapeshift<1>) -> !fir.ptr<!fir.array<?xi32>>
-    %11 = fir.declare %8(%10) {fortran_attrs = #fir.var_attrs<target>, uniq_name = "y"} : (!fir.ptr<!fir.array<?xi32>>, !fir.shapeshift<1>) -> !fir.ptr<!fir.array<?xi32>>
-    %12 = fir.embox %11(%10) : (!fir.ptr<!fir.array<?xi32>>, !fir.shapeshift<1>) -> !fir.box<!fir.array<?xi32>>
-    %c1_3 = arith.constant 1 : index
-    %c0_4 = arith.constant 0 : index
-    %13:3 = fir.box_dims %12, %c0_4 : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
-    %c0_5 = arith.constant 0 : index
-    %14 = arith.subi %13#1, %c1_3 : index
-    %15 = omp.map.bounds lower_bound(%c0_5 : index) upper_bound(%14 : index) extent(%13#1 : index) stride(%13#2 : index) start_idx(%9#0 : index) {stride_in_bytes = true}
-    
-    // CHECK-NEXT: %[[VAR_PTR_PTR:.*]] = fir.box_offset %[[ALLOCA_X]] base_addr : ([[X_TYPE]]) -> [[VAR_PTR_PTR_TYPE:.*]]
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[Y_DECL]] : [[Y_TYPE]], i32) {{.*}} -> [[Y_TYPE]]
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[ALLOCA_X]] : [[X_TYPE]], !fir.box<!fir.ptr<!fir.array<?xi32>>>) {{.*}} var_ptr_ptr(%[[VAR_PTR_PTR]] : [[VAR_PTR_PTR_TYPE]], i32) -> [[VAR_PTR_PTR_TYPE]]
-    // CHECK-NEXT: %[[MAP2:.*]] = omp.map.info var_ptr(%[[ALLOCA_X]] : [[X_TYPE]], !fir.box<!fir.ptr<!fir.array<?xi32>>>) {{.*}} members(%[[MAP1]] : [0] : [[VAR_PTR_PTR_TYPE]]) -> [[X_TYPE]]
-    %16 = omp.map.info var_ptr(%11 : !fir.ptr<!fir.array<?xi32>>, i32) map_clauses(tofrom) capture(ByRef) bounds(%15) -> !fir.ptr<!fir.array<?xi32>>
-    %17 = fir.box_offset %0 base_addr : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xi32>>>
-    %18 = omp.map.info var_ptr(%0 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>, !fir.box<!fir.ptr<!fir.array<?xi32>>>) map_clauses(implicit, to) capture(ByRef) var_ptr_ptr(%17 : !fir.llvm_ptr<!fir.ref<!fir.array<?xi32>>>, i32) bounds(%7) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xi32>>>
-    %19 = omp.map.info var_ptr(%0 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>, !fir.box<!fir.ptr<!fir.array<?xi32>>>) map_clauses(implicit, to) capture(ByRef) members(%18 : [0] : !fir.llvm_ptr<!fir.ref<!fir.array<?xi32>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
-    
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> %{{.*}}, %[[MAP2]] -> %{{.*}}, %[[MAP1]] -> {{.*}} : [[Y_TYPE]], [[X_TYPE]], [[VAR_PTR_PTR_TYPE]])
-    omp.target kernel_type(generic) map_entries(%16 -> %arg1, %19 -> %arg2, %18 -> %arg3 : !fir.ptr<!fir.array<?xi32>>, !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>, !fir.llvm_ptr<!fir.ref<!fir.array<?xi32>>>) {
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @target_data
-  // CHECK-SAME: (%[[MAPPED:.*]]: [[MAPPED_TYPE:[^)]*]], %[[USEDEVADDR:.*]]: [[USEDEVADDR_TYPE:[^)]*]], %[[USEDEVPTR:.*]]: [[USEDEVPTR_TYPE:[^)]*]])
-  func.func @target_data(%mapped: !fir.ref<i32>, %usedevaddr: !fir.ref<i32>, %usedevptr: !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) {
-    // CHECK-NEXT: %[[MAPPED_DECL:.*]] = fir.declare %[[MAPPED]] {uniq_name = "mapped"} : ([[MAPPED_TYPE]]) -> [[MAPPED_TYPE]]
-    // CHECK-NEXT: %[[USEDEVADDR_DECL:.*]] = fir.declare %[[USEDEVADDR]] {uniq_name = "usedevaddr"} : ([[USEDEVADDR_TYPE]]) -> [[USEDEVADDR_TYPE]]
-    // CHECK-NEXT: %[[USEDEVPTR_DECL:.*]] = fir.declare %[[USEDEVPTR]] {uniq_name = "usedevptr"} : ([[USEDEVPTR_TYPE]]) -> [[USEDEVPTR_TYPE]]
-    %0 = fir.declare %mapped {uniq_name = "mapped"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %1 = fir.declare %usedevaddr {uniq_name = "usedevaddr"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %2 = fir.declare %usedevptr {uniq_name = "usedevptr"} : (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
-
-    // CHECK-NEXT: %[[MAPPED_MAP:.*]] = omp.map.info var_ptr(%[[MAPPED_DECL]] : [[MAPPED_TYPE]], i32) map_clauses(tofrom) capture(ByRef) -> [[MAPPED_TYPE]]
-    // CHECK-NEXT: %[[USEDEVADDR_MAP:.*]] = omp.map.info var_ptr(%[[USEDEVADDR_DECL]] : [[USEDEVADDR_TYPE]], i32) map_clauses(tofrom) capture(ByRef) -> [[USEDEVADDR_TYPE]]
-    // CHECK-NEXT: %[[USEDEVPTR_MAP:.*]] = omp.map.info var_ptr(%[[USEDEVPTR_DECL]] : [[USEDEVPTR_TYPE]], !fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>) map_clauses(tofrom) capture(ByRef) -> [[USEDEVPTR_TYPE]]
-    %m0 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %m1 = omp.map.info var_ptr(%1 : !fir.ref<i32>, i32) map_clauses(return_param) capture(ByRef) -> !fir.ref<i32>
-    %m2 = omp.map.info var_ptr(%2 : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>, !fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>) map_clauses(return_param) capture(ByRef) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
-    
-    // CHECK-NOT: omp.target_data
-    omp.target_data map_entries(%m0 : !fir.ref<i32>) use_device_addr(%m1 -> %arg0 : !fir.ref<i32>) use_device_ptr(%m2 -> %arg1 : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) {
-      %3 = fir.declare %arg0 {uniq_name = "usedevaddr"} : (!fir.ref<i32>) -> !fir.ref<i32>
-      %4 = fir.declare %arg1 {uniq_name = "usedevptr"} : (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
-      %m3 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      %m4 = omp.map.info var_ptr(%3 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      %m5 = omp.map.info var_ptr(%4 : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>, !fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>) map_clauses(tofrom) capture(ByRef) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
-
-      // CHECK-NOT: func.call
-      func.call @foo() : () -> ()
-
-      // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAPPED_MAP]] -> %{{.*}}, %[[USEDEVADDR_MAP]] -> %{{.*}}, %[[USEDEVPTR_MAP]] -> %{{.*}} : {{.*}})
-      omp.target kernel_type(generic) map_entries(%m3 -> %arg2, %m4 -> %arg3, %m5 -> %arg4 : !fir.ref<i32>, !fir.ref<i32>, !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) {
-        omp.terminator
-      }
-
-      // CHECK-NOT: func.call
-      func.call @foo() : () -> ()
-
-      omp.terminator
-    }
-
-    // CHECK: return
-    return
-  }
-
-  // CHECK-LABEL: func.func @no_target
-  // CHECK-SAME: (%[[MAPPED:.*]]: !fir.ref<i32>)
-  func.func @no_target(%mapped: !fir.ref<i32>) {
-    // CHECK-NEXT: return
-    %0 = fir.declare %mapped {uniq_name = "mapped"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %m0 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    omp.target_data map_entries(%m0 : !fir.ref<i32>) {
-      %m1 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      omp.target_data map_entries(%m1 : !fir.ref<i32>) {
-        func.call @foo() : () -> ()
-        omp.terminator
-      }
-      func.call @foo() : () -> ()
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @map_info_members
-  // CHECK-SAME: (%[[X:.*]]: [[X_TYPE:[^)]*]])
-  func.func @map_info_members(%x: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) {
-    %c0 = arith.constant 0 : index
-    %c1 = arith.constant 1 : index
-    %c9 = arith.constant 9 : index
-    // CHECK-NEXT: %[[X_DECL:.*]] = fir.declare %[[X]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "x"} : ([[X_TYPE]]) -> [[X_TYPE]]
-    %23 = fir.declare %x {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "x"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
-    %63 = fir.load %23 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
-    %64:3 = fir.box_dims %63, %c0 : (!fir.box<!fir.heap<!fir.array<?xf32>>>, index) -> (index, index, index)
-    %65:3 = fir.box_dims %63, %c0 : (!fir.box<!fir.heap<!fir.array<?xf32>>>, index) -> (index, index, index)
-    %66 = arith.subi %c1, %64#0 : index
-    %67 = arith.subi %c9, %64#0 : index
-    %68 = fir.load %23 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
-    %69:3 = fir.box_dims %68, %c0 : (!fir.box<!fir.heap<!fir.array<?xf32>>>, index) -> (index, index, index)
-    %70 = omp.map.bounds lower_bound(%66 : index) upper_bound(%67 : index) extent(%69#1 : index) stride(%65#2 : index) start_idx(%64#0 : index) {stride_in_bytes = true}
-    // CHECK-NEXT: %[[VAR_PTR_PTR:.*]] = fir.box_offset %[[X_DECL]] base_addr : ([[X_TYPE]]) -> [[VAR_PTR_PTR_TYPE:.*]]
-    %71 = fir.box_offset %23 base_addr : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[X_DECL]] : [[X_TYPE]], !fir.box<!fir.heap<!fir.array<?xf32>>>) {{.*}} var_ptr_ptr(%[[VAR_PTR_PTR]] : [[VAR_PTR_PTR_TYPE]], f32) -> [[VAR_PTR_PTR_TYPE]]
-    %72 = omp.map.info var_ptr(%23 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.box<!fir.heap<!fir.array<?xf32>>>) map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%71 : !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>, f32) bounds(%70) -> !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[X_DECL]] : [[X_TYPE]], !fir.box<!fir.heap<!fir.array<?xf32>>>) {{.*}} members(%[[MAP0]] : [0] : [[VAR_PTR_PTR_TYPE]]) -> [[X_TYPE]]
-    %73 = omp.map.info var_ptr(%23 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.box<!fir.heap<!fir.array<?xf32>>>) map_clauses(to) capture(ByRef) members(%72 : [0] : !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> {{.*}}, %[[MAP0]] -> %{{.*}} : [[X_TYPE]], [[VAR_PTR_PTR_TYPE]])
-    omp.target kernel_type(generic) map_entries(%73 -> %arg0, %72 -> %arg1 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.llvm_ptr<!fir.ref<!fir.array<?xf32>>>) {
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @control_flow
-  // CHECK-SAME: (%[[X:.*]]: [[X_TYPE:[^,]*]], %[[COND:.*]]: [[COND_TYPE:[^)]*]])
-  func.func @control_flow(%x: !fir.ref<i32>, %cond: !fir.ref<!fir.logical<4>>) {
-    // CHECK-NEXT: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "x"} : ([[X_TYPE]]) -> [[X_TYPE]]
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[X_DECL]] : [[X_TYPE]], i32) {{.*}} -> [[X_TYPE]]
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[X_DECL]] : [[X_TYPE]], i32) {{.*}} -> [[X_TYPE]]
-    %x_decl = fir.declare %x {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %cond_decl = fir.declare %cond {uniq_name = "cond"} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<!fir.logical<4>>
-    %0 = fir.load %cond_decl : !fir.ref<!fir.logical<4>>
-    %1 = fir.convert %0 : (!fir.logical<4>) -> i1
-    cf.cond_br %1, ^bb1, ^bb2
-  ^bb1:  // pred: ^bb0
-    fir.call @foo() : () -> ()
-    %m0 = omp.map.info var_ptr(%x_decl : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> {{.*}} : [[X_TYPE]])
-    omp.target kernel_type(generic) map_entries(%m0 -> %arg2 : !fir.ref<i32>) {
-      omp.terminator
-    }
-    fir.call @foo() : () -> ()
-    cf.br ^bb2
-  ^bb2:  // 2 preds: ^bb0, ^bb1
-    // CHECK-NOT: fir.call
-    // CHECK-NOT: omp.map.info
-    // CHECK-NOT: omp.target_data
-    fir.call @foo() : () -> ()
-    %m1 = omp.map.info var_ptr(%x_decl : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    omp.target_data map_entries(%m1 : !fir.ref<i32>) {
-      fir.call @foo() : () -> ()
-      %8 = fir.load %cond_decl : !fir.ref<!fir.logical<4>>
-      %9 = fir.convert %8 : (!fir.logical<4>) -> i1
-      cf.cond_br %9, ^bb1, ^bb2
-    ^bb1:  // pred: ^bb0
-      fir.call @foo() : () -> ()
-      %m2 = omp.map.info var_ptr(%x_decl : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      // CHECK: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> {{.*}} : [[X_TYPE]])
-      omp.target kernel_type(generic) map_entries(%m2 -> %arg2 : !fir.ref<i32>) {
-        omp.terminator
-      }
-      // CHECK-NOT: fir.call
-      // CHECK-NOT: cf.br
-      fir.call @foo() : () -> ()
-      cf.br ^bb2
-    ^bb2:  // 2 preds: ^bb0, ^bb1
-      fir.call @foo() : () -> ()
-      omp.terminator
-    }
-    fir.call @foo() : () -> ()
-
-    // CHECK: return
-    return
-  }
-
-  // CHECK-LABEL: func.func @block_args
-  // CHECK-SAME: (%[[X:.*]]: [[X_TYPE:[^)]*]])
-  func.func @block_args(%x: !fir.ref<i32>) {
-    // CHECK-NEXT: %[[PLACEHOLDER0:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[ALLOCA0:.*]] = fir.convert %[[PLACEHOLDER0]] : (!fir.ref<i1>) -> !fir.ref<i32>
-    // CHECK-NEXT: %[[PLACEHOLDER1:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[ALLOCA1:.*]] = fir.convert %[[PLACEHOLDER1]] : (!fir.ref<i1>) -> !fir.ref<i32>
-    // CHECK-NEXT: %[[X_DECL0:.*]] = fir.declare %[[ALLOCA0]] {uniq_name = "x"} : ([[X_TYPE]]) -> [[X_TYPE]]
-    // CHECK-NEXT: %[[X_DECL1:.*]] = fir.declare %[[ALLOCA1]] {uniq_name = "x"} : ([[X_TYPE]]) -> [[X_TYPE]]
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[X_DECL0]] : [[X_TYPE]], i32) {{.*}} -> [[X_TYPE]]
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[X_DECL1]] : [[X_TYPE]], i32) {{.*}} -> [[X_TYPE]]
-    %x_decl = fir.declare %x {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    omp.parallel private(@privatizer %x_decl -> %arg0 : !fir.ref<i32>) {
-      %0 = fir.declare %arg0 {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
-      %m0 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> {{.*}} : [[X_TYPE]])
-      omp.target kernel_type(generic) map_entries(%m0 -> %arg2 : !fir.ref<i32>) {
-        omp.terminator
-      }
-      omp.terminator
-    }
-
-    // CHECK-NOT: omp.parallel
-    // CHECK-NOT: fir.declare
-    // CHECK-NOT: omp.map.info
-    // CHECK-NOT: omp.target_data
-    omp.parallel private(@privatizer %x_decl -> %arg0 : !fir.ref<i32>) {
-      %1 = fir.declare %arg0 {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
-      %m1 = omp.map.info var_ptr(%1 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      omp.target_data map_entries(%m1 : !fir.ref<i32>) {
-        omp.parallel private(@privatizer %1 -> %arg1 : !fir.ref<i32>) {
-          %2 = fir.declare %arg1 {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
-          %m2 = omp.map.info var_ptr(%2 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-          // CHECK: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> {{.*}} : [[X_TYPE]])
-          omp.target kernel_type(generic) map_entries(%m2 -> %arg2 : !fir.ref<i32>) {
-            omp.terminator
-          }
-          omp.terminator
-        }
-        omp.terminator
-      }
-      omp.terminator
-    }
-
-    return
-  }
-
-  // CHECK-LABEL: func.func @reuse_tests()
-  func.func @reuse_tests() {
-    // CHECK-NEXT: %[[PLACEHOLDER:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[THREAD_LIMIT:.*]] = fir.convert %[[PLACEHOLDER]] : (!fir.ref<i1>) -> i32
-    // CHECK-NEXT: %[[CONST:.*]] = arith.constant 1 : i32
-    // CHECK-NEXT: %[[GLOBAL:.*]] = fir.address_of(@global_scalar) : !fir.ref<i32>
-    %global = fir.address_of(@global_scalar) : !fir.ref<i32>
-    // CHECK-NEXT: %[[GLOBAL_DECL0:.*]] = fir.declare %[[GLOBAL]] {uniq_name = "global_scalar"}
-    // CHECK-NEXT: %[[GLOBAL_DECL1:.*]] = fir.declare %[[GLOBAL]] {uniq_name = "global_scalar"}
-    // CHECK-NEXT: %[[GLOBAL_DECL2:.*]] = fir.declare %[[GLOBAL]] {uniq_name = "global_scalar"}
-    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[GLOBAL_DECL0]] : !fir.ref<i32>, i32)
-    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[GLOBAL_DECL1]] : !fir.ref<i32>, i32)
-    // CHECK-NEXT: %[[MAP2:.*]] = omp.map.info var_ptr(%[[GLOBAL_DECL2]] : !fir.ref<i32>, i32)
-    %0 = fir.declare %global {uniq_name = "global_scalar"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %m0 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    // CHECK-NOT: omp.target_data
-    omp.target_data map_entries(%m0 : !fir.ref<i32>) {
-      %1 = fir.declare %global {uniq_name = "global_scalar"} : (!fir.ref<i32>) -> !fir.ref<i32>
-      %m1 = omp.map.info var_ptr(%0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      %m2 = omp.map.info var_ptr(%1 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-      // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> %{{.*}}, %[[MAP1]] -> {{.*}} : !fir.ref<i32>, !fir.ref<i32>)
-      omp.target kernel_type(generic) map_entries(%m1 -> %arg0, %m2 -> %arg1 : !fir.ref<i32>, !fir.ref<i32>) {
-        omp.terminator
-      }
-      omp.terminator
-    }
-    // CHECK-NOT: fir.load
-    // CHECK-NOT: fir.declare
-    // CHECK-NOT: omp.map.info
-    %2 = fir.load %global : !fir.ref<i32>
-    %3 = fir.declare %global {uniq_name = "global_scalar"} : (!fir.ref<i32>) -> !fir.ref<i32>
-    %m3 = omp.map.info var_ptr(%3 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    // CHECK: omp.target kernel_type(generic) thread_limit(%[[THREAD_LIMIT]] : i32) map_entries(%[[MAP2]] -> %{{.*}} : !fir.ref<i32>)
-    omp.target kernel_type(generic) thread_limit(%2 : i32) map_entries(%m3 -> %arg0 : !fir.ref<i32>) {
-      omp.terminator
-    }
-    // CHECK: omp.target kernel_type(generic) thread_limit(%[[CONST]] : i32)
-    %c1 = arith.constant 1 : i32
-    omp.target kernel_type(generic) thread_limit(%c1 : i32) {
-      omp.terminator
-    }
-    // CHECK: omp.target kernel_type(generic) thread_limit(%[[CONST]] : i32)
-    omp.target kernel_type(generic) thread_limit(%c1 : i32) {
-      omp.terminator
-    }
-    return
-  }
-
-  // CHECK-LABEL: func.func @all_non_map_clauses
-  // CHECK-SAME: (%[[REF:.*]]: !fir.ref<i32>, %[[INT:.*]]: i32, %[[BOOL:.*]]: i1)
-  func.func @all_non_map_clauses(%ref: !fir.ref<i32>, %int: i32, %bool: i1) {
-    %m0 = omp.map.info var_ptr(%ref : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    // CHECK-NOT: omp.target_data
-    omp.target_data device(%int : i32) if(%bool) map_entries(%m0 : !fir.ref<i32>) {
-      omp.terminator
-    }
-    // CHECK-NEXT: omp.target kernel_type(generic) allocate({{[^)]*}}) thread_limit({{[^)]*}}) in_reduction({{[^)]*}}) private({{[^)]*}}) {
-    omp.target kernel_type(generic)
-               allocate(%ref : !fir.ref<i32> -> %ref : !fir.ref<i32>)
-               depend(taskdependin -> %ref : !fir.ref<i32>)
-               device(%int : i32) if(%bool) thread_limit(%int : i32)
-               in_reduction(@reduction %ref -> %arg0 : !fir.ref<i32>)
-               private(@privatizer %ref -> %arg1 : !fir.ref<i32>) {
-      omp.terminator
-    }
-    // CHECK-NOT: omp.target_enter_data
-    omp.target_enter_data depend(taskdependin -> %ref : !fir.ref<i32>)
-                          device(%int : i32) if(%bool)
-    // CHECK-NOT: omp.target_exit_data
-    omp.target_exit_data depend(taskdependin -> %ref : !fir.ref<i32>)
-                         device(%int : i32) if(%bool)
-    // CHECK-NOT: omp.target_update
-    omp.target_update depend(taskdependin -> %ref : !fir.ref<i32>)
-                      device(%int : i32) if(%bool)
-
-    // CHECK: return
-    return
-  }
-
-  // CHECK-LABEL: func.func @assumed_length
-  // CHECK-SAME: (%[[ARG:.*]]: !fir.boxchar<1>)
-  func.func @assumed_length(%arg: !fir.boxchar<1>) {
-    // CHECK-NEXT: %[[PLACEHOLDER:.*]] = fir.alloca !fir.char<1>
-    // CHECK-NEXT: %[[ONE:.*]] = arith.constant 1 : i32
-    // CHECK-NEXT: %[[EMBOXCHAR:.*]] = fir.emboxchar %[[PLACEHOLDER]], %[[ONE]] : (!fir.ref<!fir.char<1>>, i32) -> !fir.boxchar<1>
-    // CHECK-NEXT: omp.target kernel_type(generic) private(@boxchar_firstprivatizer %[[EMBOXCHAR]] -> %{{.*}} [map_idx=0] : !fir.boxchar<1>)
-    %0 = fir.alloca !fir.boxchar<1>
-    %1 = fir.dummy_scope : !fir.dscope
-    %2:2 = fir.unboxchar %arg : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    %3 = fir.declare %2#0 typeparams %2#1 dummy_scope %1 {uniq_name = "arg"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> !fir.ref<!fir.char<1,?>>
-    %4 = fir.emboxchar %3, %2#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.boxchar<1>
-    omp.target kernel_type(generic) private(@boxchar_firstprivatizer %4 -> %arg3 [map_idx=0] : !fir.boxchar<1>) {
-      omp.terminator
-    }
-    return
-  }
-
-  omp.private {type = firstprivate} @boxchar_firstprivatizer : !fir.boxchar<1> copy {
-  ^bb0(%arg0: !fir.boxchar<1>, %arg1: !fir.boxchar<1>):
-    omp.yield(%arg0 : !fir.boxchar<1>)
-  }
-
-  // CHECK-LABEL: func.func @declare_storage
-  func.func @declare_storage(%i : index) {
-    // CHECK-NEXT: %[[PLACEHOLDER:.*]] = fir.alloca i1
-    // CHECK-NEXT: %[[VAR:.*]] = fir.convert %[[PLACEHOLDER]] : (!fir.ref<i1>) -> !fir.ref<i32>
-    // CHECK-NEXT: %[[GLOBAL:.*]] = fir.address_of(@block_) : !fir.ref<!fir.array<8xi8>>
-    %0 = fir.address_of(@block_) : !fir.ref<!fir.array<8xi8>>
-    %1 = fir.convert %0 : (!fir.ref<!fir.array<8xi8>>) -> !fir.ref<!fir.array<?xi8>>
-    %2 = fir.coordinate_of %1, %i : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
-    %3 = fir.convert %2 : (!fir.ref<i8>) -> !fir.ref<i32>
-    // CHECK-NEXT: %[[DECL:.*]] = fir.declare %[[VAR]] storage(%[[GLOBAL]][0])
-    %4 = fir.declare %3 storage (%0[0]) {uniq_name = "a"} : (!fir.ref<i32>, !fir.ref<!fir.array<8xi8>>) -> !fir.ref<i32>
-    // CHECK-NEXT: %[[MAP:.*]] = omp.map.info var_ptr(%[[DECL]] : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    %map = omp.map.info var_ptr(%4 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
-    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP]] -> %{{.*}} : !fir.ref<i32>)
-    omp.target kernel_type(generic) map_entries(%map -> %arg0 : !fir.ref<i32>) {
-      omp.terminator
-    }
-    return
-  }
-
-  func.func private @foo() -> () attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter)>}
-  omp.private {type = firstprivate} @privatizer : i32 copy {
-  ^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
-    %0 = fir.load %arg0 : !fir.ref<i32>
-    fir.store %0 to %arg1 : !fir.ref<i32>
-    omp.yield(%arg1 : !fir.ref<i32>)
-  }
-  omp.declare_reduction @reduction : i32
-  init {
-  ^bb0(%arg: i32):
-    %0 = arith.constant 0 : i32
-    omp.yield (%0 : i32)
-  }
-  combiner {
-  ^bb1(%arg0: i32, %arg1: i32):
-    %1 = arith.addi %arg0, %arg1 : i32
-    omp.yield (%1 : i32)
-  }
-
-  // CHECK: fir.global internal @global_scalar constant : i32
-  fir.global @global_scalar constant : i32
-
-  // CHECK: fir.global @declare_target_enter_any {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false>} : i32
-  // CHECK: fir.global @declare_target_enter_host {omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (enter), automap = false>} : i32
-  // CHECK: fir.global @declare_target_enter_nohost {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false>} : i32
-  fir.global @declare_target_enter_any {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false>} : i32
-  fir.global @declare_target_enter_host {omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (enter), automap = false>} : i32
-  fir.global @declare_target_enter_nohost {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false>} : i32
-
-  // CHECK: fir.global @declare_target_link_any {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (link), automap = false>} : i32
-  // CHECK: fir.global @declare_target_link_host {omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (link), automap = false>} : i32
-  // CHECK: fir.global @declare_target_link_nohost {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (link), automap = false>} : i32
-  fir.global @declare_target_link_any {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (link), automap = false>} : i32
-  fir.global @declare_target_link_host {omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (link), automap = false>} : i32
-  fir.global @declare_target_link_nohost {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (link), automap = false>} : i32
-}

diff  --git a/mlir/include/mlir/Dialect/OpenMP/Transforms/Passes.td b/mlir/include/mlir/Dialect/OpenMP/Transforms/Passes.td
index e6321ef58b45f..da90ef508ce64 100644
--- a/mlir/include/mlir/Dialect/OpenMP/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/OpenMP/Transforms/Passes.td
@@ -11,6 +11,30 @@
 
 include "mlir/Pass/PassBase.td"
 
+def HostOpFilteringPass : Pass<"omp-host-op-filter"> {
+  let summary = "Removes all non target-related ops from host functions.";
+  let description = [{
+    When compiling for an OpenMP offloading target, host functions holding
+    target regions must be maintained in order for the `omp.target` ops to be
+    translated to LLVM IR as functions.
+    
+    However, these host functions might contain more host code than just what's
+    necessary for generating code for their contained target regions on the
+    target device, potentially causing problematic artifacts to be generated
+    into the target device's module.
+
+    This pass strips out all non-essential operations from host functions when
+    compiling for an OpenMP target device, leaving just `omp.target` operations,
+    related `omp.map.info` and placeholders in the form of function arguments to
+    keep the MLIR representation legal.
+
+    It must run on a module at a low level of abstraction (i.e. directly
+    translatable to LLVM IR), after any passes that might produce new OpenMP
+    operations have already completed.
+  }];
+  let dependentDialects = ["LLVM::LLVMDialect"];
+}
+
 def MarkDeclareTargetPass : Pass<"omp-mark-declare-target", "ModuleOp"> {
   let summary = "Marks all functions called by an OpenMP declare target "
                 "function as declare target";

diff  --git a/mlir/lib/Dialect/OpenMP/Transforms/CMakeLists.txt b/mlir/lib/Dialect/OpenMP/Transforms/CMakeLists.txt
index 569786fe95cf3..bbdbc556fc52f 100644
--- a/mlir/lib/Dialect/OpenMP/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenMP/Transforms/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_mlir_dialect_library(MLIROpenMPTransforms
+  HostOpFiltering.cpp
   MarkDeclareTarget.cpp
   OpenMPOffloadPrivatizationPrepare.cpp
   StackToShared.cpp

diff  --git a/mlir/lib/Dialect/OpenMP/Transforms/HostOpFiltering.cpp b/mlir/lib/Dialect/OpenMP/Transforms/HostOpFiltering.cpp
new file mode 100644
index 0000000000000..9c61b2717af57
--- /dev/null
+++ b/mlir/lib/Dialect/OpenMP/Transforms/HostOpFiltering.cpp
@@ -0,0 +1,292 @@
+//===- HostOpFiltering.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements transforms to swap stack allocations on the target
+// device with device shared memory where applicable.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenMP/Transforms/Passes.h"
+
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/Interfaces/FunctionInterfaces.h"
+
+namespace mlir {
+namespace omp {
+#define GEN_PASS_DEF_HOSTOPFILTERINGPASS
+#include "mlir/Dialect/OpenMP/Transforms/Passes.h.inc"
+} // namespace omp
+} // namespace mlir
+
+using namespace mlir;
+
+/// Some host operations, like \c llvm.mlir.addressof and constants, must remain
+/// in the device module because they impact how device code is generated when
+/// attached to an \c omp.target operation.
+///
+/// This function identifies the operations that need this special handling.
+/// This includes cast-style operations to avoid losing information about the
+/// original source of an operand.
+static bool keepHostOpInDevice(Operation &op) {
+  return isPure(&op) &&
+         op.getDialect() ==
+             op.getContext()->getLoadedDialect<LLVM::LLVMDialect>();
+}
+
+/// Add an \c omp.map.info operation and all its members recursively to the
+/// output set to be later rewritten.
+///
+/// Dependencies across \c omp.map.info are maintained by ensuring dependencies
+/// are added to the output sets before operations based on them.
+static void collectRewrite(omp::MapInfoOp mapOp,
+                           llvm::SetVector<omp::MapInfoOp> &rewrites) {
+  for (Value member : mapOp.getMembers())
+    collectRewrite(cast<omp::MapInfoOp>(member.getDefiningOp()), rewrites);
+
+  rewrites.insert(mapOp);
+}
+
+/// Add the given value to a sorted set if it should be replaced by a
+/// placeholder when used as an operand that must remain for the device.
+///
+/// Values that are block arguments of function operations are skipped, since
+/// they will still be available after all rewrites are completed, and operands
+/// of operations that need to remain on the host are recursively collected.
+static void collectRewrite(Value value, llvm::SetVector<Value> &rewrites) {
+  if ((isa<BlockArgument>(value) &&
+       isa<FunctionOpInterface>(
+           cast<BlockArgument>(value).getOwner()->getParentOp())) ||
+      rewrites.contains(value))
+    return;
+
+  Operation *op = value.getDefiningOp();
+  if (op && keepHostOpInDevice(*op))
+    for (Value operand : op->getOperands())
+      collectRewrite(operand, rewrites);
+
+  rewrites.insert(value);
+}
+
+/// Provide the \c device_type of an \c omp.declare_target attribute, if
+/// defined.
+static std::optional<omp::DeclareTargetDeviceType>
+getDeclareTargetDevice(Operation &op) {
+  auto declareTargetOp = dyn_cast<omp::DeclareTargetInterface>(op);
+  if (declareTargetOp && declareTargetOp.isDeclareTarget())
+    return declareTargetOp.getDeclareTargetDeviceType();
+  return std::nullopt;
+}
+
+namespace {
+class HostOpFilteringPass
+    : public omp::impl::HostOpFilteringPassBase<HostOpFilteringPass> {
+public:
+  HostOpFilteringPass() = default;
+
+  void runOnOperation() override {
+    auto op = dyn_cast<omp::OffloadModuleInterface>(getOperation());
+    if (!op || !op.getIsTargetDevice())
+      return;
+
+    op->walk<WalkOrder::PreOrder>([&](LLVM::LLVMFuncOp funcOp) {
+      omp::DeclareTargetDeviceType declareType =
+          getDeclareTargetDevice(*funcOp.getOperation())
+              .value_or(omp::DeclareTargetDeviceType::host);
+
+      // Only process host function definitions.
+      if (funcOp.isExternal() ||
+          declareType != omp::DeclareTargetDeviceType::host)
+        return WalkResult::advance();
+
+      if (failed(rewriteHostFunction(funcOp))) {
+        funcOp.emitOpError() << "could not filter host-only operations";
+        return WalkResult::interrupt();
+      }
+      return WalkResult::advance();
+    });
+
+    // Make non-declare target globals internal for the device. They cannot be
+    // deleted, because they are needed in order to properly lower map clauses.
+    // However, no uses will remain in the device module, so we make them
+    // internal to prevent link time redefinitions.
+    op->walk([&](LLVM::GlobalOp globalOp) {
+      if (!getDeclareTargetDevice(*globalOp.getOperation()).has_value())
+        globalOp.setLinkage(LLVM::Linkage::Internal);
+    });
+  }
+
+private:
+  /// Rewrite the given host device function containing \c omp.target
+  /// operations, to remove host-only operations that are not used by device
+  /// codegen.
+  ///
+  /// It is based on the expected form of an MLIR module lowered to where it can
+  /// be directly translated to LLVM IR and it performs the following mutations:
+  ///   - Removes all returned values from the function.
+  ///   - \c omp.target operations are moved to the end of the function. If they
+  ///     are nested inside of any other operations, they are hoisted out of
+  ///     them.
+  ///   - \c depend, \c device, \c dyn_groupprivate, \c if and \c in_reduction
+  ///     clauses are removed from these target functions. Values used to
+  ///     initialize other clauses are replaced by placeholders as follows:
+  ///     - Values defined by block arguments are replaced by placeholders only
+  ///       if they are not attached to the parent function. In that case, they
+  ///       are passed unmodified.
+  ///     - Pure operations of the LLVM dialect are maintained, and any value
+  ///       operands they might have are also replaced by placeholders following
+  ///       the same rules.
+  ///     - Other values are replaced by new function arguments.
+  ///   - \c omp.map.info operations associated to these target regions are
+  ///     preserved. These are moved above all \c omp.target and sorted to
+  ///     satisfy dependencies among them.
+  ///   - \c bounds arguments are removed from \c omp.map.info operations.
+  ///   - \c var_ptr and \c var_ptr_ptr arguments of \c omp.map.info are
+  ///     replaced by placeholders as described above.
+  ///   - Every other operation not located inside of an \c omp.target is
+  ///     removed.
+  LogicalResult rewriteHostFunction(LLVM::LLVMFuncOp funcOp) {
+    Region &region = funcOp.getFunctionBody();
+    LLVM::LLVMFunctionType functionType = funcOp.getFunctionType();
+
+    // Collect target operations inside of the function.
+    llvm::SmallVector<omp::TargetOp> targetOps;
+    region.walk<WalkOrder::PreOrder>([&](Operation *op) {
+      // Skip the inside of omp.target regions, since these contain device code.
+      if (auto targetOp = dyn_cast<omp::TargetOp>(op)) {
+        targetOps.push_back(targetOp);
+        return WalkResult::skip();
+      }
+
+      // Replace omp.target_data entry block argument uses with the value used
+      // to initialize the associated omp.map.info operation. This way,
+      // references are still valid once the omp.target operation has been
+      // extracted out of the omp.target_data region.
+      if (auto targetDataOp = dyn_cast<omp::TargetDataOp>(op)) {
+        llvm::SmallVector<std::pair<Value, BlockArgument>> argPairs;
+        cast<omp::BlockArgOpenMPOpInterface>(*targetDataOp)
+            .getBlockArgsPairs(argPairs);
+        for (auto [operand, blockArg] : argPairs) {
+          auto mapInfo = cast<omp::MapInfoOp>(operand.getDefiningOp());
+          blockArg.replaceAllUsesWith(mapInfo.getVarPtr());
+        }
+      }
+      return WalkResult::advance();
+    });
+
+    // Make a temporary clone of the parent function with an empty region,
+    // and update all references to entry block arguments to those of the new
+    // region. Users of these arguments will later either be moved to the new
+    // region or deleted when the original region is replaced by the new.
+    OpBuilder builder(&getContext());
+    builder.setInsertionPointAfter(funcOp);
+    Operation *newFuncOp = builder.cloneWithoutRegions(funcOp);
+    Block &block = newFuncOp->getRegion(0).emplaceBlock();
+
+    llvm::SmallVector<Location> locs;
+    locs.reserve(region.getNumArguments());
+    llvm::transform(region.getArguments(), std::back_inserter(locs),
+                    [](const BlockArgument &arg) { return arg.getLoc(); });
+    block.addArguments(region.getArgumentTypes(), locs);
+
+    for (auto [oldArg, newArg] :
+         llvm::zip_equal(region.getArguments(), block.getArguments()))
+      oldArg.replaceAllUsesWith(newArg);
+
+    // Collect omp.map.info ops while satisfying interdependencies and remove
+    // operands that aren't used by target device codegen.
+    //
+    // This logic must be updated whenever operands to omp.target change.
+    llvm::SetVector<Value> rewriteValues;
+    llvm::SetVector<omp::MapInfoOp> mapInfos;
+    for (omp::TargetOp targetOp : targetOps) {
+      assert(targetOp.getHostEvalVars().empty() &&
+             "unexpected host_eval in target device module");
+
+      // Variables unused by the device.
+      targetOp.getDependVarsMutable().clear();
+      targetOp.setDependKindsAttr(nullptr);
+      targetOp.getDependIteratedMutable().clear();
+      targetOp.setDependIteratedKindsAttr(nullptr);
+      targetOp.getDeviceMutable().clear();
+      targetOp.getDynGroupprivateSizeMutable().clear();
+      targetOp.getIfExprMutable().clear();
+      targetOp.getInReductionVarsMutable().clear();
+      targetOp.setInReductionByrefAttr(nullptr);
+      targetOp.setInReductionSymsAttr(nullptr);
+
+      // TODO: Clear some of these operands rather than rewriting them,
+      // depending on whether they are needed by device codegen once support for
+      // them is fully implemented.
+      for (Value allocVar : targetOp.getAllocateVars())
+        collectRewrite(allocVar, rewriteValues);
+      for (Value allocVar : targetOp.getAllocatorVars())
+        collectRewrite(allocVar, rewriteValues);
+      for (Value isDevPtr : targetOp.getIsDevicePtrVars())
+        collectRewrite(isDevPtr, rewriteValues);
+      for (Value mapVar : targetOp.getHasDeviceAddrVars())
+        collectRewrite(cast<omp::MapInfoOp>(mapVar.getDefiningOp()), mapInfos);
+      for (Value mapVar : targetOp.getMapVars())
+        collectRewrite(cast<omp::MapInfoOp>(mapVar.getDefiningOp()), mapInfos);
+      for (Value privateVar : targetOp.getPrivateVars())
+        collectRewrite(privateVar, rewriteValues);
+      for (Value threadLimit : targetOp.getThreadLimitVars())
+        collectRewrite(threadLimit, rewriteValues);
+    }
+
+    // Move omp.map.info ops to the new block and collect dependencies.
+    for (omp::MapInfoOp mapOp : mapInfos) {
+      collectRewrite(mapOp.getVarPtr(), rewriteValues);
+
+      if (Value varPtrPtr = mapOp.getVarPtrPtr())
+        collectRewrite(varPtrPtr, rewriteValues);
+
+      // Bounds are not used during target device codegen.
+      mapOp.getBoundsMutable().clear();
+      mapOp->moveBefore(&block, block.end());
+    }
+
+    builder.setInsertionPointToStart(&block);
+
+    // We don't actually need the proper initialization for all operands, but
+    // rather just to maintain the basic form of omp.target operations. We
+    // create new function arguments as placeholders for rewritten values.
+    llvm::SmallVector<Type> newFnArgTypes(functionType.getParams());
+    for (Value value : rewriteValues) {
+      Value rewriteValue;
+      Operation *definingOp = value.getDefiningOp();
+      if (definingOp && keepHostOpInDevice(*definingOp)) {
+        rewriteValue = builder.clone(*value.getDefiningOp())->getResult(0);
+      } else {
+        rewriteValue = block.addArgument(value.getType(), value.getLoc());
+        newFnArgTypes.push_back(rewriteValue.getType());
+      }
+      value.replaceAllUsesWith(rewriteValue);
+    }
+
+    // Move target operations to the end of the new block.
+    for (omp::TargetOp targetOp : targetOps)
+      targetOp->moveBefore(&block, block.end());
+
+    // Add terminator to the new block.
+    builder.setInsertionPointToEnd(&block);
+    LLVM::ReturnOp::create(builder, funcOp.getLoc(), ValueRange());
+
+    // Replace old region with the new one, now only containing the required
+    // operations, and remove the temporary operation clone.
+    region.takeBody(newFuncOp->getRegion(0));
+    newFuncOp->erase();
+
+    // Update function type after modifying the terminator and argument list.
+    funcOp.setType(LLVM::LLVMFunctionType::get(
+        LLVM::LLVMVoidType::get(&getContext()), newFnArgTypes));
+
+    return success();
+  }
+};
+} // namespace

diff  --git a/mlir/test/Dialect/OpenMP/host-op-filtering.mlir b/mlir/test/Dialect/OpenMP/host-op-filtering.mlir
new file mode 100644
index 0000000000000..015e901126a76
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/host-op-filtering.mlir
@@ -0,0 +1,500 @@
+// RUN: mlir-opt --omp-host-op-filter %s | FileCheck %s
+module attributes {omp.is_target_device = true} {
+  // CHECK-LABEL: llvm.func @external() attributes {sym_visibility = "private"}
+  // CHECK-NOT: llvm.return
+  llvm.func @external() attributes {sym_visibility = "private"}
+
+  // CHECK-LABEL: llvm.func @basic_checks
+  // CHECK-SAME: (%[[ARG:.*]]: !llvm.ptr, %[[PLACEHOLDER:.*]]: !llvm.ptr)
+  llvm.func @basic_checks(%arg0: !llvm.ptr) -> !llvm.struct<(i32, f32)> {
+    %0 = llvm.mlir.constant(1 : i64) : i64
+    %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
+    %2 = llvm.mlir.constant(10 : i32) : i32
+    %3 = llvm.mlir.constant(2.500000e+00 : f32) : f32
+
+    llvm.call @foo() : () -> ()
+
+    // CHECK-NEXT: %[[GLOBAL:.*]] = llvm.mlir.addressof @global_scalar : !llvm.ptr
+    %4 = llvm.mlir.addressof @global_scalar : !llvm.ptr
+    %5 = llvm.mlir.constant(1 : i64) : i64
+
+    // CHECK-NEXT: %[[HDA:.*]] = omp.map.info var_ptr(%[[PLACEHOLDER]]{{.*}})
+    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[ARG]]{{.*}})
+    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[GLOBAL]]{{.*}})
+    // CHECK-NEXT: %[[MAP2:.*]] = omp.map.info var_ptr(%[[PLACEHOLDER]]{{.*}})
+    %6 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %7 = omp.map.info var_ptr(%4 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %8 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %9 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target kernel_type(generic) has_device_addr(%[[HDA]] -> {{.*}} : {{.*}}) map_entries(%[[MAP0]] -> {{.*}}, %[[MAP1]] -> {{.*}}, %[[MAP2]] -> {{.*}} : {{.*}})
+    omp.target kernel_type(generic) has_device_addr(%8 -> %arg1 : !llvm.ptr) map_entries(%6 -> %arg2, %7 -> %arg3, %9 -> %arg4 : !llvm.ptr, !llvm.ptr, !llvm.ptr) {
+      // CHECK-NEXT: llvm.call
+      llvm.call @foo() : () -> ()
+      omp.terminator
+    }
+
+    // CHECK-NOT: omp.parallel
+    // CHECK-NOT: llvm.call
+    omp.parallel {
+      llvm.call @foo() : () -> ()
+      omp.terminator
+    }
+
+    // CHECK-NOT: omp.map.info
+    %10 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %11 = omp.map.info var_ptr(%4 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %12 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %13 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NOT: omp.target_data
+    omp.target_data map_entries(%10, %11, %12, %13 : !llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr) {
+      // CHECK-NOT: llvm.call
+      llvm.call @foo() : () -> ()
+      omp.terminator
+    }
+
+    // CHECK-NOT: omp.target_enter_data
+    // CHECK-NOT: omp.target_exit_data
+    // CHECK-NOT: omp.target_update
+    %14 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(to) capture(ByRef) -> !llvm.ptr
+    omp.target_enter_data map_entries(%14 : !llvm.ptr)
+    %15 = omp.map.info var_ptr(%4 : !llvm.ptr, i32) map_clauses(from) capture(ByRef) -> !llvm.ptr
+    omp.target_exit_data map_entries(%15 : !llvm.ptr)
+    %16 = omp.map.info var_ptr(%1 : !llvm.ptr, !llvm.ptr) map_clauses(to) capture(ByRef) -> !llvm.ptr
+    omp.target_update map_entries(%16 : !llvm.ptr)
+
+    // CHECK-NOT: llvm.call
+    llvm.call @foo() : () -> ()
+
+    // CHECK: {{llvm\.return$}}
+    %17 = llvm.mlir.poison : !llvm.struct<(i32, f32)>
+    %18 = llvm.insertvalue %2, %17[0] : !llvm.struct<(i32, f32)>
+    %19 = llvm.insertvalue %3, %18[1] : !llvm.struct<(i32, f32)>
+    llvm.return %19 : !llvm.struct<(i32, f32)>
+  }
+
+  // CHECK-LABEL: llvm.func @member_map_complex_init
+  // CHECK-SAME: (%[[ARG:.*]]: !llvm.ptr, %[[PLACEHOLDER:.*]]: !llvm.ptr)
+  llvm.func @member_map_complex_init(%arg0: !llvm.ptr) {
+    // CHECK-NEXT: %[[GEP:.*]] = llvm.getelementptr %[[PLACEHOLDER]][0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<{{.*}}>
+    %0 = llvm.mlir.constant(1 : i32) : i32
+    %1 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)> {alignment = 8 : i64} : (i32) -> !llvm.ptr
+    %2 = llvm.mlir.constant(1 : i64) : i64
+    %3 = llvm.alloca %2 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)> : (i64) -> !llvm.ptr
+    %4 = llvm.mlir.constant(1 : i64) : i64
+    %5 = llvm.alloca %4 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)> : (i64) -> !llvm.ptr
+    %6 = llvm.mlir.constant(1 : i32) : i32
+    %7 = llvm.mlir.constant(0 : i8) : i8
+    %8 = llvm.mlir.constant(1 : i64) : i64
+    %9 = llvm.mlir.constant(1 : i64) : i64
+    %10 = llvm.mlir.zero : !llvm.ptr
+    llvm.call @LangRTPlaceholderFunc(%5, %arg0, %10, %7, %6) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, i8, i32) -> ()
+    %11 = llvm.mlir.constant(384 : i32) : i32
+    "llvm.intr.memcpy"(%1, %5, %11) <{arg_attrs = [{llvm.align = 8 : i64}, {llvm.align = 8 : i64}, {}], isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
+    %12 = llvm.mlir.constant(24 : i32) : i32
+    %13 = llvm.getelementptr %1[0, 3] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)>
+    %14 = llvm.load %13 : !llvm.ptr -> i8
+    %15 = llvm.sext %14 : i8 to i32
+    %16 = llvm.mlir.constant(24 : i32) : i32
+    %17 = llvm.mul %16, %15 : i32
+    %18 = llvm.add %12, %17 : i32
+    "llvm.intr.memcpy"(%3, %1, %18) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
+    %19 = llvm.getelementptr %3[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)>
+
+    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[PLACEHOLDER]] {{.*}} map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%[[GEP]] : {{.*}})
+    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[PLACEHOLDER]] {{.*}} map_clauses(to) capture(ByRef) members(%[[MAP0]] : [0] : !llvm.ptr)
+    %20 = omp.map.info var_ptr(%3 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)>) map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%19 : !llvm.ptr, f32) -> !llvm.ptr
+    %21 = omp.map.info var_ptr(%3 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<15 x array<3 x i64>>)>) map_clauses(to) capture(ByRef) members(%20 : [0] : !llvm.ptr) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> %{{.*}}, %[[MAP0]] -> %{{.*}} : {{.*}})
+    omp.target kernel_type(generic) map_entries(%21 -> %arg1, %20 -> %arg2 : !llvm.ptr, !llvm.ptr) {
+      omp.terminator
+    }
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @target_data
+  // CHECK-SAME: (%[[ARG0:.*]]: !llvm.ptr, %[[ARG1:.*]]: !llvm.ptr, %[[ARG2:.*]]: !llvm.ptr)
+  llvm.func @target_data(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) {
+    %0 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %1 = omp.map.info var_ptr(%arg1 : !llvm.ptr, i32) map_clauses(return_param) capture(ByRef) -> !llvm.ptr
+    %2 = omp.map.info var_ptr(%arg2 : !llvm.ptr, !llvm.struct<"MyStruct", (i64)>) map_clauses(return_param) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NOT: omp.target_data
+    omp.target_data map_entries(%0 : !llvm.ptr) use_device_addr(%1 -> %arg3 : !llvm.ptr) use_device_ptr(%2 -> %arg4 : !llvm.ptr) {
+      // CHECK:      %[[MAP0:.*]] = omp.map.info var_ptr(%[[ARG0]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef)
+      // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[ARG1]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef)
+      // CHECK-NEXT: %[[MAP2:.*]] = omp.map.info var_ptr(%[[ARG2]] : !llvm.ptr, !llvm.struct<"MyStruct", (i64)>) map_clauses(tofrom) capture(ByRef)
+      %3 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      %4 = omp.map.info var_ptr(%arg3 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      %5 = omp.map.info var_ptr(%arg4 : !llvm.ptr, !llvm.struct<"MyStruct", (i64)>) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+      // CHECK-NOT: llvm.call
+      llvm.call @foo() : () -> ()
+
+      // CHECK: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> %{{.*}}, %[[MAP1]] -> %{{.*}}, %[[MAP2]] -> %{{.*}} : !llvm.ptr, !llvm.ptr, !llvm.ptr)
+      omp.target kernel_type(generic) map_entries(%3 -> %arg5, %4 -> %arg6, %5 -> %arg7 : !llvm.ptr, !llvm.ptr, !llvm.ptr) {
+        omp.terminator
+      }
+
+      // CHECK-NOT: llvm.call
+      llvm.call @foo() : () -> ()
+      omp.terminator
+    }
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @no_target
+  // CHECK-SAME: (%{{.*}}: !llvm.ptr)
+  llvm.func @no_target(%arg0: !llvm.ptr) {
+    // CHECK-NEXT: llvm.return
+    %0 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    omp.target_data map_entries(%0 : !llvm.ptr) {
+      %1 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      omp.target_data map_entries(%1 : !llvm.ptr) {
+        llvm.call @foo() : () -> ()
+        omp.terminator
+      }
+      llvm.call @foo() : () -> ()
+      omp.terminator
+    }
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @map_info_members
+  // CHECK-SAME: (%[[ARG:.*]]: !llvm.ptr)
+  llvm.func @map_info_members(%arg0: !llvm.ptr) {
+    // CHECK-NEXT: %[[VAR_PTR_PTR:.*]] = llvm.getelementptr %[[ARG]][0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>
+    %0 = llvm.mlir.constant(1 : i32) : i32
+    %1 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)> {alignment = 8 : i64} : (i32) -> !llvm.ptr
+    %2 = llvm.mlir.constant(0 : index) : i64
+    %3 = llvm.mlir.constant(1 : index) : i64
+    %4 = llvm.mlir.constant(9 : index) : i64
+    %5 = llvm.mlir.constant(48 : i32) : i32
+    "llvm.intr.memcpy"(%1, %arg0, %5) <{arg_attrs = [{llvm.align = 8 : i64}, {llvm.align = 8 : i64}, {}], isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
+    %6 = llvm.getelementptr %1[0, 7, %2, 0] : (!llvm.ptr, i64) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>
+    %7 = llvm.load %6 : !llvm.ptr -> i64
+    %8 = llvm.getelementptr %1[0, 7, %2, 1] : (!llvm.ptr, i64) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>
+    %9 = llvm.load %8 : !llvm.ptr -> i64
+    %10 = llvm.getelementptr %1[0, 7, %2, 2] : (!llvm.ptr, i64) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>
+    %11 = llvm.load %10 : !llvm.ptr -> i64
+    %12 = llvm.sub %3, %7 : i64
+    %13 = llvm.sub %4, %7 : i64
+    %14 = omp.map.bounds lower_bound(%12 : i64) upper_bound(%13 : i64) extent(%9 : i64) stride(%11 : i64) start_idx(%7 : i64) {stride_in_bytes = true}
+    %15 = llvm.getelementptr %arg0[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>
+
+    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[ARG]] {{.*}} map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%[[VAR_PTR_PTR]] : !llvm.ptr, f32) -> !llvm.ptr
+    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[ARG]] {{.*}} map_clauses(to) capture(ByRef) members(%[[MAP0]] : [0] : !llvm.ptr) -> !llvm.ptr
+    %16 = omp.map.info var_ptr(%arg0 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>) map_clauses(tofrom) capture(ByRef) var_ptr_ptr(%15 : !llvm.ptr, f32) bounds(%14) -> !llvm.ptr
+    %17 = omp.map.info var_ptr(%arg0 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>) map_clauses(to) capture(ByRef) members(%16 : [0] : !llvm.ptr) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> {{.*}}, %[[MAP0]] -> {{.*}} : !llvm.ptr, !llvm.ptr)
+    omp.target kernel_type(generic) map_entries(%17 -> %arg1, %16 -> %arg2 : !llvm.ptr, !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @control_flow
+  // CHECK-SAME: (%[[ARG0:.*]]: !llvm.ptr, %[[ARG1:.*]]: !llvm.ptr)
+  llvm.func @control_flow(%arg0: !llvm.ptr, %arg1: !llvm.ptr) {
+    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[ARG0]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[ARG0]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %0 = llvm.load %arg1 : !llvm.ptr -> i32
+    %1 = llvm.mlir.constant(0 : i32) : i32
+    %2 = llvm.icmp "ne" %0, %1 : i32
+    llvm.cond_br %2, ^bb1, ^bb2
+  ^bb1:  // pred: ^bb0
+    llvm.call @foo() : () -> ()
+    %3 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> %{{.*}} : !llvm.ptr)
+    omp.target kernel_type(generic) map_entries(%3 -> %arg2 : !llvm.ptr) {
+      omp.terminator
+    }
+    llvm.call @foo() : () -> ()
+    llvm.br ^bb2
+  ^bb2:  // 2 preds: ^bb0, ^bb1
+    // CHECK-NOT: llvm.call
+    // CHECK-NOT: omp.map.info
+    // CHECK-NOT: omp.target_data
+    llvm.call @foo() : () -> ()
+
+    %4 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    omp.target_data map_entries(%4 : !llvm.ptr) {
+      llvm.call @foo() : () -> ()
+      %5 = llvm.load %arg1 : !llvm.ptr -> i32
+      %6 = llvm.mlir.constant(0 : i32) : i32
+      %7 = llvm.icmp "ne" %5, %6 : i32
+      llvm.cond_br %7, ^bb1, ^bb2
+    ^bb1:  // pred: ^bb0
+      llvm.call @foo() : () -> ()
+      %8 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+      // CHECK: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> %{{.*}} : !llvm.ptr)
+      omp.target kernel_type(generic) map_entries(%8 -> %arg2 : !llvm.ptr) {
+        omp.terminator
+      }
+
+      // CHECK-NOT: llvm.call
+      // CHECK-NOT: llvm.br
+      llvm.call @foo() : () -> ()
+      llvm.br ^bb2
+    ^bb2:  // 2 preds: ^bb0, ^bb1
+      llvm.call @foo() : () -> ()
+      omp.terminator
+    }
+    llvm.call @foo() : () -> ()
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @block_args
+  // CHECK-SAME: (%[[ARG0:.*]]: !llvm.ptr, %[[PLACEHOLDER0:.*]]: !llvm.ptr, %[[PLACEHOLDER1:.*]]: !llvm.ptr)
+  llvm.func @block_args(%arg0: !llvm.ptr) {
+    // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[PLACEHOLDER0]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[PLACEHOLDER1]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    omp.parallel private(@privatizer %arg0 -> %arg1 : !llvm.ptr) {
+      %0 = omp.map.info var_ptr(%arg1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+      // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> %{{.*}} : !llvm.ptr)
+      omp.target kernel_type(generic) map_entries(%0 -> %arg2 : !llvm.ptr) {
+        omp.terminator
+      }
+      omp.terminator
+    }
+
+    // CHECK-NOT: omp.parallel
+    // CHECK-NOT: omp.map.info
+    // CHECK-NOT: omp.target_data
+    omp.parallel private(@privatizer %arg0 -> %arg1 : !llvm.ptr) {
+      %0 = omp.map.info var_ptr(%arg1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      omp.target_data map_entries(%0 : !llvm.ptr) {
+        omp.parallel private(@privatizer %arg1 -> %arg2 : !llvm.ptr) {
+          %1 = omp.map.info var_ptr(%arg2 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+          // CHECK: omp.target kernel_type(generic) map_entries(%[[MAP1]] -> %{{.*}} : !llvm.ptr)
+          omp.target kernel_type(generic) map_entries(%1 -> %arg3 : !llvm.ptr) {
+            omp.terminator
+          }
+          omp.terminator
+        }
+        omp.terminator
+      }
+      omp.terminator
+    }
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @reuse_tests
+  // CHECK-SAME: (%[[THREAD_LIMIT:.*]]: i32)
+  llvm.func @reuse_tests() {
+    // CHECK-NEXT: %[[CONST_THREAD_LIMIT:.*]] = llvm.mlir.constant(1 : i32) : i32
+    // CHECK-NEXT: %[[GLOBAL:.*]] = llvm.mlir.addressof @global_scalar : !llvm.ptr
+    %0 = llvm.mlir.constant(1 : i32) : i32
+    %1 = llvm.mlir.addressof @global_scalar : !llvm.ptr
+    %2 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    omp.target_data map_entries(%2 : !llvm.ptr) {
+      // CHECK-NEXT: %[[MAP0:.*]] = omp.map.info var_ptr(%[[GLOBAL]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      // CHECK-NEXT: %[[MAP1:.*]] = omp.map.info var_ptr(%[[GLOBAL]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      // CHECK-NEXT: %[[MAP2:.*]] = omp.map.info var_ptr(%[[GLOBAL]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      %5 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+      %6 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+      // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP0]] -> %{{.*}}, %[[MAP1]] -> %{{.*}} : !llvm.ptr, !llvm.ptr)
+      omp.target kernel_type(generic) map_entries(%5 -> %arg0, %6 -> %arg1 : !llvm.ptr, !llvm.ptr) {
+        omp.terminator
+      }
+      omp.terminator
+    }
+
+    // CHECK-NOT: llvm.load
+    // CHECK-NOT: omp.map.info
+    %3 = llvm.load %1 : !llvm.ptr -> i32
+    %4 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK: omp.target kernel_type(generic) thread_limit(%[[THREAD_LIMIT]] : i32) map_entries(%[[MAP2]] -> %{{.*}} : !llvm.ptr)
+    omp.target kernel_type(generic) thread_limit(%3 : i32) map_entries(%4 -> %arg0 : !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK: omp.target kernel_type(generic) thread_limit(%[[CONST_THREAD_LIMIT]] : i32)
+    omp.target kernel_type(generic) thread_limit(%0 : i32) {
+      omp.terminator
+    }
+
+    // CHECK: omp.target kernel_type(generic) thread_limit(%[[CONST_THREAD_LIMIT]] : i32)
+    omp.target kernel_type(generic) thread_limit(%0 : i32) {
+      omp.terminator
+    }
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @all_non_map_clauses
+  // CHECK-SAME: (%[[ARG0:.*]]: !llvm.ptr, %[[ARG1:.*]]: i32, %[[ARG2:.*]]: i1)
+  llvm.func @all_non_map_clauses(%arg0: !llvm.ptr, %arg1: i32, %arg2: i1) {
+    %0 = omp.map.info var_ptr(%arg0 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    omp.target_data device(%arg1 : i32) if(%arg2) map_entries(%0 : !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK-NEXT: omp.target kernel_type(generic) allocate(%[[ARG0]] : !llvm.ptr -> %[[ARG0]] : !llvm.ptr) thread_limit(%[[ARG1]] : i32) private(@privatizer %[[ARG0]] -> %{{.*}} : !llvm.ptr)
+    omp.target kernel_type(generic) allocate(%arg0 : !llvm.ptr -> %arg0 : !llvm.ptr) depend(taskdependin -> %arg0 : !llvm.ptr) device(%arg1 : i32) if(%arg2) thread_limit(%arg1 : i32) in_reduction(@reduction %arg0 -> %arg3 : !llvm.ptr) private(@privatizer %arg0 -> %arg4 : !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK-NOT: omp.target_enter_data
+    // CHECK-NOT: omp.target_exit_data
+    // CHECK-NOT: omp.target_update
+    omp.target_enter_data depend(taskdependin -> %arg0 : !llvm.ptr) device(%arg1 : i32) if(%arg2)
+    omp.target_exit_data depend(taskdependin -> %arg0 : !llvm.ptr) device(%arg1 : i32) if(%arg2)
+    omp.target_update depend(taskdependin -> %arg0 : !llvm.ptr) device(%arg1 : i32) if(%arg2)
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @private_with_map_idx
+  // CHECK-SAME: (%[[ARG0:.*]]: !llvm.ptr, %[[ARG1:.*]]: i64)
+  llvm.func @private_with_map_idx(%arg0: !llvm.ptr, %arg1: i64) {
+    // CHECK-NEXT: %[[VAL0:.*]] = llvm.mlir.undef : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL1:.*]] = llvm.mlir.undef : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL2:.*]] = llvm.insertvalue %[[ARG0]], %[[VAL1]][0] : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL3:.*]] = llvm.insertvalue %[[ARG1]], %[[VAL2]][1] : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL4:.*]] = llvm.extractvalue %[[VAL3]][0] : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL5:.*]] = llvm.insertvalue %[[VAL4]], %[[VAL0]][0] : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL6:.*]] = llvm.extractvalue %[[VAL3]][1] : !llvm.struct<(ptr, i64)>
+    // CHECK-NEXT: %[[VAL7:.*]] = llvm.insertvalue %[[VAL6]], %[[VAL5]][1] : !llvm.struct<(ptr, i64)>
+    %0 = llvm.mlir.undef : !llvm.struct<(ptr, i64)>
+    %1 = llvm.insertvalue %arg0, %0[0] : !llvm.struct<(ptr, i64)>
+    %2 = llvm.insertvalue %arg1, %1[1] : !llvm.struct<(ptr, i64)>
+    %3 = llvm.extractvalue %2[0] : !llvm.struct<(ptr, i64)>
+    %4 = llvm.extractvalue %2[1] : !llvm.struct<(ptr, i64)>
+    %5 = llvm.mlir.undef : !llvm.struct<(ptr, i64)>
+    %6 = llvm.insertvalue %3, %5[0] : !llvm.struct<(ptr, i64)>
+    %7 = llvm.insertvalue %4, %6[1] : !llvm.struct<(ptr, i64)>
+
+    // CHECK: omp.target kernel_type(generic) private(@struct_firstprivatizer %[[VAL7]] -> %{{.*}} [map_idx=0] : !llvm.struct<(ptr, i64)>)
+    omp.target kernel_type(generic) private(@struct_firstprivatizer %7 -> %arg2 [map_idx=0] : !llvm.struct<(ptr, i64)>) {
+      omp.terminator
+    }
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  omp.private {type = firstprivate} @struct_firstprivatizer : !llvm.struct<(ptr, i64)> copy {
+  ^bb0(%arg0: !llvm.struct<(ptr, i64)>, %arg1: !llvm.struct<(ptr, i64)>):
+    omp.yield(%arg0 : !llvm.struct<(ptr, i64)>)
+  }
+
+  // CHECK-LABEL: llvm.func @map_from_global_gep
+  // CHECK-SAME: (%[[ARG0:.*]]: i64)
+  llvm.func @map_from_global_gep(%arg0: i64) {
+    // CHECK-NEXT: %[[GLOBAL:.*]] = llvm.mlir.addressof @global_array : !llvm.ptr
+    // CHECK-NEXT: %[[GEP:.*]] = llvm.getelementptr %[[GLOBAL]][%[[ARG0]]] : (!llvm.ptr, i64) -> !llvm.ptr, i8
+    %0 = llvm.mlir.addressof @global_array : !llvm.ptr
+    %1 = llvm.getelementptr %0[%arg0] : (!llvm.ptr, i64) -> !llvm.ptr, i8
+
+    // CHECK-NEXT: %[[MAP:.*]] = omp.map.info var_ptr(%[[GEP]] : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+    %2 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target kernel_type(generic) map_entries(%[[MAP]] -> %{{.*}} : !llvm.ptr)
+    omp.target kernel_type(generic) map_entries(%2 -> %arg1 : !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @reciprocal_a
+  // CHECK-SAME: (%[[PLACEHOLDER:.*]]: !llvm.ptr)
+  llvm.func @reciprocal_a() {
+    // CHECK-NEXT: omp.map.info
+    %0 = llvm.mlir.constant(1 : i64) : i64
+    %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
+    %2 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target
+    omp.target kernel_type(generic) map_entries(%2 -> %arg0 : !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK-NOT: @reciprocal_b
+    llvm.call @reciprocal_b(%0) : (i64) -> ()
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  // CHECK-LABEL: llvm.func @reciprocal_b
+  // CHECK-SAME: (%[[ARG0:.*]]: i64, %[[PLACEHOLDER:.*]]: !llvm.ptr)
+  llvm.func @reciprocal_b(%arg0 : i64) {
+    // CHECK-NEXT: omp.map.info
+    %0 = llvm.mlir.constant(1 : i64) : i64
+    %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
+    %2 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr
+
+    // CHECK-NEXT: omp.target
+    omp.target kernel_type(generic) map_entries(%2 -> %arg1 : !llvm.ptr) {
+      omp.terminator
+    }
+
+    // CHECK-NOT: @reciprocal_a
+    llvm.call @reciprocal_a() : () -> ()
+
+    // CHECK: llvm.return
+    llvm.return
+  }
+
+  llvm.func @foo() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter)>, sym_visibility = "private"}
+  omp.private {type = firstprivate} @privatizer : i32 copy {
+  ^bb0(%arg0: !llvm.ptr, %arg1: !llvm.ptr):
+    %0 = llvm.load %arg0 : !llvm.ptr -> i32
+    llvm.store %0, %arg1 : i32, !llvm.ptr
+    omp.yield(%arg1 : !llvm.ptr)
+  }
+  omp.declare_reduction @reduction : i32 init {
+
+  ^bb0(%arg0: i32):
+    %0 = llvm.mlir.constant(0 : i32) : i32
+    omp.yield(%0 : i32)
+  } combiner {
+
+  ^bb0(%arg0: i32, %arg1: i32):
+    %0 = llvm.add %arg0, %arg1 : i32
+    omp.yield(%0 : i32)
+  }
+
+  // CHECK: llvm.mlir.global internal constant @global_scalar
+  // CHECK: llvm.mlir.global internal @global_array
+  llvm.mlir.global external constant @global_scalar() {addr_space = 0 : i32} : i32
+  llvm.mlir.global external @global_array() {addr_space = 0 : i32} : !llvm.array<8 x i8>
+
+  // CHECK: llvm.mlir.global external @declare_target_enter_any
+  // CHECK: llvm.mlir.global external @declare_target_enter_host
+  // CHECK: llvm.mlir.global external @declare_target_enter_nohost
+  llvm.mlir.global external @declare_target_enter_any() {addr_space = 0 : i32, omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false>} : i32
+  llvm.mlir.global external @declare_target_enter_host() {addr_space = 0 : i32, omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (enter), automap = false>} : i32
+  llvm.mlir.global external @declare_target_enter_nohost() {addr_space = 0 : i32, omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false>} : i32
+
+  // CHECK: llvm.mlir.global external @declare_target_link_any
+  // CHECK: llvm.mlir.global external @declare_target_link_host
+  // CHECK: llvm.mlir.global external @declare_target_link_nohost
+  llvm.mlir.global external @declare_target_link_any() {addr_space = 0 : i32, omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (link), automap = false>} : i32
+  llvm.mlir.global external @declare_target_link_host() {addr_space = 0 : i32, omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (link), automap = false>} : i32
+  llvm.mlir.global external @declare_target_link_nohost() {addr_space = 0 : i32, omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (link), automap = false>} : i32
+
+  llvm.func @LangRTPlaceholderFunc(!llvm.ptr {llvm.nocapture}, !llvm.ptr {llvm.nocapture}, !llvm.ptr, i8 {llvm.signext}, i32) attributes {sym_visibility = "private"}
+}


        


More information about the flang-commits mailing list