[Mlir-commits] [flang] [llvm] [mlir] [flang][mlir][OpenMP] Support iterator modifier in declare mapper map clause (PR #235491)

Urvi Rav llvmlistbot at llvm.org
Sun Oct 25 10:25:56 PDT 2026


https://github.com/ravurvi20 updated https://github.com/llvm/llvm-project/pull/235491

>From 5dbe8703fa92334966ebb8db5c8d9e33cdb4bd9e Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 15 Sep 2026 18:06:24 -0500
Subject: [PATCH 01/12] [flang][OpenMP] Lower iterator modifiers in map and
 motion clauses

Add iterator lowering for `target data`, `target enter/exit data`,
`target update`, and map clauses inside `declare mapper`.

For example:

```fortran
!$omp target update to(iterator(i=1:n): a(i), b)
```

Conceptually, this becomes:

```text
entries = omp.iterator(i = 1 through n) {
  yield map_info(base = a, bounds = element i)
}
target_update(iterated_maps = entries, ordinary_maps = [b])
```

Keeping the array base and expressing selections through bounds lets the
same approach handle array elements, sections, and descriptor-backed arrays.

Each locator is expanded only over the iterators that appear in it:

```text
iterator(i=1:n, j=1:m): a(i), b(j), c(i,j)
                        |     |      |
                        i     j    i x j
```

Iterator ranges use the same canonical loop semantics throughout lowering:

```text
trip_count = canonicalTripCount(lower, upper, step, inclusive)

if any trip_count == 0:
    result = []
    skip iterator region
```

These rules preserve source behavior in edge cases. Fortran ranges include
the endpoint, so lowering marks them inclusive instead of dropping the last
entry. A range such as `5:1:1` is empty and yields no entries instead of being
mistaken for a large range. Bounds and reconstructed iterator values keep
their source integer width, preventing `INTEGER(16)` values from being
truncated.

Supports explicit and default mappers, including mapper declarations
imported from other modules.

This PR stacked on top of #197047 and #197752.

This patch is part of feature work for #188061.

Assisted with codex.
---
 flang/include/flang/Utils/OpenMP.h            |  29 +-
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 172 +++-
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |  11 +
 flang/lib/Lower/OpenMP/Clauses.cpp            |  12 +
 flang/lib/Lower/OpenMP/OpenMP.cpp             |   5 +-
 flang/lib/Lower/OpenMP/Utils.cpp              | 187 ++++
 flang/lib/Lower/OpenMP/Utils.h                |  25 +
 .../Optimizer/OpenMP/MapInfoFinalization.cpp  |  29 +-
 flang/lib/Semantics/check-omp-structure.cpp   |  66 +-
 flang/lib/Utils/OpenMP.cpp                    |   4 +-
 .../declare-mapper-iterator-unsupported.f90   |  65 ++
 .../OpenMP/Todo/declare-mapper-iterator.f90   |  11 -
 .../OpenMP/Todo/from-iterator-modifier.f90    |   8 -
 .../OpenMP/Todo/map-iterator-ref-attach.f90   |  81 ++
 ...p-motion-iterator-unsupported-locators.f90 |  77 ++
 .../Lower/OpenMP/Todo/target-map-iterator.f90 |  12 +
 .../OpenMP/Todo/to-iterator-modifier.f90      |   8 -
 .../Lower/OpenMP/declare-mapper-iterator.f90  | 277 ++++++
 flang/test/Lower/OpenMP/depend-iterator.f90   |  50 +-
 flang/test/Lower/OpenMP/iterator-ranges.f90   | 160 +++
 flang/test/Lower/OpenMP/motion-iterator.f90   | 910 ++++++++++++++++++
 flang/test/Lower/OpenMP/task-affinity.f90     |  12 +-
 ...arget-enter-data-temp-descriptor-omp61.f90 |  33 +
 .../target-enter-data-temp-descriptor.f90     |  33 +
 .../Transforms/omp-map-info-finalization.fir  |  62 ++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |   6 +
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  25 +-
 mlir/test/Dialect/OpenMP/iterator-empty.mlir  |  19 +-
 mlir/test/Dialect/OpenMP/ops.mlir             |  12 +
 .../Target/LLVMIR/openmp-iterator-empty.mlir  |   6 +-
 .../LLVMIR/openmp-iterator-multi-block.mlir   |  12 +-
 .../Target/LLVMIR/openmp-iterator-wide.mlir   |  14 +-
 mlir/test/Target/LLVMIR/openmp-iterator.mlir  | 125 ++-
 .../Target/LLVMIR/openmp-taskwait-depend.mlir |   2 +-
 34 files changed, 2415 insertions(+), 145 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/declare-mapper-iterator.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/from-iterator-modifier.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/map-iterator-ref-attach.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/target-map-iterator.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/to-iterator-modifier.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-mapper-iterator.f90
 create mode 100644 flang/test/Lower/OpenMP/iterator-ranges.f90
 create mode 100644 flang/test/Lower/OpenMP/motion-iterator.f90

diff --git a/flang/include/flang/Utils/OpenMP.h b/flang/include/flang/Utils/OpenMP.h
index 0cb77e25af9475..8c8da53f90863d 100644
--- a/flang/include/flang/Utils/OpenMP.h
+++ b/flang/include/flang/Utils/OpenMP.h
@@ -20,12 +20,28 @@ namespace Fortran::utils::openmp {
 // TODO We can probably move the stuff inside `Support/OpenMP-utils.h/.cpp` here
 // as well.
 
-/// Create an `omp.map.info` op. Parameters other than the ones documented below
-/// correspond to operation arguments in the OpenMPOps.td file, see op docs for
-/// more details.
+/// Create an `omp.map.info` op. See OpenMPOps.td for map operand semantics.
 ///
-/// \param [in] builder - MLIR operation builder.
-/// \param [in] loc     - Source location of the created op.
+/// \param builder        - Operation builder.
+/// \param loc            - Source location of the created op.
+/// \param baseAddr       - Variable address. Box values are converted to their
+/// data address with `fir.box_addr`.
+/// \param varPtrPtr      - Optional address of the mapped pointer, used for
+/// descriptor base-address maps.
+/// \param name           - Name identifying the mapped object.
+/// \param bounds         - Bounds selecting the mapped elements or section.
+/// \param members        - Child map entries of a structured map.
+/// \param membersIndex   - Placement indices of the child map entries.
+/// \param mapType        - Map type and modifier flags.
+/// \param mapCaptureType - Capture kind of the mapped variable.
+/// \param retTy          - Result type. Replaced by the data address type when
+/// \p baseAddr is a box value.
+/// \param partialMap     - Whether the record is mapped only through selected
+/// components rather than in its entirety.
+/// \param mapperId       - Optional declare mapper symbol reference.
+/// \param varPtrTy       - Pointer type used to derive the `var_ptr` type
+/// attribute. Defaults to \p retTy after box conversion. Set this when an
+/// opaque map result must retain the type of its FIR variable pointer.
 mlir::omp::MapInfoOp createMapInfoOp(mlir::OpBuilder &builder,
     mlir::Location loc, mlir::Value baseAddr, mlir::Value varPtrPtr,
     llvm::StringRef name, llvm::ArrayRef<mlir::Value> bounds,
@@ -33,7 +49,8 @@ mlir::omp::MapInfoOp createMapInfoOp(mlir::OpBuilder &builder,
     mlir::omp::ClauseMapFlags mapType,
     mlir::omp::VariableCaptureKind mapCaptureType, mlir::Type retTy,
     bool partialMap = false,
-    mlir::FlatSymbolRefAttr mapperId = mlir::FlatSymbolRefAttr());
+    mlir::FlatSymbolRefAttr mapperId = mlir::FlatSymbolRefAttr(),
+    mlir::Type varPtrTy = mlir::Type());
 
 /// For an mlir value that does not have storage, allocate temporary storage
 /// (outside the target region), store the value in that storage, and map the
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 34961d3d44ba2f..2c9f3713154ba1 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -13,6 +13,7 @@
 #include "ClauseProcessor.h"
 #include "Utils.h"
 
+#include "flang/Evaluate/tools.h"
 #include "flang/Lower/ConvertCall.h"
 #include "flang/Lower/ConvertExprToHLFIR.h"
 #include "flang/Lower/OpenMP/Clauses.h"
@@ -23,6 +24,7 @@
 #include "flang/Optimizer/Support/InternalNames.h"
 #include "flang/Semantics/tools.h"
 #include "flang/Utils/OpenMP.h"
+#include "mlir/Interfaces/DataLayoutInterfaces.h"
 #include "llvm/Frontend/OpenMP/OMP.h.inc"
 #include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
 
@@ -295,6 +297,7 @@ static mlir::Value buildIteratorOp(Fortran::lower::AbstractConverter &converter,
   auto itOp = mlir::omp::IteratorOp::create(
       builder, loc, iterTy, mlir::ValueRange{lbs}, mlir::ValueRange{ubs},
       mlir::ValueRange{steps});
+  itOp.setLoopInclusive(true);
 
   mlir::OpBuilder::InsertionGuard guard(builder);
 
@@ -329,6 +332,43 @@ static mlir::Value buildIteratorOp(Fortran::lower::AbstractConverter &converter,
   return itOp.getResult();
 }
 
+// Build an omp.iterator that yields a map entry for one locator.
+static mlir::Value buildIteratedMapEntry(
+    Fortran::lower::AbstractConverter &converter,
+    Fortran::semantics::SemanticsContext &semaCtx, mlir::Location loc,
+    llvm::ArrayRef<IteratorRange> iteratorRanges, const omp::Object &object,
+    const evaluate::ArrayRef &arrayRef, llvm::StringRef mapperIdName,
+    mlir::omp::ClauseMapFlags mapTypeBits, llvm::omp::Directive directive) {
+  mlir::Type ptrTy =
+      mlir::LLVM::LLVMPointerType::get(&converter.getMLIRContext());
+  mlir::Type iterTy =
+      mlir::omp::IteratedType::get(&converter.getMLIRContext(), ptrTy);
+
+  return buildIteratorOp(
+      converter, loc, iterTy, iteratorRanges, [&]() -> mlir::Value {
+        fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+        lower::StatementContext iterStmtCtx;
+        IteratorMapInfo mapInfo = genIteratorMapInfo(
+            converter, builder, semaCtx, iterStmtCtx, object, arrayRef, loc);
+
+        // Keep the array base in var_ptr so element and whole-array maps share
+        // a base address. Bounds select the data for this iteration; descriptor
+        // parent and attachment maps are not part of these entries.
+        mlir::Value baseAddr = mapInfo.entity.getBase();
+        if (mlir::isa<fir::BaseBoxType>(baseAddr.getType()))
+          baseAddr = fir::BoxAddrOp::create(builder, loc, baseAddr);
+        mlir::FlatSymbolRefAttr mapperId = resolveMapperId(
+            converter, loc, object, mapperIdName, mapTypeBits, directive);
+        mlir::omp::MapInfoOp mapOp = utils::openmp::createMapInfoOp(
+            builder, loc, baseAddr, /*varPtrPtr=*/mlir::Value{},
+            /*name=*/"", mapInfo.bounds, /*members=*/{},
+            /*membersIndex=*/mlir::ArrayAttr{}, mapTypeBits,
+            mlir::omp::VariableCaptureKind::ByRef, ptrTy,
+            /*partialMap=*/false, mapperId, baseAddr.getType());
+        return mapOp.getResult();
+      });
+}
+
 template <typename ClauseTuple>
 static llvm::SmallVector<IteratorRange>
 lowerIteratorRanges(const ClauseTuple &clause,
@@ -1916,6 +1956,98 @@ void ClauseProcessor::processMapObjects(
   }
 }
 
+// Lower iterator-dependent locators to mapIterated and other locators to
+// mapVars.
+void ClauseProcessor::processMapObjectsWithIterator(
+    lower::StatementContext &stmtCtx, mlir::Location clauseLocation,
+    const omp::ObjectList &objects,
+    llvm::ArrayRef<IteratorRange> iteratorRanges,
+    mlir::omp::ClauseMapFlags mapTypeBits,
+    std::map<Object, OmpMapParentAndMemberData> &parentMemberIndices,
+    mlir::omp::MapClauseOps &result, llvm::SmallVectorImpl<Object> &mapObjects,
+    llvm::StringRef mapperIdNameRef, bool isMotionModifier,
+    llvm::omp::Directive directive) const {
+  if (iteratorRanges.empty()) {
+    processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits,
+                      parentMemberIndices, result.mapVars, mapObjects,
+                      mapperIdNameRef, isMotionModifier, directive);
+    return;
+  }
+
+  auto declareMapper = mlir::dyn_cast_if_present<mlir::omp::DeclareMapperOp>(
+      converter.getFirOpBuilder().getRegion().getParentOp());
+  auto isDeclareMapperVariable =
+      [&](const IteratorMapObjectAnalysis &analysis) {
+        if (!declareMapper || !analysis.rootSym)
+          return false;
+
+        mlir::Value rootAddress = converter.getSymbolAddress(*analysis.rootSym);
+        while (rootAddress) {
+          if (auto declare = rootAddress.getDefiningOp<hlfir::DeclareOp>()) {
+            rootAddress = declare.getMemref();
+            continue;
+          }
+          if (auto declare = rootAddress.getDefiningOp<fir::DeclareOp>()) {
+            rootAddress = declare.getMemref();
+            continue;
+          }
+          break;
+        }
+
+        return rootAddress == declareMapper.getRegion().front().getArgument(0);
+      };
+
+  constexpr mlir::omp::ClauseMapFlags unsupportedReferenceModifiers =
+      mlir::omp::ClauseMapFlags::ref_ptr | mlir::omp::ClauseMapFlags::ref_ptee |
+      mlir::omp::ClauseMapFlags::attach_always |
+      mlir::omp::ClauseMapFlags::attach_never |
+      mlir::omp::ClauseMapFlags::attach_auto;
+  bool hasUnsupportedReferenceModifier =
+      (mapTypeBits & unsupportedReferenceModifiers) !=
+      mlir::omp::ClauseMapFlags::none;
+
+  // Objects in an iterator-modified clause may independently reference
+  // iterator variables, so handle each object separately.
+  for (const omp::Object &object : objects) {
+    llvm::SmallPtrSet<const semantics::Symbol *, 4> referencedSymbols;
+    if (const auto &ref = object.ref())
+      for (const semantics::SymbolRef &symbol : evaluate::CollectSymbols(*ref))
+        referencedSymbols.insert(&symbol->GetUltimate());
+
+    llvm::SmallVector<IteratorRange> objectRanges;
+    for (const IteratorRange &range : iteratorRanges)
+      if (referencedSymbols.contains(&range.ivSym->GetUltimate()))
+        objectRanges.push_back(range);
+
+    if (!objectRanges.empty()) {
+      if (hasUnsupportedReferenceModifier)
+        TODO(clauseLocation,
+             "iterator modifier with reference or attach modifier");
+      IteratorMapObjectAnalysis analysis = analyzeIteratorMapObject(object);
+      bool isMapperVariable = isDeclareMapperVariable(analysis);
+      if (analysis.isDerivedTypeMember && !isMapperVariable)
+        TODO(clauseLocation, "iterator modifier with derived type member map");
+      if (declareMapper && analysis.rootSym && !isMapperVariable)
+        TODO(clauseLocation,
+             "iterator modifier with locator outside declare mapper variable");
+      if (!analysis.arrayRef)
+        TODO(clauseLocation, "object type not supported by iterator modifier");
+      if (const auto *symbol{object.sym()};
+          symbol && semantics::IsOptional(*symbol))
+        TODO(clauseLocation, "iterator modifier with optional locator");
+      result.mapIterated.push_back(buildIteratedMapEntry(
+          converter, semaCtx, clauseLocation, objectRanges, object,
+          *analysis.arrayRef, mapperIdNameRef, mapTypeBits, directive));
+      continue;
+    }
+
+    omp::ObjectList singleObj{object};
+    processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
+                      parentMemberIndices, result.mapVars, mapObjects,
+                      mapperIdNameRef, isMotionModifier, directive);
+  }
+}
+
 /// Extract and mangle the mapper identifier name from a mapper clause.
 /// Returns "__implicit_mapper" if no mapper is specified, or "default" if
 /// the default mapper is specified, otherwise returns the mangled mapper name.
@@ -2035,15 +2167,24 @@ bool ClauseProcessor::processMap(
       }
     }
 
-    if (iterator)
-      TODO(currentLocation,
-           "Support for iterator modifiers is not implemented yet");
     TodoLocators(currentLocation, objects);
 
-    processMapObjects(stmtCtx, clauseLocation,
-                      std::get<omp::ObjectList>(clause.t), mapTypeBits,
-                      parentMemberIndices, result.mapVars, *ptrMapObjects,
-                      mapperIdName, /*isMotionModifier=*/false, directive);
+    llvm::SmallVector<IteratorRange> iteratorRanges;
+    if (iterator) {
+      // An executable target region needs a stable base capture for every
+      // iterated map object used in its body. Without such a capture, implicit
+      // mapping adds an ordinary whole-object map and changes the requested
+      // extent and direction. Reject this until omp.target can represent those
+      // captures separately from runtime map entries.
+      if (directive == llvm::omp::Directive::OMPD_target)
+        TODO(clauseLocation, "TARGET construct with MAP iterator modifier");
+
+      iteratorRanges = lowerIteratorRanges(clause, converter, stmtCtx);
+    }
+    processMapObjectsWithIterator(
+        stmtCtx, clauseLocation, objects, iteratorRanges, mapTypeBits,
+        parentMemberIndices, result, *ptrMapObjects, mapperIdName,
+        /*isMotionModifier=*/false, directive);
   };
 
   bool clauseFound = findRepeatableClause<omp::clause::Map>(process);
@@ -2058,6 +2199,11 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
   std::map<Object, OmpMapParentAndMemberData> parentMemberIndices;
   llvm::SmallVector<Object> mapObjects;
 
+  // OMPD_unknown allows the mapper resolver to synthesize default mappers for
+  // target update. Passing OMPD_target_update would disable that synthesis.
+  constexpr llvm::omp::Directive mapperDirective =
+      llvm::omp::Directive::OMPD_unknown;
+
   auto callbackFn = [&](const auto &clause, const parser::CharBlock &source) {
     mlir::Location clauseLocation = converter.genLocation(source);
     const auto &[expectation, mapper, iterator, objects] = clause.t;
@@ -2069,16 +2215,16 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
     if (expectation && *expectation == omp::clause::To::Expectation::Present)
       mapTypeBits |= mlir::omp::ClauseMapFlags::present;
 
-    // Support motion modifiers: iterator.
     std::string mapperIdName = getMapperIdentifier(converter, mapper);
 
-    if (iterator)
-      TODO(clauseLocation, "Iterator modifier is not supported yet");
     TodoLocators(clauseLocation, objects);
 
-    processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits,
-                      parentMemberIndices, result.mapVars, mapObjects,
-                      mapperIdName, /*isMotionModifier=*/true);
+    llvm::SmallVector<IteratorRange> iteratorRanges =
+        lowerIteratorRanges(clause, converter, stmtCtx);
+    processMapObjectsWithIterator(
+        stmtCtx, clauseLocation, objects, iteratorRanges, mapTypeBits,
+        parentMemberIndices, result, mapObjects, mapperIdName,
+        /*isMotionModifier=*/true, mapperDirective);
   };
 
   bool clauseFound = findRepeatableClause<omp::clause::To>(callbackFn);
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index 33f44754bb84ba..ea9cb7b0b0ab60 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -228,6 +228,17 @@ class ClauseProcessor {
       llvm::StringRef mapperIdNameRef = "", bool isMotionModifier = false,
       llvm::omp::Directive directive = llvm::omp::OMPD_unknown) const;
 
+  void processMapObjectsWithIterator(
+      lower::StatementContext &stmtCtx, mlir::Location clauseLocation,
+      const omp::ObjectList &objects,
+      llvm::ArrayRef<IteratorRange> iteratorRanges,
+      mlir::omp::ClauseMapFlags mapTypeBits,
+      std::map<Object, OmpMapParentAndMemberData> &parentMemberIndices,
+      mlir::omp::MapClauseOps &result,
+      llvm::SmallVectorImpl<Object> &mapObjects,
+      llvm::StringRef mapperIdNameRef = "", bool isMotionModifier = false,
+      llvm::omp::Directive directive = llvm::omp::OMPD_unknown) const;
+
   lower::AbstractConverter &converter;
   semantics::SemanticsContext &semaCtx;
   List<Clause> clauses;
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index 5ef2af3962ad54..ba2adcd74138b7 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -153,6 +153,18 @@ Object makeObject(const parser::OmpObject &object,
   if (auto *desg = parser::omp::GetDesignatorFromObj(object))
     return makeObject(*desg, semaCtx);
   if (auto *locator = parser::omp::GetLocatorFromObj(object)) {
+    // Module files are name-resolved but do not pass through
+    // RewriteParseTree. Recover array elements that consequently remain
+    // parsed as function references before converting them for lowering.
+    if (auto *ref = std::get_if<parser::FunctionReference>(&locator->u);
+        ref && semantics::CheckMisparsedArrayElement(semaCtx, *ref)) {
+      auto &mutableObject = const_cast<parser::OmpObject &>(object);
+      auto &mutableLocator = std::get<parser::OmpLocator>(mutableObject.u);
+      auto &mutableRef = std::get<parser::FunctionReference>(mutableLocator.u);
+      mutableObject.u = mutableRef.ConvertToArrayElementRef();
+      return makeObject(std::get<parser::Designator>(mutableObject.u), semaCtx);
+    }
+
     return common::visit( //
         common::visitors{
             [&](const parser::OmpReservedIdentifier &x) {
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index b529ca203cd617..ae023bc83e50e9 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2585,7 +2585,7 @@ genTargetClauses(lower::AbstractConverter &converter,
                   "storage-associated variable");
     }
   cp.processIsDevicePtr(stmtCtx, clauseOps, isDevicePtrObjects);
-  cp.processMap(loc, stmtCtx, clauseOps, llvm::omp::Directive::OMPD_unknown,
+  cp.processMap(loc, stmtCtx, clauseOps, llvm::omp::Directive::OMPD_target,
                 &mapObjects);
   cp.processNowait(clauseOps);
   cp.processThreadLimit(stmtCtx, clauseOps);
@@ -2608,7 +2608,8 @@ static void genTargetDataClauses(
   ClauseProcessor cp(converter, semaCtx, clauses);
   cp.processDevice(stmtCtx, clauseOps);
   cp.processIf(llvm::omp::Directive::OMPD_target_data, clauseOps);
-  cp.processMap(loc, stmtCtx, clauseOps);
+  cp.processMap(loc, stmtCtx, clauseOps,
+                llvm::omp::Directive::OMPD_target_data);
   cp.processUseDeviceAddr(stmtCtx, clauseOps, useDeviceAddrObjects);
   // Record the source UDA prefix before non-C_PTR UDP operands are promoted.
   sourceUseDeviceAddrCount = useDeviceAddrObjects.size();
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 9e37c02eac3f1f..156670dc4c78db 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1437,6 +1437,193 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
   return variants[bestIdx];
 }
 
+IteratorMapObjectAnalysis analyzeIteratorMapObject(const omp::Object &object) {
+  IteratorMapObjectAnalysis analysis;
+  const std::optional<ExprTy> &ref = object.ref();
+  if (!ref)
+    return analysis;
+
+  std::optional<evaluate::DataRef> dataRef = evaluate::ExtractDataRef(*ref);
+  if (!dataRef)
+    return analysis;
+
+  analysis.rootSym = &dataRef->GetFirstSymbol();
+
+  const auto *component = std::get_if<evaluate::Component>(&dataRef->u);
+  const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
+  analysis.isDerivedTypeMember =
+      component || (arrayRef && arrayRef->base().UnwrapComponent());
+
+  if (!arrayRef || arrayRef->subscript().empty() ||
+      evaluate::ExtractCoarrayRef(*dataRef))
+    return analysis;
+
+  for (const auto &subscript : arrayRef->subscript()) {
+    if (!std::holds_alternative<evaluate::Triplet>(subscript.u) &&
+        subscript.Rank() > 0)
+      return analysis;
+  }
+
+  analysis.arrayRef = *arrayRef;
+  return analysis;
+}
+
+// Lower the array base (v%a for v%a(i)) with shape information for bounds.
+static hlfir::Entity getIteratorMapEntity(
+    Fortran::lower::AbstractConverter &converter, fir::FirOpBuilder &builder,
+    Fortran::semantics::SemanticsContext &semaCtx,
+    Fortran::lower::StatementContext &stmtCtx, const omp::Object &object,
+    const evaluate::ArrayRef &arrayRef, mlir::Location loc) {
+  const semantics::Symbol *sym = object.sym();
+  assert(sym && "expected symbol for iterator object");
+
+  mlir::Value addr;
+  if (!sym->owner().IsDerivedType()) {
+    // Keep the same base address as ordinary map lowering for non-component
+    // objects.
+    fir::factory::AddrAndBoundsInfo info =
+        Fortran::lower::getDataOperandBaseAddr(converter, builder, *sym, loc,
+                                               /*unwrapFirBox=*/false);
+    addr = info.addr;
+  } else {
+    evaluate::ExpressionAnalyzer ea{semaCtx};
+    std::optional<ExprTy> arrayBase;
+    const evaluate::NamedEntity &base = arrayRef.base();
+    // The ordinary base-address helper takes a symbol, which does not identify
+    // the containing object of a component. Lower the component base reference
+    // here, leaving its subscripts for iterator-local map bounds.
+    if (const semantics::SymbolRef *symRef = base.UnwrapSymbolRef())
+      arrayBase = ea.Designate(evaluate::DataRef{*symRef});
+    else if (const evaluate::Component *component = base.UnwrapComponent())
+      arrayBase = ea.Designate(evaluate::DataRef{*component});
+    else
+      llvm_unreachable("unexpected NamedEntity");
+
+    assert(arrayBase);
+    // Mutable-box lowering preserves allocatable and pointer descriptors.
+    fir::ExtendedValue dataExv;
+    if (semantics::IsAllocatableOrPointer(base.GetLastSymbol()))
+      dataExv = converter.genExprMutableBox(loc, *arrayBase);
+    else
+      dataExv = converter.genExprAddr(loc, *arrayBase, stmtCtx);
+    addr = fir::getBase(dataExv);
+  }
+
+  // Load allocatable and pointer descriptors to access data and shape. Keep
+  // the box value until bounds are built; map construction extracts its data
+  // address with fir.box_addr.
+  if (fir::isBoxAddress(addr.getType()))
+    addr = fir::LoadOp::create(builder, loc, addr);
+  return hlfir::Entity{addr};
+}
+
+// Ordinary map lowering derives bounds from a fully lowered locator. Iterator
+// maps keep the array base in var_ptr, so build bounds from the subscripts in
+// the iterator body, relative to the base's Fortran lower bounds.
+//
+// Examples:
+//   a(i)     -> lower_bound == upper_bound == i - base lower bound
+//   a(i:i+1) -> lower_bound == i - base lower bound,
+//               upper_bound == i + 1 - base lower bound
+static llvm::SmallVector<mlir::Value>
+genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
+                     hlfir::Entity entity, const evaluate::ArrayRef &arrayRef,
+                     Fortran::lower::StatementContext &stmtCtx,
+                     mlir::Location loc) {
+  auto &builder = converter.getFirOpBuilder();
+  using SubscriptExpr =
+      Fortran::evaluate::Expr<Fortran::evaluate::SubscriptInteger>;
+  mlir::Type idxTy = builder.getIndexType();
+  mlir::Type boundTy = builder.getType<mlir::omp::MapBoundsType>();
+  mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+  mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+  mlir::Value box;
+  if (entity.isBoxAddressOrValue())
+    box = entity.getBase();
+
+  auto lowerSubscriptToIndex = [&](const SubscriptExpr &expr) -> mlir::Value {
+    mlir::Value value =
+        fir::getBase(converter.genExprValue(toEvExpr(expr), stmtCtx, &loc));
+    return builder.createConvert(loc, idxTy, value);
+  };
+
+  llvm::SmallVector<mlir::Value> bounds;
+  bounds.reserve(arrayRef.subscript().size());
+  for (const auto &[dim, subscript] : llvm::enumerate(arrayRef.subscript())) {
+    mlir::Value baseLb = builder.createConvert(
+        loc, idxTy, hlfir::genLBound(loc, builder, entity, dim));
+    mlir::Value extent;
+    mlir::Value stride = one;
+    bool strideInBytes = false;
+    if (box) {
+      mlir::Value dimValue = builder.createIntegerConstant(loc, idxTy, dim);
+      auto dimInfo = fir::BoxDimsOp::create(builder, loc, idxTy, idxTy, idxTy,
+                                            box, dimValue);
+      extent = dimInfo.getExtent();
+      stride = dimInfo.getByteStride();
+      strideInBytes = true;
+    } else {
+      extent = builder.createConvert(
+          loc, idxTy, hlfir::genExtent(loc, builder, entity, dim));
+    }
+    mlir::Value lbound;
+    mlir::Value ubound;
+
+    if (const auto *triplet =
+            std::get_if<Fortran::evaluate::Triplet>(&subscript.u)) {
+      // Omitted endpoints use the dimension's lower or upper bound, normalized
+      // to zero or extent - 1, respectively.
+      if (std::optional<SubscriptExpr> lowerBound = triplet->lower()) {
+        mlir::Value lower = lowerSubscriptToIndex(*lowerBound);
+        lbound = mlir::arith::SubIOp::create(builder, loc, lower, baseLb);
+      } else {
+        lbound = zero;
+      }
+
+      if (std::optional<SubscriptExpr> upperBound = triplet->upper()) {
+        mlir::Value upper = lowerSubscriptToIndex(*upperBound);
+        ubound = mlir::arith::SubIOp::create(builder, loc, upper, baseLb);
+      } else {
+        ubound = mlir::arith::SubIOp::create(builder, loc, extent, one);
+      }
+
+      // Non-unit and dynamic section strides need noncontiguous map support.
+      std::optional<std::int64_t> sectionStride =
+          Fortran::evaluate::ToInt64(triplet->GetStride());
+      if (!sectionStride || *sectionStride != 1)
+        TODO(loc, "iterator modifier with non-unit array section stride");
+    } else {
+      const auto *indirect =
+          std::get_if<Fortran::evaluate::IndirectSubscriptIntegerExpr>(
+              &subscript.u);
+      assert(indirect && "expected non-triplet subscript");
+
+      // Scalar subscripts map one element, so lower and upper are identical.
+      mlir::Value index = lowerSubscriptToIndex(indirect->value());
+      lbound = mlir::arith::SubIOp::create(builder, loc, index, baseLb);
+      ubound = lbound;
+    }
+
+    mlir::Value bound = mlir::omp::MapBoundsOp::create(
+        builder, loc, boundTy, lbound, ubound, extent, stride,
+        /*stride_in_bytes=*/strideInBytes, /*start_idx=*/baseLb);
+    bounds.push_back(bound);
+  }
+
+  return bounds;
+}
+
+IteratorMapInfo genIteratorMapInfo(
+    Fortran::lower::AbstractConverter &converter, fir::FirOpBuilder &builder,
+    Fortran::semantics::SemanticsContext &semaCtx,
+    Fortran::lower::StatementContext &stmtCtx, const omp::Object &object,
+    const evaluate::ArrayRef &arrayRef, mlir::Location loc) {
+  hlfir::Entity entity = getIteratorMapEntity(converter, builder, semaCtx,
+                                              stmtCtx, object, arrayRef, loc);
+  return IteratorMapInfo{
+      entity, genIteratorMapBounds(converter, entity, arrayRef, stmtCtx, loc)};
+}
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index c769380aabbb01..743df47c3b23d3 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -252,6 +252,31 @@ mlir::FlatSymbolRefAttr resolveMapperId(
     const omp::Object &object, llvm::StringRef mapperIdName,
     mlir::omp::ClauseMapFlags mapTypeBits, llvm::omp::Directive directive);
 
+struct IteratorMapInfo {
+  hlfir::Entity entity;
+  llvm::SmallVector<mlir::Value> bounds;
+};
+
+/// Properties needed to validate an iterator map locator without lowering it.
+/// Unsupported DataRef forms are classified without evaluating their bases.
+struct IteratorMapObjectAnalysis {
+  const semantics::Symbol *rootSym = nullptr;
+  bool isDerivedTypeMember = false;
+  // Own the validated reference so lowering can reuse it after analysis.
+  std::optional<evaluate::ArrayRef> arrayRef;
+};
+
+/// Classify a locator and retain its array reference if supported.
+IteratorMapObjectAnalysis analyzeIteratorMapObject(const omp::Object &object);
+
+/// Lower a locator to its array base and iterator-dependent map bounds.
+/// \p arrayRef must be the supported reference returned by locator analysis.
+IteratorMapInfo genIteratorMapInfo(
+    Fortran::lower::AbstractConverter &converter, fir::FirOpBuilder &builder,
+    Fortran::semantics::SemanticsContext &semaCtx,
+    Fortran::lower::StatementContext &stmtCtx, const omp::Object &object,
+    const evaluate::ArrayRef &arrayRef, mlir::Location loc);
+
 /// Walk the already-emitted MLIR parent operations starting from \p op and
 /// collect the implied OpenMP construct traits in outermost-to-innermost
 /// order. Used by metadirective lowering and declare-variant call resolution
diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index b790bee65ebe7a..531de2eea5b18d 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -1715,6 +1715,10 @@ class MapInfoFinalizationPass
     });
   }
 
+  static bool isNestedInIterator(mlir::omp::MapInfoOp op) {
+    return op->getParentOfType<mlir::omp::IteratorOp>() != nullptr;
+  }
+
   // This pass executes on omp::MapInfoOp's containing descriptor based types
   // (allocatables, pointers, assumed shape etc.) and expanding them into
   // multiple omp::MapInfoOp's for each pointer member contained within the
@@ -1745,19 +1749,26 @@ class MapInfoFinalizationPass
       deferrableDesc.clear();
       expandedBaseAddr.clear();
 
-      // Walk all of the existing maps for parents with child maps and then
-      // make sure to appropriately bind them to the target region that the
-      // parent is bound to. Necessary for the next implicit record member
-      // map step which depends on this canonicalization step. This step
-      // is executed again as the final step of this pass to maintain
-      // map to block argument consistency.
-      func->walk([&](mlir::omp::MapInfoOp op) {
+      // Iterator maps may depend on region-local values. Descriptor expansion
+      // and target operand updates must not expose those values outside the
+      // iterator region.
+      auto walkNonIteratedMapInfoOps = [&](auto &&callback) {
+        func->walk([&](mlir::omp::MapInfoOp op) {
+          if (!isNestedInIterator(op))
+            callback(op);
+        });
+      };
+
+      // Bind existing member maps to their parent's target region. Repeat after
+      // descriptor expansion to keep map operands and block arguments
+      // consistent.
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         mlir::Operation *targetUser = getFirstTargetUser(op);
         assert(targetUser && "expected user of map operation was not found");
         addImplicitMembersToTarget(op, builder, targetUser);
       });
 
-      func->walk([&](mlir::omp::MapInfoOp op) {
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         // NOTE: Currently only supports a single user for the MapInfoOp. This
         // is fine for the moment, as the Fortran frontend will generate a
         // new MapInfoOp with at most one user currently. In the case of
@@ -1827,7 +1838,7 @@ class MapInfoFinalizationPass
       // Wait until after we have generated all of our maps to add them onto
       // the target's block arguments, simplifying the process as there would be
       // no need to avoid accidental duplicate additions.
-      func->walk([&](mlir::omp::MapInfoOp op) {
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         mlir::Operation *targetUser = getFirstTargetUser(op);
         assert(targetUser && "expected user of map operation was not found");
         addImplicitMembersToTarget(op, builder, targetUser);
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 81f0f146c3f2a9..78567221a1f94f 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -527,6 +527,28 @@ class OmpUnitedTaskDesignatorChecker {
   SemanticsContext &context_;
 };
 
+class OmpIteratorReferenceChecker {
+public:
+  explicit OmpIteratorReferenceChecker(
+      const UnorderedSymbolSet &iteratorSymbols)
+      : iteratorSymbols_{iteratorSymbols} {}
+
+  template <typename T> bool Pre(const T &) { return !found_; }
+  template <typename T> void Post(const T &) {}
+
+  bool Pre(const parser::Name &name) {
+    if (name.symbol && iteratorSymbols_.count(name.symbol->GetUltimate()) != 0)
+      found_ = true;
+    return !found_;
+  }
+
+  bool found() const { return found_; }
+
+private:
+  const UnorderedSymbolSet &iteratorSymbols_;
+  bool found_{false};
+};
+
 bool OmpStructureChecker::IsAllowedClause(llvm::omp::Clause clauseId) {
   // Do not do clause checks while processing METADIRECTIVE.
   // See comment in CheckAllowedClause.
@@ -5035,9 +5057,9 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
         "The specification of modifiers without comma separators for the "
         "'MAP' clause has been deprecated in OpenMP 5.2"_port_en_US);
   }
-  if (auto *iter{OmpGetUniqueModifier<parser::OmpIterator>(modifiers)}) {
-    CheckIteratorModifier(*iter);
-  }
+  const auto *iterator{OmpGetUniqueModifier<parser::OmpIterator>(modifiers)};
+  if (iterator)
+    CheckIteratorModifier(*iterator);
 
   using Directive = llvm::omp::Directive;
   Directive dir{GetContext().directive};
@@ -5168,23 +5190,39 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
     }
   }
 
-  // If we are an enter or exit map, iterate over the maps and add them to
-  // containers that track if the symbol has been referenced in both an
-  // enter/exit map in the current scope, if it falls into the category of
-  // having a temporary stack descriptor. If we have reference modifiers, we
-  // ignore the warning and trust that the user knows what they are doing
-  // already, as they are aware the type comes with a descriptor and pointer
-  // combination.
-  //
-  // We will utilise this information to emit a warning later if the neccesary
-  // conditions are met, where we have an enter map without a corresponding exit
-  // in the current scope.
+  // Track temporary descriptor maps to warn about enter maps without matching
+  // exits in this scope. Reference modifiers control descriptor mapping
+  // explicitly. Iterator-dependent maps transfer only selected data, so they
+  // neither introduce a descriptor map nor release one on exit.
   bool hasRefModifier{
       OmpGetUniqueModifier<parser::OmpRefModifier>(modifiers) != nullptr};
   if (!hasRefModifier &&
       (llvm::is_contained(leafs, Directive::OMPD_target_enter_data) ||
           llvm::is_contained(leafs, Directive::OMPD_target_exit_data))) {
+    UnorderedSymbolSet iteratorSymbols;
+    if (iterator) {
+      for (const parser::OmpIteratorSpecifier &iterSpec : iterator->v) {
+        const auto &typeDecl{std::get<parser::TypeDeclarationStmt>(iterSpec.t)};
+        const auto &entities{
+            std::get<std::list<parser::EntityDecl>>(typeDecl.t)};
+        for (const parser::EntityDecl &entity : entities) {
+          const auto &name{std::get<parser::ObjectName>(entity.t)};
+          if (name.symbol)
+            iteratorSymbols.insert(name.symbol->GetUltimate());
+        }
+      }
+    }
+
     for (const parser::OmpObject &object : objects.v) {
+      bool referencesIterator{false};
+      if (const auto *designator{GetDesignatorFromObj(object)}) {
+        OmpIteratorReferenceChecker checker{iteratorSymbols};
+        parser::Walk(*designator, checker);
+        referencesIterator = checker.found();
+      }
+      if (referencesIterator)
+        continue;
+
       if (const Symbol *sym{GetObjectSymbol(object, /*ultimate=*/true)}) {
         if (HasTemporaryStackDescriptor(*sym)) {
           auto maybeSource{GetObjectSource(object)};
diff --git a/flang/lib/Utils/OpenMP.cpp b/flang/lib/Utils/OpenMP.cpp
index aa5ef994252f76..625aaf7c438f05 100644
--- a/flang/lib/Utils/OpenMP.cpp
+++ b/flang/lib/Utils/OpenMP.cpp
@@ -65,7 +65,7 @@ mlir::omp::MapInfoOp createMapInfoOp(mlir::OpBuilder &builder,
     llvm::ArrayRef<mlir::Value> members, mlir::ArrayAttr membersIndex,
     mlir::omp::ClauseMapFlags mapType,
     mlir::omp::VariableCaptureKind mapCaptureType, mlir::Type retTy,
-    bool partialMap, mlir::FlatSymbolRefAttr mapperId) {
+    bool partialMap, mlir::FlatSymbolRefAttr mapperId, mlir::Type varPtrTy) {
 
   auto getPtrVarType = [](mlir::Type ptrType) {
     mlir::TypeAttr varType = mlir::TypeAttr::get(
@@ -85,7 +85,7 @@ mlir::omp::MapInfoOp createMapInfoOp(mlir::OpBuilder &builder,
     retTy = baseAddr.getType();
   }
 
-  auto varPtrType = getPtrVarType(retTy);
+  auto varPtrType = getPtrVarType(varPtrTy ? varPtrTy : retTy);
   auto varPtrPtrTy =
       varPtrPtr ? getPtrVarType(varPtrPtr.getType()) : mlir::TypeAttr{};
 
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
new file mode 100644
index 00000000000000..d22b0708d5d303
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
@@ -0,0 +1,65 @@
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/coarray.f90 2>&1 | \
+! RUN:   FileCheck %t/coarray.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/external-array.f90 2>&1 | \
+! RUN:   FileCheck %t/external-array.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/external-member.f90 2>&1 | \
+! RUN:   FileCheck %t/external-member.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/object-type.f90 2>&1 | \
+! RUN:   FileCheck %t/object-type.f90
+
+! Unsupported iterator locators inside declare mapper.
+
+!--- coarray.f90
+! CHECK: not yet implemented: iterator modifier with locator outside
+! CHECK-SAME: declare mapper variable
+
+module declare_mapper_iterator_coarray
+  integer, save :: a(10)[*]
+  type :: t
+    integer :: x
+  end type
+
+  !$omp declare mapper(mm: t :: v) map(iterator(i = 1:10): a(i)[1])
+end module
+
+!--- external-array.f90
+! CHECK: not yet implemented: iterator modifier with locator outside
+! CHECK-SAME: declare mapper variable
+
+module declare_mapper_external_array
+  integer :: tbl(100)
+  type :: t
+    real :: a(100)
+  end type
+
+  !$omp declare mapper(mm: t :: v) &
+  !$omp& map(iterator(i = 1:100): v%a(i), tbl(i))
+end module
+
+!--- external-member.f90
+! CHECK: not yet implemented: iterator modifier with derived type member map
+
+module declare_mapper_external_member
+  type :: t
+    integer :: a(10)
+  end type
+  type(t) :: w
+
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:10): w%a(i))
+end module
+
+!--- object-type.f90
+! CHECK: not yet implemented: object type not supported by iterator modifier
+
+module declare_mapper_iterator_object_type
+  type :: t
+    complex :: c(10)
+  end type
+
+  !$omp declare mapper(mm: t :: v) map(iterator(i = 1:10): v%c(i)%re)
+end module
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator.f90
deleted file mode 100644
index dacd6d6246595e..00000000000000
--- a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator.f90
+++ /dev/null
@@ -1,11 +0,0 @@
-!RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-!CHECK: Support for iterator modifiers is not implemented yet
-subroutine f(arg)
-  type :: s
-    integer :: a(10)
-  end type
-  type(s) :: arg(:)
-
-  !$omp declare mapper(m: s :: v) map(mapper(m), iterator(i = 1:10): v%a(i))
-end
diff --git a/flang/test/Lower/OpenMP/Todo/from-iterator-modifier.f90 b/flang/test/Lower/OpenMP/Todo/from-iterator-modifier.f90
deleted file mode 100644
index 973d1d1d76ba48..00000000000000
--- a/flang/test/Lower/OpenMP/Todo/from-iterator-modifier.f90
+++ /dev/null
@@ -1,8 +0,0 @@
-!RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-!RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-!CHECK: not yet implemented: Iterator modifier is not supported yet
-subroutine f00(x)
-  integer :: x(10)
-  !$omp target update from(iterator(i = 1:2): x(i))
-end
diff --git a/flang/test/Lower/OpenMP/Todo/map-iterator-ref-attach.f90 b/flang/test/Lower/OpenMP/Todo/map-iterator-ref-attach.f90
new file mode 100644
index 00000000000000..ff16560140b266
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/map-iterator-ref-attach.f90
@@ -0,0 +1,81 @@
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=61 -o - %t/ref-ptr.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=61 -o - %t/ref-ptee.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=61 -o - %t/ref-ptr-ptee.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=61 -o - %t/attach-always.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=61 -o - %t/attach-never.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=61 -o - %t/attach-auto.f90 2>&1 | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=61 \
+! RUN:   -o /dev/null %t/plain-object.f90
+
+! CHECK: not yet implemented: iterator modifier with reference or attach
+! CHECK-SAME: modifier
+
+!--- ref-ptr.f90
+subroutine ref_ptr(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(ref_ptr, iterator(i = 1:10), to: a(i))
+  !$omp end target data
+end subroutine
+
+!--- ref-ptee.f90
+subroutine ref_ptee(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(ref_ptee, iterator(i = 1:10), to: a(i))
+  !$omp end target data
+end subroutine
+
+!--- ref-ptr-ptee.f90
+subroutine ref_ptr_ptee(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(ref_ptr_ptee, iterator(i = 1:10), to: a(i))
+  !$omp end target data
+end subroutine
+
+!--- attach-always.f90
+subroutine attach_always(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(attach(always), iterator(i = 1:10), to: a(i))
+  !$omp end target data
+end subroutine
+
+!--- attach-never.f90
+subroutine attach_never(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(attach(never), iterator(i = 1:10), to: a(i))
+  !$omp end target data
+end subroutine
+
+!--- attach-auto.f90
+subroutine attach_auto(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(attach(auto), iterator(i = 1:10), to: a(i))
+  !$omp end target data
+end subroutine
+
+!--- plain-object.f90
+subroutine plain_object(a)
+  integer, pointer :: a(:)
+  integer :: i
+
+  !$omp target data map(ref_ptr, iterator(i = 1:10), to: a)
+  !$omp end target data
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90 b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
new file mode 100644
index 00000000000000..93f7fb4d1e7837
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
@@ -0,0 +1,77 @@
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/array-member.f90 2>&1 | \
+! RUN:   FileCheck %t/array-member.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/map-member.f90 2>&1 | \
+! RUN:   FileCheck %t/map-member.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/motion-member.f90 2>&1 | \
+! RUN:   FileCheck %t/motion-member.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/map-optional.f90 2>&1 | \
+! RUN:   FileCheck %t/map-optional.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/motion-optional.f90 2>&1 | \
+! RUN:   FileCheck %t/motion-optional.f90
+
+! Unsupported iterator locators in map and motion clauses.
+
+!--- array-member.f90
+! CHECK: not yet implemented: iterator modifier with derived type member map
+
+subroutine map_iterator_array_derived_member
+  type :: t
+    integer :: b
+  end type
+  type(t) :: x(10)
+  integer :: i
+
+  !$omp target data map(iterator(i = 1:10), tofrom: x(i)%b)
+  !$omp end target data
+end subroutine
+
+!--- map-member.f90
+subroutine target_data_derived_member_iterator()
+  type :: s
+    integer :: a(10)
+  end type
+  type(s) :: x
+  integer :: i
+
+  !CHECK: not yet implemented: iterator modifier with derived type member map
+  !$omp target data map(iterator(i = 1:10), tofrom: x%a(i))
+  !$omp end target data
+end subroutine
+
+!--- motion-member.f90
+subroutine target_update_derived_member_iterator()
+  type :: s
+    integer :: a(10)
+  end type
+  type(s) :: x
+  integer :: i
+
+  !CHECK: not yet implemented: iterator modifier with derived type member map
+  !$omp target update to(iterator(i = 1:10): x%a(i))
+end subroutine
+
+!--- map-optional.f90
+subroutine target_enter_data_optional_iterator(a, n)
+  integer, optional, intent(inout) :: a(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  ! CHECK: not yet implemented: iterator modifier with optional locator
+  !$omp target enter data map(iterator(i = 1:n), to: a(i))
+end subroutine
+
+!--- motion-optional.f90
+subroutine target_update_optional_iterator(a, n)
+  integer, allocatable, optional, intent(inout) :: a(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  ! CHECK: not yet implemented: iterator modifier with optional locator
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90 b/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90
new file mode 100644
index 00000000000000..9de14b25e4d606
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90
@@ -0,0 +1,12 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+subroutine target_map_iterator()
+  integer :: a(8)
+  integer :: i
+
+  ! CHECK: not yet implemented: TARGET construct with MAP iterator modifier
+  !$omp target map(iterator(i = 1:8), to: a(i))
+  a(1) = 42
+  !$omp end target
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/to-iterator-modifier.f90 b/flang/test/Lower/OpenMP/Todo/to-iterator-modifier.f90
deleted file mode 100644
index a587373bf183a7..00000000000000
--- a/flang/test/Lower/OpenMP/Todo/to-iterator-modifier.f90
+++ /dev/null
@@ -1,8 +0,0 @@
-!RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-!RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-!CHECK: not yet implemented: Iterator modifier is not supported yet
-subroutine f00(x)
-  integer :: x(10)
-  !$omp target update to(iterator(i = 1:2): x(i))
-end
diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
new file mode 100644
index 00000000000000..19061c3650ecd4
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -0,0 +1,277 @@
+! RUN: rm -rf %t && split-file %s %t
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   %t/maps.f90 -o - | FileCheck %t/maps.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   %t/ranges.f90 -o %t/ranges.mlir
+! RUN: FileCheck %t/ranges.f90 < %t/ranges.mlir
+! RUN: fir-opt --canonicalize --verify-each %t/ranges.mlir | \
+! RUN:   FileCheck %t/ranges.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   -module-dir %t %t/mapper.f90 -o /dev/null
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   -J %t %t/consumer.f90 -o - | FileCheck %t/consumer.f90
+
+!--- maps.f90
+subroutine declare_mapper_nondefault_lb()
+  type :: t
+    integer :: a(-2:7)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = -2:6): v%a(i))
+end
+
+subroutine declare_mapper_alloc_section()
+  type :: t
+    integer, allocatable :: a(:)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:9): v%a(i:i+1))
+end
+
+subroutine declare_mapper_multi()
+  type :: t
+    integer :: a(10)
+    integer :: b(10)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:10): v%a(i), v%b(i))
+end
+
+subroutine declare_mapper_section()
+  type :: t
+    integer :: a(10)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:9): v%a(i:i+1))
+end
+
+subroutine declare_mapper_2d()
+  type :: t
+    integer :: a(4, 6)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:4, j = 1:6): v%a(i, j))
+end
+
+subroutine declare_mapper_pointer()
+  type :: t
+    integer, pointer :: a(:)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:10): v%a(i))
+end
+
+subroutine declare_mapper_allocatable(arg)
+  type :: t
+    integer, allocatable :: a(:)
+  end type
+  type(t) :: arg(:)
+
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:10): v%a(i))
+end
+
+subroutine declare_mapper_iterator(arg)
+  type :: s
+    integer :: a(10)
+  end type
+  type(s) :: arg(:)
+
+  !$omp declare mapper(m: s :: v) map(iterator(i = 1:10): v%a(i))
+end
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_iteratorm
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_iteratorTs{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_iteratorEv")
+! CHECK:   %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:     fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:     %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:     %[[A:.*]] = hlfir.designate %[[DECL]]#0{"a"} {{.*}} : (!fir.ref<!fir.type<_QFdeclare_mapper_iteratorTs{{.*}}>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xi32>>
+! CHECK:     %[[IV_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:     %[[IV_I64:.*]] = fir.convert %[[IV_LD]] : (i32) -> i64
+! CHECK:     %[[IV_IDX:.*]] = fir.convert %[[IV_I64]] : (i64) -> index
+! CHECK:     %[[LB:.*]] = arith.subi %[[IV_IDX]], %{{.*}} : index
+! CHECK:     %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[LB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[A]] : !fir.ref<!fir.array<10xi32>>, !fir.array<10xi32>) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:     omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_allocatablem
+! CHECK: ^bb0(%[[ARG2:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_allocatableTt{{.*}}>):
+! CHECK:   %[[DECL2:.*]]:2 = hlfir.declare %[[ARG2]] uniq_name("_QFdeclare_mapper_allocatableEv")
+! CHECK:   %[[IT2:.*]] = omp.iterator(%[[IV2:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:     %[[BOX_REF:.*]] = hlfir.designate %[[DECL2]]#0{"a"}{{.*}} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK:     %[[BOX:.*]] = fir.load %[[BOX_REF]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK:     %[[DIMS0:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.heap<!fir.array<?xi32>>>, index) -> (index, index, index)
+! CHECK:     %[[DIMS1:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.heap<!fir.array<?xi32>>>, index) -> (index, index, index)
+! CHECK:     %[[BOUNDS2:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS1]]#1 : index) stride(%[[DIMS1]]#2 : index) start_idx(%[[DIMS0]]#0 : index) stride_in_bytes(true)
+! CHECK:     %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.heap<!fir.array<?xi32>>>) -> !fir.heap<!fir.array<?xi32>>
+! CHECK:     %[[MAP2:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?xi32>>, i32) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS2]]) name("") -> !llvm.ptr
+! CHECK:     omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT2]] : !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_pointerm
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_pointerTt{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_pointerEv")
+! CHECK:   %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}}) {
+! CHECK:     %[[BOX_REF:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
+! CHECK:     %[[BOX:.*]] = fir.load %[[BOX_REF]] : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
+! CHECK:     %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.ptr<!fir.array<?xi32>>>) -> !fir.ptr<!fir.array<?xi32>>
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.ptr<!fir.array<?xi32>>, i32) map_clauses(tofrom) capture(ByRef) bounds(%{{.*}}) name("") -> !llvm.ptr
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_2dm
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_2dTt{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_2dEv")
+! CHECK:   %[[I_LB:.*]] = arith.constant 1 : i32
+! CHECK:   %[[I_UB:.*]] = arith.constant 4 : i32
+! CHECK:   %[[I_STEP:.*]] = arith.constant 1 : i32
+! CHECK:   %[[J_LB:.*]] = arith.constant 1 : i32
+! CHECK:   %[[J_UB:.*]] = arith.constant 6 : i32
+! CHECK:   %[[J_STEP:.*]] = arith.constant 1 : i32
+! CHECK:   %[[IT:.*]] = omp.iterator(%[[IV_I:.*]]: i32,
+! CHECK-SAME: %[[IV_J:.*]]: i32) =
+! CHECK-SAME: (%[[I_LB]] to %[[I_UB]] step %[[I_STEP]],
+! CHECK-SAME: %[[J_LB]] to %[[J_UB]] step %[[J_STEP]]) {
+! CHECK:     fir.store %[[IV_I]] to %[[IV_I_MEM:.*]] : !fir.ref<i32>
+! CHECK:     %[[IV_I_DECL:.*]]:2 = hlfir.declare %[[IV_I_MEM]]
+! CHECK:     fir.store %[[IV_J]] to %[[IV_J_MEM:.*]] : !fir.ref<i32>
+! CHECK:     %[[IV_J_DECL:.*]]:2 = hlfir.declare %[[IV_J_MEM]]
+! CHECK:     %[[EXT_I:.*]] = arith.constant 4 : index
+! CHECK:     %[[EXT_J:.*]] = arith.constant 6 : index
+! CHECK:     %[[A:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.ref<!fir.array<4x6xi32>>
+! CHECK:     %[[IV_I_LD:.*]] = fir.load %[[IV_I_DECL]]#0
+! CHECK-SAME: !fir.ref<i32>
+! CHECK:     %[[IV_I_I64:.*]] = fir.convert %[[IV_I_LD]]
+! CHECK-SAME: (i32) -> i64
+! CHECK:     %[[IV_I_IDX:.*]] = fir.convert %[[IV_I_I64]]
+! CHECK-SAME: (i64) -> index
+! CHECK:     %[[LOC_I:.*]] = arith.subi %[[IV_I_IDX]], %{{.*}} : index
+! CHECK:     %[[B_I:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[LOC_I]] : index)
+! CHECK-SAME: upper_bound(%[[LOC_I]] : index)
+! CHECK-SAME: extent(%[[EXT_I]] : index)
+! CHECK:     %[[IV_J_LD:.*]] = fir.load %[[IV_J_DECL]]#0
+! CHECK-SAME: !fir.ref<i32>
+! CHECK:     %[[IV_J_I64:.*]] = fir.convert %[[IV_J_LD]]
+! CHECK-SAME: (i32) -> i64
+! CHECK:     %[[IV_J_IDX:.*]] = fir.convert %[[IV_J_I64]]
+! CHECK-SAME: (i64) -> index
+! CHECK:     %[[LOC_J:.*]] = arith.subi %[[IV_J_IDX]], %{{.*}} : index
+! CHECK:     %[[B_J:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[LOC_J]] : index)
+! CHECK-SAME: upper_bound(%[[LOC_J]] : index)
+! CHECK-SAME: extent(%[[EXT_J]] : index)
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]
+! CHECK-SAME: : !fir.ref<!fir.array<4x6xi32>>,
+! CHECK-SAME: !fir.array<4x6xi32>) map_clauses(tofrom)
+! CHECK-SAME: capture(ByRef) bounds(%[[B_I]], %[[B_J]])
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_sectionm
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_sectionTt{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_sectionEv")
+! CHECK:   %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}}) {
+! CHECK:     %[[A:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.ref<!fir.array<10xi32>>
+! CHECK:     %{{.*}} = arith.addi %{{.*}}, %{{.*}} : i32
+! CHECK:     %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[A]] : !fir.ref<!fir.array<10xi32>>, !fir.array<10xi32>) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_multim
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_multiTt{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_multiEv")
+! CHECK:   %[[IT_A:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}}) {
+! CHECK:     %[[A:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.ref<!fir.array<10xi32>>
+! CHECK:     %[[MAP_A:.*]] = omp.map.info var_ptr(%[[A]] : !fir.ref<!fir.array<10xi32>>, !fir.array<10xi32>) map_clauses(tofrom) capture(ByRef) bounds(%{{.*}}) name("") -> !llvm.ptr
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   %[[IT_B:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}}) {
+! CHECK:     %[[B:.*]] = hlfir.designate %[[DECL]]#0{"b"}{{.*}} -> !fir.ref<!fir.array<10xi32>>
+! CHECK:     %[[MAP_B:.*]] = omp.map.info var_ptr(%[[B]] : !fir.ref<!fir.array<10xi32>>, !fir.array<10xi32>) map_clauses(tofrom) capture(ByRef) bounds(%{{.*}}) name("") -> !llvm.ptr
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT_A]], %[[IT_B]] : !omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_alloc_sectionm
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_alloc_sectionTt{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_alloc_sectionEv")
+! CHECK:   %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}}) {
+! CHECK:     %[[BOX_REF:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK:     %[[BOX:.*]] = fir.load %[[BOX_REF]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK:     %{{.*}} = arith.addi %{{.*}}, %{{.*}} : i32
+! CHECK:     %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index) stride_in_bytes(true)
+! CHECK:     %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.heap<!fir.array<?xi32>>>) -> !fir.heap<!fir.array<?xi32>>
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?xi32>>, i32) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_nondefault_lbm
+! CHECK: ^bb0(%[[ARG:.*]]: !fir.ref<!fir.type<_QFdeclare_mapper_nondefault_lbTt{{.*}}>):
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdeclare_mapper_nondefault_lbEv")
+! CHECK:   %[[LB:.*]] = arith.constant -2 : i32
+! CHECK:   %[[UB:.*]] = arith.constant 6 : i32
+! CHECK:   %[[STEP:.*]] = arith.constant 1 : i32
+! CHECK:   %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) =
+! CHECK-SAME: (%[[LB]] to %[[UB]] step %[[STEP]]) {
+! CHECK:     fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:     %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:     %[[EXTENT:.*]] = arith.constant 10 : index
+! CHECK:     %[[START:.*]] = arith.constant -2 : index
+! CHECK:     %[[SHAPE:.*]] = fir.shape_shift %[[START]], %[[EXTENT]]
+! CHECK:     %[[BOX:.*]] = hlfir.designate %[[DECL]]#0{"a"}
+! CHECK-SAME: shape %[[SHAPE]]
+! CHECK-SAME: -> !fir.box<!fir.array<10xi32>>
+! CHECK:     %[[DIM:.*]] = arith.constant 0 : index
+! CHECK:     %[[DIMS:.*]]:3 = fir.box_dims %[[BOX]], %[[DIM]]
+! CHECK:     %[[IV_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:     %[[IV_I64:.*]] = fir.convert %[[IV_LD]] : (i32) -> i64
+! CHECK:     %[[IV_IDX:.*]] = fir.convert %[[IV_I64]] : (i64) -> index
+! CHECK:     %[[OFFSET:.*]] = arith.subi %[[IV_IDX]], %[[START]] : index
+! CHECK:     %[[BOUNDS:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[OFFSET]] : index)
+! CHECK-SAME: upper_bound(%[[OFFSET]] : index)
+! CHECK-SAME: extent(%[[DIMS]]#1 : index)
+! CHECK-SAME: stride(%[[DIMS]]#2 : index)
+! CHECK-SAME: start_idx(%[[START]] : index)
+! CHECK-SAME: stride_in_bytes(true)
+! CHECK:     %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.array<10xi32>>) -> !fir.ref<!fir.array<10xi32>>
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.ref<!fir.array<10xi32>>, !fir.array<10xi32>) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+!--- ranges.f90
+subroutine mapper_ranges()
+  type :: t
+    integer :: a(4), b(4)
+  end type
+  !$omp declare mapper(m: t :: v) &
+  !$omp& map(iterator(i=1:4, j=2:1): v%a(i), v%b(j))
+end
+
+! CHECK-LABEL: omp.declare_mapper @_QQFmapper_rangesm
+! CHECK: %[[A:.*]] = omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.map.info
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[B:.*]] = omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.map.info
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.declare_mapper.info
+! CHECK-SAME: map_iterated(%[[A]], %[[B]]
+
+!--- mapper.f90
+module iterator_mapper_mod
+  type :: t
+    integer :: a(10)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:10): v%a(i))
+end module
+
+!--- consumer.f90
+subroutine use_iterator_mapper_mod
+  use iterator_mapper_mod
+  integer :: k
+  k = 1
+end subroutine
+
+! CHECK-LABEL: omp.declare_mapper @_QQMiterator_mapper_modm
+! CHECK: %[[ITER:.*]] = omp.iterator
+! CHECK: %[[MAP:.*]] = omp.map.info
+! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: omp.declare_mapper.info map_iterated(%[[ITER]]
diff --git a/flang/test/Lower/OpenMP/depend-iterator.f90 b/flang/test/Lower/OpenMP/depend-iterator.f90
index 275aebe09aa4a8..b3388fcf7cacc5 100644
--- a/flang/test/Lower/OpenMP/depend-iterator.f90
+++ b/flang/test/Lower/OpenMP/depend-iterator.f90
@@ -44,7 +44,7 @@ subroutine depend_per_locator(m)
 ! CHECK: %[[PTR_I:.*]] = fir.convert %[[COOR_I]]
 ! CHECK-SAME: (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK: omp.yield(%[[PTR_I]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT_J:.*]] = omp.iterator(%[[IV_J:.*]]: i32) =
 ! CHECK-SAME: (%[[J_LB]] to %[[J_UB]] step %[[J_STEP]]) {
 ! CHECK: fir.store %[[IV_J]] to %[[MEM_J:.*]] : !fir.ref<i32>
@@ -55,7 +55,7 @@ subroutine depend_per_locator(m)
 ! CHECK: %[[PTR_J:.*]] = fir.convert %[[COOR_J]]
 ! CHECK-SAME: (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK: omp.yield(%[[PTR_J]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK-NOT: omp.iterator
 ! CHECK: omp.task depend(taskdependin -> %[[C]]#0 : !fir.ref<i32>,
 ! CHECK-SAME: taskdependin -> %[[IT_I]] : !omp.iterated<!llvm.ptr>,
@@ -104,7 +104,7 @@ subroutine task_depend_iterator_simple()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IV_I64]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 ! CHECK:   omp.terminator
 ! CHECK: }
@@ -131,7 +131,7 @@ subroutine task_depend_iterator_2d()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %{{.*}} (%[[IV0_I64]], %[[IV1_I64]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependinout -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 
 subroutine task_depend_iterator_mixed()
@@ -148,7 +148,7 @@ subroutine task_depend_iterator_mixed()
 ! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFtask_depend_iterator_mixedEx")
 ! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependout -> %[[X]]#0 : !fir.ref<i32>, taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 
 subroutine task_depend_iterator_step()
@@ -166,7 +166,7 @@ subroutine task_depend_iterator_step()
 ! CHECK: %[[STEP:.*]] = arith.constant 2 : i32
 ! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 
 subroutine task_depend_iterator_multi_obj()
@@ -185,12 +185,12 @@ subroutine task_depend_iterator_multi_obj()
 ! CHECK:   %[[COOR1:.*]] = hlfir.designate %[[A]]#0 (%{{.*}})
 ! CHECK:   %[[PTR1:.*]] = fir.convert %[[COOR1]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR1]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT2:.*]] = omp.iterator(%[[IV2:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR2:.*]] = hlfir.designate %[[B]]#0 (%{{.*}})
 ! CHECK:   %[[PTR2:.*]] = fir.convert %[[COOR2]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR2]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependinout -> %[[IT1]] : !omp.iterated<!llvm.ptr>, taskdependinout -> %[[IT2]] : !omp.iterated<!llvm.ptr>) {
 
 ! Expression-based subscript using multiple iterator variables: a((i-1)*m+j)
@@ -223,7 +223,7 @@ subroutine task_depend_iterator_expr_subscript()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IDX]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependout -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 
 ! Multiple depend clauses each with their own iterator on the same task.
@@ -244,12 +244,12 @@ subroutine task_depend_multi_iter_clauses()
 ! CHECK:   %[[COOR1:.*]] = hlfir.designate %[[A]]#0 (%{{.*}})
 ! CHECK:   %[[PTR1:.*]] = fir.convert %[[COOR1]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR1]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT2:.*]] = omp.iterator(%[[IV2:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR2:.*]] = hlfir.designate %[[B]]#0 (%{{.*}})
 ! CHECK:   %[[PTR2:.*]] = fir.convert %[[COOR2]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR2]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependin -> %[[IT1]] : !omp.iterated<!llvm.ptr>, taskdependout -> %[[IT2]] : !omp.iterated<!llvm.ptr>) {
 
 subroutine task_depend_iterator_negative_step()
@@ -267,7 +267,7 @@ subroutine task_depend_iterator_negative_step()
 ! CHECK: %[[STEP:.*]] = arith.constant -1 : i32
 ! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 
 ! Mixed iterated and non-iterated objects in the same depend clause:
@@ -289,7 +289,7 @@ subroutine task_depend_iterator_mixed_within_clause()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%{{.*}})
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.task depend(taskdependin -> %[[A1]] : !fir.ref<i32>, taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) {
 
 !===============================================================================
@@ -316,7 +316,7 @@ subroutine target_depend_iterator()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IV_I64]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(tofrom) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: omp.target kernel_type(generic) depend(taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP]] -> %{{.*}} : !fir.ref<!fir.array<16xi32>>) {
 ! CHECK:   omp.terminator
@@ -341,15 +341,15 @@ subroutine target_depend_iterator_multi()
 ! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR1:.*]] = hlfir.designate %[[A]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR2:.*]] = hlfir.designate %[[B]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT3:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR3:.*]] = hlfir.designate %[[C]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP_A:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(tofrom) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<8xi32>>
 ! CHECK: %[[MAP_B:.*]] = omp.map.info var_ptr(%[[B]]#1 : {{.*}}) map_clauses(tofrom) capture(ByRef) bounds({{.*}}) name("b") -> !fir.ref<!fir.array<8xi32>>
 ! CHECK: %[[MAP_C:.*]] = omp.map.info var_ptr(%[[C]]#1 : {{.*}}) map_clauses(implicit, tofrom) capture(ByRef) bounds({{.*}}) name("c") -> !fir.ref<!fir.array<8xi32>>
@@ -377,7 +377,7 @@ subroutine target_enter_data_depend_iterator()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IV_I64]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(to) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: omp.target_enter_data depend(taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP]] : !fir.ref<!fir.array<16xi32>>)
 
@@ -408,7 +408,7 @@ subroutine target_enter_data_depend_iterator_expr()
 ! CHECK:   %[[IDX:.*]] = fir.convert %[[ADD]] : (i32) -> i64
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IDX]])
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(to) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: omp.target_enter_data depend(taskdependinout -> %[[A1]] : !fir.ref<i32>, taskdependinout -> %[[IT]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP]] : !fir.ref<!fir.array<16xi32>>)
 
@@ -434,7 +434,7 @@ subroutine target_exit_data_depend_iterator()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IV_I64]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(from) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: omp.target_exit_data depend(taskdependout -> %[[IT]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP]] : !fir.ref<!fir.array<16xi32>>)
 
@@ -456,11 +456,11 @@ subroutine target_exit_data_depend_iterator_multi()
 ! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
 ! CHECK:   %[[COOR1:.*]] = hlfir.designate %[[A]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
 ! CHECK:   %[[COOR2:.*]] = hlfir.designate %[[B]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP_A:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(from) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: %[[MAP_B:.*]] = omp.map.info var_ptr(%[[B]]#1 : {{.*}}) map_clauses(from) capture(ByRef) bounds({{.*}}) name("b") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: omp.target_exit_data depend(taskdependout -> %[[IT1]] : !omp.iterated<!llvm.ptr>, taskdependout -> %[[IT2]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP_A]], %[[MAP_B]] : !fir.ref<!fir.array<16xi32>>, !fir.ref<!fir.array<16xi32>>)
@@ -487,7 +487,7 @@ subroutine target_update_depend_iterator()
 ! CHECK:   %[[COOR:.*]] = hlfir.designate %[[A]]#0 (%[[IV_I64]])
 ! CHECK:   %[[PTR:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !llvm.ptr
 ! CHECK:   omp.yield(%[[PTR]] : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(to) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<16xi32>>
 ! CHECK: omp.target_update depend(taskdependin -> %[[IT]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP]] : !fir.ref<!fir.array<16xi32>>)
 
@@ -511,11 +511,11 @@ subroutine target_update_depend_iterator_multi()
 ! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR1:.*]] = hlfir.designate %[[A]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
 ! CHECK:   %[[COOR2:.*]] = hlfir.designate %[[B]]#0 (%{{.*}})
 ! CHECK:   omp.yield(%{{.*}} : !llvm.ptr)
-! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK: %[[MAP_A:.*]] = omp.map.info var_ptr(%[[A]]#1 : {{.*}}) map_clauses(to) capture(ByRef) bounds({{.*}}) name("a") -> !fir.ref<!fir.array<8xi32>>
 ! CHECK: %[[MAP_B:.*]] = omp.map.info var_ptr(%[[B]]#1 : {{.*}}) map_clauses(from) capture(ByRef) bounds({{.*}}) name("b") -> !fir.ref<!fir.array<8xi32>>
 ! CHECK: omp.target_update depend(taskdependinout -> %[[X]]#0 : !fir.ref<i32>, taskdependin -> %[[IT1]] : !omp.iterated<!llvm.ptr>, taskdependout -> %[[IT2]] : !omp.iterated<!llvm.ptr>) map_entries(%[[MAP_A]], %[[MAP_B]] : !fir.ref<!fir.array<8xi32>>, !fir.ref<!fir.array<8xi32>>)
diff --git a/flang/test/Lower/OpenMP/iterator-ranges.f90 b/flang/test/Lower/OpenMP/iterator-ranges.f90
new file mode 100644
index 00000000000000..ebe576f99997b5
--- /dev/null
+++ b/flang/test/Lower/OpenMP/iterator-ranges.f90
@@ -0,0 +1,160 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o %t
+! RUN: FileCheck %s < %t
+! RUN: fir-opt --canonicalize --verify-each %t | FileCheck %s --check-prefix=CAN
+
+! Fortran endpoints are inclusive, including singleton and descending ranges.
+subroutine singleton(a)
+  integer :: a(4)
+  !$omp target update to(iterator(i=1:1): a(i))
+end
+! CHECK-LABEL: func.func @_QPsingleton
+! CHECK: omp.iterator
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CAN-LABEL: func.func @_QPsingleton
+! CAN: %[[ONE:.*]] = arith.constant 1 : i32
+! CAN: omp.iterator(%{{.*}}: i32) =
+! CAN-SAME: (%[[ONE]] to %[[ONE]] step %[[ONE]])
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+
+subroutine ascending(a)
+  integer :: a(4)
+  !$omp target enter data map(iterator(i=1:4), to: a(i))
+end
+! CHECK-LABEL: func.func @_QPascending
+! CHECK: omp.iterator
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CAN-LABEL: func.func @_QPascending
+! CAN-DAG: %[[ONE:.*]] = arith.constant 1 : i32
+! CAN-DAG: %[[FOUR:.*]] = arith.constant 4 : i32
+! CAN: omp.iterator(%{{.*}}: i32) =
+! CAN-SAME: (%[[ONE]] to %[[FOUR]] step %[[ONE]])
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+
+subroutine descending(a)
+  integer :: a(4)
+  !$omp target exit data map(iterator(i=4:1:-1), from: a(i))
+end
+! CHECK-LABEL: func.func @_QPdescending
+! CHECK: omp.iterator
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CAN-LABEL: func.func @_QPdescending
+! CAN-DAG: %[[ONE:.*]] = arith.constant 1 : i32
+! CAN-DAG: %[[FOUR:.*]] = arith.constant 4 : i32
+! CAN-DAG: %[[NEG:.*]] = arith.constant -1 : i32
+! CAN: omp.iterator(%{{.*}}: i32) =
+! CAN-SAME: (%[[FOUR]] to %[[ONE]] step %[[NEG]])
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+
+! Empty ranges must survive canonicalization and verification. The containing
+! directive and its unrelated map entry must remain present.
+subroutine empty_positive(a, b)
+  integer :: a(4), b
+  !$omp target update to(iterator(i=2:1): a(i), b) nowait
+end
+! CHECK-LABEL: func.func @_QPempty_positive
+! CHECK: omp.iterator
+! CAN-LABEL: func.func @_QPempty_positive
+! CAN-DAG: %[[ONE:.*]] = arith.constant 1 : i32
+! CAN-DAG: %[[TWO:.*]] = arith.constant 2 : i32
+! CAN: omp.iterator(%{{.*}}: i32) =
+! CAN-SAME: (%[[TWO]] to %[[ONE]] step %[[ONE]])
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+! CAN: omp.target_update
+! CAN-SAME: map_entries(
+! CAN-SAME: map_iterated(
+! CAN-SAME: nowait
+
+subroutine empty_negative(a)
+  integer :: a(4)
+  !$omp target update from(iterator(i=1:2:-1): a(i))
+end
+! CHECK-LABEL: func.func @_QPempty_negative
+! CHECK: omp.iterator
+! CAN-LABEL: func.func @_QPempty_negative
+! CAN-DAG: %[[ONE:.*]] = arith.constant 1 : i32
+! CAN-DAG: %[[TWO:.*]] = arith.constant 2 : i32
+! CAN-DAG: %[[NEG:.*]] = arith.constant -1 : i32
+! CAN: omp.iterator(%{{.*}}: i32) =
+! CAN-SAME: (%[[ONE]] to %[[TWO]] step %[[NEG]])
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+
+subroutine dynamic_range(a, lo, hi, step)
+  integer :: a(4), lo, hi, step
+  !$omp target data map(iterator(i=lo:hi:step), to: a(i))
+  !$omp end target data
+end
+! CHECK-LABEL: func.func @_QPdynamic_range
+! CHECK: omp.iterator
+! CAN-LABEL: func.func @_QPdynamic_range
+! CAN: omp.iterator
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+! CAN: omp.target_data
+! CAN-SAME: map_iterated(
+
+! Each locator uses only its own dimensions. An unused empty range cannot
+! suppress a(i), and unrelated range expressions are still evaluated.
+subroutine separate_ranges(a, b, c, n)
+  integer :: a(4), b(4), c(4, 4), n
+  integer, external :: extent
+  !$omp target update to(iterator(i=1:4, j=1:n, k=1:extent()): &
+  !$omp& a(i), b(j), c(i,j))
+end
+! CHECK-LABEL: func.func @_QPseparate_ranges
+! CHECK: fir.call @_QPextent()
+! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.iterator(%{{[^:]+}}: i32, %{{[^:]+}}: i32) =
+! CHECK: omp.target_update
+! CAN-LABEL: func.func @_QPseparate_ranges
+! CAN: fir.call @_QPextent()
+! CAN: omp.iterator(%{{[^:]+}}: i32) =
+! CAN: omp.iterator(%{{[^:]+}}: i32) =
+! CAN: omp.iterator(%{{[^:]+}}: i32, %{{[^:]+}}: i32) =
+! CAN: omp.target_update
+
+subroutine unused_empty_range(a)
+  integer :: a(4)
+  !$omp target enter data map(iterator(i=1:4, j=1:0), to: a(i))
+end
+! CHECK-LABEL: func.func @_QPunused_empty_range
+! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CAN-LABEL: func.func @_QPunused_empty_range
+! CAN-DAG: %[[ONE:.*]] = arith.constant 1 : i32
+! CAN-DAG: %[[FOUR:.*]] = arith.constant 4 : i32
+! CAN: omp.iterator(%{{[^:]+}}: i32) =
+! CAN-SAME: (%[[ONE]] to %[[FOUR]] step %[[ONE]])
+
+! Values beyond the index width must reach the iterator and its body intact.
+subroutine wide_range(a, hi, step)
+  integer :: a(2)
+  integer(16) :: hi, step
+  !$omp target update to(iterator(integer(16) :: i=0_16:hi:step): &
+  !$omp& a(int(i/step)+1))
+end
+! CHECK-LABEL: func.func @_QPwide_range
+! CHECK: %[[HI:.*]] = fir.load {{.*}} : !fir.ref<i128>
+! CHECK: %[[STEP:.*]] = fir.load {{.*}} : !fir.ref<i128>
+! CHECK-NOT: fir.convert
+! CHECK: omp.iterator(%[[IV:.*]]: i128) =
+! CHECK-SAME: ({{.*}} to %[[HI]] step %[[STEP]])
+! CHECK: fir.store %[[IV]] to {{.*}} : !fir.ref<i128>
+! CHECK: arith.divsi {{.*}} : i128
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CAN-LABEL: func.func @_QPwide_range
+! CAN: omp.iterator(%{{.*}}: i128) =
+! CAN: arith.divsi {{.*}} : i128
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
+
+subroutine wide_default_step(a, hi)
+  integer :: a(2)
+  integer(16) :: hi
+  !$omp target enter data map(iterator(integer(16) :: i=hi:hi), &
+  !$omp& to: a(int(i-hi)+1))
+end
+! CHECK-LABEL: func.func @_QPwide_default_step
+! CHECK: %[[ONE:.*]] = arith.constant 1 : i128
+! CHECK: omp.iterator(%{{.*}}: i128) =
+! CHECK-SAME: ({{.*}} to {{.*}} step %[[ONE]])
+! CAN-LABEL: func.func @_QPwide_default_step
+! CAN: omp.iterator(%{{.*}}: i128) =
+! CAN: } inclusive -> !omp.iterated<!llvm.ptr>
diff --git a/flang/test/Lower/OpenMP/motion-iterator.f90 b/flang/test/Lower/OpenMP/motion-iterator.f90
new file mode 100644
index 00000000000000..8c5b50cf07ced9
--- /dev/null
+++ b/flang/test/Lower/OpenMP/motion-iterator.f90
@@ -0,0 +1,910 @@
+! RUN: split-file %s %t
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   %t/motion.f90 -o - | FileCheck %t/motion.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   %t/descriptors.f90 -o - | FileCheck %t/descriptors.f90 \
+! RUN:   --implicit-check-not=fir.box_offset \
+! RUN:   --implicit-check-not=map_entries \
+! RUN:   --implicit-check-not='map_clauses(attach'
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   %t/implicit-mapper.f90 -o - | FileCheck %t/implicit-mapper.f90
+
+!--- motion.f90
+! Tests for the iterator modifier on map and to/from motion clauses.
+
+!===============================================================================
+! target update
+!===============================================================================
+
+subroutine target_update_to_simple()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_to_simple()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_to_simpleEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:   %[[IV_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[IV_I64:.*]] = fir.convert %[[IV_LD]] : (i32) -> i64
+! CHECK:   %[[IV_IDX:.*]] = fir.convert %[[IV_I64]] : (i64) -> index
+! CHECK:   %[[LB:.*]] = arith.subi %[[IV_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[LB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_from_simple()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target update from(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_from_simple()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_from_simpleEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(from) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_assumed_shape(a, n)
+  integer, intent(in) :: n
+  real :: a(:)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_assumed_shape
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_assumed_shapeEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[DIMS:.*]]:3 = fir.box_dims %[[A]]#0, %{{.*}} : (!fir.box<!fir.array<?xf32>>, index) -> (index, index, index)
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS]]#1 : index) stride(%[[DIMS]]#2 : index) start_idx(%{{.*}} : index) stride_in_bytes(true)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%{{.*}} : !fir.ref<!fir.array<?xf32>>, f32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_assumed_shape_2d(a, n, m)
+  integer, intent(in) :: n, m
+  real :: a(:, :)
+  integer :: i, j
+
+  !$omp target update to(iterator(i = 1:n, j = 1:m): a(i, j))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_assumed_shape_2d
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_assumed_shape_2dEa")
+! CHECK: %[[M:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 3
+! CHECK-SAME: uniq_name("_QFtarget_update_assumed_shape_2dEm")
+! CHECK: %[[N:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 2
+! CHECK-SAME: uniq_name("_QFtarget_update_assumed_shape_2dEn")
+! CHECK: %[[N_LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[N_UB:.*]] = fir.load %[[N]]#0 : !fir.ref<i32>
+! CHECK: %[[N_STEP:.*]] = arith.constant 1 : i32
+! CHECK: %[[M_LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[M_UB:.*]] = fir.load %[[M]]#0 : !fir.ref<i32>
+! CHECK: %[[M_STEP:.*]] = arith.constant 1 : i32
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV0:.*]]: i32,
+! CHECK-SAME: %[[IV1:.*]]: i32) =
+! CHECK-SAME: (%[[N_LB]] to %[[N_UB]] step %[[N_STEP]],
+! CHECK-SAME: %[[M_LB]] to %[[M_UB]] step %[[M_STEP]]) {
+! CHECK: fir.store %[[IV0]] to %[[IV0_ADDR:.*]] : !fir.ref<i32>
+! CHECK: %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_ADDR]]
+! CHECK: fir.store %[[IV1]] to %[[IV1_ADDR:.*]] : !fir.ref<i32>
+! CHECK: %[[IV1_DECL:.*]]:2 = hlfir.declare %[[IV1_ADDR]]
+! CHECK: %[[START0:.*]] = arith.constant 1 : index
+! CHECK: %[[DIM0:.*]] = arith.constant 0 : index
+! CHECK: %[[DIMS0:.*]]:3 = fir.box_dims %[[A]]#0, %[[DIM0]]
+! CHECK: %[[IV0_LOAD:.*]] = fir.load %[[IV0_DECL]]#0
+! CHECK: %[[IV0_I64:.*]] = fir.convert %[[IV0_LOAD]] : (i32) -> i64
+! CHECK: %[[IV0_INDEX:.*]] = fir.convert %[[IV0_I64]] : (i64) -> index
+! CHECK: %[[INDEX0:.*]] = arith.subi %[[IV0_INDEX]], %[[START0]]
+! CHECK: %[[BOUNDS0:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[INDEX0]] : index)
+! CHECK-SAME: upper_bound(%[[INDEX0]] : index)
+! CHECK-SAME: extent(%[[DIMS0]]#1 : index)
+! CHECK-SAME: stride(%[[DIMS0]]#2 : index)
+! CHECK-SAME: start_idx(%[[START0]] : index)
+! CHECK-SAME: stride_in_bytes(true)
+! CHECK: %[[START1:.*]] = arith.constant 1 : index
+! CHECK: %[[DIM1:.*]] = arith.constant 1 : index
+! CHECK: %[[DIMS1:.*]]:3 = fir.box_dims %[[A]]#0, %[[DIM1]]
+! CHECK: %[[IV1_LOAD:.*]] = fir.load %[[IV1_DECL]]#0
+! CHECK: %[[IV1_I64:.*]] = fir.convert %[[IV1_LOAD]] : (i32) -> i64
+! CHECK: %[[IV1_INDEX:.*]] = fir.convert %[[IV1_I64]] : (i64) -> index
+! CHECK: %[[INDEX1:.*]] = arith.subi %[[IV1_INDEX]], %[[START1]]
+! CHECK: %[[BOUNDS1:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[INDEX1]] : index)
+! CHECK-SAME: upper_bound(%[[INDEX1]] : index)
+! CHECK-SAME: extent(%[[DIMS1]]#1 : index)
+! CHECK-SAME: stride(%[[DIMS1]]#2 : index)
+! CHECK-SAME: start_idx(%[[START1]] : index)
+! CHECK-SAME: stride_in_bytes(true)
+! CHECK: %[[BASE:.*]] = fir.box_addr %[[A]]#0
+! CHECK: %[[MAP:.*]] = omp.map.info
+! CHECK-SAME: var_ptr(%[[BASE]] : !fir.ref<!fir.array<?x?xf32>>, f32)
+! CHECK-SAME: map_clauses(to)
+! CHECK-SAME: capture(ByRef)
+! CHECK-SAME: bounds(%[[BOUNDS0]], %[[BOUNDS1]])
+! CHECK-SAME: name("")
+! CHECK-SAME: -> !llvm.ptr
+! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_allocatable(a, n)
+  integer, allocatable :: a(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_allocatable
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_allocatableEa") fortran_attrs<allocatable>
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK:   %[[DIMS0:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.heap<!fir.array<?xi32>>>, index) -> (index, index, index)
+! CHECK:   %[[DIMS1:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.heap<!fir.array<?xi32>>>, index) -> (index, index, index)
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS1]]#1 : index) stride(%[[DIMS1]]#2 : index) start_idx(%[[DIMS0]]#0 : index) stride_in_bytes(true)
+! CHECK:   %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.heap<!fir.array<?xi32>>>) -> !fir.heap<!fir.array<?xi32>>
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?xi32>>, i32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_pointer(a, n)
+  integer, pointer :: a(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_pointer
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_pointerEa") fortran_attrs<pointer>
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>
+! CHECK:   %[[DIMS0:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.ptr<!fir.array<?xi32>>>, index) -> (index, index, index)
+! CHECK:   %[[DIMS1:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.ptr<!fir.array<?xi32>>>, index) -> (index, index, index)
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS1]]#1 : index) stride(%[[DIMS1]]#2 : index) start_idx(%[[DIMS0]]#0 : index) stride_in_bytes(true)
+! CHECK:   %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.ptr<!fir.array<?xi32>>>) -> !fir.ptr<!fir.array<?xi32>>
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.ptr<!fir.array<?xi32>>, i32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_allocatable_2d(a, n, m)
+  integer, allocatable :: a(:, :)
+  integer, intent(in) :: n, m
+  integer :: i, j
+
+  !$omp target update to(iterator(i = 1:n, j = 1:m): a(i, j))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_allocatable_2d
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_allocatable_2dEa") fortran_attrs<allocatable>
+! CHECK: %[[M:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 3
+! CHECK-SAME: uniq_name("_QFtarget_update_allocatable_2dEm")
+! CHECK: %[[N:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 2
+! CHECK-SAME: uniq_name("_QFtarget_update_allocatable_2dEn")
+! CHECK: %[[N_LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[N_UB:.*]] = fir.load %[[N]]#0 : !fir.ref<i32>
+! CHECK: %[[N_STEP:.*]] = arith.constant 1 : i32
+! CHECK: %[[M_LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[M_UB:.*]] = fir.load %[[M]]#0 : !fir.ref<i32>
+! CHECK: %[[M_STEP:.*]] = arith.constant 1 : i32
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV0:.*]]: i32,
+! CHECK-SAME: %[[IV1:.*]]: i32) =
+! CHECK-SAME: (%[[N_LB]] to %[[N_UB]] step %[[N_STEP]],
+! CHECK-SAME: %[[M_LB]] to %[[M_UB]] step %[[M_STEP]]) {
+! CHECK: fir.store %[[IV0]] to %[[IV0_ADDR:.*]] : !fir.ref<i32>
+! CHECK: %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_ADDR]]
+! CHECK: fir.store %[[IV1]] to %[[IV1_ADDR:.*]] : !fir.ref<i32>
+! CHECK: %[[IV1_DECL:.*]]:2 = hlfir.declare %[[IV1_ADDR]]
+! CHECK: %[[BOX:.*]] = fir.load %[[A]]#0
+! CHECK: %[[C0:.*]] = arith.constant 0 : index
+! CHECK: %[[DIMS0_LB:.*]]:3 = fir.box_dims %[[BOX]], %[[C0]]
+! CHECK: %[[C0_EXTENT:.*]] = arith.constant 0 : index
+! CHECK: %[[DIMS0_EXTENT:.*]]:3 = fir.box_dims %[[BOX]], %[[C0_EXTENT]]
+! CHECK: %[[IV0_LOAD:.*]] = fir.load %[[IV0_DECL]]#0
+! CHECK: %[[IV0_I64:.*]] = fir.convert %[[IV0_LOAD]] : (i32) -> i64
+! CHECK: %[[IV0_INDEX:.*]] = fir.convert %[[IV0_I64]] : (i64) -> index
+! CHECK: %[[INDEX0:.*]] = arith.subi %[[IV0_INDEX]], %[[DIMS0_LB]]#0
+! CHECK: %[[BOUNDS0:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[INDEX0]] : index)
+! CHECK-SAME: upper_bound(%[[INDEX0]] : index)
+! CHECK-SAME: extent(%[[DIMS0_EXTENT]]#1 : index)
+! CHECK-SAME: stride(%[[DIMS0_EXTENT]]#2 : index)
+! CHECK-SAME: start_idx(%[[DIMS0_LB]]#0 : index)
+! CHECK-SAME: stride_in_bytes(true)
+! CHECK: %[[C1:.*]] = arith.constant 1 : index
+! CHECK: %[[DIMS1_LB:.*]]:3 = fir.box_dims %[[BOX]], %[[C1]]
+! CHECK: %[[C1_EXTENT:.*]] = arith.constant 1 : index
+! CHECK: %[[DIMS1_EXTENT:.*]]:3 = fir.box_dims %[[BOX]], %[[C1_EXTENT]]
+! CHECK: %[[IV1_LOAD:.*]] = fir.load %[[IV1_DECL]]#0
+! CHECK: %[[IV1_I64:.*]] = fir.convert %[[IV1_LOAD]] : (i32) -> i64
+! CHECK: %[[IV1_INDEX:.*]] = fir.convert %[[IV1_I64]] : (i64) -> index
+! CHECK: %[[INDEX1:.*]] = arith.subi %[[IV1_INDEX]], %[[DIMS1_LB]]#0
+! CHECK: %[[BOUNDS1:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[INDEX1]] : index)
+! CHECK-SAME: upper_bound(%[[INDEX1]] : index)
+! CHECK-SAME: extent(%[[DIMS1_EXTENT]]#1 : index)
+! CHECK-SAME: stride(%[[DIMS1_EXTENT]]#2 : index)
+! CHECK-SAME: start_idx(%[[DIMS1_LB]]#0 : index)
+! CHECK-SAME: stride_in_bytes(true)
+! CHECK: %[[BASE:.*]] = fir.box_addr %[[BOX]]
+! CHECK: %[[MAP:.*]] = omp.map.info
+! CHECK-SAME: var_ptr(%[[BASE]] : !fir.heap<!fir.array<?x?xi32>>, i32)
+! CHECK-SAME: map_clauses(to)
+! CHECK-SAME: capture(ByRef)
+! CHECK-SAME: bounds(%[[BOUNDS0]], %[[BOUNDS1]])
+! CHECK-SAME: name("")
+! CHECK-SAME: -> !llvm.ptr
+! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_deferred_char(a, n)
+  character(:), allocatable :: a(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_deferred_char
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_deferred_charEa") fortran_attrs<allocatable>
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,?>>>>>
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index) stride_in_bytes(true)
+! CHECK:   %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.heap<!fir.array<?x!fir.char<1,?>>>>) -> !fir.heap<!fir.array<?x!fir.char<1,?>>>
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?x!fir.char<1,?>>>, !fir.char<1,?>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_class_star(a, n)
+  class(*), allocatable :: a(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_class_star
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_class_starEa") fortran_attrs<allocatable>
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.class<!fir.heap<!fir.array<?xnone>>>>
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index) stride_in_bytes(true)
+! CHECK:   %[[BASE:.*]] = fir.box_addr %[[BOX]] : (!fir.class<!fir.heap<!fir.array<?xnone>>>) -> !fir.heap<!fir.array<?xnone>>
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?xnone>>, none) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_to_section()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n-1): a(i:i+1))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_to_section()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_to_sectionEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:   %[[IV_LB_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[LB_I64:.*]] = fir.convert %[[IV_LB_LD]] : (i32) -> i64
+! CHECK:   %[[LB_IDX:.*]] = fir.convert %[[LB_I64]] : (i64) -> index
+! CHECK:   %[[LB:.*]] = arith.subi %[[LB_IDX]], %{{.*}} : index
+! CHECK:   %[[IV_UB_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[C1_I32:.*]] = arith.constant 1 : i32
+! CHECK:   %[[UB_EXPR:.*]] = arith.addi %[[IV_UB_LD]], %[[C1_I32]] : i32
+! CHECK:   %[[UB_I64:.*]] = fir.convert %[[UB_EXPR]] : (i32) -> i64
+! CHECK:   %[[UB_IDX:.*]] = fir.convert %[[UB_I64]] : (i64) -> index
+! CHECK:   %[[UB:.*]] = arith.subi %[[UB_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[UB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_2d()
+  integer, parameter :: n = 4, m = 6
+  integer :: a(n, m)
+  integer :: i, j
+
+  !$omp target update to(iterator(i = 1:n, j = 1:m): a(i, j))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_2d()
+! CHECK: %[[EXT_I:.*]] = arith.constant 4 : index
+! CHECK: %[[EXT_J:.*]] = arith.constant 6 : index
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_2dEa")
+! CHECK: %[[I_LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[I_UB:.*]] = arith.constant 4 : i32
+! CHECK: %[[I_STEP:.*]] = arith.constant 1 : i32
+! CHECK: %[[J_LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[J_UB:.*]] = arith.constant 6 : i32
+! CHECK: %[[J_STEP:.*]] = arith.constant 1 : i32
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV_I:.*]]: i32,
+! CHECK-SAME: %[[IV_J:.*]]: i32) =
+! CHECK-SAME: (%[[I_LB]] to %[[I_UB]] step %[[I_STEP]],
+! CHECK-SAME: %[[J_LB]] to %[[J_UB]] step %[[J_STEP]]) {
+! CHECK:   fir.store %[[IV_I]] to %[[IV_I_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_I_DECL:.*]]:2 = hlfir.declare %[[IV_I_MEM]]
+! CHECK:   fir.store %[[IV_J]] to %[[IV_J_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_J_DECL:.*]]:2 = hlfir.declare %[[IV_J_MEM]]
+! CHECK:   %[[IV_I_LD:.*]] = fir.load %[[IV_I_DECL]]#0
+! CHECK-SAME: !fir.ref<i32>
+! CHECK:   %[[IV_I_I64:.*]] = fir.convert %[[IV_I_LD]]
+! CHECK-SAME: (i32) -> i64
+! CHECK:   %[[IV_I_IDX:.*]] = fir.convert %[[IV_I_I64]]
+! CHECK-SAME: (i64) -> index
+! CHECK:   %[[LOC_I:.*]] = arith.subi %[[IV_I_IDX]], %{{.*}} : index
+! CHECK:   %[[B_I:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[LOC_I]] : index)
+! CHECK-SAME: upper_bound(%[[LOC_I]] : index)
+! CHECK-SAME: extent(%[[EXT_I]] : index)
+! CHECK:   %[[IV_J_LD:.*]] = fir.load %[[IV_J_DECL]]#0
+! CHECK-SAME: !fir.ref<i32>
+! CHECK:   %[[IV_J_I64:.*]] = fir.convert %[[IV_J_LD]]
+! CHECK-SAME: (i32) -> i64
+! CHECK:   %[[IV_J_IDX:.*]] = fir.convert %[[IV_J_I64]]
+! CHECK-SAME: (i64) -> index
+! CHECK:   %[[LOC_J:.*]] = arith.subi %[[IV_J_IDX]], %{{.*}} : index
+! CHECK:   %[[B_J:.*]] = omp.map.bounds
+! CHECK-SAME: lower_bound(%[[LOC_J]] : index)
+! CHECK-SAME: upper_bound(%[[LOC_J]] : index)
+! CHECK-SAME: extent(%[[EXT_J]] : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0
+! CHECK-SAME: : !fir.ref<!fir.array<4x6xi32>>,
+! CHECK-SAME: !fir.array<4x6xi32>) map_clauses(to)
+! CHECK-SAME: capture(ByRef) bounds(%[[B_I]], %[[B_J]])
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_step()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n:2): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_step()
+! CHECK: %[[LB:.*]] = arith.constant 1 : i32
+! CHECK: %[[UB:.*]] = arith.constant 16 : i32
+! CHECK: %[[STEP:.*]] = arith.constant 2 : i32
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%{{.*}} : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_negative_step()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = n:1:-1): a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_negative_step()
+! CHECK: %[[LB:.*]] = arith.constant 16 : i32
+! CHECK: %[[UB:.*]] = arith.constant 1 : i32
+! CHECK: %[[STEP:.*]] = arith.constant -1 : i32
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%{{.*}} : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_multi_obj()
+  integer, parameter :: n = 16
+  integer :: a(n), b(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i), b(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_multi_obj()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_multi_objEa")
+! CHECK: %[[B:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_multi_objEb")
+! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS1:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP1:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS1]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP1]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS2:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP2:.*]] = omp.map.info var_ptr(%[[B]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS2]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT1]], %[[IT2]] : !omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_mixed_same_clause()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = 2:n:2): a(1), a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_mixed_same_clause()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_mixed_same_clauseEa")
+! CHECK: %[[MAP_PLAIN:.*]] = omp.map.info var_ptr(%[[A]]#1 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds({{.*}}) name("a(1)") -> !fir.ref<!fir.array<16xi32>>
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS_IT:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP_IT:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS_IT]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP_IT]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_entries(%[[MAP_PLAIN]] : !fir.ref<!fir.array<16xi32>>) map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_multi_clause()
+  integer, parameter :: n = 8
+  integer :: a(n), b(n)
+  integer :: i, j
+
+  !$omp target update to(iterator(i = 1:n): a(i)) &
+  !$omp&              from(iterator(j = 1:n:2): b(j))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_multi_clause()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_multi_clauseEa")
+! CHECK: %[[B:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_multi_clauseEb")
+! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS1:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP1:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<8xi32>>, !fir.array<8xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS1]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP1]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS2:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP2:.*]] = omp.map.info var_ptr(%[[B]]#0 : !fir.ref<!fir.array<8xi32>>, !fir.array<8xi32>) map_clauses(from) capture(ByRef) bounds(%[[BOUNDS2]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT1]], %[[IT2]] : !omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_mixed_clauses()
+  integer, parameter :: n = 16
+  integer :: a(n), b(n)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:n): a(i)) from(b)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_mixed_clauses()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_mixed_clausesEa")
+! CHECK: %[[B:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_mixed_clausesEb")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS_IT:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP_IT:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS_IT]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP_IT]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[MAP_B:.*]] = omp.map.info var_ptr(%[[B]]#1 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(from) capture(ByRef) bounds({{.*}}) name("b") -> !fir.ref<!fir.array<16xi32>>
+! CHECK: omp.target_update map_entries(%[[MAP_B]] : !fir.ref<!fir.array<16xi32>>) map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_mapper()
+  type :: s
+    integer :: a
+  end type
+  type(s) :: x(10)
+  integer :: i
+
+  !$omp declare mapper(m: s :: v) map(to: v%a)
+  !$omp target update to(mapper(m), iterator(i = 1:10): x(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_mapper()
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_mapperEx")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[X]]#0 : !fir.ref<!fir.array<10x!fir.type<_QFtarget_update_mapperTs{a:i32}>>>, !fir.array<10x!fir.type<_QFtarget_update_mapperTs{a:i32}>>) map_clauses(to) capture(ByRef) mapper(@_QQFtarget_update_mapperm) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_update_default_mapper()
+  type :: s
+    integer :: a
+  end type
+  type(s) :: x
+
+  !$omp declare mapper(s :: v) map(to: v%a)
+  !$omp target update to(x)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_default_mapper()
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFtarget_update_default_mapperEx")
+! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[X]]#1 : !fir.ref<!fir.type<_QFtarget_update_default_mapperTs{a:i32}>>, !fir.type<_QFtarget_update_default_mapperTs{a:i32}>) map_clauses(to) capture(ByRef) mapper(@_QQFtarget_update_default_mappers_omp_default_mapper) name("x") -> !fir.ref<!fir.type<_QFtarget_update_default_mapperTs{a:i32}>>
+! CHECK: omp.target_update map_entries(%[[MAP]] : !fir.ref<!fir.type<_QFtarget_update_default_mapperTs{a:i32}>>)
+
+subroutine target_update_iterated_default_mapper()
+  type :: s
+    integer :: a
+  end type
+  type(s) :: x(10)
+  integer :: i
+
+  !$omp declare mapper(s :: v) map(to: v%a)
+  !$omp target update to(iterator(i = 1:10): x(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_iterated_default_mapper()
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_update_iterated_default_mapperEx")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[X]]#0 : !fir.ref<!fir.array<10x!fir.type<_QFtarget_update_iterated_default_mapperTs{a:i32}>>>, !fir.array<10x!fir.type<_QFtarget_update_iterated_default_mapperTs{a:i32}>>) map_clauses(to) capture(ByRef) mapper(@_QQFtarget_update_iterated_default_mappers_omp_default_mapper) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! Assumed-shape array of a derived type with a mapper, mapped per iteration.
+subroutine target_update_assumed_shape_mapper(x, n)
+  type :: s
+    integer :: a
+  end type
+  type(s) :: x(:)
+  integer, intent(in) :: n
+  integer :: i
+
+  !$omp declare mapper(s :: v) map(to: v%a)
+  !$omp target update to(iterator(i = 1:n): x(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_update_assumed_shape_mapper
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 uniq_name("_QFtarget_update_assumed_shape_mapperEx")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[DIMS:.*]]:3 = fir.box_dims %[[X]]#0, %{{.*}} : (!fir.box<!fir.array<?x!fir.type<_QFtarget_update_assumed_shape_mapperTs{a:i32}>>>, index) -> (index, index, index)
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS]]#1 : index) stride(%[[DIMS]]#2 : index) start_idx(%{{.*}} : index) stride_in_bytes(true)
+! CHECK:   %[[BASE:.*]] = fir.box_addr %[[X]]#0 : (!fir.box<!fir.array<?x!fir.type<_QFtarget_update_assumed_shape_mapperTs{a:i32}>>>) -> !fir.ref<!fir.array<?x!fir.type<_QFtarget_update_assumed_shape_mapperTs{a:i32}>>>
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.ref<!fir.array<?x!fir.type<_QFtarget_update_assumed_shape_mapperTs{a:i32}>>>, !fir.type<_QFtarget_update_assumed_shape_mapperTs{a:i32}>) map_clauses(to) capture(ByRef) mapper(@_QQFtarget_update_assumed_shape_mappers_omp_default_mapper) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+!===============================================================================
+! target data
+!===============================================================================
+
+subroutine target_data_section()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target data map(iterator(i = 1:n-1), tofrom: a(i:i+1))
+  !$omp end target data
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_data_section()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_data_sectionEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:   %[[IV_LB_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[LB_I64:.*]] = fir.convert %[[IV_LB_LD]] : (i32) -> i64
+! CHECK:   %[[LB_IDX:.*]] = fir.convert %[[LB_I64]] : (i64) -> index
+! CHECK:   %[[LB:.*]] = arith.subi %[[LB_IDX]], %{{.*}} : index
+! CHECK:   %[[IV_UB_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[C1_I32:.*]] = arith.constant 1 : i32
+! CHECK:   %[[UB_EXPR:.*]] = arith.addi %[[IV_UB_LD]], %[[C1_I32]] : i32
+! CHECK:   %[[UB_I64:.*]] = fir.convert %[[UB_EXPR]] : (i32) -> i64
+! CHECK:   %[[UB_IDX:.*]] = fir.convert %[[UB_I64]] : (i64) -> index
+! CHECK:   %[[UB:.*]] = arith.subi %[[UB_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[UB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_data_mapper()
+  type :: s
+    integer :: a
+  end type
+  type(s) :: x(10)
+  integer :: i
+
+  !$omp declare mapper(m: s :: v) map(to: v%a)
+  !$omp target data map(mapper(m), iterator(i = 1:10), tofrom: x(i))
+  !$omp end target data
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_data_mapper()
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_data_mapperEx")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[X]]#0 : !fir.ref<!fir.array<10x!fir.type<_QFtarget_data_mapperTs{a:i32}>>>, !fir.array<10x!fir.type<_QFtarget_data_mapperTs{a:i32}>>) map_clauses(tofrom) capture(ByRef) mapper(@_QQFtarget_data_mapperm) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_data_default_mapper()
+  type :: s
+    integer :: a
+  end type
+  type(s) :: x(10)
+  integer :: i
+
+  !$omp declare mapper(s :: v) map(to: v%a)
+  !$omp target data map(iterator(i = 1:10), tofrom: x(i))
+  !$omp end target data
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_data_default_mapper()
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_data_default_mapperEx")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[X]]#0 : !fir.ref<!fir.array<10x!fir.type<_QFtarget_data_default_mapperTs{a:i32}>>>, !fir.array<10x!fir.type<_QFtarget_data_default_mapperTs{a:i32}>>) map_clauses(tofrom) capture(ByRef) mapper(@_QQFtarget_data_default_mappers_omp_default_mapper) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_data_implicit_mapper()
+  type :: s
+    integer, allocatable :: a(:)
+  end type
+  type(s) :: x(10)
+  integer :: i
+
+  !$omp target data map(iterator(i = 1:10), tofrom: x(i))
+  !$omp end target data
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_data_implicit_mapper()
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_data_implicit_mapperEx")
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[X]]#0 : !fir.ref<!fir.array<10x!fir.type<_QFtarget_data_implicit_mapperTs{a:!fir.box<!fir.heap<!fir.array<?xi32>>>}>>>, !fir.array<10x!fir.type<_QFtarget_data_implicit_mapperTs{a:!fir.box<!fir.heap<!fir.array<?xi32>>>}>>) map_clauses(tofrom) capture(ByRef) mapper(@{{.*omp_default_mapper}}) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+!===============================================================================
+! target enter data
+!===============================================================================
+
+subroutine target_enter_data_simple()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target enter data map(iterator(i = 1:n), to: a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_enter_data_simple()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_enter_data_simpleEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:   %[[IV_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[IV_I64:.*]] = fir.convert %[[IV_LD]] : (i32) -> i64
+! CHECK:   %[[IV_IDX:.*]] = fir.convert %[[IV_I64]] : (i64) -> index
+! CHECK:   %[[LB:.*]] = arith.subi %[[IV_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[LB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_enter_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+subroutine target_enter_data_section()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target enter data map(iterator(i = 1:n-2), to: a(i:i+2))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_enter_data_section()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_enter_data_sectionEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:   %[[IV_LB_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[LB_I64:.*]] = fir.convert %[[IV_LB_LD]] : (i32) -> i64
+! CHECK:   %[[LB_IDX:.*]] = fir.convert %[[LB_I64]] : (i64) -> index
+! CHECK:   %[[LB:.*]] = arith.subi %[[LB_IDX]], %{{.*}} : index
+! CHECK:   %[[IV_UB_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[C2_I32:.*]] = arith.constant 2 : i32
+! CHECK:   %[[UB_EXPR:.*]] = arith.addi %[[IV_UB_LD]], %[[C2_I32]] : i32
+! CHECK:   %[[UB_I64:.*]] = fir.convert %[[UB_EXPR]] : (i32) -> i64
+! CHECK:   %[[UB_IDX:.*]] = fir.convert %[[UB_I64]] : (i64) -> index
+! CHECK:   %[[UB:.*]] = arith.subi %[[UB_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[UB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_enter_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! Expression-based subscript using multiple iterator variables: a((i-1)*m+j)
+! maps a 2D logical iteration space onto a 1D array.
+subroutine target_enter_data_expr_subscript()
+  integer, parameter :: m = 4
+  integer, parameter :: n = m * m
+  integer :: a(n)
+  integer :: i, j
+
+  !$omp target enter data map(iterator(i = 1:m, j = 1:m), to: a((i-1)*m+j))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_enter_data_expr_subscript()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_enter_data_expr_subscriptEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV0:.*]]: i32, %[[IV1:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV0]] to %[[IV0_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_MEM]]
+! CHECK:   fir.store %[[IV1]] to %[[IV1_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV1_DECL:.*]]:2 = hlfir.declare %[[IV1_MEM]]
+! CHECK:   %[[IV0_LD:.*]] = fir.load %[[IV0_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[C1_I32:.*]] = arith.constant 1 : i32
+! CHECK:   %[[SUB:.*]] = arith.subi %[[IV0_LD]], %[[C1_I32]] : i32
+! CHECK:   %[[NOREASSOC:.*]] = hlfir.no_reassoc %[[SUB]] : i32
+! CHECK:   %[[MUL:.*]] = arith.muli %{{.*}}, %[[NOREASSOC]] : i32
+! CHECK:   %[[IV1_LD:.*]] = fir.load %[[IV1_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[ADD:.*]] = arith.addi %[[MUL]], %[[IV1_LD]] : i32
+! CHECK:   %[[IDX:.*]] = fir.convert %[[ADD]] : (i32) -> i64
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_enter_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+!===============================================================================
+! target exit data
+!===============================================================================
+
+subroutine target_exit_data_simple()
+  integer, parameter :: n = 16
+  integer :: a(n)
+  integer :: i
+
+  !$omp target exit data map(iterator(i = 1:n), from: a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_exit_data_simple()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_exit_data_simpleEa")
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: i32) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   fir.store %[[IV]] to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:   %[[IV_LD:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[IV_I64:.*]] = fir.convert %[[IV_LD]] : (i32) -> i64
+! CHECK:   %[[IV_IDX:.*]] = fir.convert %[[IV_I64]] : (i64) -> index
+! CHECK:   %[[LB:.*]] = arith.subi %[[IV_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[LB]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(from) capture(ByRef) bounds(%[[BOUNDS]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_exit_data map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! Multiple objects with negative step, producing separate iterators.
+subroutine target_exit_data_multi_obj()
+  integer, parameter :: n = 16
+  integer :: a(n), b(n)
+  integer :: i
+
+  !$omp target exit data map(iterator(i = n:1:-1), from: a(i), b(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtarget_exit_data_multi_obj()
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_exit_data_multi_objEa")
+! CHECK: %[[B:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) uniq_name("_QFtarget_exit_data_multi_objEb")
+! CHECK: %[[LB:.*]] = arith.constant 16 : i32
+! CHECK: %[[UB:.*]] = arith.constant 1 : i32
+! CHECK: %[[STEP:.*]] = arith.constant -1 : i32
+! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
+! CHECK:   %[[BOUNDS1:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP1:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(from) capture(ByRef) bounds(%[[BOUNDS1]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP1]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: i32) = (%[[LB]] to %[[UB]] step %[[STEP]]) {
+! CHECK:   %[[BOUNDS2:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP2:.*]] = omp.map.info var_ptr(%[[B]]#0 : !fir.ref<!fir.array<16xi32>>, !fir.array<16xi32>) map_clauses(from) capture(ByRef) bounds(%[[BOUNDS2]]) name("") -> !llvm.ptr
+! CHECK:   omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_exit_data map_iterated(%[[IT1]], %[[IT2]] : !omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>)
+
+!--- descriptors.f90
+! Verify that descriptor-backed iterator maps describe only selected data and
+! MapInfoFinalization does not add descriptor parent or attachment maps.
+
+subroutine iter_map_assumed_shape(a, n)
+  integer :: a(:)
+  integer :: n
+  integer :: i
+
+  !$omp target enter data map(iterator(i = 1:n), to: a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPiter_map_assumed_shape(
+! CHECK: %[[IT:.*]] = omp.iterator
+! CHECK: %[[ADDR:.*]] = fir.box_addr
+! CHECK: %[[MAP:.*]] = omp.map.info
+! CHECK-SAME: var_ptr(%[[ADDR]]
+! CHECK-SAME: map_clauses(to) capture(ByRef)
+! CHECK-SAME: bounds(%{{.*}})
+! CHECK: omp.yield(%[[MAP]]
+! CHECK: } inclusive -> !omp.iterated
+! CHECK: omp.target_enter_data map_iterated(%[[IT]]
+
+subroutine iter_map_allocatable(a, n)
+  integer, allocatable :: a(:)
+  integer :: n
+  integer :: i
+
+  !$omp target enter data map(iterator(i = 1:n), to: a(i))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPiter_map_allocatable(
+! CHECK: %[[IT:.*]] = omp.iterator
+! CHECK: %[[BOX:.*]] = fir.load
+! CHECK: %[[ADDR:.*]] = fir.box_addr %[[BOX]]
+! CHECK: %[[MAP:.*]] = omp.map.info
+! CHECK-SAME: var_ptr(%[[ADDR]]
+! CHECK-SAME: map_clauses(to) capture(ByRef)
+! CHECK-SAME: bounds(%{{.*}})
+! CHECK: omp.yield(%[[MAP]]
+! CHECK: } inclusive -> !omp.iterated
+! CHECK: omp.target_enter_data map_iterated(%[[IT]]
+
+!--- implicit-mapper.f90
+subroutine implicit_mapper_to()
+  type :: t
+    integer, allocatable :: a(:)
+  end type
+  type(t) :: x
+
+  !$omp target update to(x)
+end subroutine
+
+subroutine implicit_mapper_from()
+  type :: inner
+    integer, allocatable :: a(:)
+  end type
+  type :: outer
+    type(inner) :: nested
+  end type
+  type(outer) :: x
+
+  !$omp target update from(x)
+end subroutine
+
+subroutine implicit_mapper_iterator()
+  type :: t
+    integer, allocatable :: a(:)
+  end type
+  type(t) :: x(4)
+  integer :: i
+
+  !$omp target update to(iterator(i = 1:4): x(i))
+end subroutine
+
+! CHECK-DAG: omp.declare_mapper
+! CHECK-DAG: omp.declare_mapper
+! CHECK-DAG: omp.declare_mapper
+
+! CHECK-LABEL: func.func @_QPimplicit_mapper_to
+! CHECK: omp.map.info {{.*}} map_clauses(to) {{.*}}mapper(@{{.*}})
+! CHECK: omp.target_update
+
+! CHECK-LABEL: func.func @_QPimplicit_mapper_from
+! CHECK: omp.map.info {{.*}} map_clauses(from) {{.*}}mapper(@{{.*}})
+! CHECK: omp.target_update
+
+! CHECK-LABEL: func.func @_QPimplicit_mapper_iterator
+! CHECK: omp.iterator
+! CHECK: omp.map.info {{.*}} map_clauses(to) {{.*}}mapper(@{{.*}})
+! CHECK: omp.target_update {{.*}}map_iterated
diff --git a/flang/test/Lower/OpenMP/task-affinity.f90 b/flang/test/Lower/OpenMP/task-affinity.f90
index 645dd836f96871..61ea5e13b3eeb0 100644
--- a/flang/test/Lower/OpenMP/task-affinity.f90
+++ b/flang/test/Lower/OpenMP/task-affinity.f90
@@ -296,7 +296,7 @@ subroutine affinity_per_locator(m)
 ! CHECK: %[[ENTRY_I:.*]] = omp.affinity_entry %[[PTR_I]], %[[SIZE_I]]
 ! CHECK: omp.yield(%[[ENTRY_I]] :
 ! CHECK-SAME: !omp.affinity_entry_ty<!fir.ref<i8>, i64>)
-! CHECK: } -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
+! CHECK: } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
 ! CHECK: %[[IT_J:.*]] = omp.iterator(%[[IV_J:.*]]: i32) =
 ! CHECK-SAME: (%[[J_LB]] to %[[J_UB]] step %[[J_STEP]]) {
 ! CHECK: fir.store %[[IV_J]] to %[[MEM_J:.*]] : !fir.ref<i32>
@@ -310,7 +310,7 @@ subroutine affinity_per_locator(m)
 ! CHECK: %[[ENTRY_J:.*]] = omp.affinity_entry %[[PTR_J]], %[[SIZE_J]]
 ! CHECK: omp.yield(%[[ENTRY_J]] :
 ! CHECK-SAME: !omp.affinity_entry_ty<!fir.ref<i8>, i64>)
-! CHECK: } -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
+! CHECK: } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
 ! CHECK: %[[C_PTR:.*]] = fir.convert %[[C]]#0
 ! CHECK-SAME: (!fir.ref<i32>) -> !fir.ref<i8>
 ! CHECK: %[[C_ENTRY:.*]] = omp.affinity_entry %[[C_PTR]],
@@ -348,7 +348,7 @@ subroutine task_affinity_iterator_simple()
 ! CHECK:   %[[ADDRI8:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !fir.ref<i8>
 ! CHECK:   %[[ENTRY:.*]] = omp.affinity_entry %[[ADDRI8]], %[[C4]] : (!fir.ref<i8>, i64) -> !omp.affinity_entry_ty<!fir.ref<i8>, i64>
 ! CHECK:   omp.yield(%[[ENTRY]] : !omp.affinity_entry_ty<!fir.ref<i8>, i64>)
-! CHECK: } -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
+! CHECK: } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
 ! CHECK: omp.task affinity(%{{.*}} : !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>) {
 
 subroutine task_affinity_iterator_nondefault_lb()
@@ -378,7 +378,7 @@ subroutine task_affinity_iterator_nondefault_lb()
 ! CHECK:   %[[ADDRI8_NDLB:.*]] = fir.convert %[[COOR_NDLB]] : (!fir.ref<i32>) -> !fir.ref<i8>
 ! CHECK:   %[[ENTRY_NDLB:.*]] = omp.affinity_entry %[[ADDRI8_NDLB]], %[[ELEM_NDLB_I64]] : (!fir.ref<i8>, i64) -> !omp.affinity_entry_ty<!fir.ref<i8>, i64>
 ! CHECK:   omp.yield(%[[ENTRY_NDLB]] : !omp.affinity_entry_ty<!fir.ref<i8>, i64>)
-! CHECK: } -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
+! CHECK: } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
 ! CHECK: omp.task affinity(%[[ITERATED_NDLB]] : !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>) {
 
 subroutine task_affinity_iterator_nondefault_lb_2d()
@@ -413,7 +413,7 @@ subroutine task_affinity_iterator_nondefault_lb_2d()
 ! CHECK:   %[[ADDRI8_NDLB2:.*]] = fir.convert %[[COOR_NDLB2]] : (!fir.ref<i32>) -> !fir.ref<i8>
 ! CHECK:   %[[ENTRY_NDLB2:.*]] = omp.affinity_entry %[[ADDRI8_NDLB2]], %[[ELEM_NDLB2_I64]] : (!fir.ref<i8>, i64) -> !omp.affinity_entry_ty<!fir.ref<i8>, i64>
 ! CHECK:   omp.yield(%[[ENTRY_NDLB2]] : !omp.affinity_entry_ty<!fir.ref<i8>, i64>)
-! CHECK: } -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
+! CHECK: } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
 ! CHECK: omp.task affinity(%[[ITERATED_NDLB2]] : !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>) {
 
 subroutine task_affinity_iterator_multi_dimension()
@@ -449,7 +449,7 @@ subroutine task_affinity_iterator_multi_dimension()
 ! CHECK:   %[[ADDRI8:.*]] = fir.convert %[[COOR]] : (!fir.ref<i32>) -> !fir.ref<i8>
 ! CHECK:   %[[ENTRY:.*]] = omp.affinity_entry %[[ADDRI8]], %[[C4]] : (!fir.ref<i8>, i64) -> !omp.affinity_entry_ty<!fir.ref<i8>, i64>
 ! CHECK:   omp.yield(%[[ENTRY]] : !omp.affinity_entry_ty<!fir.ref<i8>, i64>)
-! CHECK: } -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
+! CHECK: } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>
 ! CHECK: omp.task affinity(%[[ITER]] : !omp.iterated<!omp.affinity_entry_ty<!fir.ref<i8>, i64>>)
 
 subroutine task_affinity_iterator_reordered()
diff --git a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90 b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90
index e766b1b44d0faf..8e0efb765ff6ae 100644
--- a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90
+++ b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90
@@ -95,3 +95,36 @@ subroutine test_pointer_dummy_no_warning(ptr)
   integer, pointer, intent(inout) :: ptr(:)
   !$omp target enter data map(to: ptr)
 end subroutine
+
+subroutine test_iterator_assumed_shape_no_warning(arr, n)
+  integer, intent(inout) :: arr(:)
+  integer, intent(in) :: n
+  integer :: i
+  !$omp target enter data map(iterator(i = 1:n), to: arr(i))
+end subroutine
+
+subroutine test_iterator_local_allocatable_no_warning()
+  integer, allocatable :: local_arr(:)
+  integer :: i
+  allocate(local_arr(100))
+  !$omp target enter data map(iterator(i = 1:100), to: local_arr(i))
+  deallocate(local_arr)
+end subroutine
+
+subroutine test_iterator_mixed_object_warning(arr, other, n)
+  integer, intent(inout) :: arr(:)
+  integer, intent(inout) :: other(n)
+  integer, intent(in) :: n
+  integer :: i
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+  !$omp target enter data map(iterator(i = 1:n), to: arr, other(i))
+end subroutine
+
+subroutine test_plain_enter_iterator_exit_warning(arr, n)
+  integer, intent(inout) :: arr(:)
+  integer, intent(in) :: n
+  integer :: i
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+  !$omp target exit data map(iterator(i = 1:n), from: arr(i))
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90 b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
index bd1eb98ebec60a..3eb8b9d7c50af4 100644
--- a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
+++ b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
@@ -91,3 +91,36 @@ subroutine test_local_allocatable_with_exit()
   !$omp target exit data map(from: local_arr)
   deallocate(local_arr)
 end subroutine
+
+subroutine test_iterator_assumed_shape_no_warning(arr, n)
+  integer, intent(inout) :: arr(:)
+  integer, intent(in) :: n
+  integer :: i
+  !$omp target enter data map(iterator(i = 1:n), to: arr(i))
+end subroutine
+
+subroutine test_iterator_local_allocatable_no_warning()
+  integer, allocatable :: local_arr(:)
+  integer :: i
+  allocate(local_arr(100))
+  !$omp target enter data map(iterator(i = 1:100), to: local_arr(i))
+  deallocate(local_arr)
+end subroutine
+
+subroutine test_iterator_mixed_object_warning(arr, other, n)
+  integer, intent(inout) :: arr(:)
+  integer, intent(inout) :: other(n)
+  integer, intent(in) :: n
+  integer :: i
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(iterator(i = 1:n), to: arr, other(i))
+end subroutine
+
+subroutine test_plain_enter_iterator_exit_warning(arr, n)
+  integer, intent(inout) :: arr(:)
+  integer, intent(in) :: n
+  integer :: i
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+  !$omp target exit data map(iterator(i = 1:n), from: arr(i))
+end subroutine
diff --git a/flang/test/Transforms/omp-map-info-finalization.fir b/flang/test/Transforms/omp-map-info-finalization.fir
index e9ac6d5f3d505f..c26e825cacd3c5 100644
--- a/flang/test/Transforms/omp-map-info-finalization.fir
+++ b/flang/test/Transforms/omp-map-info-finalization.fir
@@ -43,6 +43,68 @@ func.func @test_descriptor_expansion_pass(%arg0: !fir.box<!fir.array<?xi32>>) {
 
 // -----
 
+func.func @test_iterator_descriptor_map_is_not_expanded(%arg0: !fir.box<!fir.array<?xf32>>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c10 = arith.constant 10 : index
+  %dims:3 = fir.box_dims %arg0, %c0 : (!fir.box<!fir.array<?xf32>>, index) -> (index, index, index)
+  %it = omp.iterator(%iv: index) = (%c1 to %c10 step %c1) {
+    %addr = fir.box_addr %arg0 : (!fir.box<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+    %lb = arith.subi %iv, %c1 : index
+    %bounds = omp.map.bounds lower_bound(%lb : index) upper_bound(%lb : index) extent(%dims#1 : index) stride(%dims#2 : index) start_idx(%dims#0 : index) stride_in_bytes(true)
+    %map = omp.map.info var_ptr(%addr : !fir.ref<!fir.array<?xf32>>, !fir.array<?xf32>) map_clauses(to) capture(ByRef) bounds(%bounds) name("") -> !fir.ref<!fir.array<?xf32>>
+    omp.yield(%map : !fir.ref<!fir.array<?xf32>>)
+  } -> !omp.iterated<!fir.ref<!fir.array<?xf32>>>
+  omp.target_enter_data map_iterated(%it : !omp.iterated<!fir.ref<!fir.array<?xf32>>>) {}
+  return
+}
+
+// CHECK-LABEL: func.func @test_iterator_descriptor_map_is_not_expanded
+// CHECK-NOT: fir.box_offset
+// CHECK: omp.iterator
+// CHECK: omp.map.info var_ptr({{.*}} : !fir.ref<!fir.array<?xf32>>, !fir.array<?xf32>) map_clauses(to) capture(ByRef) bounds({{.*}}) name("") -> !fir.ref<!fir.array<?xf32>>
+// CHECK: omp.target_enter_data map_iterated
+
+// -----
+
+func.func @test_iterator_member_map_is_not_finalized(
+    %arg0: !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>) {
+  %c1 = arith.constant 1 : index
+  %c10 = arith.constant 10 : index
+  %it = omp.iterator(%iv: index) = (%c1 to %c10 step %c1) {
+    %member = fir.coordinate_of %arg0, x :
+        (!fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>) -> !fir.ref<i32>
+    %member_map = omp.map.info
+        var_ptr(%member : !fir.ref<i32>, i32)
+        map_clauses(close, to) capture(ByRef) name("record%x")
+        -> !fir.ref<i32>
+    %parent_map = omp.map.info
+        var_ptr(%arg0 :
+          !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>,
+          !fir.type<_QFiterator_memberTt{x:i32}>)
+        map_clauses(to) capture(ByRef)
+        members(%member_map : [0] : !fir.ref<i32>) name("record")
+        partial_map(true) -> !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>
+    omp.yield(
+      %parent_map : !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>)
+  } -> !omp.iterated<
+    !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>>
+  omp.target_enter_data map_iterated(
+    %it : !omp.iterated<
+      !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>>) {}
+  return
+}
+
+// CHECK-LABEL: func.func @test_iterator_member_map_is_not_finalized
+// CHECK: omp.iterator
+// CHECK: omp.map.info
+// CHECK-SAME: map_clauses(close, to)
+// CHECK: omp.map.info
+// CHECK-SAME: members(
+// CHECK: omp.target_enter_data map_iterated
+
+// -----
+
 func.func @test_derived_type_map_operand_and_block_addition(%arg0: !fir.ref<!fir.type<_QFTdtype{ix:i32,rx:f32,zx:complex<f32>,nested:!fir.box<!fir.heap<!fir.type<_QFTdtype>>>,ry:f32}>>) {
   %0 = hlfir.designate %arg0{"rx"}   : (!fir.ref<!fir.type<_QFTdtype{ix:i32,rx:f32,zx:complex<f32>,nested:!fir.box<!fir.heap<!fir.type<_QFTdtype>>>,ry:f32}>>) -> !fir.ref<f32>
   %1 = omp.map.info var_ptr(%0 : !fir.ref<f32>, f32) map_clauses(from) capture(ByRef) name("scalar_struct%rx") -> !fir.ref<f32>
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 307b91ebe99af2..091d1befbdda5f 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2781,6 +2781,12 @@ def IteratorOp : OpenMP_Op<"iterator", [AttrSizedOperandSegments],
     The region may contain multiple blocks. Exactly one of them is terminated
     by `omp.yield`; every other block branches to a block of the region.
 
+    Each range has a nonzero step and an exclusive upper bound unless
+    `inclusive` is specified. A range is empty when its lower bound is beyond
+    its upper bound in the direction of the step (or equal to an exclusive
+    upper bound). If any range is empty, the result is an empty list and the
+    region is not evaluated.
+
     Example:
       %it = omp.iterator(%i, %j) =
         (%lb_i to %ub_i step %st_i, %lb_j to %ub_j step %st_j) {
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index e8bcc22464439b..d7fdc5d2bb0a3f 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -2956,6 +2956,8 @@ class IteratorInfo {
     steps.resize(dims);
     trips.resize(dims);
 
+    llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
+    llvm::OpenMPIRBuilder::LocationDescription loc(builder);
     for (unsigned d = 0; d < dims; ++d) {
       llvm::Value *lb =
           moduleTranslation.lookupValue(itersOp.getLoopLowerBounds()[d]);
@@ -2965,6 +2967,8 @@ class IteratorInfo {
           moduleTranslation.lookupValue(itersOp.getLoopSteps()[d]);
       assert(lb && ub && st &&
              "Expect lowerBounds, upperBounds, and steps in IteratorOp");
+      assert(lb->getType() == ub->getType() && lb->getType() == st->getType() &&
+             "Expect matching iterator range types");
       assert((!llvm::isa<llvm::ConstantInt>(st) ||
               !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
              "Expect non-zero step in IteratorOp");
@@ -2980,10 +2984,9 @@ class IteratorInfo {
       llvm::Value *start = builder.CreateSExt(lb, countTy);
       llvm::Value *stop = builder.CreateSExt(ub, countTy);
       llvm::Value *step = builder.CreateSExt(st, countTy);
-      llvm::Value *count =
-          moduleTranslation.getOpenMPBuilder()->calculateCanonicalLoopTripCount(
-              builder, start, stop, step, /*IsSigned=*/true,
-              /*InclusiveStop=*/true);
+      llvm::Value *count = ompBuilder.calculateCanonicalLoopTripCount(
+          loc, start, stop, step, /*IsSigned=*/true, itersOp.getLoopInclusive(),
+          "iterator");
       trips[d] = builder.CreateZExtOrTrunc(count, builder.getInt64Ty());
     }
 
@@ -3119,10 +3122,16 @@ static mlir::LogicalResult convertIteratorRegion(
   llvm::Value *tmp = linearIV;
   for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
     llvm::Value *trip = iterInfo.getTrips()[d];
-    // idx_d = tmp % trip_d
-    llvm::Value *idx = builder.CreateURem(tmp, trip);
-    // tmp = tmp / trip_d
-    tmp = builder.CreateUDiv(tmp, trip);
+    llvm::Value *zero = llvm::ConstantInt::get(trip->getType(), 0);
+    llvm::Value *one = llvm::ConstantInt::get(trip->getType(), 1);
+    // A zero trip count makes the linearized loop empty. Use one in its
+    // unreachable IV decoder so the generated IR contains no division by zero.
+    llvm::Value *divisor =
+        builder.CreateSelect(builder.CreateICmpEQ(trip, zero), one, trip);
+    // idx_d = tmp % divisor_d
+    llvm::Value *idx = builder.CreateURem(tmp, divisor);
+    // tmp = tmp / divisor_d
+    tmp = builder.CreateUDiv(tmp, divisor);
 
     // physIV_d = lb_d + idx_d * step_d
     idx =
diff --git a/mlir/test/Dialect/OpenMP/iterator-empty.mlir b/mlir/test/Dialect/OpenMP/iterator-empty.mlir
index c1d8e27571e97d..e8609dba6deddd 100644
--- a/mlir/test/Dialect/OpenMP/iterator-empty.mlir
+++ b/mlir/test/Dialect/OpenMP/iterator-empty.mlir
@@ -1,18 +1,31 @@
 // RUN: mlir-opt %s | mlir-opt | FileCheck %s
+// RUN: mlir-opt %s --canonicalize | FileCheck %s
 
-// A range pointing away from its endpoint is valid and produces no entries.
+// A range pointing away from its endpoint is valid and produces no entries,
+// for either endpoint convention.
 // CHECK-LABEL: func.func @empty_ranges
-func.func @empty_ranges(%x: !llvm.ptr) {
+func.func @empty_ranges(%x: !llvm.ptr)
+    -> (!omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>,
+        !omp.iterated<!llvm.ptr>) {
   %c1 = arith.constant 1 : index
   %c3 = arith.constant 3 : index
   %neg = arith.constant -1 : index
   // CHECK: omp.iterator
+  // CHECK: } -> !omp.iterated<!llvm.ptr>
   %positive = omp.iterator(%i: index) = (%c3 to %c1 step %c1) {
     omp.yield(%x : !llvm.ptr)
   } -> !omp.iterated<!llvm.ptr>
   // CHECK: omp.iterator
+  // CHECK: } -> !omp.iterated<!llvm.ptr>
   %negative = omp.iterator(%i: index) = (%c1 to %c3 step %neg) {
     omp.yield(%x : !llvm.ptr)
   } -> !omp.iterated<!llvm.ptr>
-  return
+  // CHECK: omp.iterator
+  // CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
+  %inclusive = omp.iterator(%i: index) = (%c3 to %c1 step %c1) {
+    omp.yield(%x : !llvm.ptr)
+  } inclusive -> !omp.iterated<!llvm.ptr>
+  return %positive, %negative, %inclusive
+      : !omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>,
+        !omp.iterated<!llvm.ptr>
 }
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index a9e3efdf843daa..8b86bceb8de9b1 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -4275,6 +4275,18 @@ func.func @omp_iterator_negative_step(%s2 : !llvm.struct<(ptr, i64)>) -> () {
   return
 }
 
+// CHECK-LABEL: func.func @omp_iterator_inclusive_wide
+func.func @omp_iterator_inclusive_wide(%hi : i128, %st : i128) -> () {
+  // CHECK: omp.iterator(%[[IV:.*]]: i128) = (%{{.*}} to %{{.*}} step %{{.*}}) {
+  // CHECK:   omp.yield(%[[IV]] : i128)
+  // CHECK: } inclusive -> !omp.iterated<i128>
+  %lb = arith.constant 0 : i128
+  %0 = omp.iterator(%iv: i128) = (%lb to %hi step %st) {
+    omp.yield(%iv : i128)
+  } inclusive -> !omp.iterated<i128>
+  return
+}
+
 // CHECK-LABEL: func.func @omp_iterator_multi_block
 func.func @omp_iterator_multi_block(%a : !llvm.ptr, %b : !llvm.ptr) -> () {
   // CHECK: omp.iterator(%[[IV:.*]]: index) = (%{{.*}} to %{{.*}} step %{{.*}}) {
diff --git a/mlir/test/Target/LLVMIR/openmp-iterator-empty.mlir b/mlir/test/Target/LLVMIR/openmp-iterator-empty.mlir
index 794f1e2e50e2a9..a99400f7c2501b 100644
--- a/mlir/test/Target/LLVMIR/openmp-iterator-empty.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-iterator-empty.mlir
@@ -7,7 +7,7 @@ llvm.func @empty_positive(%x: !llvm.ptr) {
   %c3 = llvm.mlir.constant(3 : i64) : i64
   %it = omp.iterator(%i: i64) = (%c3 to %c2 step %c2) {
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -24,7 +24,7 @@ llvm.func @empty_negative(%x: !llvm.ptr) {
   %step = llvm.mlir.constant(-2 : i64) : i64
   %it = omp.iterator(%i: i64) = (%c2 to %c3 step %step) {
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -42,7 +42,7 @@ llvm.func @empty_product(%x: !llvm.ptr) {
   %it = omp.iterator(%i: i64, %j: i64) =
       (%c3 to %c1 step %c1, %c3 to %c1 step %c1) {
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
diff --git a/mlir/test/Target/LLVMIR/openmp-iterator-multi-block.mlir b/mlir/test/Target/LLVMIR/openmp-iterator-multi-block.mlir
index 3487897912eb51..d98dcc4fb15cd7 100644
--- a/mlir/test/Target/LLVMIR/openmp-iterator-multi-block.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-iterator-multi-block.mlir
@@ -12,7 +12,7 @@ llvm.func @depend_diamond(%a : !llvm.ptr, %b : !llvm.ptr) {
     llvm.cond_br %first, ^bb1(%a : !llvm.ptr), ^bb1(%b : !llvm.ptr)
   ^bb1(%p : !llvm.ptr):
     omp.yield(%p : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
   }
@@ -55,7 +55,7 @@ llvm.func @affinity_split(%a : !llvm.ptr, %b : !llvm.ptr) {
     %e = omp.affinity_entry %p, %len
         : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
     omp.yield(%e : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-  } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+  } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
   omp.task affinity(%it : !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>) {
     omp.terminator
   }
@@ -93,7 +93,7 @@ llvm.func @depend_inner_loop(%addr : !llvm.ptr) {
   ^exit:
     %p = llvm.getelementptr %addr[%acc] : (!llvm.ptr, i64) -> !llvm.ptr, i8
     omp.yield(%p : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
   }
@@ -130,7 +130,7 @@ llvm.func @depend_2d(%a : !llvm.ptr, %b : !llvm.ptr) {
     llvm.cond_br %lt, ^bb1(%a : !llvm.ptr), ^bb1(%b : !llvm.ptr)
   ^bb1(%p : !llvm.ptr):
     omp.yield(%p : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
   }
@@ -160,7 +160,7 @@ llvm.func @depend_narrow(%x : !llvm.ptr) {
   ^next(%v: i32):
     llvm.store %v, %x : i32, !llvm.ptr
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -183,7 +183,7 @@ llvm.func @depend_empty_dynamic(%a : !llvm.ptr, %m : i32, %d : i32) {
   ^bb1(%k : i32):
     %p = llvm.getelementptr %a[%k] : (!llvm.ptr, i32) -> !llvm.ptr, i32
     omp.yield(%p : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
   }
diff --git a/mlir/test/Target/LLVMIR/openmp-iterator-wide.mlir b/mlir/test/Target/LLVMIR/openmp-iterator-wide.mlir
index 013b20636d9dfb..feecf39b44f3ee 100644
--- a/mlir/test/Target/LLVMIR/openmp-iterator-wide.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-iterator-wide.mlir
@@ -7,7 +7,7 @@ llvm.func @empty_positive(%x: !llvm.ptr) {
   %st = llvm.mlir.constant(1 : i128) : i128
   %it = omp.iterator(%i: i128) = (%lb to %ub step %st) {
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -23,7 +23,7 @@ llvm.func @empty_negative(%x: !llvm.ptr) {
   %st = llvm.mlir.constant(-1 : i128) : i128
   %it = omp.iterator(%i: i128) = (%lb to %ub step %st) {
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -38,7 +38,7 @@ llvm.func @dynamic(%x: !llvm.ptr, %lb: i128, %ub: i128, %st: i128) {
   %it = omp.iterator(%i: i128) = (%lb to %ub step %st) {
     llvm.store %i, %x : i128, !llvm.ptr
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -62,7 +62,7 @@ llvm.func @narrow(%x: !llvm.ptr, %lb: i32, %ub: i32, %st: i32) {
   %it = omp.iterator(%i: i32) = (%lb to %ub step %st) {
     llvm.store %i, %x : i32, !llvm.ptr
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -82,7 +82,7 @@ llvm.func @nonempty_positive(%x: !llvm.ptr) {
   %it = omp.iterator(%i: i128) = (%lb to %ub step %st) {
     llvm.store %i, %x : i128, !llvm.ptr
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -102,7 +102,7 @@ llvm.func @nonempty_negative(%x: !llvm.ptr) {
   %it = omp.iterator(%i: i128) = (%lb to %ub step %st) {
     llvm.store %i, %x : i128, !llvm.ptr
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
@@ -122,7 +122,7 @@ llvm.func @wide_step(%x: !llvm.ptr) {
   %it = omp.iterator(%i: i128) = (%lb to %ub step %st) {
     llvm.store %i, %x : i128, !llvm.ptr
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
   llvm.return
 }
diff --git a/mlir/test/Target/LLVMIR/openmp-iterator.mlir b/mlir/test/Target/LLVMIR/openmp-iterator.mlir
index ad8d603d7d266d..9ebee61b0237b6 100644
--- a/mlir/test/Target/LLVMIR/openmp-iterator.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-iterator.mlir
@@ -21,7 +21,7 @@ llvm.func @task_affinity_iterator_1d(%arr: !llvm.ptr {llvm.nocapture}) {
         %entry = omp.affinity_entry %arr, %len
             : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
         omp.yield(%entry : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-      } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
 
       omp.task affinity(%it : !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>) {
         omp.terminator
@@ -86,7 +86,7 @@ llvm.func @task_affinity_iterator_3d(%arr: !llvm.ptr {llvm.nocapture}) {
         %entry = omp.affinity_entry %arr, %len
             : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
         omp.yield(%entry : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-      } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
 
       omp.task affinity(%it : !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>) {
         omp.terminator
@@ -151,14 +151,14 @@ llvm.func @task_affinity_iterator_multiple(%arr: !llvm.ptr {llvm.nocapture}) {
         %entry0 = omp.affinity_entry %arr, %len
             : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
         omp.yield(%entry0 : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-      } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
 
       // second iterator: 1-D (3)
       %it1 = omp.iterator(%k: i64) = (%c1 to %c3 step %c1) {
         %entry1 = omp.affinity_entry %arr, %len
             : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
         omp.yield(%entry1 : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-      } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
 
       // Multiple iterators in a single affinity clause.
       omp.task affinity(%it0: !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>,
@@ -247,7 +247,7 @@ llvm.func @task_affinity_iterator_dynamic_tripcount(
         %entry = omp.affinity_entry %arr, %len
             : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
         omp.yield(%entry : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-      } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
 
       omp.task affinity(%it : !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>) {
         omp.terminator
@@ -280,7 +280,7 @@ llvm.func @task_affinity_iterator_negative_step(%arr: !llvm.ptr {llvm.nocapture}
         %entry = omp.affinity_entry %arr, %i
             : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
         omp.yield(%entry : !omp.affinity_entry_ty<!llvm.ptr, i64>)
-      } -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
 
       omp.task affinity(%it : !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>) {
         omp.terminator
@@ -305,6 +305,38 @@ llvm.func @task_affinity_iterator_negative_step(%arr: !llvm.ptr {llvm.nocapture}
 // CHECK: [[LENPTR:%.*]] = getelementptr inbounds nuw { i64, i64, i32 }, ptr [[ENTRY]], i32 0, i32 1
 // CHECK: store i64 [[PHYSIV]], ptr [[LENPTR]]
 
+llvm.func @task_affinity_iterator_empty(%arr: !llvm.ptr {llvm.nocapture}) {
+  %c1 = llvm.mlir.constant(1 : i64) : i64
+  %c5 = llvm.mlir.constant(5 : i64) : i64
+  %len = llvm.mlir.constant(4 : i64) : i64
+
+  omp.parallel {
+    omp.single {
+      %it = omp.iterator(%i: i64) = (%c5 to %c1 step %c1) {
+        %entry = omp.affinity_entry %arr, %len
+            : (!llvm.ptr, i64) -> !omp.affinity_entry_ty<!llvm.ptr, i64>
+        omp.yield(%entry : !omp.affinity_entry_ty<!llvm.ptr, i64>)
+      } inclusive -> !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>
+
+      omp.task affinity(
+          %it : !omp.iterated<!omp.affinity_entry_ty<!llvm.ptr, i64>>) {
+        omp.terminator
+      }
+      omp.terminator
+    }
+    omp.terminator
+  }
+  llvm.return
+}
+
+// CHECK-LABEL: define internal void @task_affinity_iterator_empty
+// CHECK: %[[EMPTY_AFFINITY:.*]] = alloca { i64, i64, i32 }, i64 0
+// CHECK: omp_iterator.cond:
+// CHECK: icmp ult i64 %omp_iterator.iv, 0
+// CHECK: call i32 @__kmpc_omp_reg_task_with_affinity(
+// CHECK-SAME: ptr @{{[^,]+}}, i32 %{{[^,]+}}, ptr %{{[^,]+}},
+// CHECK-SAME: i32 0, ptr %[[EMPTY_AFFINITY]])
+
 // --------------------------------------------------------------------
 // Depend clause
 // --------------------------------------------------------------------
@@ -316,7 +348,7 @@ llvm.func @omp_task_depend_iterator_simple(%addr : !llvm.ptr) {
 
   %it = omp.iterator(%iv: i64) = (%c1 to %c10 step %step) {
     omp.yield(%addr : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
 
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
@@ -362,7 +394,7 @@ llvm.func @omp_task_depend_iterator_mixed(%addr : !llvm.ptr, %plain : !llvm.ptr)
 
   %it = omp.iterator(%iv: i64) = (%c1 to %c10 step %step) {
     omp.yield(%addr : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
 
   omp.task depend(taskdependout -> %plain : !llvm.ptr, taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
@@ -401,7 +433,7 @@ llvm.func @omp_task_depend_iterator_dynamic(%addr : !llvm.ptr,
     %lb : i64, %ub : i64, %step : i64) {
   %it = omp.iterator(%iv: i64) = (%lb to %ub step %step) {
     omp.yield(%addr : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
 
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
@@ -444,7 +476,7 @@ llvm.func @omp_task_depend_iterator_dynamic_mixed(%addr : !llvm.ptr,
     %plain : !llvm.ptr, %lb : i64, %ub : i64, %step : i64) {
   %it = omp.iterator(%iv: i64) = (%lb to %ub step %step) {
     omp.yield(%addr : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
 
   omp.task depend(taskdependout -> %plain : !llvm.ptr, taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
@@ -476,7 +508,7 @@ llvm.func @omp_task_depend_iterator_wide_span(%addr : !llvm.ptr) {
   %max = llvm.mlir.constant(9223372036854775807 : i64) : i64
   %it = omp.iterator(%iv: i64) = (%min to %zero step %max) {
     omp.yield(%addr : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
   }
@@ -496,7 +528,7 @@ llvm.func @omp_task_depend_iterator_min_step(%addr : !llvm.ptr) {
   %min = llvm.mlir.constant(-9223372036854775808 : i64) : i64
   %it = omp.iterator(%iv: i64) = (%zero to %end step %min) {
     omp.yield(%addr : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
     omp.terminator
   }
@@ -508,6 +540,73 @@ llvm.func @omp_task_depend_iterator_min_step(%addr : !llvm.ptr) {
 // CHECK: call i32 @__kmpc_omp_task_with_deps(
 // CHECK-SAME: ptr {{.*}}, i32 {{.*}}, ptr {{.*}}, i32 1,
 
+llvm.func @omp_task_depend_iterator_empty(%addr : !llvm.ptr) {
+  %c1 = llvm.mlir.constant(1 : i64) : i64
+  %c5 = llvm.mlir.constant(5 : i64) : i64
+
+  %it = omp.iterator(%iv: i64) = (%c5 to %c1 step %c1) {
+    omp.yield(%addr : !llvm.ptr)
+  } inclusive -> !omp.iterated<!llvm.ptr>
+
+  omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
+    omp.terminator
+  }
+  llvm.return
+}
+
+// CHECK-LABEL: define void @omp_task_depend_iterator_empty
+// CHECK: %[[EMPTY_DEP_ARR:.*]] = tail call ptr @malloc(i64 0)
+// CHECK: omp_dep_iterator.cond:
+// CHECK: icmp ult i64 %omp_dep_iterator.iv, 0
+// CHECK: call i32 @__kmpc_omp_task_with_deps(
+// CHECK-SAME: ptr @{{[^,]+}}, i32 %{{[^,]+}}, ptr %{{[^,]+}},
+// CHECK-SAME: i32 0, ptr %[[EMPTY_DEP_ARR]], i32 0, ptr null)
+
+llvm.func @omp_task_depend_iterator_exclusive(%addr : !llvm.ptr) {
+  %c1 = llvm.mlir.constant(1 : i64) : i64
+  %c5 = llvm.mlir.constant(5 : i64) : i64
+
+  %it = omp.iterator(%iv: i64) = (%c1 to %c5 step %c1) {
+    omp.yield(%addr : !llvm.ptr)
+  } -> !omp.iterated<!llvm.ptr>
+
+  omp.task depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) {
+    omp.terminator
+  }
+  llvm.return
+}
+
+// CHECK-LABEL: define void @omp_task_depend_iterator_exclusive
+// CHECK: %[[EXCLUSIVE_DEP_ARR:.*]] = tail call ptr @malloc(i64 80)
+// CHECK: omp_dep_iterator.cond:
+// CHECK: icmp ult i64 %omp_dep_iterator.iv, 4
+// CHECK: call i32 @__kmpc_omp_task_with_deps(
+// CHECK-SAME: ptr @{{[^,]+}}, i32 %{{[^,]+}}, ptr %{{[^,]+}},
+// CHECK-SAME: i32 4, ptr %[[EXCLUSIVE_DEP_ARR]], i32 0, ptr null)
+
+// An inclusive i8 range may need 256 entries. Its trip count must not wrap
+// when one is added to the span in the range's own type.
+llvm.func @omp_taskwait_iterator_full_i8_range(%addr : !llvm.ptr) {
+  %lo = llvm.mlir.constant(-128 : i8) : i8
+  %hi = llvm.mlir.constant(127 : i8) : i8
+  %step = llvm.mlir.constant(1 : i8) : i8
+  %it = omp.iterator(%iv: i8) = (%lo to %hi step %step) {
+    %element = llvm.getelementptr %addr[%iv]
+        : (!llvm.ptr, i8) -> !llvm.ptr, i8
+    omp.yield(%element : !llvm.ptr)
+  } inclusive -> !omp.iterated<!llvm.ptr>
+  omp.taskwait depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>)
+  llvm.return
+}
+
+// CHECK-LABEL: define void @omp_taskwait_iterator_full_i8_range
+// CHECK: omp_dep_iterator.cond:
+// CHECK: icmp ult i64 %omp_dep_iterator.iv, 256
+// CHECK: omp_dep_iterator.body:
+// CHECK: getelementptr i8, ptr %{{.*}}, i8 %{{.*}}
+// CHECK: call void @__kmpc_omp_taskwait_deps_51(
+// CHECK-SAME: i32 256, ptr
+
 //--- target.mlir
 
 // --------------------------------------------------------------------
@@ -525,7 +624,7 @@ module attributes {omp.is_target_device = false, omp.target_triples = ["amdgcn-a
 
     %it = omp.iterator(%iv: i64) = (%c1 to %c10 step %step) {
       omp.yield(%addr : !llvm.ptr)
-    } -> !omp.iterated<!llvm.ptr>
+    } inclusive -> !omp.iterated<!llvm.ptr>
 
     %map = omp.map.info var_ptr(%addr : !llvm.ptr, i32) map_clauses(to) capture(ByRef) name("data") -> !llvm.ptr
     omp.target kernel_type(generic) depend(taskdependin -> %it : !omp.iterated<!llvm.ptr>) map_entries(%map -> %arg0 : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-taskwait-depend.mlir b/mlir/test/Target/LLVMIR/openmp-taskwait-depend.mlir
index 305cbf9c275181..d8eb1ec73ae083 100644
--- a/mlir/test/Target/LLVMIR/openmp-taskwait-depend.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-taskwait-depend.mlir
@@ -18,7 +18,7 @@ llvm.func @taskwait_depend_iterator(%x: !llvm.ptr) {
 
   %ix = omp.iterator(%i: i64) = (%c1 to %c10 step %step) {
     omp.yield(%x : !llvm.ptr)
-  } -> !omp.iterated<!llvm.ptr>
+  } inclusive -> !omp.iterated<!llvm.ptr>
   omp.taskwait depend(taskdependin -> %ix : !omp.iterated<!llvm.ptr>)
   llvm.return
 }

>From c79818cae80033c9c0c34694d99075ec733be06c Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 14 Oct 2026 12:10:43 -0500
Subject: [PATCH 02/12] Select map and motion iterators from source references

Map and motion locators chose their iterator ranges from the folded
designator, so a reference such as a(0*i + 1) lost its iterator and was
mapped unconditionally, even when the range was empty.

Build Map, To and From locator lists with the source iterator references
already used for AFFINITY and DEPEND, and select each locator's ranges from
them. Locators without an iterator modifier are unchanged.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp  | 12 ++----------
 flang/lib/Lower/OpenMP/Clauses.cpp          |  9 ++++++---
 flang/test/Lower/OpenMP/iterator-ranges.f90 | 21 +++++++++++++++++++++
 3 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 2c9f3713154ba1..06c1043ba0c584 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2009,16 +2009,8 @@ void ClauseProcessor::processMapObjectsWithIterator(
   // Objects in an iterator-modified clause may independently reference
   // iterator variables, so handle each object separately.
   for (const omp::Object &object : objects) {
-    llvm::SmallPtrSet<const semantics::Symbol *, 4> referencedSymbols;
-    if (const auto &ref = object.ref())
-      for (const semantics::SymbolRef &symbol : evaluate::CollectSymbols(*ref))
-        referencedSymbols.insert(&symbol->GetUltimate());
-
-    llvm::SmallVector<IteratorRange> objectRanges;
-    for (const IteratorRange &range : iteratorRanges)
-      if (referencedSymbols.contains(&range.ivSym->GetUltimate()))
-        objectRanges.push_back(range);
-
+    llvm::SmallVector<IteratorRange> objectRanges =
+        getIteratorRangesForObject(object, iteratorRanges);
     if (!objectRanges.empty()) {
       if (hasUnsupportedReferenceModifier)
         TODO(clauseLocation,
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index ba2adcd74138b7..50750e5023597f 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1080,10 +1080,11 @@ From make(const parser::OmpClause::From &inp,
     return std::nullopt;
   }();
 
+  auto objects = makeIteratorObjects(t5, iterator, semaCtx);
   return From{{/*Expectation=*/maybeExp,
                /*Mappers=*/std::move(mappers),
                /*Iterator=*/std::move(iterator),
-               /*LocatorList=*/makeObjects(t5, semaCtx)}};
+               /*LocatorList=*/std::move(objects)}};
 }
 
 // Full: empty
@@ -1398,12 +1399,13 @@ Map make(const parser::OmpClause::Map &inp,
     return std::nullopt;
   }();
 
+  auto objects = makeIteratorObjects(t2, iterator, semaCtx);
   return Map{{/*MapType=*/std::move(type),
               /*MapTypeModifiers=*/std::move(maybeTypeMods),
               /*AttachModifier=*/std::move(attachMod),
               /*RefModifier=*/std::move(refMod), /*Mapper=*/std::move(mappers),
               /*Iterator=*/std::move(iterator),
-              /*LocatorList=*/makeObjects(t2, semaCtx)}};
+              /*LocatorList=*/std::move(objects)}};
 }
 
 Match make(const parser::OmpClause::Match &inp,
@@ -1820,10 +1822,11 @@ To make(const parser::OmpClause::To &inp,
     return std::nullopt;
   }();
 
+  auto objects = makeIteratorObjects(t5, iterator, semaCtx);
   return To{{/*Expectation=*/maybeExp,
              /*Mappers=*/{std::move(mappers)},
              /*Iterator=*/std::move(iterator),
-             /*LocatorList=*/makeObjects(t5, semaCtx)}};
+             /*LocatorList=*/std::move(objects)}};
 }
 
 UnifiedAddress make(const parser::OmpClause::UnifiedAddress &inp,
diff --git a/flang/test/Lower/OpenMP/iterator-ranges.f90 b/flang/test/Lower/OpenMP/iterator-ranges.f90
index ebe576f99997b5..b017126aa89edd 100644
--- a/flang/test/Lower/OpenMP/iterator-ranges.f90
+++ b/flang/test/Lower/OpenMP/iterator-ranges.f90
@@ -124,6 +124,27 @@ subroutine unused_empty_range(a)
 ! CAN: omp.iterator(%{{[^:]+}}: i32) =
 ! CAN-SAME: (%[[ONE]] to %[[FOUR]] step %[[ONE]])
 
+! A reference removed by folding still selects its range, so an empty range
+! removes the locator instead of mapping it unconditionally.
+subroutine folded_reference(a, n)
+  integer, parameter :: stride = 0
+  integer :: a(10), n
+  !$omp target update from(iterator(i=1:n): a(stride*i + 1))
+  !$omp target update to(iterator(i=1:n): a(stride*i + 1))
+  !$omp target enter data map(iterator(i=1:0), to: a(1 + 0*i))
+end
+! CHECK-LABEL: func.func @_QPfolded_reference
+! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.target_update map_iterated(
+! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.target_update map_iterated(
+! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.target_enter_data map_iterated(
+! CAN-LABEL: func.func @_QPfolded_reference
+! CAN: omp.target_update map_iterated(
+! CAN: omp.target_update map_iterated(
+! CAN: omp.target_enter_data map_iterated(
+
 ! Values beyond the index width must reach the iterator and its body intact.
 subroutine wide_range(a, hi, step)
   integer :: a(2)

>From f92a3f96d4cab2faff6efa63e84f9f88b2a56673 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Thu, 15 Oct 2026 11:42:03 -0500
Subject: [PATCH 03/12] Reject iterator map locators that lowering cannot
 represent

Several locators in iterator-modified MAP, TO and FROM clauses reached
lowering paths that crash or silently map the whole base. A locator with an
array-valued parent, such as v%p(1:1)%a(i) or x(1:2)%c, has a base with a
second ranked part. Items that do not reference an iterator take ordinary map
lowering, which aborts on substrings, complex parts and coindexed items, and
maps the whole base for vector subscripts, non-unit strides and constant
zero-sized sections.

Report these locators as not yet implemented. A scalar parent element, as in
v%p(1)%a(i), still lowers.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 41 +++++++-
 flang/lib/Lower/OpenMP/Utils.cpp              | 10 ++
 flang/lib/Lower/OpenMP/Utils.h                |  1 +
 .../declare-mapper-iterator-unsupported.f90   | 72 ++++++++++++++
 ...p-motion-iterator-unsupported-locators.f90 | 95 +++++++++++++++++++
 .../Lower/OpenMP/declare-mapper-iterator.f90  | 28 ++++++
 6 files changed, 246 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 06c1043ba0c584..b4f4a944e8a32f 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1956,6 +1956,35 @@ void ClauseProcessor::processMapObjects(
   }
 }
 
+// Ordinary map bounds report these sections as errors without failing, and
+// then map the whole base.
+static bool hasUnsupportedMapSection(const omp::SomeExpr &ref) {
+  if (evaluate::HasVectorSubscript(ref))
+    return true;
+  std::optional<evaluate::DataRef> dataRef = evaluate::ExtractDataRef(ref);
+  const auto *arrayRef =
+      dataRef ? std::get_if<evaluate::ArrayRef>(&dataRef->u) : nullptr;
+  if (!arrayRef)
+    return false;
+  for (const evaluate::Subscript &subscript : arrayRef->subscript()) {
+    const auto *triplet = std::get_if<evaluate::Triplet>(&subscript.u);
+    if (!triplet)
+      continue;
+    std::optional<std::int64_t> stride =
+        evaluate::ToInt64(triplet->GetStride());
+    if (!stride || *stride != 1)
+      return true;
+    std::optional<std::int64_t> lower, upper;
+    if (const auto &expr = triplet->lower())
+      lower = evaluate::ToInt64(*expr);
+    if (const auto &expr = triplet->upper())
+      upper = evaluate::ToInt64(*expr);
+    if (lower && upper && *upper < *lower)
+      return true;
+  }
+  return false;
+}
+
 // Lower iterator-dependent locators to mapIterated and other locators to
 // mapVars.
 void ClauseProcessor::processMapObjectsWithIterator(
@@ -2011,17 +2040,19 @@ void ClauseProcessor::processMapObjectsWithIterator(
   for (const omp::Object &object : objects) {
     llvm::SmallVector<IteratorRange> objectRanges =
         getIteratorRangesForObject(object, iteratorRanges);
+    IteratorMapObjectAnalysis analysis = analyzeIteratorMapObject(object);
     if (!objectRanges.empty()) {
       if (hasUnsupportedReferenceModifier)
         TODO(clauseLocation,
              "iterator modifier with reference or attach modifier");
-      IteratorMapObjectAnalysis analysis = analyzeIteratorMapObject(object);
       bool isMapperVariable = isDeclareMapperVariable(analysis);
       if (analysis.isDerivedTypeMember && !isMapperVariable)
         TODO(clauseLocation, "iterator modifier with derived type member map");
       if (declareMapper && analysis.rootSym && !isMapperVariable)
         TODO(clauseLocation,
              "iterator modifier with locator outside declare mapper variable");
+      if (analysis.hasArrayValuedParent)
+        TODO(clauseLocation, "iterator modifier with array-valued parent");
       if (!analysis.arrayRef)
         TODO(clauseLocation, "object type not supported by iterator modifier");
       if (const auto *symbol{object.sym()};
@@ -2033,6 +2064,14 @@ void ClauseProcessor::processMapObjectsWithIterator(
       continue;
     }
 
+    // Ordinary map lowering cannot lower these locators either.
+    if (analysis.hasArrayValuedParent)
+      TODO(clauseLocation, "iterator modifier with array-valued parent");
+    if (object.ref() && (!evaluate::ExtractDataRef(*object.ref()) ||
+                         evaluate::ExtractCoarrayRef(*object.ref())))
+      TODO(clauseLocation, "object type not supported by iterator modifier");
+    if (object.ref() && hasUnsupportedMapSection(*object.ref()))
+      TODO(clauseLocation, "iterator modifier with unsupported array section");
     omp::ObjectList singleObj{object};
     processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
                       parentMemberIndices, result.mapVars, mapObjects,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 156670dc4c78db..14926c11b2b4cf 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1454,6 +1454,16 @@ IteratorMapObjectAnalysis analyzeIteratorMapObject(const omp::Object &object) {
   analysis.isDerivedTypeMember =
       component || (arrayRef && arrayRef->base().UnwrapComponent());
 
+  // A ranked part before the last one, as in v%p(1:1)%a(i) or x(1:2)%c, can be
+  // neither kept in an array base nor described by map bounds.
+  const evaluate::Component *lastComponent =
+      component ? component
+                : (arrayRef ? arrayRef->base().UnwrapComponent() : nullptr);
+  if (lastComponent && lastComponent->base().Rank() > 0) {
+    analysis.hasArrayValuedParent = true;
+    return analysis;
+  }
+
   if (!arrayRef || arrayRef->subscript().empty() ||
       evaluate::ExtractCoarrayRef(*dataRef))
     return analysis;
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 743df47c3b23d3..95a4528925d77c 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -262,6 +262,7 @@ struct IteratorMapInfo {
 struct IteratorMapObjectAnalysis {
   const semantics::Symbol *rootSym = nullptr;
   bool isDerivedTypeMember = false;
+  bool hasArrayValuedParent = false;
   // Own the validated reference so lowering can reuse it after analysis.
   std::optional<evaluate::ArrayRef> arrayRef;
 };
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
index d22b0708d5d303..645404b47af8f7 100644
--- a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
+++ b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
@@ -11,6 +11,18 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
 ! RUN:   -fopenmp-version=52 -o - %t/object-type.f90 2>&1 | \
 ! RUN:   FileCheck %t/object-type.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/section-parent.f90 2>&1 | \
+! RUN:   FileCheck %t/section-parent.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/array-parent.f90 2>&1 | \
+! RUN:   FileCheck %t/array-parent.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/nested-parent.f90 2>&1 | \
+! RUN:   FileCheck %t/nested-parent.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-parent.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-parent.f90
 
 ! Unsupported iterator locators inside declare mapper.
 
@@ -63,3 +75,63 @@ module declare_mapper_iterator_object_type
 
   !$omp declare mapper(mm: t :: v) map(iterator(i = 1:10): v%c(i)%re)
 end module
+
+!--- section-parent.f90
+! CHECK: not yet implemented: iterator modifier with array-valued parent
+
+module declare_mapper_iterator_section_parent
+  type :: inner_t
+    integer :: a(2)
+  end type
+  type :: outer_t
+    type(inner_t) :: p(1)
+  end type
+
+  !$omp declare mapper(m: outer_t :: v) map(iterator(i=1:1), to: v%p(1:1)%a(i))
+end module
+
+!--- array-parent.f90
+! CHECK: not yet implemented: iterator modifier with array-valued parent
+
+module declare_mapper_iterator_array_parent
+  type :: inner_t
+    integer :: a(2)
+  end type
+  type :: outer_t
+    type(inner_t) :: p(1)
+  end type
+
+  !$omp declare mapper(m: outer_t :: v) map(iterator(i=1:1), to: v%p%a(i))
+end module
+
+!--- nested-parent.f90
+! CHECK: not yet implemented: iterator modifier with array-valued parent
+
+module declare_mapper_iterator_nested_parent
+  type :: inner_t
+    integer :: a(2)
+  end type
+  type :: middle_t
+    type(inner_t) :: p(1)
+  end type
+  type :: outer_t
+    type(middle_t) :: q(1)
+  end type
+
+  !$omp declare mapper(m: outer_t :: v) &
+  !$omp& map(iterator(i=1:1), to: v%q(1:1)%p(1)%a(i))
+end module
+
+!--- unreferenced-parent.f90
+! CHECK: not yet implemented: iterator modifier with array-valued parent
+
+module declare_mapper_iterator_unreferenced_parent
+  type :: inner_t
+    integer :: a(2)
+  end type
+  type :: outer_t
+    type(inner_t) :: p(1)
+  end type
+
+  !$omp declare mapper(m: outer_t :: v) map(iterator(i=1:1), to: v%p(1:1)%a(1))
+end module
diff --git a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90 b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
index 93f7fb4d1e7837..727582057ab748 100644
--- a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
+++ b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
@@ -14,6 +14,30 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
 ! RUN:   -fopenmp-version=52 -o - %t/motion-optional.f90 2>&1 | \
 ! RUN:   FileCheck %t/motion-optional.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-parent.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-parent.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-section-member.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-section-member.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-substring.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-substring.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-complex-part.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-complex-part.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fcoarray \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-coindexed.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-coindexed.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-vector.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-vector.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-stride.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-stride.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-empty.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-empty.f90
 
 ! Unsupported iterator locators in map and motion clauses.
 
@@ -75,3 +99,74 @@ subroutine target_update_optional_iterator(a, n)
   ! CHECK: not yet implemented: iterator modifier with optional locator
   !$omp target update to(iterator(i = 1:n): a(i))
 end subroutine
+
+!--- unreferenced-parent.f90
+subroutine target_update_unreferenced_array_parent()
+  type :: s
+    integer :: a(2)
+  end type
+  type(s) :: x(2)
+
+  ! CHECK: not yet implemented: iterator modifier with array-valued parent
+  !$omp target update to(iterator(i = 1:1): x(1:2)%a(1))
+end subroutine
+
+!--- unreferenced-section-member.f90
+subroutine target_exit_data_unreferenced_section_member()
+  type :: s
+    integer :: c
+  end type
+  type(s) :: x(4)
+
+  ! CHECK: not yet implemented: iterator modifier with array-valued parent
+  !$omp target exit data map(iterator(i = 1:1), from: x(1:2)%c)
+end subroutine
+
+!--- unreferenced-substring.f90
+subroutine target_enter_data_unreferenced_substring()
+  character(len=4) :: s
+
+  ! CHECK: not yet implemented: object type not supported by iterator modifier
+  !$omp target enter data map(iterator(i = 1:1), to: s(1:2))
+end subroutine
+
+!--- unreferenced-complex-part.f90
+subroutine target_enter_data_unreferenced_complex_part()
+  complex :: z
+
+  ! CHECK: not yet implemented: object type not supported by iterator modifier
+  !$omp target enter data map(iterator(i = 1:1), to: z%re)
+end subroutine
+
+!--- unreferenced-coindexed.f90
+subroutine target_update_unreferenced_coindexed(a)
+  integer :: a(2)
+  integer, save :: ca(2)[*]
+
+  ! CHECK: not yet implemented: object type not supported by iterator modifier
+  !$omp target update to(iterator(i = 1:2): a(i), ca(1)[1])
+end subroutine
+
+!--- unreferenced-vector.f90
+subroutine target_exit_data_unreferenced_vector(a)
+  integer :: a(4)
+
+  ! CHECK: not yet implemented: iterator modifier with unsupported array section
+  !$omp target exit data map(iterator(i = 1:1), from: a([1, 3]))
+end subroutine
+
+!--- unreferenced-stride.f90
+subroutine target_update_unreferenced_stride(a)
+  integer :: a(4)
+
+  ! CHECK: not yet implemented: iterator modifier with unsupported array section
+  !$omp target update from(iterator(i = 1:1): a(1:4:2))
+end subroutine
+
+!--- unreferenced-empty.f90
+subroutine target_exit_data_unreferenced_empty(a)
+  integer :: a(4)
+
+  ! CHECK: not yet implemented: iterator modifier with unsupported array section
+  !$omp target exit data map(iterator(i = 1:1), from: a(1:0))
+end subroutine
diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
index 19061c3650ecd4..9dd4ec1e1c96f8 100644
--- a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -12,6 +12,16 @@
 ! RUN:   -J %t %t/consumer.f90 -o - | FileCheck %t/consumer.f90
 
 !--- maps.f90
+subroutine declare_mapper_scalar_parent()
+  type :: inner_t
+    integer :: a(2)
+  end type
+  type :: outer_t
+    type(inner_t) :: p(3)
+  end type
+  !$omp declare mapper(m: outer_t :: v) map(iterator(i = 1:2): v%p(2)%a(i))
+end
+
 subroutine declare_mapper_nondefault_lb()
   type :: t
     integer :: a(-2:7)
@@ -236,6 +246,24 @@ subroutine declare_mapper_iterator(arg)
 ! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
 ! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
 
+! A scalar parent element keeps a single ranked part in the array base.
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_scalar_parentm
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFdeclare_mapper_scalar_parentEv")
+! CHECK:   %[[IT:.*]] = omp.iterator(%{{.*}}: i32) =
+! CHECK:     fir.store %{{.*}} to %[[IV_MEM:.*]] : !fir.ref<i32>
+! CHECK:     %[[IV_DECL:.*]]:2 = hlfir.declare %[[IV_MEM]]
+! CHECK:     %[[P_IDX:.*]] = arith.constant 2 : index
+! CHECK:     %[[P:.*]] = hlfir.designate %[[DECL]]#0{"p"} <%{{.*}}> (%[[P_IDX]])
+! CHECK:     %[[A:.*]] = hlfir.designate %[[P]]{"a"} {{.*}} -> !fir.ref<!fir.array<2xi32>>
+! CHECK:     %[[IV:.*]] = fir.load %[[IV_DECL]]#0 : !fir.ref<i32>
+! CHECK:     %[[IV_I64:.*]] = fir.convert %[[IV]] : (i32) -> i64
+! CHECK:     %[[IV_IDX:.*]] = fir.convert %[[IV_I64]] : (i64) -> index
+! CHECK:     %[[LB:.*]] = arith.subi %[[IV_IDX]], %{{.*}} : index
+! CHECK:     %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[LB]] : index)
+! CHECK:     %[[MAP:.*]] = omp.map.info var_ptr(%[[A]] : !fir.ref<!fir.array<2xi32>>, !fir.array<2xi32>) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]])
+! CHECK:     omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
 !--- ranges.f90
 subroutine mapper_ranges()
   type :: t

>From 916e0ec90efed668b5b39358402759f4ca4d672d Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Thu, 15 Oct 2026 12:27:30 -0500
Subject: [PATCH 04/12] Keep declare mapper iterator clauses within the mapper
 region

Only the mapper variable and the iterators have values in the declare mapper
region. An item without an iterator reference skipped the check that it is
rooted in the mapper variable, and variables of the enclosing scope used in
subscripts or iterator ranges were lowered to values defined outside the
region. Statement cleanups were also emitted outside the region, and some
cleanups and intrinsics need control flow that the single-block region cannot
hold.

Report such references, and any declare mapper region that uses an outside
value or needs control flow, as not yet implemented. Emit the remaining
statement cleanups before the mapper's terminator. References through the
mapper variable, as in iterator(i=1:v%n): v%a(v%idx(i)), still lower.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    |  99 +++++++++++----
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  28 +++++
 .../declare-mapper-iterator-unsupported.f90   | 118 ++++++++++++++++++
 .../Lower/OpenMP/declare-mapper-iterator.f90  |  54 ++++++++
 4 files changed, 276 insertions(+), 23 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index b4f4a944e8a32f..616382dc67e6bd 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1956,6 +1956,72 @@ void ClauseProcessor::processMapObjects(
   }
 }
 
+static mlir::omp::DeclareMapperOp
+getEnclosingDeclareMapper(lower::AbstractConverter &converter) {
+  return mlir::dyn_cast_if_present<mlir::omp::DeclareMapperOp>(
+      converter.getFirOpBuilder().getRegion().getParentOp());
+}
+
+static bool isDeclareMapperVariable(lower::AbstractConverter &converter,
+                                    mlir::omp::DeclareMapperOp declareMapper,
+                                    const semantics::Symbol *symbol) {
+  if (!declareMapper || !symbol)
+    return false;
+
+  mlir::Value address = converter.getSymbolAddress(*symbol);
+  while (address) {
+    if (auto declare = address.getDefiningOp<hlfir::DeclareOp>()) {
+      address = declare.getMemref();
+      continue;
+    }
+    if (auto declare = address.getDefiningOp<fir::DeclareOp>()) {
+      address = declare.getMemref();
+      continue;
+    }
+    break;
+  }
+  return address == declareMapper.getRegion().front().getArgument(0);
+}
+
+// Only the mapper variable and the iterators have values in the mapper region.
+// Locator roots are diagnosed per item.
+static void checkDeclareMapperIteratorReferences(
+    lower::AbstractConverter &converter,
+    mlir::omp::DeclareMapperOp declareMapper, mlir::Location loc,
+    const omp::clause::Iterator &iterator, const omp::ObjectList &objects) {
+  llvm::SmallPtrSet<const semantics::Symbol *, 4> iteratorSymbols;
+  for (const auto &specifier : iterator)
+    iteratorSymbols.insert(&std::get<1>(specifier.t).sym()->GetUltimate());
+
+  auto check = [&](const omp::SomeExpr &expr, const semantics::Symbol *root) {
+    for (const semantics::Symbol &symbol : evaluate::CollectSymbols(expr)) {
+      const semantics::Symbol &ultimate = symbol.GetUltimate();
+      if (&ultimate == root || iteratorSymbols.contains(&ultimate) ||
+          ultimate.owner().IsDerivedType() ||
+          !(semantics::IsVariableName(ultimate) ||
+            semantics::IsProcedurePointer(ultimate)))
+        continue;
+      if (!isDeclareMapperVariable(converter, declareMapper, &ultimate))
+        TODO(loc, "iterator modifier with reference outside declare mapper "
+                  "variable");
+    }
+  };
+
+  for (const auto &specifier : iterator) {
+    const auto &[lb, ub, step] = std::get<2>(specifier.t).t;
+    check(lb, nullptr);
+    check(ub, nullptr);
+    if (step)
+      check(*step, nullptr);
+  }
+  for (const omp::Object &object : objects) {
+    if (const auto &ref = object.ref()) {
+      const semantics::Symbol *root = analyzeIteratorMapObject(object).rootSym;
+      check(*ref, root ? &root->GetUltimate() : nullptr);
+    }
+  }
+}
+
 // Ordinary map bounds report these sections as errors without failing, and
 // then map the whole base.
 static bool hasUnsupportedMapSection(const omp::SomeExpr &ref) {
@@ -2003,28 +2069,7 @@ void ClauseProcessor::processMapObjectsWithIterator(
     return;
   }
 
-  auto declareMapper = mlir::dyn_cast_if_present<mlir::omp::DeclareMapperOp>(
-      converter.getFirOpBuilder().getRegion().getParentOp());
-  auto isDeclareMapperVariable =
-      [&](const IteratorMapObjectAnalysis &analysis) {
-        if (!declareMapper || !analysis.rootSym)
-          return false;
-
-        mlir::Value rootAddress = converter.getSymbolAddress(*analysis.rootSym);
-        while (rootAddress) {
-          if (auto declare = rootAddress.getDefiningOp<hlfir::DeclareOp>()) {
-            rootAddress = declare.getMemref();
-            continue;
-          }
-          if (auto declare = rootAddress.getDefiningOp<fir::DeclareOp>()) {
-            rootAddress = declare.getMemref();
-            continue;
-          }
-          break;
-        }
-
-        return rootAddress == declareMapper.getRegion().front().getArgument(0);
-      };
+  auto declareMapper = getEnclosingDeclareMapper(converter);
 
   constexpr mlir::omp::ClauseMapFlags unsupportedReferenceModifiers =
       mlir::omp::ClauseMapFlags::ref_ptr | mlir::omp::ClauseMapFlags::ref_ptee |
@@ -2045,7 +2090,8 @@ void ClauseProcessor::processMapObjectsWithIterator(
       if (hasUnsupportedReferenceModifier)
         TODO(clauseLocation,
              "iterator modifier with reference or attach modifier");
-      bool isMapperVariable = isDeclareMapperVariable(analysis);
+      bool isMapperVariable =
+          isDeclareMapperVariable(converter, declareMapper, analysis.rootSym);
       if (analysis.isDerivedTypeMember && !isMapperVariable)
         TODO(clauseLocation, "iterator modifier with derived type member map");
       if (declareMapper && analysis.rootSym && !isMapperVariable)
@@ -2072,6 +2118,10 @@ void ClauseProcessor::processMapObjectsWithIterator(
       TODO(clauseLocation, "object type not supported by iterator modifier");
     if (object.ref() && hasUnsupportedMapSection(*object.ref()))
       TODO(clauseLocation, "iterator modifier with unsupported array section");
+    if (declareMapper &&
+        !isDeclareMapperVariable(converter, declareMapper, analysis.rootSym))
+      TODO(clauseLocation,
+           "iterator modifier with locator outside declare mapper variable");
     omp::ObjectList singleObj{object};
     processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
                       parentMemberIndices, result.mapVars, mapObjects,
@@ -2210,6 +2260,9 @@ bool ClauseProcessor::processMap(
       if (directive == llvm::omp::Directive::OMPD_target)
         TODO(clauseLocation, "TARGET construct with MAP iterator modifier");
 
+      if (auto declareMapper = getEnclosingDeclareMapper(converter))
+        checkDeclareMapperIteratorReferences(
+            converter, declareMapper, clauseLocation, *iterator, objects);
       iteratorRanges = lowerIteratorRanges(clause, converter, stmtCtx);
     }
     processMapObjectsWithIterator(
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index ae023bc83e50e9..0b641f72576a51 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -7060,7 +7060,35 @@ genOpenMPDeclareMapperImpl(lower::AbstractConverter &converter,
   List<Clause> clauses = makeClauses(construct.v.Clauses(), semaCtx);
   ClauseProcessor cp(converter, semaCtx, clauses);
   cp.processMap(loc, stmtCtx, clauseOps);
+  // Emit cleanups inside the mapper region, before its terminator.
+  stmtCtx.finalizeAndReset();
   mlir::omp::DeclareMapperInfoOp::create(firOpBuilder, loc, clauseOps);
+
+  // Values of the enclosing procedure are not available in the mapper region.
+  mlir::WalkResult usesOutsideValue = region.walk([&](mlir::Operation *op) {
+    for (mlir::Value operand : op->getOperands())
+      if (!region.isAncestor(operand.getParentRegion()))
+        return mlir::WalkResult::interrupt();
+    return mlir::WalkResult::advance();
+  });
+  if (usesOutsideValue.wasInterrupted())
+    TODO(loc, "declare mapper using a value from outside the mapper region");
+
+  // The mapper region must remain a single block once control flow is lowered.
+  // With an iterator modifier, intrinsic simplification may also add loops.
+  bool hasIterator = llvm::any_of(clauses, [](const Clause &clause) {
+    const auto *map = std::get_if<clause::Map>(&clause.u);
+    return map && std::get<std::optional<clause::Iterator>>(map->t).has_value();
+  });
+  for (mlir::Operation &op : region.front()) {
+    bool mayAddLoops =
+        hasIterator && mlir::isa<hlfir::hlfirDialect>(op.getDialect()) &&
+        !mlir::isa<hlfir::DeclareOp, hlfir::DesignateOp, hlfir::AssociateOp,
+                   hlfir::EndAssociateOp>(op);
+    if (mayAddLoops ||
+        (op.getNumRegions() != 0 && !mlir::isa<mlir::omp::IteratorOp>(op)))
+      TODO(loc, "declare mapper with control flow");
+  }
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
index 645404b47af8f7..492ef3034cbdc0 100644
--- a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
+++ b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
@@ -23,6 +23,27 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
 ! RUN:   -fopenmp-version=52 -o - %t/unreferenced-parent.f90 2>&1 | \
 ! RUN:   FileCheck %t/unreferenced-parent.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/unreferenced-external.f90 2>&1 | \
+! RUN:   FileCheck %t/unreferenced-external.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/host-subscript.f90 2>&1 | \
+! RUN:   FileCheck %t/host-subscript.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/host-range.f90 2>&1 | \
+! RUN:   FileCheck %t/host-range.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/procedure-pointer.f90 2>&1 | \
+! RUN:   FileCheck %t/procedure-pointer.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/parameter-subscript.f90 2>&1 | \
+! RUN:   FileCheck %t/parameter-subscript.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/control-flow.f90 2>&1 | \
+! RUN:   FileCheck %t/control-flow.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/intrinsic-range.f90 2>&1 | \
+! RUN:   FileCheck %t/intrinsic-range.f90
 
 ! Unsupported iterator locators inside declare mapper.
 
@@ -135,3 +156,100 @@ module declare_mapper_iterator_unreferenced_parent
 
   !$omp declare mapper(m: outer_t :: v) map(iterator(i=1:1), to: v%p(1:1)%a(1))
 end module
+
+!--- unreferenced-external.f90
+! CHECK: not yet implemented: iterator modifier with locator outside
+! CHECK-SAME: declare mapper variable
+
+module declare_mapper_iterator_unreferenced_external
+  integer :: tbl(1)
+  type :: t
+    integer :: a(1)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=1:1), to: v%a(i), tbl(1))
+end module
+
+!--- host-subscript.f90
+! CHECK: not yet implemented: iterator modifier with reference outside
+! CHECK-SAME: declare mapper variable
+
+subroutine declare_mapper_iterator_host_subscript(idx)
+  integer, intent(in) :: idx(1)
+  type :: t
+    integer :: a(2)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=1:1), to: v%a(idx(i)))
+end subroutine
+
+!--- host-range.f90
+! CHECK: not yet implemented: iterator modifier with reference outside
+! CHECK-SAME: declare mapper variable
+
+subroutine declare_mapper_iterator_host_range(n)
+  integer, intent(in) :: n
+  type :: t
+    integer :: a(2)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=n:n), to: v%a(1+0*i))
+end subroutine
+
+!--- procedure-pointer.f90
+! CHECK: not yet implemented: iterator modifier with reference outside
+! CHECK-SAME: declare mapper variable
+
+module declare_mapper_iterator_procedure_pointer
+  abstract interface
+    integer function ifn()
+    end function
+  end interface
+  procedure(ifn), pointer :: pp
+  type :: t
+    integer :: a(4)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=1:pp()), to: v%a(i))
+end module
+
+!--- parameter-subscript.f90
+! CHECK: not yet implemented: declare mapper using a value from outside the
+! CHECK-SAME: mapper region
+
+subroutine declare_mapper_iterator_parameter_subscript
+  integer, parameter :: p(4) = [4, 3, 2, 1]
+  type :: t
+    integer :: a(4)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=1:2), to: v%a(p(i)))
+end subroutine
+
+!--- control-flow.f90
+! CHECK: not yet implemented: declare mapper with control flow
+
+subroutine declare_mapper_iterator_control_flow
+  interface
+    function extent() result(r)
+      integer, allocatable :: r
+    end function
+  end interface
+  type :: t
+    integer :: a(4)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=1:extent()), to: v%a(i))
+end subroutine
+
+!--- intrinsic-range.f90
+! CHECK: not yet implemented: declare mapper with control flow
+
+subroutine declare_mapper_iterator_intrinsic_range
+  type :: t
+    integer :: b(4)
+    integer :: a(10)
+  end type
+
+  !$omp declare mapper(m: t :: v) map(iterator(i=1:maxval(v%b)), to: v%a(i))
+end subroutine
diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
index 9dd4ec1e1c96f8..ed4ae1c3693233 100644
--- a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -10,8 +10,21 @@
 ! RUN:   -module-dir %t %t/mapper.f90 -o /dev/null
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
 ! RUN:   -J %t %t/consumer.f90 -o - | FileCheck %t/consumer.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN:   %t/cleanup.f90 -o - | FileCheck %t/cleanup.f90
+! RUN: %flang_fc1 -emit-llvm -O2 -fopenmp -fopenmp-version=52 \
+! RUN:   %t/cleanup.f90 -o - | FileCheck %t/cleanup.f90 --check-prefix=LLVM
 
 !--- maps.f90
+subroutine declare_mapper_mapper_references()
+  type :: t
+    integer :: n
+    integer :: idx(4)
+    integer :: a(4)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:v%n): v%a(v%idx(i)))
+end
+
 subroutine declare_mapper_scalar_parent()
   type :: inner_t
     integer :: a(2)
@@ -264,6 +277,22 @@ subroutine declare_mapper_iterator(arg)
 ! CHECK:     omp.yield(%[[MAP]] : !llvm.ptr)
 ! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
 
+! Ranges and subscripts may reference the mapper variable.
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_mapper_referencesm
+! CHECK:   %[[DECL:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFdeclare_mapper_mapper_referencesEv")
+! CHECK:   %[[N_REF:.*]] = hlfir.designate %[[DECL]]#0{"n"}
+! CHECK:   %[[N:.*]] = fir.load %[[N_REF]] : !fir.ref<i32>
+! CHECK:   %[[IT:.*]] = omp.iterator(%{{.*}}: i32) = (%{{.*}} to %[[N]] step %{{.*}}) {
+! CHECK:     %[[A:.*]] = hlfir.designate %[[DECL]]#0{"a"}
+! CHECK:     %[[IDX:.*]] = hlfir.designate %[[DECL]]#0{"idx"}
+! CHECK:     %[[SUB:.*]] = fir.load %[[IDX]] : !fir.ref<i32>
+! CHECK:     %[[SUB_I64:.*]] = fir.convert %[[SUB]] : (i32) -> i64
+! CHECK:     %[[SUB_IDX:.*]] = fir.convert %[[SUB_I64]] : (i64) -> index
+! CHECK:     %[[LB:.*]] = arith.subi %[[SUB_IDX]], %{{.*}} : index
+! CHECK:     %[[BOUNDS:.*]] = omp.map.bounds lower_bound(%[[LB]] : index) upper_bound(%[[LB]] : index)
+! CHECK:     omp.map.info var_ptr(%[[A]] : {{.*}}) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]])
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
 !--- ranges.f90
 subroutine mapper_ranges()
   type :: t
@@ -303,3 +332,28 @@ subroutine use_iterator_mapper_mod
 ! CHECK: %[[MAP:.*]] = omp.map.info
 ! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
 ! CHECK: omp.declare_mapper.info map_iterated(%[[ITER]]
+
+!--- cleanup.f90
+! Cleanups for the range expression stay inside the single-block mapper region.
+subroutine declare_mapper_range_cleanup()
+  interface
+    integer function extent_of(n)
+      integer :: n
+    end function
+  end interface
+  type :: t
+    integer :: n
+    integer :: a(4)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:extent_of(v%n + 1)): v%a(i))
+end
+
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_range_cleanupm
+! CHECK:   %[[TMP:.*]]:3 = hlfir.associate
+! CHECK:   fir.call @_QPextent_of(%[[TMP]]#0)
+! CHECK:   %[[IT:.*]] = omp.iterator
+! CHECK:   } inclusive -> !omp.iterated<!llvm.ptr>
+! CHECK-NEXT: hlfir.end_associate %[[TMP]]#1, %[[TMP]]#2
+! CHECK-NEXT: omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! LLVM: define void @declare_mapper_range_cleanup_()

>From 1e82cdae26faf98324e7fd595e8a3cf2ebf849c0 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Thu, 15 Oct 2026 13:46:46 -0500
Subject: [PATCH 05/12] Check which iterator range each locator uses

The range tests checked that each locator got an iterator, but not which
range, so exchanging the ranges still passed. Bind each range to the component
or array it maps, both before and after canonicalization.
---
 .../Lower/OpenMP/declare-mapper-iterator.f90  | 11 +++++---
 flang/test/Lower/OpenMP/iterator-ranges.f90   | 28 ++++++++++++++++---
 2 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
index ed4ae1c3693233..ea4a24af7140d6 100644
--- a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -303,14 +303,17 @@ subroutine mapper_ranges()
 end
 
 ! CHECK-LABEL: omp.declare_mapper @_QQFmapper_rangesm
-! CHECK: %[[A:.*]] = omp.iterator(%{{[^:]+}}: i32) =
+! CHECK-DAG: %[[TWO:.*]] = arith.constant 2 : i32
+! CHECK-DAG: %[[FOUR:.*]] = arith.constant 4 : i32
+! CHECK: %[[A:.*]] = omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[FOUR]] step
+! CHECK: hlfir.designate %{{.*}}{"a"}
 ! CHECK: omp.map.info
 ! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
-! CHECK: %[[B:.*]] = omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: %[[B:.*]] = omp.iterator(%{{[^:]+}}: i32) = (%[[TWO]] to %{{[^ ]+}} step
+! CHECK: hlfir.designate %{{.*}}{"b"}
 ! CHECK: omp.map.info
 ! CHECK: } inclusive -> !omp.iterated<!llvm.ptr>
-! CHECK: omp.declare_mapper.info
-! CHECK-SAME: map_iterated(%[[A]], %[[B]]
+! CHECK: omp.declare_mapper.info map_iterated(%[[A]], %[[B]]
 
 !--- mapper.f90
 module iterator_mapper_mod
diff --git a/flang/test/Lower/OpenMP/iterator-ranges.f90 b/flang/test/Lower/OpenMP/iterator-ranges.f90
index b017126aa89edd..c0b1f44c0e6dc8 100644
--- a/flang/test/Lower/OpenMP/iterator-ranges.f90
+++ b/flang/test/Lower/OpenMP/iterator-ranges.f90
@@ -100,16 +100,36 @@ subroutine separate_ranges(a, b, c, n)
   !$omp& a(i), b(j), c(i,j))
 end
 ! CHECK-LABEL: func.func @_QPseparate_ranges
+! CHECK-DAG: %[[FOUR:.*]] = arith.constant 4 : i32
+! CHECK-DAG: %[[A:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEa")
+! CHECK-DAG: %[[B:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEb")
+! CHECK-DAG: %[[C:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEc")
+! CHECK-DAG: %[[N:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEn")
+! CHECK: %[[N_VAL:.*]] = fir.load %[[N]]#0 : !fir.ref<i32>
 ! CHECK: fir.call @_QPextent()
-! CHECK: omp.iterator(%{{[^:]+}}: i32) =
-! CHECK: omp.iterator(%{{[^:]+}}: i32) =
+! CHECK: omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[FOUR]] step %{{[^)]+}}) {
+! CHECK:   omp.map.info var_ptr(%[[A]]#0
+! CHECK: omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[N_VAL]] step %{{[^)]+}}) {
+! CHECK:   omp.map.info var_ptr(%[[B]]#0
 ! CHECK: omp.iterator(%{{[^:]+}}: i32, %{{[^:]+}}: i32) =
+! CHECK-SAME: (%{{[^ ]+}} to %[[FOUR]] step %{{[^,]+}}, %{{[^ ]+}} to %[[N_VAL]] step %{{[^)]+}}) {
+! CHECK:   omp.map.info var_ptr(%[[C]]#0
 ! CHECK: omp.target_update
 ! CAN-LABEL: func.func @_QPseparate_ranges
+! CAN-DAG: %[[FOUR:.*]] = arith.constant 4 : i32
+! CAN-DAG: %[[A:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEa")
+! CAN-DAG: %[[B:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEb")
+! CAN-DAG: %[[C:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEc")
+! CAN-DAG: %[[N:.*]]:2 = hlfir.declare {{.*}}uniq_name("_QFseparate_rangesEn")
+! CAN: %[[N_VAL:.*]] = fir.load %[[N]]#0 : !fir.ref<i32>
 ! CAN: fir.call @_QPextent()
-! CAN: omp.iterator(%{{[^:]+}}: i32) =
-! CAN: omp.iterator(%{{[^:]+}}: i32) =
+! CAN: omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[FOUR]] step %{{[^)]+}}) {
+! CAN:   omp.map.info var_ptr(%[[A]]#0
+! CAN: omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[N_VAL]] step %{{[^)]+}}) {
+! CAN:   omp.map.info var_ptr(%[[B]]#0
 ! CAN: omp.iterator(%{{[^:]+}}: i32, %{{[^:]+}}: i32) =
+! CAN-SAME: (%{{[^ ]+}} to %[[FOUR]] step %{{[^,]+}}, %{{[^ ]+}} to %[[N_VAL]] step %{{[^)]+}}) {
+! CAN:   omp.map.info var_ptr(%[[C]]#0
 ! CAN: omp.target_update
 
 subroutine unused_empty_range(a)

>From 5aca394f2dc542444335da672a770b6b5294ddb8 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 20 Oct 2026 13:49:13 -0500
Subject: [PATCH 06/12] Accept parenthesized expressions in declare mapper
 iterator clauses

Parentheses lower to hlfir.no_reassoc, which adds no control flow, but the
single-block check for declare mappers with an iterator modifier rejected it.
Accept it while still rejecting operations that optimization may expand into
loops.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp                 |  2 +-
 .../test/Lower/OpenMP/declare-mapper-iterator.f90 | 15 +++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 0b641f72576a51..b862320c28477c 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -7084,7 +7084,7 @@ genOpenMPDeclareMapperImpl(lower::AbstractConverter &converter,
     bool mayAddLoops =
         hasIterator && mlir::isa<hlfir::hlfirDialect>(op.getDialect()) &&
         !mlir::isa<hlfir::DeclareOp, hlfir::DesignateOp, hlfir::AssociateOp,
-                   hlfir::EndAssociateOp>(op);
+                   hlfir::EndAssociateOp, hlfir::NoReassocOp>(op);
     if (mayAddLoops ||
         (op.getNumRegions() != 0 && !mlir::isa<mlir::omp::IteratorOp>(op)))
       TODO(loc, "declare mapper with control flow");
diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
index ea4a24af7140d6..30df4393717b7c 100644
--- a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -337,6 +337,15 @@ subroutine use_iterator_mapper_mod
 ! CHECK: omp.declare_mapper.info map_iterated(%[[ITER]]
 
 !--- cleanup.f90
+! Parentheses add no control flow to the single-block mapper region.
+subroutine declare_mapper_paren_range()
+  type :: t
+    integer :: n
+    integer :: a(4)
+  end type
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:(v%n)): v%a(i))
+end
+
 ! Cleanups for the range expression stay inside the single-block mapper region.
 subroutine declare_mapper_range_cleanup()
   interface
@@ -359,4 +368,10 @@ integer function extent_of(n)
 ! CHECK-NEXT: hlfir.end_associate %[[TMP]]#1, %[[TMP]]#2
 ! CHECK-NEXT: omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
 
+! CHECK-LABEL: omp.declare_mapper @_QQFdeclare_mapper_paren_rangem
+! CHECK:   %[[N:.*]] = hlfir.no_reassoc
+! CHECK:   %[[IT:.*]] = omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[N]] step
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
+! LLVM: define void @declare_mapper_paren_range_()
 ! LLVM: define void @declare_mapper_range_cleanup_()

>From 443e0067bbaa541daea40f31beae19e81f8e96ee Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 20 Oct 2026 13:49:14 -0500
Subject: [PATCH 07/12] Check every clause of a directive with an iterator
 modifier

Unsupported items in a MAP, TO or FROM clause without an iterator modifier
reached ordinary map lowering even when another clause of the same directive
or declare mapper had one, so they crashed or mapped the whole base depending
on clause order. Once any clause has an iterator modifier, apply the item
checks to all clauses.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 58 ++++++++++++-------
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |  2 +-
 .../declare-mapper-iterator-unsupported.f90   | 17 ++++++
 ...p-motion-iterator-unsupported-locators.f90 | 35 +++++++++++
 flang/test/Lower/OpenMP/iterator-ranges.f90   | 10 ++++
 5 files changed, 99 insertions(+), 23 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 616382dc67e6bd..4011daf7c26789 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2052,17 +2052,17 @@ static bool hasUnsupportedMapSection(const omp::SomeExpr &ref) {
 }
 
 // Lower iterator-dependent locators to mapIterated and other locators to
-// mapVars.
+// mapVars. With an iterator modifier on any clause, check items of all clauses.
 void ClauseProcessor::processMapObjectsWithIterator(
     lower::StatementContext &stmtCtx, mlir::Location clauseLocation,
     const omp::ObjectList &objects,
-    llvm::ArrayRef<IteratorRange> iteratorRanges,
+    llvm::ArrayRef<IteratorRange> iteratorRanges, bool hasIteratorModifier,
     mlir::omp::ClauseMapFlags mapTypeBits,
     std::map<Object, OmpMapParentAndMemberData> &parentMemberIndices,
     mlir::omp::MapClauseOps &result, llvm::SmallVectorImpl<Object> &mapObjects,
     llvm::StringRef mapperIdNameRef, bool isMotionModifier,
     llvm::omp::Directive directive) const {
-  if (iteratorRanges.empty()) {
+  if (!hasIteratorModifier) {
     processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits,
                       parentMemberIndices, result.mapVars, mapObjects,
                       mapperIdNameRef, isMotionModifier, directive);
@@ -2155,6 +2155,18 @@ getMapperIdentifier(lower::AbstractConverter &converter,
   return mapperIdName;
 }
 
+static bool hasIteratorModifier(const List<Clause> &clauses) {
+  auto hasIterator = [](const auto *clause) {
+    return clause && std::get<std::optional<omp::clause::Iterator>>(clause->t)
+                         .has_value();
+  };
+  return llvm::any_of(clauses, [&](const Clause &clause) {
+    return hasIterator(std::get_if<omp::clause::Map>(&clause.u)) ||
+           hasIterator(std::get_if<omp::clause::To>(&clause.u)) ||
+           hasIterator(std::get_if<omp::clause::From>(&clause.u));
+  });
+}
+
 bool ClauseProcessor::processMap(
     mlir::Location currentLocation, lower::StatementContext &stmtCtx,
     mlir::omp::MapClauseOps &result, llvm::omp::Directive directive,
@@ -2166,6 +2178,7 @@ bool ClauseProcessor::processMap(
   llvm::SmallVectorImpl<Object> *ptrMapObjects =
       mapObjects ? mapObjects : &localMapObjects;
   std::map<Object, OmpMapParentAndMemberData> parentMemberIndices;
+  bool hasIterator = hasIteratorModifier(clauses);
 
   auto process = [&](const omp::clause::Map &clause,
                      const parser::CharBlock &source) {
@@ -2250,24 +2263,24 @@ bool ClauseProcessor::processMap(
 
     TodoLocators(currentLocation, objects);
 
-    llvm::SmallVector<IteratorRange> iteratorRanges;
-    if (iterator) {
-      // An executable target region needs a stable base capture for every
-      // iterated map object used in its body. Without such a capture, implicit
-      // mapping adds an ordinary whole-object map and changes the requested
-      // extent and direction. Reject this until omp.target can represent those
-      // captures separately from runtime map entries.
-      if (directive == llvm::omp::Directive::OMPD_target)
-        TODO(clauseLocation, "TARGET construct with MAP iterator modifier");
-
-      if (auto declareMapper = getEnclosingDeclareMapper(converter))
-        checkDeclareMapperIteratorReferences(
-            converter, declareMapper, clauseLocation, *iterator, objects);
-      iteratorRanges = lowerIteratorRanges(clause, converter, stmtCtx);
-    }
+    // An executable target region needs a stable base capture for every
+    // iterated map object used in its body. Without such a capture, implicit
+    // mapping adds an ordinary whole-object map and changes the requested
+    // extent and direction. Reject this until omp.target can represent those
+    // captures separately from runtime map entries.
+    if (iterator && directive == llvm::omp::Directive::OMPD_target)
+      TODO(clauseLocation, "TARGET construct with MAP iterator modifier");
+
+    if (auto declareMapper = getEnclosingDeclareMapper(converter);
+        declareMapper && hasIterator)
+      checkDeclareMapperIteratorReferences(
+          converter, declareMapper, clauseLocation,
+          iterator ? *iterator : omp::clause::Iterator{}, objects);
+    llvm::SmallVector<IteratorRange> iteratorRanges =
+        lowerIteratorRanges(clause, converter, stmtCtx);
     processMapObjectsWithIterator(
-        stmtCtx, clauseLocation, objects, iteratorRanges, mapTypeBits,
-        parentMemberIndices, result, *ptrMapObjects, mapperIdName,
+        stmtCtx, clauseLocation, objects, iteratorRanges, hasIterator,
+        mapTypeBits, parentMemberIndices, result, *ptrMapObjects, mapperIdName,
         /*isMotionModifier=*/false, directive);
   };
 
@@ -2287,6 +2300,7 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
   // target update. Passing OMPD_target_update would disable that synthesis.
   constexpr llvm::omp::Directive mapperDirective =
       llvm::omp::Directive::OMPD_unknown;
+  bool hasIterator = hasIteratorModifier(clauses);
 
   auto callbackFn = [&](const auto &clause, const parser::CharBlock &source) {
     mlir::Location clauseLocation = converter.genLocation(source);
@@ -2306,8 +2320,8 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
     llvm::SmallVector<IteratorRange> iteratorRanges =
         lowerIteratorRanges(clause, converter, stmtCtx);
     processMapObjectsWithIterator(
-        stmtCtx, clauseLocation, objects, iteratorRanges, mapTypeBits,
-        parentMemberIndices, result, mapObjects, mapperIdName,
+        stmtCtx, clauseLocation, objects, iteratorRanges, hasIterator,
+        mapTypeBits, parentMemberIndices, result, mapObjects, mapperIdName,
         /*isMotionModifier=*/true, mapperDirective);
   };
 
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index ea9cb7b0b0ab60..2d864dbf983e2f 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -231,7 +231,7 @@ class ClauseProcessor {
   void processMapObjectsWithIterator(
       lower::StatementContext &stmtCtx, mlir::Location clauseLocation,
       const omp::ObjectList &objects,
-      llvm::ArrayRef<IteratorRange> iteratorRanges,
+      llvm::ArrayRef<IteratorRange> iteratorRanges, bool hasIteratorModifier,
       mlir::omp::ClauseMapFlags mapTypeBits,
       std::map<Object, OmpMapParentAndMemberData> &parentMemberIndices,
       mlir::omp::MapClauseOps &result,
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
index 492ef3034cbdc0..a4926a699d0999 100644
--- a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
+++ b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-unsupported.f90
@@ -44,6 +44,9 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
 ! RUN:   -fopenmp-version=52 -o - %t/intrinsic-range.f90 2>&1 | \
 ! RUN:   FileCheck %t/intrinsic-range.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/later-clause-external.f90 2>&1 | \
+! RUN:   FileCheck %t/later-clause-external.f90
 
 ! Unsupported iterator locators inside declare mapper.
 
@@ -253,3 +256,17 @@ subroutine declare_mapper_iterator_intrinsic_range
 
   !$omp declare mapper(m: t :: v) map(iterator(i=1:maxval(v%b)), to: v%a(i))
 end subroutine
+
+!--- later-clause-external.f90
+! CHECK: not yet implemented: iterator modifier with locator outside
+! CHECK-SAME: declare mapper variable
+
+module declare_mapper_iterator_later_clause_external
+  integer :: tbl(1)
+  type :: t
+    integer :: a(1)
+  end type
+
+  !$omp declare mapper(m: t :: v) &
+  !$omp& map(iterator(i=1:1), to: v%a(i)) map(to: tbl(1))
+end module
diff --git a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90 b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
index 727582057ab748..5076018ae9236e 100644
--- a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
+++ b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
@@ -38,6 +38,15 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
 ! RUN:   -fopenmp-version=52 -o - %t/unreferenced-empty.f90 2>&1 | \
 ! RUN:   FileCheck %t/unreferenced-empty.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/later-clause-complex-part.f90 2>&1 | \
+! RUN:   FileCheck %t/later-clause-complex-part.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/earlier-clause-vector.f90 2>&1 | \
+! RUN:   FileCheck %t/earlier-clause-vector.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/earlier-map-clause-vector.f90 2>&1 | \
+! RUN:   FileCheck %t/earlier-map-clause-vector.f90
 
 ! Unsupported iterator locators in map and motion clauses.
 
@@ -170,3 +179,29 @@ subroutine target_exit_data_unreferenced_empty(a)
   ! CHECK: not yet implemented: iterator modifier with unsupported array section
   !$omp target exit data map(iterator(i = 1:1), from: a(1:0))
 end subroutine
+
+!--- later-clause-complex-part.f90
+subroutine target_enter_data_later_clause_complex_part(a, z)
+  integer :: a(4)
+  complex :: z
+
+  ! CHECK: not yet implemented: object type not supported by iterator modifier
+  !$omp target enter data map(iterator(i = 1:2), to: a(i)) map(to: z%re)
+end subroutine
+
+!--- earlier-clause-vector.f90
+! TO clauses are processed before FROM clauses.
+subroutine target_update_earlier_clause_vector(a, b)
+  integer :: a(4), b(4)
+
+  ! CHECK: not yet implemented: iterator modifier with unsupported array section
+  !$omp target update from(iterator(i = 1:2): a(i)) to(b([1, 3]))
+end subroutine
+
+!--- earlier-map-clause-vector.f90
+subroutine target_enter_data_earlier_clause_vector(a, b)
+  integer :: a(4), b(4)
+
+  ! CHECK: not yet implemented: iterator modifier with unsupported array section
+  !$omp target enter data map(to: b([1, 3])) map(iterator(i = 1:2), to: a(i))
+end subroutine
diff --git a/flang/test/Lower/OpenMP/iterator-ranges.f90 b/flang/test/Lower/OpenMP/iterator-ranges.f90
index c0b1f44c0e6dc8..de59bd3620db36 100644
--- a/flang/test/Lower/OpenMP/iterator-ranges.f90
+++ b/flang/test/Lower/OpenMP/iterator-ranges.f90
@@ -144,6 +144,16 @@ subroutine unused_empty_range(a)
 ! CAN: omp.iterator(%{{[^:]+}}: i32) =
 ! CAN-SAME: (%[[ONE]] to %[[FOUR]] step %[[ONE]])
 
+! Items of a clause without an iterator modifier still use ordinary maps.
+subroutine other_clause(a, b)
+  integer :: a(4), b(4)
+  !$omp target update to(iterator(i=1:2): a(i)) from(b(1:2))
+end
+! CHECK-LABEL: func.func @_QPother_clause
+! CHECK: %[[IT:.*]] = omp.iterator
+! CHECK: %[[B:.*]] = omp.map.info {{.*}}map_clauses(from) {{.*}}name("b(1:2)")
+! CHECK: omp.target_update map_entries(%[[B]] : {{.*}}) map_iterated(%[[IT]] :
+
 ! A reference removed by folding still selects its range, so an empty range
 ! removes the locator instead of mapping it unconditionally.
 subroutine folded_reference(a, n)

>From 512c444ba52f05313fd6c14e6ac3a91bfe7b2d65 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 20 Oct 2026 14:23:51 -0500
Subject: [PATCH 08/12] Check declare mapper iterator cleanups at FIR instead
 of LLVM IR

The -O2 run only matched the subroutine definitions in LLVM IR,
because an unused mapper is not translated. Stop at FIR and check
that the -O2 HLFIR-to-FIR pipeline adds no control flow to the
mapper regions.
---
 .../Lower/OpenMP/declare-mapper-iterator.f90  | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
index 30df4393717b7c..70fd4cc9e0ad34 100644
--- a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -12,8 +12,8 @@
 ! RUN:   -J %t %t/consumer.f90 -o - | FileCheck %t/consumer.f90
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
 ! RUN:   %t/cleanup.f90 -o - | FileCheck %t/cleanup.f90
-! RUN: %flang_fc1 -emit-llvm -O2 -fopenmp -fopenmp-version=52 \
-! RUN:   %t/cleanup.f90 -o - | FileCheck %t/cleanup.f90 --check-prefix=LLVM
+! RUN: %flang_fc1 -emit-fir -O2 -fopenmp -fopenmp-version=52 \
+! RUN:   %t/cleanup.f90 -o - | FileCheck %t/cleanup.f90 --check-prefix=FIR
 
 !--- maps.f90
 subroutine declare_mapper_mapper_references()
@@ -373,5 +373,16 @@ integer function extent_of(n)
 ! CHECK:   %[[IT:.*]] = omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[N]] step
 ! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
 
-! LLVM: define void @declare_mapper_paren_range_()
-! LLVM: define void @declare_mapper_range_cleanup_()
+! The -O2 HLFIR-to-FIR pipeline adds no control flow to the mapper regions.
+! FIR-LABEL: omp.declare_mapper @_QQFdeclare_mapper_range_cleanupm
+! FIR-NOT:     {{fir\.(if|do_loop|iterate_while)}}
+! FIR:         fir.call @_QPextent_of
+! FIR-NOT:     {{fir\.(if|do_loop|iterate_while)}}
+! FIR:         omp.declare_mapper.info map_iterated
+
+! FIR-LABEL: omp.declare_mapper @_QQFdeclare_mapper_paren_rangem
+! FIR-NOT:     {{fir\.(if|do_loop|iterate_while)}}
+! FIR:         %[[N:.*]] = fir.no_reassoc
+! FIR:         omp.iterator(%{{[^:]+}}: i32) = (%{{[^ ]+}} to %[[N]] step
+! FIR-NOT:     {{fir\.(if|do_loop|iterate_while)}}
+! FIR:         omp.declare_mapper.info map_iterated

>From acbc07fc0feef8ef72c8fa2fbdaa8d29dbfc9e61 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 20 Oct 2026 17:48:48 -0500
Subject: [PATCH 09/12] Reject TARGET constructs that use a declare mapper with
 an iterator

LLVM IR translation cannot yet emit a mapper with iterator map entries,
and an omp.target that uses one aborts in OpenMPIRBuilder instead of
reporting an error.

Report such TARGET constructs as not yet implemented in lowering,
following map members and nested mappers. Also check omp.target in
translation, because FIR passes can attach a mapper after lowering, for
example to a privatized variable of a kind-parameterized type.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 31 ++++++++++
 .../Lower/OpenMP/Todo/target-map-iterator.f90 | 60 ++++++++++++++++++-
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 31 ++++++++++
 mlir/test/Target/LLVMIR/openmp-todo.mlir      | 36 +++++++++++
 4 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index b862320c28477c..e284f6a680655d 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1007,6 +1007,29 @@ extractMappedBaseValues(llvm::ArrayRef<mlir::Value> vars,
   });
 }
 
+static bool
+usesIteratorDeclareMapper(mlir::ModuleOp module, mlir::Value map,
+                          llvm::SmallPtrSetImpl<mlir::Operation *> &visited) {
+  auto mapInfo = map.getDefiningOp<mlir::omp::MapInfoOp>();
+  if (!mapInfo)
+    return false;
+  if (llvm::any_of(mapInfo.getMembers(), [&](mlir::Value member) {
+        return usesIteratorDeclareMapper(module, member, visited);
+      }))
+    return true;
+  mlir::FlatSymbolRefAttr mapperId = mapInfo.getMapperIdAttr();
+  if (!mapperId)
+    return false;
+  auto mapper = module.lookupSymbol<mlir::omp::DeclareMapperOp>(mapperId);
+  if (!mapper || !visited.insert(mapper).second)
+    return false;
+  mlir::omp::DeclareMapperInfoOp info = mapper.getDeclareMapperInfo();
+  return !info.getMapIterated().empty() ||
+         llvm::any_of(info.getMapVars(), [&](mlir::Value var) {
+           return usesIteratorDeclareMapper(module, var, visited);
+         });
+}
+
 static lower::pft::Evaluation *
 getCollapsedLoopEval(lower::pft::Evaluation &eval, int collapseValue) {
   // Return the Evaluation of the innermost collapsed loop, or the current one
@@ -4298,6 +4321,14 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
 
   lower::pft::visitAllSymbols(eval, captureImplicitMap);
 
+  // Translating omp.target cannot yet emit a mapper with iterated entries.
+  llvm::SmallPtrSet<mlir::Operation *, 4> visitedMappers;
+  if (llvm::any_of(clauseOps.mapVars, [&](mlir::Value map) {
+        return usesIteratorDeclareMapper(converter.getModuleOp(), map,
+                                         visitedMappers);
+      }))
+    TODO(loc, "TARGET construct using a declare mapper with iterator modifier");
+
   auto targetOp = mlir::omp::TargetOp::create(firOpBuilder, loc, clauseOps);
 
   llvm::SmallVector<mlir::Value> hasDeviceAddrBaseValues, mapBaseValues;
diff --git a/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90 b/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90
index 9de14b25e4d606..04eb41e22210f3 100644
--- a/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90
+++ b/flang/test/Lower/OpenMP/Todo/target-map-iterator.f90
@@ -1,6 +1,18 @@
+! RUN: rm -rf %t && split-file %s %t
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
-! RUN:   -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN:   -fopenmp-version=52 -o - %t/map-clause.f90 2>&1 | \
+! RUN:   FileCheck %t/map-clause.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/explicit-mapper.f90 2>&1 | \
+! RUN:   FileCheck %t/explicit-mapper.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/default-mapper.f90 2>&1 | \
+! RUN:   FileCheck %t/default-mapper.f90
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %t/nested-mapper.f90 2>&1 | \
+! RUN:   FileCheck %t/nested-mapper.f90
 
+!--- map-clause.f90
 subroutine target_map_iterator()
   integer :: a(8)
   integer :: i
@@ -10,3 +22,49 @@ subroutine target_map_iterator()
   a(1) = 42
   !$omp end target
 end subroutine
+
+!--- explicit-mapper.f90
+subroutine target_explicit_iterator_mapper()
+  type :: t
+    integer :: a(2)
+  end type
+  type(t) :: x
+  !$omp declare mapper(m: t :: v) map(iterator(i = 1:2), to: v%a(i))
+
+  ! CHECK: not yet implemented: TARGET construct using a declare mapper with iterator modifier
+  !$omp target map(mapper(m), tofrom: x)
+  x%a(1) = 1
+  !$omp end target
+end subroutine
+
+!--- default-mapper.f90
+subroutine target_default_iterator_mapper()
+  type :: t
+    integer :: a(2)
+  end type
+  type(t) :: x
+  !$omp declare mapper(t :: v) map(iterator(i = 1:2), to: v%a(i))
+
+  ! CHECK: not yet implemented: TARGET construct using a declare mapper with iterator modifier
+  !$omp target
+  x%a(1) = 1
+  !$omp end target
+end subroutine
+
+!--- nested-mapper.f90
+subroutine target_nested_iterator_mapper()
+  type :: inner
+    integer :: a(2)
+  end type
+  type :: outer
+    type(inner) :: in
+  end type
+  type(outer) :: x
+  !$omp declare mapper(mi: inner :: v) map(iterator(i = 1:2), to: v%a(i))
+  !$omp declare mapper(mo: outer :: w) map(mapper(mi), tofrom: w%in)
+
+  ! CHECK: not yet implemented: TARGET construct using a declare mapper with iterator modifier
+  !$omp target map(mapper(mo), tofrom: x)
+  x%in%a(1) = 1
+  !$omp end target
+end subroutine
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index d7fdc5d2bb0a3f..98b4ba6dbe7ac1 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -426,6 +426,31 @@ static omp::PrivateClauseOp findPrivatizer(Operation *from,
   return privatizer;
 }
 
+/// Returns true if \p map, one of its members, or a mapper reachable from them
+/// has map entries with an iterator modifier.
+static bool usesIteratorMapper(Operation *from, Value map,
+                               SmallPtrSetImpl<Operation *> &visited) {
+  auto mapInfo = map.getDefiningOp<omp::MapInfoOp>();
+  if (!mapInfo)
+    return false;
+  if (llvm::any_of(mapInfo.getMembers(), [&](Value member) {
+        return usesIteratorMapper(from, member, visited);
+      }))
+    return true;
+  FlatSymbolRefAttr mapperId = mapInfo.getMapperIdAttr();
+  if (!mapperId)
+    return false;
+  auto mapper = SymbolTable::lookupNearestSymbolFrom<omp::DeclareMapperOp>(
+      from, mapperId);
+  if (!mapper || !visited.insert(mapper).second)
+    return false;
+  omp::DeclareMapperInfoOp info = mapper.getDeclareMapperInfo();
+  return !info.getMapIterated().empty() ||
+         llvm::any_of(info.getMapVars(), [&](Value var) {
+           return usesIteratorMapper(from, var, visited);
+         });
+}
+
 /// Check whether translation to LLVM IR for the given operation is currently
 /// supported. If not, descriptive diagnostics will be emitted to let users know
 /// this is a not-yet-implemented feature.
@@ -656,6 +681,12 @@ static LogicalResult checkImplementationStatus(Operation &op) {
         checkInReduction(op, result);
         checkMap(op, result);
         checkThreadLimit(op, result);
+        // createTarget cannot report a mapper translation failure.
+        SmallPtrSet<Operation *, 4> visitedMappers;
+        if (llvm::any_of(op.getMapVars(), [&](Value map) {
+              return usesIteratorMapper(op, map, visitedMappers);
+            }))
+          result = todo("mapper with iterator modifier");
       })
       .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
       .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index 2b502ac2ef0288..747a3ee1ca420e 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -879,3 +879,39 @@ module attributes {omp.target_triples = ["amdgcn-amd-amdhsa"]} {
     llvm.return
   }
 }
+
+// -----
+
+module attributes {omp.target_triples = ["amdgcn-amd-amdhsa"]} {
+  omp.declare_mapper @inner_iterator_mapper : !llvm.struct<"inner", (i32)> {
+  ^bb0(%arg: !llvm.ptr):
+    %c0 = llvm.mlir.constant(0 : i64) : i64
+    %c10 = llvm.mlir.constant(10 : i64) : i64
+    %c1 = llvm.mlir.constant(1 : i64) : i64
+    %it = omp.iterator(%iv: i64) = (%c0 to %c10 step %c1) {
+      %m = omp.map.info var_ptr(%arg : !llvm.ptr, !llvm.struct<"inner", (i32)>) map_clauses(tofrom) capture(ByRef) name("") -> !llvm.ptr
+      omp.yield(%m : !llvm.ptr)
+    } -> !omp.iterated<!llvm.ptr>
+    omp.declare_mapper.info map_iterated(%it : !omp.iterated<!llvm.ptr>)
+  }
+
+  omp.declare_mapper @outer_mapper : !llvm.struct<"outer", (struct<"inner", (i32)>)> {
+  ^bb0(%arg: !llvm.ptr):
+    %in = llvm.getelementptr %arg[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<"outer", (struct<"inner", (i32)>)>
+    %m = omp.map.info var_ptr(%in : !llvm.ptr, !llvm.struct<"inner", (i32)>) map_clauses(tofrom) capture(ByRef) mapper(@inner_iterator_mapper) name("") -> !llvm.ptr
+    omp.declare_mapper.info map_entries(%m : !llvm.ptr)
+  }
+
+  // The iterator mapper is reached through a member map and a nested mapper.
+  llvm.func @target_mapper_iterator(%addr : !llvm.ptr) {
+    %field = llvm.getelementptr %addr[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<"wrap", (struct<"outer", (struct<"inner", (i32)>)>)>
+    %member = omp.map.info var_ptr(%field : !llvm.ptr, !llvm.struct<"outer", (struct<"inner", (i32)>)>) map_clauses(tofrom) capture(ByRef) mapper(@outer_mapper) name("") -> !llvm.ptr
+    %parent = omp.map.info var_ptr(%addr : !llvm.ptr, !llvm.struct<"wrap", (struct<"outer", (struct<"inner", (i32)>)>)>) map_clauses(tofrom) capture(ByRef) members(%member : [0] : !llvm.ptr) name("") partial_map(true) -> !llvm.ptr
+    // expected-error at below {{not yet implemented: Unhandled clause mapper with iterator modifier in omp.target operation}}
+    // expected-error at below {{LLVM Translation failed for operation: omp.target}}
+    omp.target kernel_type(generic) map_entries(%parent -> %arg0 : !llvm.ptr) {
+      omp.terminator
+    }
+    llvm.return
+  }
+}

>From 1bb9229811f878e3523bde1127ca7ba2052ffc30 Mon Sep 17 00:00:00 2001
From: Urvi Rav <urvi.rav20 at gmail.com>
Date: Fri, 23 Oct 2026 02:56:43 -0500
Subject: [PATCH 10/12] Update unreferenced-substring.f90 for improved semantic
 diagnostic on main

---
 .../OpenMP/Todo/map-motion-iterator-unsupported-locators.f90   | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90 b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
index 5076018ae9236e..3dbd99d3aed6cd 100644
--- a/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
+++ b/flang/test/Lower/OpenMP/Todo/map-motion-iterator-unsupported-locators.f90
@@ -135,7 +135,8 @@ subroutine target_exit_data_unreferenced_section_member()
 subroutine target_enter_data_unreferenced_substring()
   character(len=4) :: s
 
-  ! CHECK: not yet implemented: object type not supported by iterator modifier
+  ! A substring locator is now rejected by semantic checks before lowering.
+  ! CHECK: error: A substring cannot appear in a MAP clause
   !$omp target enter data map(iterator(i = 1:1), to: s(1:2))
 end subroutine
 

>From 239aa1c4d257480e221efdefead4bf8dd5590a65 Mon Sep 17 00:00:00 2001
From: Urvi Rav <urvi.rav20 at gmail.com>
Date: Fri, 23 Oct 2026 03:22:30 -0500
Subject: [PATCH 11/12] Port DynamicSegment mapper infrastructure from
 iterator-modifier-map

---
 .../llvm/Frontend/OpenMP/OMPIRBuilder.h       |  31 +++
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     | 197 +++++++++++-------
 2 files changed, 147 insertions(+), 81 deletions(-)

diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index a1ed0d2b065759..979980358d759a 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -30,6 +30,7 @@
 #include "llvm/Support/VirtualFileSystemFwd.h"
 #include "llvm/TargetParser/Triple.h"
 #include <forward_list>
+#include <functional>
 #include <map>
 #include <optional>
 
@@ -3022,6 +3023,34 @@ class OpenMPIRBuilder {
     MapHasAttachPtrArrayTy HasAttachPtr;
     StructNonContiguousInfo NonContigInfo;
 
+    /// Represents a group of mapper sub-components produced by an `iterator`
+    /// modifier on a `declare mapper`'s map clause: the component count is
+    /// only known at runtime, so these are pushed one at a time via
+    /// __tgt_push_mapper_component from inside a generated loop, rather than
+    /// being added to the arrays above. Only consumed by
+    /// emitUserDefinedMapper; emitOffloadingArrays ignores this field.
+    struct DynamicSegment {
+      /// Runtime (i64) trip count for this segment.
+      Value *Count = nullptr;
+      /// Emits the code computing one iteration's (BasePointer, Pointer,
+      /// Size) triple, given the loop's linear induction variable (0-based,
+      /// i64). Owning (not function_ref): the callable is typically a
+      /// lambda temporary assigned in after construction, which would
+      /// otherwise dangle by the time this is invoked. Returns an Error if the
+      /// entry could not be computed, e.g. on region conversion failure.
+      std::function<Expected<SmallVector<Value *, 3>>(IRBuilderBase &, Value *)>
+          GenEntry;
+      /// Map type/HasAttachPtr, constant across the segment.
+      omp::OpenMPOffloadMappingFlags Type =
+          omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
+      bool HasAttachPtr = false;
+      /// User-defined mapper to invoke for this segment's component, if the
+      /// underlying map clause carries one (mirrors CustomMapperCB for the
+      /// statically-known components above).
+      Function *ChildMapperFn = nullptr;
+    };
+    SmallVector<DynamicSegment, 0> DynamicSegments;
+
     /// Append arrays in \a CurInfo.
     void append(MapInfosTy &CurInfo) {
       BasePointers.append(CurInfo.BasePointers.begin(),
@@ -3042,6 +3071,8 @@ class OpenMPIRBuilder {
                                   CurInfo.NonContigInfo.Counts.end());
       NonContigInfo.Strides.append(CurInfo.NonContigInfo.Strides.begin(),
                                    CurInfo.NonContigInfo.Strides.end());
+      DynamicSegments.append(CurInfo.DynamicSegments.begin(),
+                             CurInfo.DynamicSegments.end());
     }
   };
   using MapInfosOrErrorTy = Expected<MapInfosTy &>;
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index ff35966ccfe3ab..fad185217533dc 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -10788,86 +10788,81 @@ Expected<Function *> OpenMPIRBuilder::emitUserDefinedMapper(
   Value *ShiftedPreviousSize =
       Builder.CreateShl(PreviousSize, Builder.getInt64(getFlagMemberOffset()));
 
-  // Fill up the runtime mapper handle for all components.
-  for (unsigned I = 0; I < Info->BasePointers.size(); ++I) {
-    Value *CurBaseArg = Info->BasePointers[I];
-    Value *CurBeginArg = Info->Pointers[I];
-    Value *CurSizeArg = Info->Sizes[I];
-    Value *CurNameArg = Info->Names.size()
-                            ? Info->Names[I]
-                            : Constant::getNullValue(Builder.getPtrTy());
-
-    Value *OriMapType = Builder.getInt64(
-        static_cast<std::underlying_type_t<OpenMPOffloadMappingFlags>>(
-            Info->Types[I]));
-    auto RawType =
-        static_cast<std::underlying_type_t<OpenMPOffloadMappingFlags>>(
-            Info->Types[I]);
+  // Emits a single mapper sub-component: computes the MEMBER_OF-adjusted map
+  // type, applies map-type-modifier propagation, and pushes it either via a
+  // child mapper function or __tgt_push_mapper_component. Returns the last
+  // basic block emitted, so callers can update the outer per-array-element
+  // loop's PHI incoming block.
+  //
+  // Add MEMBER_OF (ShiftedPreviousSize) to group this sub-map with the
+  // current array element (N = __tgt_mapper_num_components() at loop body
+  // start).
+  //
+  // Example 1:
+  //   struct S { int x; int *p; };
+  //
+  //   mapper:  #pragma omp declare mapper(id: S s) map(s.x, s.p[0:10])
+  //   use:     S arr[2]; ... map(arr)
+  //   entries per element:
+  //
+  //     &arr[i],      &arr[i].x,    sizeof(int),    MEMBER_OF(N)|TO|FROM
+  //     &arr[i].p[0], &arr[i].p[0], 10*sizeof(int), TO|FROM        (*)
+  //     &arr[i].p,    &arr[i].p[0], sizeof(int*),   ATTACH         (**)
+  //
+  // Example 2:
+  //   struct S1 { int x; int y; };
+  //   struct S2 { int z; S1 *s1p; };
+  //
+  //   mapper:  #pragma omp declare mapper(S2 s2) map(s2.z, s2.s1p->x,
+  //                                                  s2.s1p->y)
+  //   use:     S2 arr[2]; ... map(arr)
+  //   entries per element:
+  //
+  //     &arr[i],        &arr[i].z,      sizeof(int), MEMBER_OF(N)|TO|FROM
+  //     &arr[i].s1p[0], &arr[i].s1p->x, sizeof(s1p->x..y), ALLOC (*)
+  //     &arr[i].s1p[0], &arr[i].s1p->x, 4, MEMBER_OF(N+2)|TO|FROM (*)(***)
+  //     &arr[i].s1p[0], &arr[i].s1p->y, 4, MEMBER_OF(N+2)|TO|FROM (*)(***)
+  //     &arr[i].s1p,    &arr[i].s1p->x, sizeof(ptr), ATTACH (**)
+  //
+  //     x/y carry inner MEMBER_OF(2)
+  //          which is shifted by N to become MEMBER_OF(N+2).
+  //
+  //     HasAttachPtr is set on all of the s1p entries except the ATTACH one:
+  //     the combined ALLOC entry for the s1p->x..y block, and the individual
+  //     x/y entries that are MEMBER_OF that block, all describe storage
+  //     reached through the attach ptr arr[i].s1p.
+  //
+  // Entries of the following kinds do NOT receive a new outer MEMBER_OF
+  // linking them to the parent struct:
+  //
+  //   * (*) Entries with HasAttachPtr: they represent pointee data that
+  //     occupies a different storage block than the struct being mapped, so
+  //     they are not a member of it. They may still be MEMBER_OF an entry
+  //     within that pointee block, in which case those pre-existing bits are
+  //     shifted -- see (***).
+  //   * (**) ATTACH entries: they are not a member of anything — they just
+  //     link a ptr to its ptee.
+  //   * All entries when PreserveMemberOfFlags is set (the Flang/MLIR path):
+  //     its pre-shaped entries already carry their final MEMBER_OF bits.
+  //     TODO: set HasAttachPtr from Flang for entries whose storage is the
+  //     pointee's (e.g. s%p(0:10)) and drop PreserveMemberOfFlags in favor of
+  //     it.
+  //
+  // (***) If such an entry already has its own MEMBER_OF bits (e.g. the
+  // s1p->x/y entries above), those bits are still shifted by N.
+  auto pushComponent = [&](Value *CurBaseArg, Value *CurBeginArg,
+                           Value *CurSizeArg, Value *CurNameArg,
+                           uint64_t RawType, bool HasAttachPtrFlag,
+                           Function *ChildMapperFn) -> BasicBlock * {
+    Value *OriMapType = Builder.getInt64(RawType);
     constexpr uint64_t MemberOfMask =
         static_cast<uint64_t>(OpenMPOffloadMappingFlags::OMP_MAP_MEMBER_OF);
     constexpr uint64_t AttachBit =
         static_cast<std::underlying_type_t<OpenMPOffloadMappingFlags>>(
             OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
 
-    // Add MEMBER_OF (ShiftedPreviousSize) to group this sub-map with the
-    // current array element (N = __tgt_mapper_num_components() at loop body
-    // start).
-    //
-    // Example 1:
-    //   struct S { int x; int *p; };
-    //
-    //   mapper:  #pragma omp declare mapper(id: S s) map(s.x, s.p[0:10])
-    //   use:     S arr[2]; ... map(arr)
-    //   entries per element:
-    //
-    //     &arr[i],      &arr[i].x,    sizeof(int),    MEMBER_OF(N)|TO|FROM
-    //     &arr[i].p[0], &arr[i].p[0], 10*sizeof(int), TO|FROM        (*)
-    //     &arr[i].p,    &arr[i].p[0], sizeof(int*),   ATTACH         (**)
-    //
-    // Example 2:
-    //   struct S1 { int x; int y; };
-    //   struct S2 { int z; S1 *s1p; };
-    //
-    //   mapper:  #pragma omp declare mapper(S2 s2) map(s2.z, s2.s1p->x,
-    //                                                  s2.s1p->y)
-    //   use:     S2 arr[2]; ... map(arr)
-    //   entries per element:
-    //
-    //     &arr[i],        &arr[i].z,      sizeof(int), MEMBER_OF(N)|TO|FROM
-    //     &arr[i].s1p[0], &arr[i].s1p->x, sizeof(s1p->x..y), ALLOC (*)
-    //     &arr[i].s1p[0], &arr[i].s1p->x, 4, MEMBER_OF(N+2)|TO|FROM (*)(***)
-    //     &arr[i].s1p[0], &arr[i].s1p->y, 4, MEMBER_OF(N+2)|TO|FROM (*)(***)
-    //     &arr[i].s1p,    &arr[i].s1p->x, sizeof(ptr), ATTACH (**)
-    //
-    //     x/y carry inner MEMBER_OF(2)
-    //          which is shifted by N to become MEMBER_OF(N+2).
-    //
-    //     HasAttachPtr is set on all of the s1p entries except the ATTACH one:
-    //     the combined ALLOC entry for the s1p->x..y block, and the individual
-    //     x/y entries that are MEMBER_OF that block, all describe storage
-    //     reached through the attach ptr arr[i].s1p.
-    //
-    // Entries of the following kinds do NOT receive a new outer MEMBER_OF
-    // linking them to the parent struct:
-    //
-    //   * (*) Entries with HasAttachPtr: they represent pointee data that
-    //     occupies a different storage block than the struct being mapped, so
-    //     they are not a member of it. They may still be MEMBER_OF an entry
-    //     within that pointee block, in which case those pre-existing bits are
-    //     shifted -- see (***).
-    //   * (**) ATTACH entries: they are not a member of anything — they just
-    //     link a ptr to its ptee.
-    //   * All entries when PreserveMemberOfFlags is set (the Flang/MLIR path):
-    //     its pre-shaped entries already carry their final MEMBER_OF bits.
-    //     TODO: set HasAttachPtr from Flang for entries whose storage is the
-    //     pointee's (e.g. s%p(0:10)) and drop PreserveMemberOfFlags in favor of
-    //     it.
-    //
-    // (***) If such an entry already has its own MEMBER_OF bits (e.g. the
-    // s1p->x/y entries above), those bits are still shifted by N.
     Value *MemberMapType;
-    if (PreserveMemberOfFlags || (RawType & AttachBit) ||
-        Info->HasAttachPtr[I]) {
+    if (PreserveMemberOfFlags || (RawType & AttachBit) || HasAttachPtrFlag) {
       if (RawType & MemberOfMask)
         MemberMapType = Builder.CreateNUWAdd(OriMapType, ShiftedPreviousSize);
       else
@@ -10944,7 +10939,6 @@ Expected<Function *> OpenMPIRBuilder::emitUserDefinedMapper(
                 OpenMPOffloadMappingFlags::OMP_MAP_TO)));
     // In case of tofrom, do nothing.
     emitBlock(EndBB, MapperFn);
-    LastBB = EndBB;
     PHINode *CurMapType =
         Builder.CreatePHI(Builder.getInt64Ty(), 4, "omp.maptype");
     CurMapType->addIncoming(AllocMapType, AllocBB);
@@ -10992,7 +10986,7 @@ Expected<Function *> OpenMPIRBuilder::emitUserDefinedMapper(
             OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS |
             OpenMPOffloadMappingFlags::OMP_MAP_DELETE |
             OpenMPOffloadMappingFlags::OMP_MAP_CLOSE);
-    if (PropagatePresentToPointee && Info->HasAttachPtr[I])
+    if (PropagatePresentToPointee && HasAttachPtrFlag)
       ModifierBits |=
           static_cast<std::underlying_type_t<OpenMPOffloadMappingFlags>>(
               OpenMPOffloadMappingFlags::OMP_MAP_PRESENT);
@@ -11009,13 +11003,9 @@ Expected<Function *> OpenMPIRBuilder::emitUserDefinedMapper(
 
     Value *OffloadingArgs[] = {MapperHandle, CurBaseArg,   CurBeginArg,
                                CurSizeArg,   FinalMapType, CurNameArg};
-
-    auto ChildMapperFn = CustomMapperCB(I);
-    if (!ChildMapperFn)
-      return ChildMapperFn.takeError();
-    if (*ChildMapperFn) {
+    if (ChildMapperFn) {
       // Call the corresponding mapper function.
-      createRuntimeFunctionCall(*ChildMapperFn, OffloadingArgs)
+      createRuntimeFunctionCall(ChildMapperFn, OffloadingArgs)
           ->setDoesNotThrow();
     } else {
       // Call the runtime API __tgt_push_mapper_component to fill up the runtime
@@ -11024,6 +11014,51 @@ Expected<Function *> OpenMPIRBuilder::emitUserDefinedMapper(
           getOrCreateRuntimeFunction(M, OMPRTL___tgt_push_mapper_component),
           OffloadingArgs);
     }
+    return EndBB;
+  };
+
+  // Fill up the runtime mapper handle for all statically-known components.
+  for (unsigned I = 0; I < Info->BasePointers.size(); ++I) {
+    Value *CurNameArg = Info->Names.size()
+                            ? Info->Names[I]
+                            : Constant::getNullValue(Builder.getPtrTy());
+    Expected<Function *> ChildMapperFn = CustomMapperCB(I);
+    if (!ChildMapperFn)
+      return ChildMapperFn.takeError();
+    LastBB = pushComponent(
+        Info->BasePointers[I], Info->Pointers[I], Info->Sizes[I], CurNameArg,
+        static_cast<std::underlying_type_t<OpenMPOffloadMappingFlags>>(
+            Info->Types[I]),
+        Info->HasAttachPtr[I], *ChildMapperFn);
+  }
+
+  // Fill up the runtime mapper handle for components whose count is only
+  // known at runtime (produced by an `iterator` modifier on the mapper's own
+  // map clause): push each one individually, from inside a generated loop,
+  // via the same __tgt_push_mapper_component call used above.
+  for (MapInfosTy::DynamicSegment &Seg : Info->DynamicSegments) {
+    LocationDescription Loc(Builder);
+    auto BodyGen = [&](InsertPointTy BodyIP, Value *LinearIV) -> Error {
+      Builder.restoreIP(BodyIP);
+      Expected<SmallVector<Value *, 3>> EntryOrErr =
+          Seg.GenEntry(Builder, LinearIV);
+      if (!EntryOrErr)
+        return EntryOrErr.takeError();
+      SmallVector<Value *, 3> &Entry = *EntryOrErr;
+      pushComponent(
+          Entry[0], Entry[1], Entry[2],
+          Constant::getNullValue(Builder.getPtrTy()),
+          static_cast<std::underlying_type_t<OpenMPOffloadMappingFlags>>(
+              Seg.Type),
+          Seg.HasAttachPtr, Seg.ChildMapperFn);
+      return Error::success();
+    };
+    InsertPointOrErrorTy AfterIP =
+        createIteratorLoop(Loc, Seg.Count, BodyGen, "mapper.iterator");
+    if (!AfterIP)
+      return AfterIP.takeError();
+    Builder.restoreIP(*AfterIP);
+    LastBB = Builder.GetInsertBlock();
   }
 
   // Update the pointer to point to the next element that needs to be mapped,

>From 252c9918ade1ac9ee13abdf47caa500c12ff6f7e Mon Sep 17 00:00:00 2001
From: Urvi Rav <urvi.rav20 at gmail.com>
Date: Sun, 25 Oct 2026 11:57:36 -0500
Subject: [PATCH 12/12] Translate declare mapper map entries produced by an
 iterator

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    |  51 +++++++
 .../OpenMP/declare-mapper-iterator.f90        |  35 +++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 134 +++++++++++++++++-
 .../openmp-declare-mapper-iterator.mlir       |  83 +++++++++++
 mlir/test/Target/LLVMIR/openmp-todo.mlir      |  24 ----
 5 files changed, 299 insertions(+), 28 deletions(-)
 create mode 100644 flang/test/Integration/OpenMP/declare-mapper-iterator.f90
 create mode 100644 mlir/test/Target/LLVMIR/openmp-declare-mapper-iterator.mlir

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 4011daf7c26789..9369df92a164ba 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2167,6 +2167,54 @@ static bool hasIteratorModifier(const List<Clause> &clauses) {
   });
 }
 
+// When a declare mapper variable is given an explicit mapper (e.g.
+// `map(mapper(other): v)`) and also has members produced by an iterator
+// modifier (e.g. `map(iterator(i=1:4): v%a(i))`), that mapper would
+// otherwise also remap those same elements, duplicating the mapping at
+// runtime. This mirrors the equivalent exclusivity rule already applied to
+// ordinary (non-iterator) members in insertChildMapInfoIntoParent, which
+// mapIterated locators bypass since they never populate memberMap.
+static void reconcileDeclareMapperIteratedMembers(
+    lower::AbstractConverter &converter,
+    mlir::omp::DeclareMapperOp declareMapper,
+    llvm::ArrayRef<mlir::Value> mapIterated,
+    llvm::SmallVectorImpl<mlir::Value> &mapVars,
+    llvm::SmallVectorImpl<Object> &mapObjects) {
+  if (!declareMapper || mapIterated.empty())
+    return;
+
+  for (auto [mapVal, object] : llvm::zip_equal(mapVars, mapObjects)) {
+    if (!isDeclareMapperVariable(converter, declareMapper, object.sym()))
+      continue;
+    auto mapOp = mapVal.getDefiningOp<mlir::omp::MapInfoOp>();
+    if (!mapOp)
+      continue;
+
+    if (mapOp.getMapperIdAttr())
+      mapOp.setMapperIdAttr(nullptr);
+
+    // If the mapper variable's own map does not request close mapping,
+    // close mapping on its iterator-produced members must be ignored, the
+    // same as for ordinary members.
+    if ((mapOp.getMapType() & mlir::omp::ClauseMapFlags::close) ==
+        mlir::omp::ClauseMapFlags::close)
+      continue;
+    for (mlir::Value iterVal : mapIterated) {
+      auto itersOp = iterVal.getDefiningOp<mlir::omp::IteratorOp>();
+      if (!itersOp)
+        continue;
+      mlir::omp::YieldOp yieldOp = itersOp.getYieldOp();
+      auto iteratedMapOp =
+          yieldOp.getResults()[0].getDefiningOp<mlir::omp::MapInfoOp>();
+      if (iteratedMapOp &&
+          (iteratedMapOp.getMapType() & mlir::omp::ClauseMapFlags::close) ==
+              mlir::omp::ClauseMapFlags::close)
+        iteratedMapOp.setMapType(iteratedMapOp.getMapType() &
+                                 ~mlir::omp::ClauseMapFlags::close);
+    }
+  }
+}
+
 bool ClauseProcessor::processMap(
     mlir::Location currentLocation, lower::StatementContext &stmtCtx,
     mlir::omp::MapClauseOps &result, llvm::omp::Directive directive,
@@ -2287,6 +2335,9 @@ bool ClauseProcessor::processMap(
   bool clauseFound = findRepeatableClause<omp::clause::Map>(process);
   insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices,
                                result.mapVars, *ptrMapObjects);
+  reconcileDeclareMapperIteratedMembers(
+      converter, getEnclosingDeclareMapper(converter), result.mapIterated,
+      result.mapVars, *ptrMapObjects);
 
   return clauseFound;
 }
diff --git a/flang/test/Integration/OpenMP/declare-mapper-iterator.f90 b/flang/test/Integration/OpenMP/declare-mapper-iterator.f90
new file mode 100644
index 00000000000000..a9af70a908e159
--- /dev/null
+++ b/flang/test/Integration/OpenMP/declare-mapper-iterator.f90
@@ -0,0 +1,35 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+! A declare mapper's map clause with an iterator modifier lowers (via the
+! shared map/motion iterator lowering) to a per-iteration, bounds-described
+! omp.map.info. This exercises the user-defined-mapper LLVM IR generation for
+! that shape end to end: the array base from var_ptr, indexed by the bounds'
+! lower_bound, and pushed via __tgt_push_mapper_component since the iterator's
+! trip count is only known at runtime.
+
+! RUN: %flang_fc1 -emit-llvm -fopenmp -fopenmp-version=52 -fopenmp-targets=amdgcn-amd-amdhsa -o - %s | FileCheck %s
+
+subroutine f(arg)
+  type :: s
+    integer :: a(10)
+  end type
+  type(s) :: arg
+
+  !$omp declare mapper(m: s :: v) map(mapper(m), iterator(i = 1:10): v%a(i))
+  !$omp target data map(mapper(m): arg)
+  !$omp end target data
+end
+
+! CHECK-LABEL: define internal void @.omp_mapper._QQFfm
+! CHECK: omp_mapper.iterator.cond:
+! CHECK: icmp ult i64 %{{.*}}, 10
+! CHECK: omp_mapper.iterator.body:
+! CHECK: %[[FIELD:.*]] = getelementptr %_QFfTs, ptr %omp.arraymap.ptrcurrent, i32 0, i32 0
+! CHECK: %[[ELEM:.*]] = getelementptr inbounds [10 x i32], ptr %[[FIELD]], i64 0, i64 %{{.*}}
+! CHECK: call void @__tgt_push_mapper_component(ptr %{{.*}}, ptr %[[ELEM]], ptr %[[ELEM]], i64 4, i64 %{{.*}}, ptr null)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 98b4ba6dbe7ac1..966ff2f71adb97 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -595,6 +595,41 @@ static LogicalResult checkImplementationStatus(Operation &op) {
       result = todo("dyn_groupprivate");
   };
 
+  // `declare_mapper.info` supports a single-dimension iterator whose region
+  // (which may itself span several blocks) yields one member-free omp.map.info
+  // for a locator described by var_ptr + bounds. Descriptor-indirect
+  // (var_ptr_ptr) locators aren't handled yet. Anything else falls back to the
+  // generic (not yet implemented) diagnostic above.
+  auto checkDeclareMapperMap = [&todo](omp::DeclareMapperInfoOp op,
+                                       LogicalResult &result) {
+    if (auto declMapperOp = op->getParentOfType<omp::DeclareMapperOp>())
+      if (!declMapperOp.getRegion().hasOneBlock()) {
+        result = todo("control flow in a declare mapper body");
+        return;
+      }
+
+    for (Value iterVal : op.getMapIterated()) {
+      auto itersOp = iterVal.getDefiningOp<omp::IteratorOp>();
+      bool supported = itersOp && itersOp.getLoopLowerBounds().size() == 1;
+      if (supported) {
+        omp::YieldOp yieldOp = itersOp.getYieldOp();
+        supported = yieldOp && yieldOp.getResults().size() == 1;
+        if (supported) {
+          auto mapInfoOp =
+              yieldOp.getResults()[0].getDefiningOp<omp::MapInfoOp>();
+          supported =
+              mapInfoOp && !mapInfoOp.getVarPtrPtr() &&
+              mapInfoOp.getMembers().empty() &&
+              mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByRef;
+        }
+      }
+      if (!supported) {
+        result = todo("map/motion clause with iterator modifier");
+        return;
+      }
+    }
+  };
+
   LogicalResult result = success();
   llvm::TypeSwitch<Operation &>(op)
       .Case([&](omp::DistributeOp op) {
@@ -689,7 +724,9 @@ static LogicalResult checkImplementationStatus(Operation &op) {
           result = todo("mapper with iterator modifier");
       })
       .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
-      .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
+      .Case([&](omp::DeclareMapperInfoOp op) {
+        checkDeclareMapperMap(op, result);
+      })
       .Default([](Operation &) {
         // Assume all clauses for an operation can be translated unless they are
         // checked above.
@@ -8677,9 +8714,93 @@ emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
     genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
                 targetDirective);
 
-    // Drop the mapping that is no longer necessary so that the same region
-    // can be processed multiple times.
-    moduleTranslation.forgetMapping(declMapperOp.getRegion());
+    // Components produced by an `iterator` modifier on the mapper's own map
+    // clause: their count is only known at runtime, so they are recorded as
+    // dynamic segments and pushed one at a time (via
+    // __tgt_push_mapper_component) from a generated loop, mirroring how the
+    // depend/affinity clauses already handle iterator-produced entries. Each
+    // per-iteration locator keeps its array base in var_ptr with bounds
+    // selecting the mapped element, the same shape as an ordinary
+    // array-section map, so the existing calculateBoundsOffset helper
+    // computes its address.
+    for (mlir::Value iterVal : declMapperInfoOp.getMapIterated()) {
+      auto itersOp = iterVal.getDefiningOp<mlir::omp::IteratorOp>();
+      assert(itersOp && "map_iterated value must be defined by omp.iterator");
+      // Lowering the locator's indices (e.g. involving LEN_TRIM) can
+      // introduce control flow inside the iterator, so its `omp.yield` is
+      // not necessarily in the region's first block.
+      mlir::omp::YieldOp yieldOp = itersOp.getYieldOp();
+      assert(yieldOp && "expected exactly one omp.yield in the iterator body");
+      auto mapInfoOp =
+          yieldOp.getResults()[0].getDefiningOp<mlir::omp::MapInfoOp>();
+      assert(mapInfoOp && "expected an omp.map.info in the map_iterated body");
+
+      mlir::Type varPtrMlirTy = mapInfoOp.getVarPtrType();
+      mlir::Type elemMlirTy = varPtrMlirTy;
+      if (auto arrTy = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(varPtrMlirTy))
+        elemMlirTy = arrTy.getElementType();
+      llvm::Type *baseLLVMTy = moduleTranslation.convertType(varPtrMlirTy);
+      llvm::Value *sizeVal = builder.getInt64(dl.getTypeSize(elemMlirTy));
+
+      llvm::omp::OpenMPOffloadMappingFlags mapType =
+          convertClauseMapFlags(mapInfoOp.getMapType());
+      bool hasAttachPtr =
+          (mapType & llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) !=
+          llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
+
+      IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
+      llvm::Value *count = iterInfo.getTotalTrips();
+
+      combinedInfo.DynamicSegments.push_back(
+          MapInfosTy::DynamicSegment{count, {}, mapType, hasAttachPtr});
+      MapInfosTy::DynamicSegment &seg = combinedInfo.DynamicSegments.back();
+      // Capture `iterInfo` by value: it already holds the lb/ub/step/trip
+      // llvm::Value*s looked up while `declMapperOp`'s region mapping was
+      // still valid. GenEntry runs later (from within
+      // OpenMPIRBuilder::emitUserDefinedMapper), after the mapping for that
+      // region has been dropped below via forgetMapping, so re-deriving a
+      // fresh IteratorInfo from `itersOp` at that point would fail to find
+      // the lb/ub/step values in the (already cleared) ModuleTranslation
+      // value map.
+      seg.GenEntry = [&moduleTranslation, itersOp, mapInfoOp, baseLLVMTy,
+                      sizeVal, iterInfo](llvm::IRBuilderBase &b,
+                                         llvm::Value *linearIV) mutable
+          -> llvm::Expected<llvm::SmallVector<llvm::Value *, 3>> {
+        if (failed(convertIteratorRegion(
+                linearIV, iterInfo, itersOp.getRegion(), b, moduleTranslation)))
+          return llvm::make_error<PreviouslyReportedError>();
+        llvm::Value *base =
+            moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
+        bool isByteOffset = false;
+        std::vector<llvm::Value *> offsetIdx =
+            calculateBoundsOffset(moduleTranslation, b, baseLLVMTy->isArrayTy(),
+                                  mapInfoOp.getBounds(), isByteOffset);
+        llvm::Value *addr = base;
+        if (!offsetIdx.empty())
+          addr = b.CreateInBoundsGEP(isByteOffset ? b.getInt8Ty() : baseLLVMTy,
+                                     base, offsetIdx);
+        moduleTranslation.forgetMapping(itersOp.getRegion());
+        return llvm::SmallVector<llvm::Value *, 3>{addr, addr, sizeVal};
+      };
+
+      if (mapInfoOp.getMapperId()) {
+        auto childMapperOp =
+            SymbolTable::lookupNearestSymbolFrom<omp::DeclareMapperOp>(
+                mapInfoOp, mapInfoOp.getMapperIdAttr());
+        // Emitting the child mapper for the first time leaves builder's
+        // insertion point inside the child function; restore it so that
+        // later iterator segments are emitted into this (the parent)
+        // mapper function instead.
+        llvm::IRBuilderBase::InsertPointGuard childMapperIPGuard(builder);
+        llvm::Expected<llvm::Function *> childMapperFn =
+            getOrCreateUserDefinedMapperFunc(
+                childMapperOp, builder, moduleTranslation, targetDirective);
+        if (!childMapperFn)
+          return childMapperFn.takeError();
+        seg.ChildMapperFn = *childMapperFn;
+      }
+    }
+
     return combinedInfo;
   };
 
@@ -8693,6 +8814,11 @@ emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
   llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
       genMapInfoCB, varType, mapperFuncName, customMapperCB,
       /*PreserveMemberOfFlags=*/true);
+  // Only now that emitUserDefinedMapper has fully finished (including running
+  // any iterator-produced DynamicSegment::GenEntry callbacks, which look up
+  // values defined in declMapperOp's region) is it safe to drop the mapping,
+  // so that the same region can be processed multiple times.
+  moduleTranslation.forgetMapping(declMapperOp.getRegion());
   if (!newFn)
     return newFn.takeError();
   if ([[maybe_unused]] llvm::Function *mappedFunc =
diff --git a/mlir/test/Target/LLVMIR/openmp-declare-mapper-iterator.mlir b/mlir/test/Target/LLVMIR/openmp-declare-mapper-iterator.mlir
new file mode 100644
index 00000000000000..e839ac80b53c42
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-declare-mapper-iterator.mlir
@@ -0,0 +1,83 @@
+// RUN: mlir-translate -mlir-to-llvmir -split-input-file %s | FileCheck %s
+
+// A declare_mapper map clause with an iterator modifier yields one
+// bounds-described omp.map.info per iteration: the array base stays in
+// var_ptr and bounds select the mapped element, the same shape used for an
+// ordinary array-section map. Each iteration is pushed at runtime via
+// __tgt_push_mapper_component since the trip count is not statically known.
+
+module attributes {omp.target_triples = ["amdgcn-amd-amdhsa"]} {
+  omp.declare_mapper @mapper_with_iterator : !llvm.struct<"mapper_type", (array<10 x i32>)> {
+  ^bb0(%arg: !llvm.ptr):
+    %c0 = llvm.mlir.constant(0 : i64) : i64
+    %c9 = llvm.mlir.constant(9 : i64) : i64
+    %c1 = llvm.mlir.constant(1 : i64) : i64
+    %it = omp.iterator(%iv: i64) = (%c0 to %c9 step %c1) {
+      %field = llvm.getelementptr %arg[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<"mapper_type", (array<10 x i32>)>
+      %bounds = omp.map.bounds lower_bound(%iv : i64) upper_bound(%iv : i64) stride(%c1 : i64) start_idx(%c0 : i64)
+      %m = omp.map.info var_ptr(%field : !llvm.ptr, !llvm.array<10 x i32>) map_clauses(tofrom) capture(ByRef) bounds(%bounds) name("") -> !llvm.ptr
+      omp.yield(%m : !llvm.ptr)
+    } inclusive -> !omp.iterated<!llvm.ptr>
+    omp.declare_mapper.info map_iterated(%it : !omp.iterated<!llvm.ptr>)
+  }
+
+  llvm.func @target_data_mapper_iterator(%addr : !llvm.ptr) {
+    %map = omp.map.info var_ptr(%addr : !llvm.ptr, !llvm.struct<"mapper_type", (array<10 x i32>)>) map_clauses(tofrom) capture(ByRef) mapper(@mapper_with_iterator) name("") -> !llvm.ptr
+    omp.target_data map_entries(%map : !llvm.ptr) {}
+    llvm.return
+  }
+}
+
+// CHECK-LABEL: define void @target_data_mapper_iterator
+// CHECK: call void @__tgt_target_data_begin_mapper(ptr @{{.*}}, i64 -1, i32 1, ptr %{{.*}}, ptr %{{.*}}, ptr @.offload_sizes, ptr @.offload_maptypes, ptr @.offload_mapnames, ptr %.offload_mappers)
+
+// CHECK-LABEL: define internal void @.omp_mapper.mapper_with_iterator
+// CHECK: omp_mapper.iterator.cond:
+// CHECK: %[[CMP:.*]] = icmp ult i64 %{{.*}}, 10
+// CHECK: br i1 %[[CMP]], label %omp_mapper.iterator.body, label %omp_mapper.iterator.exit
+//
+// The field GEP (struct index 0) is computed once per iteration, then the
+// bounds' lower_bound selects the element within it via an array GEP.
+// CHECK: omp_mapper.iterator.body:
+// CHECK: %[[FIELD:.*]] = getelementptr %mapper_type, ptr %omp.arraymap.ptrcurrent, i32 0, i32 0
+// CHECK: %[[ELEM:.*]] = getelementptr inbounds [10 x i32], ptr %[[FIELD]], i64 0, i64 %{{.*}}
+// CHECK: call void @__tgt_push_mapper_component(ptr %{{.*}}, ptr %[[ELEM]], ptr %[[ELEM]], i64 4, i64 %{{.*}}, ptr null)
+
+// -----
+
+// The iterated locator's mapper (its own `mapper(...)` on the yielded
+// omp.map.info) must still be invoked for each element, the same as a
+// statically-known member with a mapper.
+
+module attributes {omp.target_triples = ["amdgcn-amd-amdhsa"]} {
+  omp.declare_mapper @inner_mapper : !llvm.struct<"inner", (i32)> {
+  ^bb0(%arg: !llvm.ptr):
+    %m = omp.map.info var_ptr(%arg : !llvm.ptr, !llvm.struct<"inner", (i32)>) map_clauses(tofrom) capture(ByRef) name("") -> !llvm.ptr
+    omp.declare_mapper.info map_entries(%m : !llvm.ptr)
+  }
+
+  omp.declare_mapper @outer_mapper_with_iterator : !llvm.struct<"outer", (array<2 x struct<"inner", (i32)>>)> {
+  ^bb0(%arg: !llvm.ptr):
+    %c0 = llvm.mlir.constant(0 : i64) : i64
+    %c1 = llvm.mlir.constant(1 : i64) : i64
+    %it = omp.iterator(%iv: i64) = (%c0 to %c1 step %c1) {
+      %field = llvm.getelementptr %arg[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<"outer", (array<2 x struct<"inner", (i32)>>)>
+      %bounds = omp.map.bounds lower_bound(%iv : i64) upper_bound(%iv : i64) stride(%c1 : i64) start_idx(%c0 : i64)
+      %m = omp.map.info var_ptr(%field : !llvm.ptr, !llvm.array<2 x struct<"inner", (i32)>>) map_clauses(tofrom) capture(ByRef) mapper(@inner_mapper) bounds(%bounds) name("") -> !llvm.ptr
+      omp.yield(%m : !llvm.ptr)
+    } inclusive -> !omp.iterated<!llvm.ptr>
+    omp.declare_mapper.info map_iterated(%it : !omp.iterated<!llvm.ptr>)
+  }
+
+  llvm.func @target_data_outer_mapper_iterator(%addr : !llvm.ptr) {
+    %map = omp.map.info var_ptr(%addr : !llvm.ptr, !llvm.struct<"outer", (array<2 x struct<"inner", (i32)>>)>) map_clauses(tofrom) capture(ByRef) mapper(@outer_mapper_with_iterator) name("") -> !llvm.ptr
+    omp.target_data map_entries(%map : !llvm.ptr) {}
+    llvm.return
+  }
+}
+
+// CHECK-LABEL: define internal void @.omp_mapper.outer_mapper_with_iterator
+// CHECK: omp_mapper.iterator.body:
+// CHECK: %[[FIELD2:.*]] = getelementptr %outer, ptr %omp.arraymap.ptrcurrent, i32 0, i32 0
+// CHECK: %[[ELEM2:.*]] = getelementptr inbounds [2 x %inner], ptr %[[FIELD2]], i64 0, i64 %{{.*}}
+// CHECK: call void @.omp_mapper.inner_mapper(ptr %{{.*}}, ptr %[[ELEM2]], ptr %[[ELEM2]], i64 4, i64 %{{.*}}, ptr null)
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index 747a3ee1ca420e..cf43aff45c26ce 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -858,30 +858,6 @@ llvm.func @target_data_map_iterator(%addr : !llvm.ptr) {
 
 // -----
 
-module attributes {omp.target_triples = ["amdgcn-amd-amdhsa"]} {
-  omp.declare_mapper @mapper_with_iterator : !llvm.struct<"mapper_type", (i32)> {
-  ^bb0(%arg: !llvm.ptr):
-    %c0 = llvm.mlir.constant(0 : i64) : i64
-    %c10 = llvm.mlir.constant(10 : i64) : i64
-    %c1 = llvm.mlir.constant(1 : i64) : i64
-    %it = omp.iterator(%iv: i64) = (%c0 to %c10 step %c1) {
-      %m = omp.map.info var_ptr(%arg : !llvm.ptr, !llvm.struct<"mapper_type", (i32)>) map_clauses(tofrom) capture(ByRef) name("") -> !llvm.ptr
-      omp.yield(%m : !llvm.ptr)
-    } -> !omp.iterated<!llvm.ptr>
-    // expected-error at below {{not yet implemented: Unhandled clause map/motion clause with iterator modifier in omp.declare_mapper.info operation}}
-    omp.declare_mapper.info map_iterated(%it : !omp.iterated<!llvm.ptr>)
-  }
-
-  llvm.func @target_data_mapper_iterator(%addr : !llvm.ptr) {
-    %map = omp.map.info var_ptr(%addr : !llvm.ptr, !llvm.struct<"mapper_type", (i32)>) map_clauses(tofrom) capture(ByRef) mapper(@mapper_with_iterator) name("") -> !llvm.ptr
-    // expected-error at below {{LLVM Translation failed for operation: omp.target_data}}
-    omp.target_data map_entries(%map : !llvm.ptr) {}
-    llvm.return
-  }
-}
-
-// -----
-
 module attributes {omp.target_triples = ["amdgcn-amd-amdhsa"]} {
   omp.declare_mapper @inner_iterator_mapper : !llvm.struct<"inner", (i32)> {
   ^bb0(%arg: !llvm.ptr):



More information about the Mlir-commits mailing list