[flang-commits] [flang] [Flang][OpenMP] Support iterator modifier in map and motion clauses (PR #197757)

via flang-commits flang-commits at lists.llvm.org
Thu Aug 6 09:53:08 PDT 2026


https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/197757

>From 37593219816525757a8b1367459324b62b9ca941 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 31 Mar 2026 14:00:48 -0500
Subject: [PATCH 01/22] [Flang][OpenMP] Support iterator modifiers in map and
 motion clauses

Support iterated array elements and array sections in map and motion clauses for
target data, target enter data, target exit data, and target update constructs.

Preserve mapper resolution for iterated entries, including explicit mappers,
user-defined default mappers, declare mapper entries, and implicit default
mappers.

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

This patch is part of the feature work for #188061.

Assisted with copilot.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 110 +++-
 flang/lib/Lower/OpenMP/Utils.cpp              | 183 ++++++
 flang/lib/Lower/OpenMP/Utils.h                |  12 +
 .../Optimizer/OpenMP/MapInfoFinalization.cpp  |  15 +
 .../OpenMP/Todo/declare-mapper-iterator.f90   |  11 -
 .../OpenMP/Todo/from-iterator-modifier.f90    |   8 -
 .../OpenMP/Todo/to-iterator-modifier.f90      |   8 -
 .../Lower/OpenMP/declare-mapper-iterator.f90  |  28 +
 flang/test/Lower/OpenMP/motion-iterator.f90   | 540 ++++++++++++++++++
 9 files changed, 876 insertions(+), 39 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/declare-mapper-iterator.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/from-iterator-modifier.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/motion-iterator.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 2f45b70db8fe6..690709ba0517f 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -337,6 +337,54 @@ static mlir::Value buildIteratorOp(Fortran::lower::AbstractConverter &converter,
   return itOp.getResult();
 }
 
+// Build an omp.iterator op that yields a MapInfoOp for a single
+// iterated object.
+static mlir::Value buildIteratedMapEntry(
+    Fortran::lower::AbstractConverter &converter,
+    Fortran::semantics::SemanticsContext &semaCtx, mlir::Location loc,
+    llvm::ArrayRef<IteratorRange> iteratorRanges, const omp::Object &object,
+    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,
+      [&](fir::FirOpBuilder &builder, mlir::Location loc,
+          llvm::ArrayRef<mlir::Value> /*ivs*/) -> mlir::Value {
+        lower::StatementContext iterStmtCtx;
+        std::optional<IteratorMapInfo> mapInfo = genIteratorMapInfo(
+            converter, builder, semaCtx, iterStmtCtx, object, loc);
+        if (!mapInfo)
+          TODO(loc, "object type not supported by iterator modifier");
+
+        // Use the array base as var_ptr with bounds so the runtime can
+        // associate this mapping with whole-array mappings via the base
+        // address.
+        mlir::Value baseAddr = mapInfo->entity.getBase();
+        if (mlir::isa<fir::BaseBoxType>(baseAddr.getType()))
+          baseAddr = fir::BoxAddrOp::create(builder, loc, baseAddr);
+        auto ptrLike =
+            llvm::cast<mlir::omp::PointerLikeType>(baseAddr.getType());
+        mlir::TypeAttr varType = mlir::TypeAttr::get(ptrLike.getElementType());
+        mlir::FlatSymbolRefAttr mapperId =
+            resolveMapperId(converter, loc, object, mapperIdName, mapTypeBits,
+                            directive, /*hasParentObj=*/false);
+        mlir::omp::MapInfoOp mapOp = mlir::omp::MapInfoOp::create(
+            builder, loc, ptrTy, baseAddr, varType,
+            builder.getAttr<mlir::omp::ClauseMapFlagsAttr>(mapTypeBits),
+            builder.getAttr<mlir::omp::VariableCaptureKindAttr>(
+                mlir::omp::VariableCaptureKind::ByRef),
+            /*varPtrPtr=*/mlir::Value{}, /*varPtrPtrType=*/nullptr,
+            /*members=*/mlir::ValueRange{},
+            /*membersIndex=*/mlir::ArrayAttr{}, mapInfo->bounds, mapperId,
+            builder.getStringAttr(""), builder.getBoolAttr(false));
+        return mapOp.getResult();
+      });
+}
+
 template <typename ClauseTuple>
 static void collectIteratorIVs(
     const ClauseTuple &clause, Fortran::lower::AbstractConverter &converter,
@@ -2043,15 +2091,32 @@ 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);
+    if (iterator) {
+      llvm::SmallVector<IteratorRange> iteratorRanges;
+      llvm::SmallPtrSet<const Fortran::semantics::Symbol *, 4> ivSyms;
+      collectIteratorIVs(clause, converter, stmtCtx, iteratorRanges, ivSyms);
+
+      for (const omp::Object &object : objects) {
+        if (hasIteratorIVReference(object, ivSyms)) {
+          result.mapIterated.push_back(buildIteratedMapEntry(
+              converter, semaCtx, clauseLocation, iteratorRanges, object,
+              mapperIdName, mapTypeBits, directive));
+        } else {
+          omp::ObjectList singleObj{object};
+          processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
+                            parentMemberIndices, result.mapVars, *ptrMapObjects,
+                            mapperIdName, /*isMotionModifier=*/false,
+                            directive);
+        }
+      }
+    } else {
+      processMapObjects(stmtCtx, clauseLocation,
+                        std::get<omp::ObjectList>(clause.t), mapTypeBits,
+                        parentMemberIndices, result.mapVars, *ptrMapObjects,
+                        mapperIdName, /*isMotionModifier=*/false, directive);
+    }
   };
 
   bool clauseFound = findRepeatableClause<omp::clause::Map>(process);
@@ -2080,13 +2145,34 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
     // 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);
+    if (iterator) {
+      // Iterator modifier present: route each object to iterated or plain path.
+      llvm::SmallVector<IteratorRange> iteratorRanges;
+      llvm::SmallPtrSet<const Fortran::semantics::Symbol *, 4> ivSyms;
+      collectIteratorIVs(clause, converter, stmtCtx, iteratorRanges, ivSyms);
+
+      for (const omp::Object &object : objects) {
+        if (hasIteratorIVReference(object, ivSyms)) {
+          result.mapIterated.push_back(buildIteratedMapEntry(
+              converter, semaCtx, clauseLocation, iteratorRanges, object,
+              mapperIdName, mapTypeBits,
+              llvm::omp::Directive::OMPD_target_update));
+        } else {
+          omp::ObjectList singleObj{object};
+          processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
+                            parentMemberIndices, result.mapVars, mapObjects,
+                            mapperIdName, /*isMotionModifier=*/true,
+                            llvm::omp::Directive::OMPD_target_update);
+        }
+      }
+    } else {
+      processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits,
+                        parentMemberIndices, result.mapVars, mapObjects,
+                        mapperIdName, /*isMotionModifier=*/true,
+                        llvm::omp::Directive::OMPD_target_update);
+    }
   };
 
   bool clauseFound = findRepeatableClause<omp::clause::To>(callbackFn);
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 8f57f00d59c58..b786c84a55f63 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1447,6 +1447,189 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
   return variants[bestIdx];
 }
 
+// Lower the stable base entity for an iterator map/motion locator.
+//
+// MapInfoOp stores this base in var_ptr and represents the iterator-selected
+// elements or sections with map bounds. For component array locators such as
+// v%a(i) or v%a(i:i+1), return v%a so each iteration maps offsets from the
+// same base.
+static std::optional<hlfir::Entity>
+getIteratorMapEntity(Fortran::lower::AbstractConverter &converter,
+                     fir::FirOpBuilder &builder,
+                     Fortran::semantics::SemanticsContext &semaCtx,
+                     Fortran::lower::StatementContext &stmtCtx,
+                     const omp::Object &object, mlir::Location loc) {
+  const semantics::Symbol *sym = object.sym();
+  assert(sym && "expected symbol for iterator object");
+
+  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);
+    return hlfir::Entity{info.addr};
+  }
+
+  const std::optional<ExprTy> &ref = object.ref();
+  if (!ref)
+    return std::nullopt;
+
+  auto arrayRef = Fortran::lower::detail::getRef<evaluate::ArrayRef>(*ref);
+  if (!arrayRef)
+    return std::nullopt;
+
+  evaluate::ExpressionAnalyzer ea{semaCtx};
+  std::optional<ExprTy> arrayBase;
+  const evaluate::NamedEntity &base = arrayRef->base();
+  // Component array references carry the array base separately from the
+  // subscript list. Lower that base, e.g. v%a in v%a(i), and leave the
+  // subscript-dependent part to 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);
+  // Preserve mutable-box lowering for allocatable and pointer bases; MapInfoOp
+  // still records the address returned by the lowered base expression.
+  fir::ExtendedValue dataExv;
+  if (semantics::IsAllocatableOrPointer(base.GetLastSymbol()))
+    dataExv = converter.genExprMutableBox(loc, *arrayBase);
+  else
+    dataExv = converter.genExprAddr(loc, *arrayBase, stmtCtx);
+  return hlfir::Entity{fir::getBase(dataExv)};
+}
+
+// Build normalized map bounds for an iterator-dependent map/motion object.
+// MapInfoOp keeps the array base address as var_ptr; these bounds describe the
+// selected element or contiguous array section for each dimension.
+//
+// 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 std::optional<llvm::SmallVector<mlir::Value>>
+genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
+                     hlfir::Entity entity, const omp::Object &object,
+                     Fortran::lower::StatementContext &stmtCtx,
+                     mlir::Location loc) {
+  const std::optional<ExprTy> &ref = object.ref();
+  assert(ref && "expected iterator-dependent object to have a reference");
+
+  std::optional<Fortran::evaluate::DataRef> dataRef =
+      Fortran::evaluate::ExtractDataRef(*ref);
+  if (!dataRef)
+    return std::nullopt;
+  const auto *arrayRef = std::get_if<Fortran::evaluate::ArrayRef>(&dataRef->u);
+  if (!arrayRef || arrayRef->subscript().empty())
+    return std::nullopt;
+
+  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);
+
+  auto convertToIndex = [&](mlir::Value value) -> mlir::Value {
+    if (value.getType().isIndex())
+      return value;
+    return fir::ConvertOp::create(builder, loc, idxTy, value);
+  };
+
+  auto lowerSubscriptToIndex = [&](const SubscriptExpr &expr) -> mlir::Value {
+    mlir::Value value = fir::getBase(
+        converter.genExprValue(toEvExpr(expr), stmtCtx, &loc));
+    return convertToIndex(value);
+  };
+
+  llvm::SmallVector<mlir::Value> bounds;
+  bounds.reserve(arrayRef->subscript().size());
+  // Translate each Fortran subscript into an OpenMP map bound for the
+  // corresponding array dimension.
+  for (const auto &[dim, subscript] : llvm::enumerate(arrayRef->subscript())) {
+    mlir::Value baseLb =
+        convertToIndex(hlfir::genLBound(loc, builder, entity, dim));
+    mlir::Value extent =
+        convertToIndex(hlfir::genExtent(loc, builder, entity, dim));
+    mlir::Value lbound;
+    mlir::Value ubound;
+
+    if (const auto *triplet =
+            std::get_if<Fortran::evaluate::Triplet>(&subscript.u)) {
+      // Triplet subscripts map a section. Missing lower/upper bounds select the
+      // whole dimension, which normalizes to 0/extent-1.
+      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);
+      }
+
+      // Sema only rejects statically-known non-positive strides, so valid
+      // OpenMP may still reach here with a positive non-unit or dynamic stride.
+      std::optional<std::int64_t> stride =
+          Fortran::evaluate::ToInt64(triplet->GetStride());
+      if (!stride || *stride != 1)
+        TODO(loc, "iterator modifier with non-unit array section stride");
+    } else {
+      // Not handling vector subscripts for now.
+      if (subscript.Rank() > 0)
+        return std::nullopt;
+
+      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=*/one,
+        /*stride_in_bytes=*/false, /*start_idx=*/baseLb);
+    bounds.push_back(bound);
+  }
+
+  return bounds;
+}
+
+// Lower an iterated map/motion object to its stable base entity and
+// normalized bounds. The entity serves as var_ptr in MapInfoOp, and the bounds
+// describe the iterator-selected element or section.
+std::optional<IteratorMapInfo>
+genIteratorMapInfo(Fortran::lower::AbstractConverter &converter,
+                   fir::FirOpBuilder &builder,
+                   Fortran::semantics::SemanticsContext &semaCtx,
+                   Fortran::lower::StatementContext &stmtCtx,
+                   const omp::Object &object, mlir::Location loc) {
+  std::optional<hlfir::Entity> entity =
+      getIteratorMapEntity(converter, builder, semaCtx, stmtCtx, object, loc);
+  if (!entity)
+    return std::nullopt;
+
+  std::optional<llvm::SmallVector<mlir::Value>> bounds =
+      genIteratorMapBounds(converter, *entity, object, stmtCtx, loc);
+  if (!bounds)
+    return std::nullopt;
+
+  return IteratorMapInfo{*entity, std::move(*bounds)};
+}
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..89e589c1e0709 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -255,6 +255,18 @@ resolveMapperId(Fortran::lower::AbstractConverter &converter,
                 mlir::omp::ClauseMapFlags mapTypeBits,
                 llvm::omp::Directive directive, bool hasParentObj);
 
+struct IteratorMapInfo {
+  hlfir::Entity entity;
+  llvm::SmallVector<mlir::Value> bounds;
+};
+
+std::optional<IteratorMapInfo>
+genIteratorMapInfo(Fortran::lower::AbstractConverter &converter,
+                   fir::FirOpBuilder &builder,
+                   Fortran::semantics::SemanticsContext &semaCtx,
+                   Fortran::lower::StatementContext &stmtCtx,
+                   const omp::Object &object, mlir::Location loc);
+
 std::optional<llvm::SmallVector<mlir::Value>> getIteratorElementIndices(
     Fortran::lower::AbstractConverter &converter, const omp::Object &object,
     Fortran::lower::StatementContext &stmtCtx, mlir::Location loc);
diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index 949da8f20cbbe..b0dcb3244acf8 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -722,6 +722,21 @@ class MapInfoFinalizationPass
 
       if (auto mapUser = llvm::dyn_cast<mlir::omp::MapInfoOp>(user))
         return getFirstTargetUser(mapUser);
+
+      // MapInfoOp inside an omp.iterator body is yielded back to the iterator,
+      // whose result is used by the target op.
+      if (llvm::isa<mlir::omp::YieldOp>(user)) {
+        if (auto iterOp = user->getParentOfType<mlir::omp::IteratorOp>()) {
+          for (auto *iterUser : iterOp->getUsers()) {
+            if (llvm::isa<mlir::omp::TargetOp, mlir::omp::TargetDataOp,
+                          mlir::omp::TargetUpdateOp,
+                          mlir::omp::TargetExitDataOp,
+                          mlir::omp::TargetEnterDataOp,
+                          mlir::omp::DeclareMapperInfoOp>(iterUser))
+              return iterUser;
+          }
+        }
+      }
     }
 
     return nullptr;
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 dacd6d6246595..0000000000000
--- 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 973d1d1d76ba4..0000000000000
--- 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/to-iterator-modifier.f90 b/flang/test/Lower/OpenMP/Todo/to-iterator-modifier.f90
deleted file mode 100644
index a587373bf183a..0000000000000
--- 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 0000000000000..c402129f27e15
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -0,0 +1,28 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s | FileCheck %s
+
+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
+! 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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:     %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:     fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:     omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK:   } -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !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 0000000000000..a4c3adb5e6aec
--- /dev/null
+++ b/flang/test/Lower/OpenMP/motion-iterator.f90
@@ -0,0 +1,540 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s | FileCheck %s
+
+! 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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:   fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:   fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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: %[[A:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "_QFtarget_update_2dEa"}
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV0:.*]]: index, %[[IV1:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV0_I32:.*]] = fir.convert %[[IV0]] : (index) -> i32
+! CHECK:   fir.store %[[IV0_I32]] to %[[IV0_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_MEM]]
+! CHECK:   %[[IV1_I32:.*]] = fir.convert %[[IV1]] : (index) -> i32
+! CHECK:   fir.store %[[IV1_I32]] 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:   %[[IV0_I64:.*]] = fir.convert %[[IV0_LD]] : (i32) -> i64
+! CHECK:   %[[IV0_IDX:.*]] = fir.convert %[[IV0_I64]] : (i64) -> index
+! CHECK:   %[[LB0:.*]] = arith.subi %[[IV0_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS0:.*]] = omp.map.bounds lower_bound(%[[LB0]] : index) upper_bound(%[[LB0]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[IV1_LD:.*]] = fir.load %[[IV1_DECL]]#0 : !fir.ref<i32>
+! CHECK:   %[[IV1_I64:.*]] = fir.convert %[[IV1_LD]] : (i32) -> i64
+! CHECK:   %[[IV1_IDX:.*]] = fir.convert %[[IV1_I64]] : (i64) -> index
+! CHECK:   %[[LB1:.*]] = arith.subi %[[IV1_IDX]], %{{.*}} : index
+! CHECK:   %[[BOUNDS1:.*]] = omp.map.bounds lower_bound(%[[LB1]] : index) upper_bound(%[[LB1]] : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[A]]#0 : !fir.ref<!fir.array<4x6xi32>>, !fir.array<4x6xi32>) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS0]], %[[BOUNDS1]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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: %[[C1_I32:.*]] = arith.constant 1 : i32
+! CHECK: %[[C16_I32:.*]] = arith.constant 16 : i32
+! CHECK: %[[LB:.*]] = fir.convert %[[C1_I32]] : (i32) -> index
+! CHECK: %[[UB:.*]] = fir.convert %[[C16_I32]] : (i32) -> index
+! CHECK: %[[C2_I32:.*]] = arith.constant 2 : i32
+! CHECK: %[[STEP:.*]] = fir.convert %[[C2_I32]] : (i32) -> index
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: index) = (%[[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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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: %[[C16_I32:.*]] = arith.constant 16 : i32
+! CHECK: %[[C1_I32:.*]] = arith.constant 1 : i32
+! CHECK: %[[LB:.*]] = fir.convert %[[C16_I32]] : (i32) -> index
+! CHECK: %[[UB:.*]] = fir.convert %[[C1_I32]] : (i32) -> index
+! CHECK: %[[CM1_I32:.*]] = arith.constant -1 : i32
+! CHECK: %[[STEP:.*]] = fir.convert %[[CM1_I32]] : (i32) -> index
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: index) = (%[[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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP1]] : !llvm.ptr)
+! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK: } -> !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({{.*}}) -> !fir.ref<!fir.array<16xi32>> {name = "a(1)"}
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV:.*]]: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP_IT]] : !llvm.ptr)
+! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP1]] : !llvm.ptr)
+! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP_IT]] : !llvm.ptr)
+! CHECK: } -> !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({{.*}}) -> !fir.ref<!fir.array<16xi32>> {name = "b"}
+! 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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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) -> !fir.ref<!fir.type<_QFtarget_update_default_mapperTs{a:i32}>> {name = "x"}
+! 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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:   fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:   fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:   fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index, %[[IV1:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV0_I32:.*]] = fir.convert %[[IV0]] : (index) -> i32
+! CHECK:   fir.store %[[IV0_I32]] to %[[IV0_MEM:.*]] : !fir.ref<i32>
+! CHECK:   %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_MEM]]
+! CHECK:   %[[IV1_I32:.*]] = fir.convert %[[IV1]] : (index) -> i32
+! CHECK:   fir.store %[[IV1_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[IV_I32:.*]] = fir.convert %[[IV]] : (index) -> i32
+! CHECK:   fir.store %[[IV_I32]] 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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: %[[C16_I32:.*]] = arith.constant 16 : i32
+! CHECK: %[[C1_I32:.*]] = arith.constant 1 : i32
+! CHECK: %[[LB:.*]] = fir.convert %[[C16_I32]] : (i32) -> index
+! CHECK: %[[UB:.*]] = fir.convert %[[C1_I32]] : (i32) -> index
+! CHECK: %[[CM1_I32:.*]] = arith.constant -1 : i32
+! CHECK: %[[STEP:.*]] = fir.convert %[[CM1_I32]] : (i32) -> index
+! CHECK: %[[IT1:.*]] = omp.iterator(%{{.*}}: index) = (%[[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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP1]] : !llvm.ptr)
+! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: %[[IT2:.*]] = omp.iterator(%{{.*}}: index) = (%[[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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_exit_data map_iterated(%[[IT1]], %[[IT2]] : !omp.iterated<!llvm.ptr>, !omp.iterated<!llvm.ptr>)

>From 28cfff2f1a4a99713e249438961a07791d08d3ff Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 9 Jun 2026 15:01:19 -0500
Subject: [PATCH 02/22] Resolve feedback about MapInfoFinalization

Keep MapInfoFinalization from expanding `omp.map.info` operations nested
in omp.iterator regions. Those maps are yielded through the iterator
result, so rewriting them as outer target operands can violate SSA
dominance.
---
 .../Optimizer/OpenMP/MapInfoFinalization.cpp  | 10 ++++++++
 .../Transforms/omp-map-info-finalization.fir  | 24 +++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index b0dcb3244acf8..99120a69f2f28 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -1365,6 +1365,10 @@ class MapInfoFinalizationPass
     return false;
   }
 
+  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
@@ -1402,6 +1406,8 @@ class MapInfoFinalizationPass
       // is executed again as the final step of this pass to maintain
       // map to block argument consistency.
       func->walk([&](mlir::omp::MapInfoOp op) {
+        if (isNestedInIterator(op))
+          return;
         mlir::Operation *targetUser = getFirstTargetUser(op);
         assert(targetUser && "expected user of map operation was not found");
         addImplicitMembersToTarget(op, builder, targetUser);
@@ -1610,6 +1616,8 @@ class MapInfoFinalizationPass
       });
 
       func->walk([&](mlir::omp::MapInfoOp op) {
+        if (isNestedInIterator(op))
+          return;
         // 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
@@ -1705,6 +1713,8 @@ class MapInfoFinalizationPass
       // 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) {
+        if (isNestedInIterator(op))
+          return;
         mlir::Operation *targetUser = getFirstTargetUser(op);
         assert(targetUser && "expected user of map operation was not found");
         addImplicitMembersToTarget(op, builder, targetUser);
diff --git a/flang/test/Transforms/omp-map-info-finalization.fir b/flang/test/Transforms/omp-map-info-finalization.fir
index 42cc4b47fe4e1..7b90322d3929c 100644
--- a/flang/test/Transforms/omp-map-info-finalization.fir
+++ b/flang/test/Transforms/omp-map-info-finalization.fir
@@ -43,6 +43,30 @@ 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) -> !fir.ref<!fir.array<?xf32>> {name = ""}
+    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({{.*}}) -> !fir.ref<!fir.array<?xf32>> {name = ""}
+// 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) -> !fir.ref<f32> {name = "scalar_struct%rx"}

>From cdd4c6c47130b69d1206fb850866bd95c7509c01 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 9 Jun 2026 15:01:20 -0500
Subject: [PATCH 03/22] Resolve feedback about createMapInfoOp usage

Build iterator-generated map entries through the shared createMapInfoOp
helper while preserving the FIR `var_ptr` type used to derive the OpenMP
`varPtrType` for `!llvm.ptr` iterator results.
---
 flang/include/flang/Utils/OpenMP.h         | 18 +++++++++++++++++-
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 18 ++++++------------
 flang/lib/Utils/OpenMP.cpp                 |  4 ++--
 3 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/flang/include/flang/Utils/OpenMP.h b/flang/include/flang/Utils/OpenMP.h
index e8627347fd578..5188fded4bbb3 100644
--- a/flang/include/flang/Utils/OpenMP.h
+++ b/flang/include/flang/Utils/OpenMP.h
@@ -26,6 +26,21 @@ namespace Fortran::utils::openmp {
 ///
 /// \param [in] builder - MLIR operation builder.
 /// \param [in] loc     - Source location of the created op.
+/// \param [in] baseAddr Address to use as the map `var_ptr` operand. If this
+///        is a FIR box value, a `fir.box_addr` is generated and used instead.
+/// \param [in] varPtrPtr Optional secondary pointer operand for maps that need
+///        a `var_ptr_ptr` value, such as descriptor base-address maps.
+/// \param [in] name Name attribute for the generated map.
+/// \param [in] bounds Map bounds operands attached to the map.
+/// \param [in] members Child map entries for partial or structured maps.
+/// \param [in] membersIndex Placement indices for the child map entries.
+/// \param [in] mapType OpenMP map type flags.
+/// \param [in] mapCaptureType OpenMP map capture kind.
+/// \param [in] retTy Result type of the generated `omp.map.info` op.
+/// \param [in] partialMap Whether the generated map is a partial map.
+/// \param [in] mapperId Optional declare mapper symbol reference.
+/// \param [in] varPtrTy Optional type to use when deriving the `var_ptr` type
+///        attribute. When omitted, `retTy` is used.
 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 +48,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 690709ba0517f..92b42197986fa 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -366,21 +366,15 @@ static mlir::Value buildIteratedMapEntry(
         mlir::Value baseAddr = mapInfo->entity.getBase();
         if (mlir::isa<fir::BaseBoxType>(baseAddr.getType()))
           baseAddr = fir::BoxAddrOp::create(builder, loc, baseAddr);
-        auto ptrLike =
-            llvm::cast<mlir::omp::PointerLikeType>(baseAddr.getType());
-        mlir::TypeAttr varType = mlir::TypeAttr::get(ptrLike.getElementType());
         mlir::FlatSymbolRefAttr mapperId =
             resolveMapperId(converter, loc, object, mapperIdName, mapTypeBits,
                             directive, /*hasParentObj=*/false);
-        mlir::omp::MapInfoOp mapOp = mlir::omp::MapInfoOp::create(
-            builder, loc, ptrTy, baseAddr, varType,
-            builder.getAttr<mlir::omp::ClauseMapFlagsAttr>(mapTypeBits),
-            builder.getAttr<mlir::omp::VariableCaptureKindAttr>(
-                mlir::omp::VariableCaptureKind::ByRef),
-            /*varPtrPtr=*/mlir::Value{}, /*varPtrPtrType=*/nullptr,
-            /*members=*/mlir::ValueRange{},
-            /*membersIndex=*/mlir::ArrayAttr{}, mapInfo->bounds, mapperId,
-            builder.getStringAttr(""), builder.getBoolAttr(false));
+        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();
       });
 }
diff --git a/flang/lib/Utils/OpenMP.cpp b/flang/lib/Utils/OpenMP.cpp
index 9c22cc950e3f7..0f1fb1f562a1b 100644
--- a/flang/lib/Utils/OpenMP.cpp
+++ b/flang/lib/Utils/OpenMP.cpp
@@ -24,7 +24,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(
@@ -44,7 +44,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{};
 

>From ae435dcd1a20af44ce79a1d046570a540ff906b6 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 9 Jun 2026 15:32:48 -0500
Subject: [PATCH 04/22] Resolve feedback about iterator member maps

Reject iterator-dependent derived type member maps for now. These maps
would otherwise bypass the parent/member bookkeeping in processMapObjects,
including parentMemberIndices construction and mapper resolution with
hasParentObj=true.

Without that bookkeeping, lowering map(iterator(...): x%a(i)) could emit
an incomplete parent partial map. Emit a TODO instead of silently generating
incorrect or incomplete mapping IR.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp          | 10 ++++++++++
 flang/lib/Lower/OpenMP/OpenMP.cpp                   |  5 +++--
 .../OpenMP/Todo/map-iterator-derived-member.f90     | 13 +++++++++++++
 .../OpenMP/Todo/motion-iterator-derived-member.f90  | 12 ++++++++++++
 .../OpenMP/Todo/target-iterator-derived-member.f90  | 13 +++++++++++++
 5 files changed, 51 insertions(+), 2 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 92b42197986fa..64fd5696301db 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -379,6 +379,13 @@ static mlir::Value buildIteratedMapEntry(
       });
 }
 
+static void
+iteratorMemberMapNotSupported(mlir::Location loc, const omp::Object &object,
+                              Fortran::semantics::SemanticsContext &semaCtx) {
+  if (getBaseObject(object, semaCtx))
+    TODO(loc, "iterator modifier with derived type member map");
+}
+
 template <typename ClauseTuple>
 static void collectIteratorIVs(
     const ClauseTuple &clause, Fortran::lower::AbstractConverter &converter,
@@ -2094,6 +2101,8 @@ bool ClauseProcessor::processMap(
 
       for (const omp::Object &object : objects) {
         if (hasIteratorIVReference(object, ivSyms)) {
+          if (directive != llvm::omp::Directive::OMPD_unknown)
+            iteratorMemberMapNotSupported(clauseLocation, object, semaCtx);
           result.mapIterated.push_back(buildIteratedMapEntry(
               converter, semaCtx, clauseLocation, iteratorRanges, object,
               mapperIdName, mapTypeBits, directive));
@@ -2149,6 +2158,7 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
 
       for (const omp::Object &object : objects) {
         if (hasIteratorIVReference(object, ivSyms)) {
+          iteratorMemberMapNotSupported(clauseLocation, object, semaCtx);
           result.mapIterated.push_back(buildIteratedMapEntry(
               converter, semaCtx, clauseLocation, iteratorRanges, object,
               mapperIdName, mapTypeBits,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3876799b3a081..5a250dc20829d 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2733,7 +2733,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);
@@ -2755,7 +2755,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);
   cp.processUseDevicePtr(stmtCtx, clauseOps, useDevicePtrObjects);
 
diff --git a/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90
new file mode 100644
index 0000000000000..2087121810219
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90
@@ -0,0 +1,13 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+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
\ No newline at end of file
diff --git a/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90
new file mode 100644
index 0000000000000..bf0b99db9b4a7
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90
@@ -0,0 +1,12 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+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
\ No newline at end of file
diff --git a/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
new file mode 100644
index 0000000000000..8beb69a30bbf6
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
@@ -0,0 +1,13 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+subroutine target_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 map(iterator(i = 1:10), tofrom: x%a(i))
+  !$omp end target
+end subroutine
\ No newline at end of file

>From 2eba3988cd6873feca95616c7fcff0356583087f Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 9 Jun 2026 16:46:18 -0500
Subject: [PATCH 05/22] Add comment about iterator map bounds

Clarify why iterator map lowering keeps a stable base address while
lowering iterator-dependent subscripts inside the omp.iterator body.
This documents the difference from ordinary map bounds generation.
---
 flang/lib/Lower/OpenMP/Utils.cpp | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index b786c84a55f63..a8009146bdd87 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1450,9 +1450,10 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
 // Lower the stable base entity for an iterator map/motion locator.
 //
 // MapInfoOp stores this base in var_ptr and represents the iterator-selected
-// elements or sections with map bounds. For component array locators such as
-// v%a(i) or v%a(i:i+1), return v%a so each iteration maps offsets from the
-// same base.
+// elements or sections with map bounds computed inside the omp.iterator body.
+// For component array locators such as v%a(i) or v%a(i:i+1), return v%a so each
+// iteration maps offsets from the same base instead of lowering the subscripted
+// component address as the var_ptr.
 static std::optional<hlfir::Entity>
 getIteratorMapEntity(Fortran::lower::AbstractConverter &converter,
                      fir::FirOpBuilder &builder,
@@ -1482,9 +1483,9 @@ getIteratorMapEntity(Fortran::lower::AbstractConverter &converter,
   evaluate::ExpressionAnalyzer ea{semaCtx};
   std::optional<ExprTy> arrayBase;
   const evaluate::NamedEntity &base = arrayRef->base();
-  // Component array references carry the array base separately from the
+  // Component array references carry the component base separately from the
   // subscript list. Lower that base, e.g. v%a in v%a(i), and leave the
-  // subscript-dependent part to map bounds.
+  // subscript-dependent part to the iterator-local map bounds below.
   if (const semantics::SymbolRef *symRef = base.UnwrapSymbolRef())
     arrayBase = ea.Designate(evaluate::DataRef{*symRef});
   else if (const evaluate::Component *component = base.UnwrapComponent())
@@ -1504,8 +1505,11 @@ getIteratorMapEntity(Fortran::lower::AbstractConverter &converter,
 }
 
 // Build normalized map bounds for an iterator-dependent map/motion object.
-// MapInfoOp keeps the array base address as var_ptr; these bounds describe the
-// selected element or contiguous array section for each dimension.
+// Ordinary map lowering can lower a complete locator and derive bounds from the
+// resulting extended value. Iterator lowering instead keeps a stable array base
+// address as var_ptr and lowers the iterator-dependent subscripts inside the
+// omp.iterator body, so each generated bound describes the selected element or
+// contiguous array section for one iteration.
 //
 // Examples:
 //   a(i)     -> lower_bound == upper_bound == i - base lower bound

>From 384bbfc7eefb183c2f67047107b0ce5d15073809 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 9 Jun 2026 16:45:29 -0500
Subject: [PATCH 06/22] Preserve iterator-dependent locators in shared map
 lowering

Share the iterated-map handling between map and motion clauses while
preserving iterator-dependent locators on map_iterated and keeping
standard map lowering for other clause objects.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 95 +++++++++++++---------
 flang/lib/Lower/OpenMP/ClauseProcessor.h   | 12 +++
 2 files changed, 69 insertions(+), 38 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 64fd5696301db..bdfb8393b0eb9 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1973,6 +1973,46 @@ void ClauseProcessor::processMapObjects(
   }
 }
 
+// Process objects in map/motion clauses, lowering iterator-dependent
+// locators into `result.mapIterated` and all others through the regular
+// map-lowering path. A null `ivSyms` indicates that no iterator modifier
+// is present.
+void ClauseProcessor::processMapObjectsWithIterator(
+    lower::StatementContext &stmtCtx, mlir::Location clauseLocation,
+    const omp::ObjectList &objects,
+    llvm::ArrayRef<IteratorRange> iteratorRanges,
+    const llvm::SmallPtrSetImpl<const semantics::Symbol *> *ivSyms,
+    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 (!ivSyms) {
+    processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits,
+                      parentMemberIndices, result.mapVars, mapObjects,
+                      mapperIdNameRef, isMotionModifier, directive);
+    return;
+  }
+
+  // Objects in an iterator-modified clause may independently reference
+  // iterator variables, so handle each object separately.
+  for (const omp::Object &object : objects) {
+    if (hasIteratorIVReference(object, *ivSyms)) {
+      if (directive != llvm::omp::Directive::OMPD_unknown)
+        iteratorMemberMapNotSupported(clauseLocation, object, semaCtx);
+      result.mapIterated.push_back(buildIteratedMapEntry(
+          converter, semaCtx, clauseLocation, iteratorRanges, object,
+          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.
@@ -2099,26 +2139,15 @@ bool ClauseProcessor::processMap(
       llvm::SmallPtrSet<const Fortran::semantics::Symbol *, 4> ivSyms;
       collectIteratorIVs(clause, converter, stmtCtx, iteratorRanges, ivSyms);
 
-      for (const omp::Object &object : objects) {
-        if (hasIteratorIVReference(object, ivSyms)) {
-          if (directive != llvm::omp::Directive::OMPD_unknown)
-            iteratorMemberMapNotSupported(clauseLocation, object, semaCtx);
-          result.mapIterated.push_back(buildIteratedMapEntry(
-              converter, semaCtx, clauseLocation, iteratorRanges, object,
-              mapperIdName, mapTypeBits, directive));
-        } else {
-          omp::ObjectList singleObj{object};
-          processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
-                            parentMemberIndices, result.mapVars, *ptrMapObjects,
-                            mapperIdName, /*isMotionModifier=*/false,
-                            directive);
-        }
-      }
+      processMapObjectsWithIterator(
+          stmtCtx, clauseLocation, objects, iteratorRanges, &ivSyms,
+          mapTypeBits, parentMemberIndices, result, *ptrMapObjects,
+          mapperIdName, /*isMotionModifier=*/false, directive);
     } else {
-      processMapObjects(stmtCtx, clauseLocation,
-                        std::get<omp::ObjectList>(clause.t), mapTypeBits,
-                        parentMemberIndices, result.mapVars, *ptrMapObjects,
-                        mapperIdName, /*isMotionModifier=*/false, directive);
+      processMapObjectsWithIterator(
+          stmtCtx, clauseLocation, objects, /*iteratorRanges=*/{},
+          /*ivSyms=*/nullptr, mapTypeBits, parentMemberIndices, result,
+          *ptrMapObjects, mapperIdName, /*isMotionModifier=*/false, directive);
     }
   };
 
@@ -2156,26 +2185,16 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
       llvm::SmallPtrSet<const Fortran::semantics::Symbol *, 4> ivSyms;
       collectIteratorIVs(clause, converter, stmtCtx, iteratorRanges, ivSyms);
 
-      for (const omp::Object &object : objects) {
-        if (hasIteratorIVReference(object, ivSyms)) {
-          iteratorMemberMapNotSupported(clauseLocation, object, semaCtx);
-          result.mapIterated.push_back(buildIteratedMapEntry(
-              converter, semaCtx, clauseLocation, iteratorRanges, object,
-              mapperIdName, mapTypeBits,
-              llvm::omp::Directive::OMPD_target_update));
-        } else {
-          omp::ObjectList singleObj{object};
-          processMapObjects(stmtCtx, clauseLocation, singleObj, mapTypeBits,
-                            parentMemberIndices, result.mapVars, mapObjects,
-                            mapperIdName, /*isMotionModifier=*/true,
-                            llvm::omp::Directive::OMPD_target_update);
-        }
-      }
+      processMapObjectsWithIterator(
+          stmtCtx, clauseLocation, objects, iteratorRanges, &ivSyms,
+          mapTypeBits, parentMemberIndices, result, mapObjects, mapperIdName,
+          /*isMotionModifier=*/true, llvm::omp::Directive::OMPD_target_update);
     } else {
-      processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits,
-                        parentMemberIndices, result.mapVars, mapObjects,
-                        mapperIdName, /*isMotionModifier=*/true,
-                        llvm::omp::Directive::OMPD_target_update);
+      processMapObjectsWithIterator(
+          stmtCtx, clauseLocation, objects, /*iteratorRanges=*/{},
+          /*ivSyms=*/nullptr, mapTypeBits, parentMemberIndices, result,
+          mapObjects, mapperIdName, /*isMotionModifier=*/true,
+          llvm::omp::Directive::OMPD_target_update);
     }
   };
 
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index 2a78cd52ee633..191b47bd05975 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -224,6 +224,18 @@ 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,
+      const llvm::SmallPtrSetImpl<const semantics::Symbol *> *ivSyms,
+      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;

>From f1040394ca638f1e861050e51879b8f8651197c2 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 23 Jun 2026 13:55:05 -0500
Subject: [PATCH 07/22] Preserve descriptor strides for iterator map bounds

Iterator map lowering keeps a stable array base pointer and describes the
selected element or section with bounds inside omp.iterator. For boxed arrays,
those bounds must use the byte stride stored for each descriptor dimension.

Use fir.box_dims for boxed iterator map bounds and pass through each
dimension's descriptor byte stride. This preserves non-contiguous
assumed-shape actuals while keeping unit element strides for non-box arrays.
---
 flang/lib/Lower/OpenMP/Utils.cpp            | 25 +++++++++++---
 flang/test/Lower/OpenMP/motion-iterator.f90 | 38 +++++++++++++++++++++
 2 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index a8009146bdd87..612cfb4c7053d 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1538,6 +1538,12 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
   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();
+    if (entity.isBoxAddress())
+      box = fir::LoadOp::create(builder, loc, box);
+  }
 
   auto convertToIndex = [&](mlir::Value value) -> mlir::Value {
     if (value.getType().isIndex())
@@ -1558,8 +1564,19 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
   for (const auto &[dim, subscript] : llvm::enumerate(arrayRef->subscript())) {
     mlir::Value baseLb =
         convertToIndex(hlfir::genLBound(loc, builder, entity, dim));
-    mlir::Value extent =
-        convertToIndex(hlfir::genExtent(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 = convertToIndex(hlfir::genExtent(loc, builder, entity, dim));
+    }
     mlir::Value lbound;
     mlir::Value ubound;
 
@@ -1604,8 +1621,8 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
     }
 
     mlir::Value bound = mlir::omp::MapBoundsOp::create(
-        builder, loc, boundTy, lbound, ubound, extent, /*stride=*/one,
-        /*stride_in_bytes=*/false, /*start_idx=*/baseLb);
+        builder, loc, boundTy, lbound, ubound, extent, stride,
+        /*stride_in_bytes=*/strideInBytes, /*start_idx=*/baseLb);
     bounds.push_back(bound);
   }
 
diff --git a/flang/test/Lower/OpenMP/motion-iterator.f90 b/flang/test/Lower/OpenMP/motion-iterator.f90
index a4c3adb5e6aec..6c831bb557b68 100644
--- a/flang/test/Lower/OpenMP/motion-iterator.f90
+++ b/flang/test/Lower/OpenMP/motion-iterator.f90
@@ -47,6 +47,44 @@ subroutine target_update_from_simple()
 ! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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: %[[IT:.*]] = omp.iterator(%{{.*}}: index, %{{.*}}: index) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[DIMS0:.*]]:3 = fir.box_dims %[[A]]#0, %{{.*}} : (!fir.box<!fir.array<?x?xf32>>, index) -> (index, index, index)
+! CHECK:   %[[BOUNDS0:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS0]]#1 : index) stride(%[[DIMS0]]#2 : index) start_idx(%{{.*}} : index) {stride_in_bytes = true}
+! CHECK:   %[[DIMS1:.*]]:3 = fir.box_dims %[[A]]#0, %{{.*}} : (!fir.box<!fir.array<?x?xf32>>, index) -> (index, index, index)
+! CHECK:   %[[BOUNDS1:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS1]]#1 : index) stride(%[[DIMS1]]#2 : index) start_idx(%{{.*}} : index) {stride_in_bytes = true}
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%{{.*}} : !fir.ref<!fir.array<?x?xf32>>, f32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS0]], %[[BOUNDS1]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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)

>From 698f71e7f8b905a055a83edc03f4f5bd8c86e269 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 23 Jun 2026 14:09:36 -0500
Subject: [PATCH 08/22] Remove unnecessary lambda and helpers

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 12 +++---------
 flang/lib/Lower/OpenMP/Utils.cpp           | 15 +++++----------
 2 files changed, 8 insertions(+), 19 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index bdfb8393b0eb9..4cc0e017609d5 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -379,13 +379,6 @@ static mlir::Value buildIteratedMapEntry(
       });
 }
 
-static void
-iteratorMemberMapNotSupported(mlir::Location loc, const omp::Object &object,
-                              Fortran::semantics::SemanticsContext &semaCtx) {
-  if (getBaseObject(object, semaCtx))
-    TODO(loc, "iterator modifier with derived type member map");
-}
-
 template <typename ClauseTuple>
 static void collectIteratorIVs(
     const ClauseTuple &clause, Fortran::lower::AbstractConverter &converter,
@@ -1998,8 +1991,9 @@ void ClauseProcessor::processMapObjectsWithIterator(
   // iterator variables, so handle each object separately.
   for (const omp::Object &object : objects) {
     if (hasIteratorIVReference(object, *ivSyms)) {
-      if (directive != llvm::omp::Directive::OMPD_unknown)
-        iteratorMemberMapNotSupported(clauseLocation, object, semaCtx);
+      if (directive != llvm::omp::Directive::OMPD_unknown &&
+          getBaseObject(object, semaCtx))
+        TODO(clauseLocation, "iterator modifier with derived type member map");
       result.mapIterated.push_back(buildIteratedMapEntry(
           converter, semaCtx, clauseLocation, iteratorRanges, object,
           mapperIdNameRef, mapTypeBits, directive));
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 612cfb4c7053d..2c7866c9498d5 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1545,16 +1545,10 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
       box = fir::LoadOp::create(builder, loc, box);
   }
 
-  auto convertToIndex = [&](mlir::Value value) -> mlir::Value {
-    if (value.getType().isIndex())
-      return value;
-    return fir::ConvertOp::create(builder, loc, idxTy, value);
-  };
-
   auto lowerSubscriptToIndex = [&](const SubscriptExpr &expr) -> mlir::Value {
     mlir::Value value = fir::getBase(
         converter.genExprValue(toEvExpr(expr), stmtCtx, &loc));
-    return convertToIndex(value);
+    return builder.createConvert(loc, idxTy, value);
   };
 
   llvm::SmallVector<mlir::Value> bounds;
@@ -1562,8 +1556,8 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
   // Translate each Fortran subscript into an OpenMP map bound for the
   // corresponding array dimension.
   for (const auto &[dim, subscript] : llvm::enumerate(arrayRef->subscript())) {
-    mlir::Value baseLb =
-        convertToIndex(hlfir::genLBound(loc, builder, entity, dim));
+    mlir::Value baseLb = builder.createConvert(
+        loc, idxTy, hlfir::genLBound(loc, builder, entity, dim));
     mlir::Value extent;
     mlir::Value stride = one;
     bool strideInBytes = false;
@@ -1575,7 +1569,8 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
       stride = dimInfo.getByteStride();
       strideInBytes = true;
     } else {
-      extent = convertToIndex(hlfir::genExtent(loc, builder, entity, dim));
+      extent = builder.createConvert(
+          loc, idxTy, hlfir::genExtent(loc, builder, entity, dim));
     }
     mlir::Value lbound;
     mlir::Value ubound;

>From 578db39633059080817f996c5b18e99899096feb Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 23 Jun 2026 16:57:12 -0500
Subject: [PATCH 09/22] Fix iterator map var_ptr for allocatable/pointer arrays

For allocatable or pointer arrays used in iterator map/motion locators (for
example, `target update to(iterator(i = 1:n): a(i))`), getIteratorMapEntity
returned the descriptor address, causing omp.map.info to record the Fortran
descriptor in var_ptr instead of the array data. This affected both whole-array
locators and derived-type component locators such as v%a(i) (via declare
mappers).

Load the descriptor in getIteratorMapEntity and use fir.box_addr for var_ptr,
while continuing to derive bounds and byte strides from the descriptor.
Assumed-shape and non-allocatable entities are unaffected.

Also replace the indirect `directive != OMPD_unknown` declare-mapper check with
a direct lookup of an enclosing omp.declare_mapper region.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    |  10 +-
 flang/lib/Lower/OpenMP/Utils.cpp              |  72 +++++----
 .../Todo/map-iterator-derived-member.f90      |   2 +-
 .../Todo/motion-iterator-derived-member.f90   |   2 +-
 .../Todo/target-iterator-derived-member.f90   |   2 +-
 .../Lower/OpenMP/declare-mapper-iterator.f90  | 140 +++++++++++++++++-
 flang/test/Lower/OpenMP/motion-iterator.f90   | 124 ++++++++++++++++
 7 files changed, 315 insertions(+), 37 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 4cc0e017609d5..da4923f628cf4 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1987,12 +1987,18 @@ void ClauseProcessor::processMapObjectsWithIterator(
     return;
   }
 
+  // Inside a declare mapper, the mapper variable's components are mapped
+  // directly, allowing member locators such as v%a(i). Mapping a derived-type
+  // member outside a declare mapper requires parent/member handling that
+  // iterator modifiers do not support yet.
+  bool inDeclareMapper = mlir::isa_and_present<mlir::omp::DeclareMapperOp>(
+      converter.getFirOpBuilder().getRegion().getParentOp());
+
   // Objects in an iterator-modified clause may independently reference
   // iterator variables, so handle each object separately.
   for (const omp::Object &object : objects) {
     if (hasIteratorIVReference(object, *ivSyms)) {
-      if (directive != llvm::omp::Directive::OMPD_unknown &&
-          getBaseObject(object, semaCtx))
+      if (!inDeclareMapper && getBaseObject(object, semaCtx))
         TODO(clauseLocation, "iterator modifier with derived type member map");
       result.mapIterated.push_back(buildIteratedMapEntry(
           converter, semaCtx, clauseLocation, iteratorRanges, object,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 2c7866c9498d5..002e6c5997e4f 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1463,45 +1463,55 @@ getIteratorMapEntity(Fortran::lower::AbstractConverter &converter,
   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);
-    return hlfir::Entity{info.addr};
+    addr = info.addr;
+  } else {
+    const std::optional<ExprTy> &ref = object.ref();
+    if (!ref)
+      return std::nullopt;
+
+    auto arrayRef = Fortran::lower::detail::getRef<evaluate::ArrayRef>(*ref);
+    if (!arrayRef)
+      return std::nullopt;
+
+    evaluate::ExpressionAnalyzer ea{semaCtx};
+    std::optional<ExprTy> arrayBase;
+    const evaluate::NamedEntity &base = arrayRef->base();
+    // Component array references carry the component base separately from the
+    // subscript list. Lower that base, e.g. v%a in v%a(i), and leave the
+    // subscript-dependent part to the iterator-local map bounds below.
+    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);
+    // Use mutable-box lowering for allocatable and pointer bases. The shared
+    // descriptor load below turns the box address into a box value.
+    fir::ExtendedValue dataExv;
+    if (semantics::IsAllocatableOrPointer(base.GetLastSymbol()))
+      dataExv = converter.genExprMutableBox(loc, *arrayBase);
+    else
+      dataExv = converter.genExprAddr(loc, *arrayBase, stmtCtx);
+    addr = fir::getBase(dataExv);
   }
 
-  const std::optional<ExprTy> &ref = object.ref();
-  if (!ref)
-    return std::nullopt;
-
-  auto arrayRef = Fortran::lower::detail::getRef<evaluate::ArrayRef>(*ref);
-  if (!arrayRef)
-    return std::nullopt;
-
-  evaluate::ExpressionAnalyzer ea{semaCtx};
-  std::optional<ExprTy> arrayBase;
-  const evaluate::NamedEntity &base = arrayRef->base();
-  // Component array references carry the component base separately from the
-  // subscript list. Lower that base, e.g. v%a in v%a(i), and leave the
-  // subscript-dependent part to the iterator-local map bounds below.
-  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);
-  // Preserve mutable-box lowering for allocatable and pointer bases; MapInfoOp
-  // still records the address returned by the lowered base expression.
-  fir::ExtendedValue dataExv;
-  if (semantics::IsAllocatableOrPointer(base.GetLastSymbol()))
-    dataExv = converter.genExprMutableBox(loc, *arrayBase);
-  else
-    dataExv = converter.genExprAddr(loc, *arrayBase, stmtCtx);
-  return hlfir::Entity{fir::getBase(dataExv)};
+  // Allocatable and pointer locators yield a descriptor address
+  // (!fir.ref<!fir.box<...>>) for both whole objects and components. Load the
+  // descriptor so var_ptr maps the array data, not the descriptor.
+  // Assumed-shape arrays and non-allocatable components are already box values
+  // or plain addresses and need no load.
+  if (fir::isBoxAddress(addr.getType()))
+    addr = fir::LoadOp::create(builder, loc, addr);
+  return hlfir::Entity{addr};
 }
 
 // Build normalized map bounds for an iterator-dependent map/motion object.
diff --git a/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90
index 2087121810219..f482f79333cb0 100644
--- a/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90
+++ b/flang/test/Lower/OpenMP/Todo/map-iterator-derived-member.f90
@@ -10,4 +10,4 @@ subroutine target_data_derived_member_iterator()
   !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
\ No newline at end of file
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90
index bf0b99db9b4a7..66ecba9c92ffd 100644
--- a/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90
+++ b/flang/test/Lower/OpenMP/Todo/motion-iterator-derived-member.f90
@@ -9,4 +9,4 @@ subroutine target_update_derived_member_iterator()
 
   !CHECK: not yet implemented: iterator modifier with derived type member map
   !$omp target update to(iterator(i = 1:10): x%a(i))
-end subroutine
\ No newline at end of file
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
index 8beb69a30bbf6..989b21592109e 100644
--- a/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
+++ b/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
@@ -10,4 +10,4 @@ subroutine target_derived_member_iterator()
   !CHECK: not yet implemented: iterator modifier with derived type member map
   !$omp target map(iterator(i = 1:10), tofrom: x%a(i))
   !$omp end target
-end subroutine
\ No newline at end of file
+end subroutine
diff --git a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90 b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
index c402129f27e15..fe8cfabea1751 100644
--- a/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
+++ b/flang/test/Lower/OpenMP/declare-mapper-iterator.f90
@@ -1,5 +1,57 @@
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s | FileCheck %s
 
+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)
@@ -9,7 +61,7 @@ subroutine declare_mapper_iterator(arg)
   !$omp declare mapper(m: s :: v) map(iterator(i = 1:10): v%a(i))
 end
 
-! CHECK-LABEL: omp.declare_mapper
+! 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:.*]]: index) = ({{.*}} to {{.*}} step {{.*}}) {
@@ -26,3 +78,89 @@ subroutine declare_mapper_iterator(arg)
 ! CHECK:     omp.yield(%[[MAP]] : !llvm.ptr)
 ! CHECK:   } -> !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:.*]]: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:     omp.yield(%[[MAP2]] : !llvm.ptr)
+! CHECK:   } -> !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(%{{.*}}: index) = ({{.*}}) {
+! 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(%{{.*}}) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !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:   %[[IT:.*]] = omp.iterator(%{{.*}}: index, %{{.*}}: index) = ({{.*}}) {
+! CHECK:     %[[A:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.ref<!fir.array<4x6xi32>>
+! CHECK:     %[[B0:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index)
+! CHECK:     %[[B1:.*]] = 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<4x6xi32>>, !fir.array<4x6xi32>) map_clauses(tofrom) capture(ByRef) bounds(%[[B0]], %[[B1]]) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !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(%{{.*}}: index) = ({{.*}}) {
+! 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !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(%{{.*}}: index) = ({{.*}}) {
+! 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(%{{.*}}) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !omp.iterated<!llvm.ptr>
+! CHECK:   %[[IT_B:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}}) {
+! 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(%{{.*}}) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !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(%{{.*}}: index) = ({{.*}}) {
+! 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !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:   %[[IT:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}}) {
+! CHECK:     %[[BOX:.*]] = hlfir.designate %[[DECL]]#0{"a"}{{.*}} -> !fir.box<!fir.array<10xi32>>
+! CHECK:     %[[DIMS:.*]]:3 = fir.box_dims %[[BOX]], %{{.*}} : (!fir.box<!fir.array<10xi32>>, 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 %[[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]]) -> !llvm.ptr {name = ""}
+! CHECK:   } -> !omp.iterated<!llvm.ptr>
+! CHECK:   omp.declare_mapper.info map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
diff --git a/flang/test/Lower/OpenMP/motion-iterator.f90 b/flang/test/Lower/OpenMP/motion-iterator.f90
index 6c831bb557b68..a79506469c880 100644
--- a/flang/test/Lower/OpenMP/motion-iterator.f90
+++ b/flang/test/Lower/OpenMP/motion-iterator.f90
@@ -85,6 +85,106 @@ subroutine target_update_assumed_shape_2d(a, n, m)
 ! CHECK: } -> !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 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtarget_update_allocatableEa"}
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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 {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFtarget_update_pointerEa"}
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtarget_update_allocatable_2dEa"}
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: index, %{{.*}}: index) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
+! CHECK:   %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>
+! CHECK:   %[[BOUNDS0:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index) {stride_in_bytes = true}
+! CHECK:   %[[BOUNDS1:.*]] = 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?xi32>>>) -> !fir.heap<!fir.array<?x?xi32>>
+! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?x?xi32>>, i32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS0]], %[[BOUNDS1]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtarget_update_deferred_charEa"}
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtarget_update_class_starEa"}
+! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !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)
@@ -330,6 +430,30 @@ subroutine target_update_iterated_default_mapper()
 ! CHECK: } -> !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(%{{.*}}: index) = ({{.*}} 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]]) -> !llvm.ptr {name = ""}
+! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: } -> !omp.iterated<!llvm.ptr>
+! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
+
 !===============================================================================
 ! target data
 !===============================================================================

>From 932ffbbfbf5eed394f143fad175724554c996f5d Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 4 Aug 2026 14:07:52 -0500
Subject: [PATCH 10/22] Reject target map iterator modifiers

Executable target regions need stable block arguments for iterator-mapped
objects. Without separate captures, implicit lowering adds a whole-object
`tofrom` map that changes the requested extent and direction.

Reject this unsupported case until target map iterator captures are
available, and add TODO coverage for the reported array use.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp          |  9 +++++++++
 .../OpenMP/Todo/target-iterator-derived-member.f90  | 13 -------------
 .../test/Lower/OpenMP/Todo/target-map-iterator.f90  | 12 ++++++++++++
 3 files changed, 21 insertions(+), 13 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/target-map-iterator.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index da4923f628cf4..b2adb330e5646 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2135,6 +2135,15 @@ bool ClauseProcessor::processMap(
     TodoLocators(currentLocation, objects);
 
     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");
+
       llvm::SmallVector<IteratorRange> iteratorRanges;
       llvm::SmallPtrSet<const Fortran::semantics::Symbol *, 4> ivSyms;
       collectIteratorIVs(clause, converter, stmtCtx, iteratorRanges, ivSyms);
diff --git a/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90 b/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
deleted file mode 100644
index 989b21592109e..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/target-iterator-derived-member.f90
+++ /dev/null
@@ -1,13 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-subroutine target_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 map(iterator(i = 1:10), tofrom: x%a(i))
-  !$omp end target
-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 0000000000000..9de14b25e4d60
--- /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

>From fdadf2f308b0657e5903d3b980e977c0bce73fe1 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 4 Aug 2026 14:26:15 -0500
Subject: [PATCH 11/22] Make iterator map finalization more robust

MapInfoFinalization skips maps nested in `omp.iterator`, so descriptor-backed
iterator maps are not expanded. Add end-to-end Fortran coverage for
assumed-shape and allocatable arrays.

Make the iterator guard explicit in the record-member walk and remove the
unreachable `omp.yield` traversal from `getFirstTargetUser`.
---
 .../Optimizer/OpenMP/MapInfoFinalization.cpp  | 18 ++------
 .../map-iterator-descriptor-finalization.f90  | 46 +++++++++++++++++++
 2 files changed, 49 insertions(+), 15 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90

diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index 99120a69f2f28..f7093747f3c7e 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -722,21 +722,6 @@ class MapInfoFinalizationPass
 
       if (auto mapUser = llvm::dyn_cast<mlir::omp::MapInfoOp>(user))
         return getFirstTargetUser(mapUser);
-
-      // MapInfoOp inside an omp.iterator body is yielded back to the iterator,
-      // whose result is used by the target op.
-      if (llvm::isa<mlir::omp::YieldOp>(user)) {
-        if (auto iterOp = user->getParentOfType<mlir::omp::IteratorOp>()) {
-          for (auto *iterUser : iterOp->getUsers()) {
-            if (llvm::isa<mlir::omp::TargetOp, mlir::omp::TargetDataOp,
-                          mlir::omp::TargetUpdateOp,
-                          mlir::omp::TargetExitDataOp,
-                          mlir::omp::TargetEnterDataOp,
-                          mlir::omp::DeclareMapperInfoOp>(iterUser))
-              return iterUser;
-          }
-        }
-      }
     }
 
     return nullptr;
@@ -1416,6 +1401,9 @@ class MapInfoFinalizationPass
       // Next, walk `omp.map.info` ops to see if any record members should be
       // implicitly mapped.
       func->walk([&](mlir::omp::MapInfoOp op) {
+        if (isNestedInIterator(op))
+          return mlir::WalkResult::advance();
+
         mlir::Type underlyingType =
             fir::unwrapRefType(op.getVarPtr().getType());
 
diff --git a/flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90 b/flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90
new file mode 100644
index 0000000000000..756e32693f87d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90
@@ -0,0 +1,46 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | \
+! RUN:   FileCheck %s --implicit-check-not=fir.box_offset \
+! RUN:   --implicit-check-not=map_entries \
+! RUN:   --implicit-check-not='map_clauses(attach'
+
+! Verify that MapInfoFinalization does not expand iterator-nested maps for
+! descriptor-backed arrays.
+
+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: } -> !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: } -> !omp.iterated
+! CHECK: omp.target_enter_data map_iterated(%[[IT]]

>From 7c30f67547d9bce4bde980111f0af7f2bf31dd0e Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 4 Aug 2026 14:41:29 -0500
Subject: [PATCH 12/22] Align descriptor warnings with iterator maps

Iterator maps lower descriptor-backed arrays to data-only entries. Skip the
temporary-descriptor warning for those maps and document the contract.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    |  4 +-
 flang/lib/Semantics/check-omp-structure.cpp   | 54 +++++++++++++++++--
 .../map-iterator-descriptor-finalization.f90  |  4 +-
 ...arget-enter-data-temp-descriptor-omp61.f90 | 24 +++++++++
 .../target-enter-data-temp-descriptor.f90     | 24 +++++++++
 5 files changed, 103 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index b2adb330e5646..db2a3cda23c71 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -362,7 +362,9 @@ static mlir::Value buildIteratedMapEntry(
 
         // Use the array base as var_ptr with bounds so the runtime can
         // associate this mapping with whole-array mappings via the base
-        // address.
+        // address. Iterator maps represent selected data directly and do not
+        // include the descriptor parent or attachment entries used by ordinary
+        // descriptor maps.
         mlir::Value baseAddr = mapInfo->entity.getBase();
         if (mlir::isa<fir::BaseBoxType>(baseAddr.getType()))
           baseAddr = fir::BoxAddrOp::create(builder, loc, baseAddr);
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 7bd5f1720fb3c..e2786a1f869ee 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -433,6 +433,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.
@@ -4784,9 +4806,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};
@@ -4922,7 +4944,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
   // 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.
+  // combination. Iterator-dependent objects are also ignored because lowering
+  // maps only their selected data, not the descriptor.
   //
   // 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
@@ -4932,7 +4955,30 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
   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/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90 b/flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90
index 756e32693f87d..00d84cd6fbef4 100644
--- a/flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90
+++ b/flang/test/Lower/OpenMP/map-iterator-descriptor-finalization.f90
@@ -3,8 +3,8 @@
 ! RUN:   --implicit-check-not=map_entries \
 ! RUN:   --implicit-check-not='map_clauses(attach'
 
-! Verify that MapInfoFinalization does not expand iterator-nested maps for
-! descriptor-backed arrays.
+! 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(:)
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 e766b1b44d0fa..f7f99db8f76c3 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,27 @@ 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
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 bd1eb98ebec60..d40f83af279b2 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,27 @@ 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

>From 67c30390f049be2d627800f41963a3cf25400c8b Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 4 Aug 2026 16:28:21 -0500
Subject: [PATCH 13/22] Reject optional iterator locators

Optional iterator locators require presence-aware map construction. Reject
them until lowering can omit absent optional list items safely.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp            |  3 +++
 .../test/Lower/OpenMP/Todo/map-iterator-optional.f90  | 11 +++++++++++
 .../Lower/OpenMP/Todo/motion-iterator-optional.f90    | 11 +++++++++++
 3 files changed, 25 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/Todo/map-iterator-optional.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/motion-iterator-optional.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index db2a3cda23c71..82f40301b003d 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2002,6 +2002,9 @@ void ClauseProcessor::processMapObjectsWithIterator(
     if (hasIteratorIVReference(object, *ivSyms)) {
       if (!inDeclareMapper && getBaseObject(object, semaCtx))
         TODO(clauseLocation, "iterator modifier with derived type member map");
+      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, iteratorRanges, object,
           mapperIdNameRef, mapTypeBits, directive));
diff --git a/flang/test/Lower/OpenMP/Todo/map-iterator-optional.f90 b/flang/test/Lower/OpenMP/Todo/map-iterator-optional.f90
new file mode 100644
index 0000000000000..c5b7812b41064
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/map-iterator-optional.f90
@@ -0,0 +1,11 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+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
diff --git a/flang/test/Lower/OpenMP/Todo/motion-iterator-optional.f90 b/flang/test/Lower/OpenMP/Todo/motion-iterator-optional.f90
new file mode 100644
index 0000000000000..8570d724468b0
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/motion-iterator-optional.f90
@@ -0,0 +1,11 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+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

>From 459d6a9368b3e1f39be8f13cd455de1fb7592be6 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 4 Aug 2026 16:53:18 -0500
Subject: [PATCH 14/22] Apply clang-format to iterator lowering

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 3 +--
 flang/lib/Lower/OpenMP/Utils.cpp           | 4 ++--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 82f40301b003d..8abb2e847be8d 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2146,8 +2146,7 @@ bool ClauseProcessor::processMap(
       // 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");
+        TODO(clauseLocation, "TARGET construct with MAP iterator modifier");
 
       llvm::SmallVector<IteratorRange> iteratorRanges;
       llvm::SmallPtrSet<const Fortran::semantics::Symbol *, 4> ivSyms;
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 002e6c5997e4f..6e99f4e2c89fc 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1556,8 +1556,8 @@ genIteratorMapBounds(Fortran::lower::AbstractConverter &converter,
   }
 
   auto lowerSubscriptToIndex = [&](const SubscriptExpr &expr) -> mlir::Value {
-    mlir::Value value = fir::getBase(
-        converter.genExprValue(toEvExpr(expr), stmtCtx, &loc));
+    mlir::Value value =
+        fir::getBase(converter.genExprValue(toEvExpr(expr), stmtCtx, &loc));
     return builder.createConvert(loc, idxTy, value);
   };
 

>From b9630c050030167b0174d8f59cacc4fbb5aa89b1 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 5 Aug 2026 12:22:30 -0500
Subject: [PATCH 15/22] Preserve implicit default mappers for target update

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 11 ++--
 .../OpenMP/target-update-implicit-mapper.f90  | 50 +++++++++++++++++++
 2 files changed, 58 insertions(+), 3 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/target-update-implicit-mapper.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 8abb2e847be8d..d2b211d1d1034 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -2176,6 +2176,12 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
   std::map<Object, OmpMapParentAndMemberData> parentMemberIndices;
   llvm::SmallVector<Object> mapObjects;
 
+  // Preserve implicit default mapper synthesis for target update. The mapper
+  // resolver currently treats OMPD_unknown as the motion-directive path that
+  // may generate an implicit mapper.
+  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;
@@ -2201,13 +2207,12 @@ bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx,
       processMapObjectsWithIterator(
           stmtCtx, clauseLocation, objects, iteratorRanges, &ivSyms,
           mapTypeBits, parentMemberIndices, result, mapObjects, mapperIdName,
-          /*isMotionModifier=*/true, llvm::omp::Directive::OMPD_target_update);
+          /*isMotionModifier=*/true, mapperDirective);
     } else {
       processMapObjectsWithIterator(
           stmtCtx, clauseLocation, objects, /*iteratorRanges=*/{},
           /*ivSyms=*/nullptr, mapTypeBits, parentMemberIndices, result,
-          mapObjects, mapperIdName, /*isMotionModifier=*/true,
-          llvm::omp::Directive::OMPD_target_update);
+          mapObjects, mapperIdName, /*isMotionModifier=*/true, mapperDirective);
     }
   };
 
diff --git a/flang/test/Lower/OpenMP/target-update-implicit-mapper.f90 b/flang/test/Lower/OpenMP/target-update-implicit-mapper.f90
new file mode 100644
index 0000000000000..efb23cf976bdc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/target-update-implicit-mapper.f90
@@ -0,0 +1,50 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s | \
+! RUN:   FileCheck %s
+
+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

>From 359c8ed3e7919352e70cc779ef3aff0d01568270 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 5 Aug 2026 12:25:39 -0500
Subject: [PATCH 16/22] Reject reference modifiers on iterator maps

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 12 +++
 .../OpenMP/Todo/map-iterator-ref-attach.f90   | 81 +++++++++++++++++++
 2 files changed, 93 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/Todo/map-iterator-ref-attach.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index d2b211d1d1034..90c11724c1f27 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1996,10 +1996,22 @@ void ClauseProcessor::processMapObjectsWithIterator(
   bool inDeclareMapper = mlir::isa_and_present<mlir::omp::DeclareMapperOp>(
       converter.getFirOpBuilder().getRegion().getParentOp());
 
+  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) {
     if (hasIteratorIVReference(object, *ivSyms)) {
+      if (hasUnsupportedReferenceModifier)
+        TODO(clauseLocation,
+             "iterator modifier with reference or attach modifier");
       if (!inDeclareMapper && getBaseObject(object, semaCtx))
         TODO(clauseLocation, "iterator modifier with derived type member map");
       if (const auto *symbol{object.sym()};
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 0000000000000..ff16560140b26
--- /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

>From 377d24362f798ef44b7635abb4f54abbed8698af Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 5 Aug 2026 12:30:28 -0500
Subject: [PATCH 17/22] Validate declare mapper iterator member bases

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 34 +++++++++++++++----
 ...eclare-mapper-iterator-external-member.f90 | 13 +++++++
 2 files changed, 40 insertions(+), 7 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-member.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 90c11724c1f27..4b899d1244040 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1989,12 +1989,32 @@ void ClauseProcessor::processMapObjectsWithIterator(
     return;
   }
 
-  // Inside a declare mapper, the mapper variable's components are mapped
-  // directly, allowing member locators such as v%a(i). Mapping a derived-type
-  // member outside a declare mapper requires parent/member handling that
-  // iterator modifiers do not support yet.
-  bool inDeclareMapper = mlir::isa_and_present<mlir::omp::DeclareMapperOp>(
-      converter.getFirOpBuilder().getRegion().getParentOp());
+  auto isDeclareMapperVariable = [&](const omp::Object &object) {
+    auto declareMapper = mlir::dyn_cast_if_present<mlir::omp::DeclareMapperOp>(
+        converter.getFirOpBuilder().getRegion().getParentOp());
+    if (!declareMapper)
+      return false;
+
+    omp::Object rootObject{object};
+    while (std::optional<omp::Object> baseObject =
+               getBaseObject(rootObject, semaCtx))
+      rootObject = *baseObject;
+
+    mlir::Value rootAddress = converter.getSymbolAddress(*rootObject.sym());
+    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 |
@@ -2012,7 +2032,7 @@ void ClauseProcessor::processMapObjectsWithIterator(
       if (hasUnsupportedReferenceModifier)
         TODO(clauseLocation,
              "iterator modifier with reference or attach modifier");
-      if (!inDeclareMapper && getBaseObject(object, semaCtx))
+      if (getBaseObject(object, semaCtx) && !isDeclareMapperVariable(object))
         TODO(clauseLocation, "iterator modifier with derived type member map");
       if (const auto *symbol{object.sym()};
           symbol && semantics::IsOptional(*symbol))
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-member.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-member.f90
new file mode 100644
index 0000000000000..94f5f6d0c782c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-member.f90
@@ -0,0 +1,13 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! 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

>From 6a07adc06c18b8390c83a1c9dde8683d4adc2caf Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 5 Aug 2026 12:31:33 -0500
Subject: [PATCH 18/22] Test descriptor warning for iterator exit maps

---
 .../OpenMP/target-enter-data-temp-descriptor-omp61.f90   | 9 +++++++++
 .../OpenMP/target-enter-data-temp-descriptor.f90         | 9 +++++++++
 2 files changed, 18 insertions(+)

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 f7f99db8f76c3..8e0efb765ff6a 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
@@ -119,3 +119,12 @@ subroutine test_iterator_mixed_object_warning(arr, other, n)
   !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 d40f83af279b2..3eb8b9d7c50af 100644
--- a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
+++ b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
@@ -115,3 +115,12 @@ subroutine test_iterator_mixed_object_warning(arr, other, n)
   !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

>From dcbc24f1c7ba146c9d7f96f5d7c8574bda2d4e57 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 5 Aug 2026 12:33:36 -0500
Subject: [PATCH 19/22] Strengthen two-dimensional iterator bounds checks

---
 flang/test/Lower/OpenMP/motion-iterator.f90 | 49 ++++++++++++++++++---
 1 file changed, 42 insertions(+), 7 deletions(-)

diff --git a/flang/test/Lower/OpenMP/motion-iterator.f90 b/flang/test/Lower/OpenMP/motion-iterator.f90
index a79506469c880..748f7688adc8b 100644
--- a/flang/test/Lower/OpenMP/motion-iterator.f90
+++ b/flang/test/Lower/OpenMP/motion-iterator.f90
@@ -137,13 +137,48 @@ subroutine target_update_allocatable_2d(a, n, m)
 
 ! CHECK-LABEL: func.func @_QPtarget_update_allocatable_2d
 ! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} dummy_scope %{{.*}} arg 1 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtarget_update_allocatable_2dEa"}
-! CHECK: %[[IT:.*]] = omp.iterator(%{{.*}}: index, %{{.*}}: index) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
-! CHECK:   %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>
-! CHECK:   %[[BOUNDS0:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) start_idx(%{{.*}} : index) {stride_in_bytes = true}
-! CHECK:   %[[BOUNDS1:.*]] = 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?xi32>>>) -> !fir.heap<!fir.array<?x?xi32>>
-! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]] : !fir.heap<!fir.array<?x?xi32>>, i32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS0]], %[[BOUNDS1]]) -> !llvm.ptr {name = ""}
-! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV0:.*]]: index,
+! CHECK-SAME: %[[IV1:.*]]: index) =
+! CHECK: %[[IV0_I32:.*]] = fir.convert %[[IV0]] : (index) -> i32
+! CHECK: fir.store %[[IV0_I32]] to %[[IV0_ADDR:.*]] : !fir.ref<i32>
+! CHECK: %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_ADDR]]
+! CHECK: %[[IV1_I32:.*]] = fir.convert %[[IV1]] : (index) -> i32
+! CHECK: fir.store %[[IV1_I32]] 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: %[[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: %[[BASE:.*]] = fir.box_addr %[[BOX]]
+! CHECK: %[[MAP:.*]] = omp.map.info var_ptr(%[[BASE]]
+! CHECK-SAME: map_clauses(to)
+! CHECK-SAME: bounds(%[[BOUNDS0]], %[[BOUNDS1]])
+! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
 ! CHECK: } -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
 

>From b98b49c029023b09c121b2a0124affa5ff47a8e4 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 5 Aug 2026 12:38:31 -0500
Subject: [PATCH 20/22] Centralize iterator map filtering in
 MapInfoFinalization

---
 .../Optimizer/OpenMP/MapInfoFinalization.cpp  | 36 ++++++++---------
 .../Transforms/omp-map-info-finalization.fir  | 39 +++++++++++++++++++
 2 files changed, 55 insertions(+), 20 deletions(-)

diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index f7093747f3c7e..a6dbd67390921 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -1384,15 +1384,20 @@ class MapInfoFinalizationPass
       deferrableDesc.clear();
       expandedBaseAddr.clear();
 
+      auto walkNonIteratedMapInfoOps = [&](auto &&callback) {
+        func->walk([&](mlir::omp::MapInfoOp op) {
+          if (!isNestedInIterator(op))
+            callback(op);
+        });
+      };
+
       // 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) {
-        if (isNestedInIterator(op))
-          return;
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         mlir::Operation *targetUser = getFirstTargetUser(op);
         assert(targetUser && "expected user of map operation was not found");
         addImplicitMembersToTarget(op, builder, targetUser);
@@ -1400,17 +1405,14 @@ class MapInfoFinalizationPass
 
       // Next, walk `omp.map.info` ops to see if any record members should be
       // implicitly mapped.
-      func->walk([&](mlir::omp::MapInfoOp op) {
-        if (isNestedInIterator(op))
-          return mlir::WalkResult::advance();
-
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         mlir::Type underlyingType =
             fir::unwrapRefType(op.getVarPtr().getType());
 
         // TODO Test with and support more complicated cases; like arrays for
         // records, for example.
         if (!fir::isRecordWithAllocatableMember(underlyingType))
-          return mlir::WalkResult::advance();
+          return;
 
         // TODO For now, only consider `omp.target` ops. Other ops that support
         // `map` clauses will follow later.
@@ -1419,7 +1421,7 @@ class MapInfoFinalizationPass
                 getFirstTargetUser(op));
 
         if (!target)
-          return mlir::WalkResult::advance();
+          return;
 
         auto mapClauseOwner =
             llvm::dyn_cast<mlir::omp::MapClauseOwningOpInterface>(*target);
@@ -1554,7 +1556,7 @@ class MapInfoFinalizationPass
         }
 
         if (newMapOpsForFields.empty())
-          return mlir::WalkResult::advance();
+          return;
 
         // Deduplicate by index path to avoid emitting duplicate members for
         // the same component. Use a set-based key to keep this near O(n).
@@ -1588,14 +1590,12 @@ class MapInfoFinalizationPass
         op.setMembersIndexAttr(builder.create2DI64ArrayAttr(newMemberIndices));
         // Set to partial map only if there is no user-defined mapper.
         op.setPartialMap(op.getMapperIdAttr() == nullptr);
-
-        return mlir::WalkResult::advance();
       });
 
       // Expand type(C_PTR) only when unified_shared_memory is required,
       // to ensure device-visible pointer size/behavior in USM scenarios
       // without changing default expectations elsewhere.
-      func->walk([&](mlir::omp::MapInfoOp op) {
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         // Only expand C_PTR members when unified_shared_memory is required.
         if (!moduleRequiresUSM(func->getParentOfType<mlir::ModuleOp>()))
           return;
@@ -1603,9 +1603,7 @@ class MapInfoFinalizationPass
         genCptrMemberMap(op, builder);
       });
 
-      func->walk([&](mlir::omp::MapInfoOp op) {
-        if (isNestedInIterator(op))
-          return;
+      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
@@ -1631,7 +1629,7 @@ class MapInfoFinalizationPass
         }
       });
 
-      func->walk([&](mlir::omp::MapInfoOp op) {
+      walkNonIteratedMapInfoOps([&](mlir::omp::MapInfoOp op) {
         // If a record type is not mapped with the `close` modifier while some
         // of its members are (e.g. descriptor maps), then in USM mode, the
         // memory for the record will be allocated in unified memory while the
@@ -1700,9 +1698,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) {
-        if (isNestedInIterator(op))
-          return;
+      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/test/Transforms/omp-map-info-finalization.fir b/flang/test/Transforms/omp-map-info-finalization.fir
index 7b90322d3929c..5ada48100511d 100644
--- a/flang/test/Transforms/omp-map-info-finalization.fir
+++ b/flang/test/Transforms/omp-map-info-finalization.fir
@@ -67,6 +67,45 @@ func.func @test_iterator_descriptor_map_is_not_expanded(%arg0: !fir.box<!fir.arr
 
 // -----
 
+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) -> !fir.ref<i32>
+        {name = "record%x"}
+    %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>)
+        -> !fir.ref<!fir.type<_QFiterator_memberTt{x:i32}>>
+        {name = "record", partial_map = true}
+    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) -> !fir.ref<f32> {name = "scalar_struct%rx"}

>From 1180bdcf957c31d9d77e5b47826854451d40e9d0 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Thu, 6 Aug 2026 11:32:25 -0500
Subject: [PATCH 21/22] Reject external iterator locators in declare mappers

---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp        | 10 +++++++---
 .../declare-mapper-iterator-external-array.f90    | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 3 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-array.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 4b899d1244040..f8035d0dbb6f2 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1989,9 +1989,9 @@ void ClauseProcessor::processMapObjectsWithIterator(
     return;
   }
 
+  auto declareMapper = mlir::dyn_cast_if_present<mlir::omp::DeclareMapperOp>(
+      converter.getFirOpBuilder().getRegion().getParentOp());
   auto isDeclareMapperVariable = [&](const omp::Object &object) {
-    auto declareMapper = mlir::dyn_cast_if_present<mlir::omp::DeclareMapperOp>(
-        converter.getFirOpBuilder().getRegion().getParentOp());
     if (!declareMapper)
       return false;
 
@@ -2029,11 +2029,15 @@ void ClauseProcessor::processMapObjectsWithIterator(
   // iterator variables, so handle each object separately.
   for (const omp::Object &object : objects) {
     if (hasIteratorIVReference(object, *ivSyms)) {
+      bool isMapperVariable = isDeclareMapperVariable(object);
       if (hasUnsupportedReferenceModifier)
         TODO(clauseLocation,
              "iterator modifier with reference or attach modifier");
-      if (getBaseObject(object, semaCtx) && !isDeclareMapperVariable(object))
+      if (getBaseObject(object, semaCtx) && !isMapperVariable)
         TODO(clauseLocation, "iterator modifier with derived type member map");
+      if (declareMapper && !isMapperVariable)
+        TODO(clauseLocation,
+             "iterator modifier with locator outside declare mapper variable");
       if (const auto *symbol{object.sym()};
           symbol && semantics::IsOptional(*symbol))
         TODO(clauseLocation, "iterator modifier with optional locator");
diff --git a/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-array.f90 b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-array.f90
new file mode 100644
index 0000000000000..11d6bcfbac9da
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/declare-mapper-iterator-external-array.f90
@@ -0,0 +1,15 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp \
+! RUN:   -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! 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

>From 5159a83d55c5d74114209269d5e969009b7d535a Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Thu, 6 Aug 2026 11:35:48 -0500
Subject: [PATCH 22/22] Strengthen two-dimensional descriptor iterator checks

---
 flang/test/Lower/OpenMP/motion-iterator.f90 | 60 ++++++++++++++++++---
 1 file changed, 52 insertions(+), 8 deletions(-)

diff --git a/flang/test/Lower/OpenMP/motion-iterator.f90 b/flang/test/Lower/OpenMP/motion-iterator.f90
index 748f7688adc8b..0c14d8a9156b7 100644
--- a/flang/test/Lower/OpenMP/motion-iterator.f90
+++ b/flang/test/Lower/OpenMP/motion-iterator.f90
@@ -75,13 +75,51 @@ subroutine target_update_assumed_shape_2d(a, n, m)
 
 ! 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: %[[IT:.*]] = omp.iterator(%{{.*}}: index, %{{.*}}: index) = ({{.*}} to {{.*}} step {{.*}}, {{.*}} to {{.*}} step {{.*}}) {
-! CHECK:   %[[DIMS0:.*]]:3 = fir.box_dims %[[A]]#0, %{{.*}} : (!fir.box<!fir.array<?x?xf32>>, index) -> (index, index, index)
-! CHECK:   %[[BOUNDS0:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS0]]#1 : index) stride(%[[DIMS0]]#2 : index) start_idx(%{{.*}} : index) {stride_in_bytes = true}
-! CHECK:   %[[DIMS1:.*]]:3 = fir.box_dims %[[A]]#0, %{{.*}} : (!fir.box<!fir.array<?x?xf32>>, index) -> (index, index, index)
-! CHECK:   %[[BOUNDS1:.*]] = omp.map.bounds lower_bound(%{{.*}} : index) upper_bound(%{{.*}} : index) extent(%[[DIMS1]]#1 : index) stride(%[[DIMS1]]#2 : index) start_idx(%{{.*}} : index) {stride_in_bytes = true}
-! CHECK:   %[[MAP:.*]] = omp.map.info var_ptr(%{{.*}} : !fir.ref<!fir.array<?x?xf32>>, f32) map_clauses(to) capture(ByRef) bounds(%[[BOUNDS0]], %[[BOUNDS1]]) -> !llvm.ptr {name = ""}
-! CHECK:   omp.yield(%[[MAP]] : !llvm.ptr)
+! CHECK: %[[IT:.*]] = omp.iterator(%[[IV0:.*]]: index,
+! CHECK-SAME: %[[IV1:.*]]: index) =
+! CHECK: %[[IV0_I32:.*]] = fir.convert %[[IV0]] : (index) -> i32
+! CHECK: fir.store %[[IV0_I32]] to %[[IV0_ADDR:.*]] : !fir.ref<i32>
+! CHECK: %[[IV0_DECL:.*]]:2 = hlfir.declare %[[IV0_ADDR]]
+! CHECK: %[[IV1_I32:.*]] = fir.convert %[[IV1]] : (index) -> i32
+! CHECK: fir.store %[[IV1_I32]] 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: -> !llvm.ptr
+! CHECK-SAME: {name = ""}
+! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
 ! CHECK: } -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)
 
@@ -160,6 +198,7 @@ subroutine target_update_allocatable_2d(a, n, m)
 ! 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
@@ -174,10 +213,15 @@ subroutine target_update_allocatable_2d(a, n, m)
 ! 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 var_ptr(%[[BASE]]
+! 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: -> !llvm.ptr
+! CHECK-SAME: {name = ""}
 ! CHECK: omp.yield(%[[MAP]] : !llvm.ptr)
 ! CHECK: } -> !omp.iterated<!llvm.ptr>
 ! CHECK: omp.target_update map_iterated(%[[IT]] : !omp.iterated<!llvm.ptr>)



More information about the flang-commits mailing list