[flang-commits] [flang] [flang][OpenMP] Diagnose task reduction array sections and elements (PR #215997)

Tom Eccles via flang-commits flang-commits at lists.llvm.org
Tue Aug 25 04:35:04 PDT 2026


https://github.com/tblah updated https://github.com/llvm/llvm-project/pull/215997

>From 2b0356789055d3687be9621ab84457384d2de063 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Mon, 24 Aug 2026 13:04:37 +0100
Subject: [PATCH 01/18] [flang][OpenMP] NFC: Extract intrinsic reduction shadow
 lookup

Centralize lookup of user-defined reductions that shadow supported
intrinsic procedure reductions. Keep existing behavior while making the
classification available to other OpenMP lowering paths.

This will help with #215997

Assisted-by: Codex
---
 .../flang/Lower/Support/ReductionProcessor.h  |   8 ++
 .../lib/Lower/Support/ReductionProcessor.cpp  | 131 ++++++++++--------
 2 files changed, 81 insertions(+), 58 deletions(-)

diff --git a/flang/include/flang/Lower/Support/ReductionProcessor.h b/flang/include/flang/Lower/Support/ReductionProcessor.h
index b86cc07159246..fdc1e7320684b 100644
--- a/flang/include/flang/Lower/Support/ReductionProcessor.h
+++ b/flang/include/flang/Lower/Support/ReductionProcessor.h
@@ -33,6 +33,7 @@ namespace lower {
 class AbstractConverter;
 } // namespace lower
 namespace semantics {
+class Scope;
 class SemanticsContext;
 } // namespace semantics
 } // namespace Fortran
@@ -93,6 +94,13 @@ class ReductionProcessor {
   static bool
   supportedIntrinsicProcReduction(const omp::clause::ProcedureDesignator &pd);
 
+  /// Find a user-defined reduction that shadows a supported intrinsic
+  /// procedure reduction for \p type in \p scope.
+  static const semantics::Symbol *findUserDefinedReductionForIntrinsic(
+      const semantics::Scope &scope,
+      const omp::clause::ProcedureDesignator &reductionIntrinsic,
+      const semantics::DeclTypeSpec *type);
+
   static const semantics::SourceName
   getRealName(const semantics::Symbol *symbol);
 
diff --git a/flang/lib/Lower/Support/ReductionProcessor.cpp b/flang/lib/Lower/Support/ReductionProcessor.cpp
index de6aeefa48fc4..7b502880f50f3 100644
--- a/flang/lib/Lower/Support/ReductionProcessor.cpp
+++ b/flang/lib/Lower/Support/ReductionProcessor.cpp
@@ -195,6 +195,26 @@ bool ReductionProcessor::supportedIntrinsicProcReduction(
   return redType;
 }
 
+const semantics::Symbol *
+ReductionProcessor::findUserDefinedReductionForIntrinsic(
+    const semantics::Scope &scope,
+    const omp::clause::ProcedureDesignator &reductionIntrinsic,
+    const semantics::DeclTypeSpec *type) {
+  const semantics::Symbol *symbol = reductionIntrinsic.v.sym();
+  if (!symbol || !type)
+    return nullptr;
+
+  std::string mangledName = "op." + getRealName(symbol).ToString();
+  const semantics::Symbol *reductionSymbol =
+      scope.FindSymbol(parser::CharBlock{mangledName});
+  if (!reductionSymbol)
+    return nullptr;
+
+  const semantics::Symbol &ultimate = reductionSymbol->GetUltimate();
+  const auto *details = ultimate.detailsIf<semantics::UserReductionDetails>();
+  return details && details->SupportsType(*type) ? &ultimate : nullptr;
+}
+
 std::string
 ReductionProcessor::getReductionName(llvm::StringRef name,
                                      const fir::KindMapping &kindMap,
@@ -1344,70 +1364,65 @@ bool ReductionProcessor::processReductionArguments(
         // resolve-names). If one is visible in the current scope and supports
         // the variable's type, bind to the omp.declare_reduction op the
         // directive materialized for it instead of generating the intrinsic.
-        semantics::Symbol *sym = reductionIntrinsic->v.sym();
-        std::string mangledName = "op." + getRealName(sym).ToString();
-        if (const semantics::Symbol *redSym =
-                converter.getCurrentScope().FindSymbol(
-                    parser::CharBlock{mangledName})) {
-          const semantics::Symbol &ultimate = redSym->GetUltimate();
-          const semantics::UserReductionDetails *userDetails =
-              ultimate.detailsIf<semantics::UserReductionDetails>();
-          const semantics::DeclTypeSpec *varType =
-              reductionSymbols[idx]->GetUltimate().GetType();
+        const semantics::DeclTypeSpec *varType =
+            reductionSymbols[idx]->GetUltimate().GetType();
+        if (const semantics::Symbol *userReduction =
+                findUserDefinedReductionForIntrinsic(
+                    converter.getCurrentScope(), *reductionIntrinsic,
+                    varType)) {
+          const semantics::Symbol &ultimate = *userReduction;
           // A user-defined reduction shadows the intrinsic only for the types
           // it is declared for. If it does not cover this variable's type, the
           // user has not redefined the reduction for that type and the
           // implicit intrinsic reduction still applies, so fall through to it.
-          if (userDetails && varType && userDetails->SupportsType(*varType)) {
-            // The user declaration takes precedence over the intrinsic for this
-            // type. A declaration listing several types (or several merged
-            // declarations) is handled the same way as the operator and named
-            // paths: the directive emits one op per type and the variable's
-            // type selects the matching per-type name below. A USE-associated
-            // shadowing reduction is found by FindSymbol as a use wrapper;
-            // naming from its ultimate (name, owner) below binds the source
-            // module's op, materialized on demand here for separate
-            // compilation, exactly as the named path does. A renamed shadowing
-            // intrinsic does not reach here: the renamed name resolves to the
-            // intrinsic rather than the user reduction, so semantics rejects
-            // the clause with a type-incompatibility error before lowering.
-            std::string opName = ReductionProcessor::getScopedUserReductionName(
-                converter, ultimate, namingType, isByRef);
-            mlir::ModuleOp module = builder.getModule();
-            auto existingDecl = module.lookupSymbol<OpType>(opName);
-            // Separate compilation: materialize the imported shadowing
-            // reduction on demand when its defining module is a mod file, then
-            // re-look it up (same-file ops already exist here).
-            if (!existingDecl && semaCtx && ultimate.owner().symbol() &&
-                ultimate.owner().symbol()->test(
-                    semantics::Symbol::Flag::ModFile)) {
-              Fortran::lower::materializeUserReduction(
-                  converter, *semaCtx, ultimate, opName, namingType, isByRef);
-              existingDecl = module.lookupSymbol<OpType>(opName);
-            }
-            if (isBoxedTrivialElemReduction) {
-              // Trivial-element allocatable/pointer: synthesize the boxed op.
-              if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>(
-                      converter, semaCtx, ultimate, redType, currentLocation)) {
-                reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
-                    builder.getContext(), boxedDecl.getSymName()));
-                ++idx;
-                continue;
-              }
-            }
-            if (!existingDecl ||
-                fir::unwrapRefType(existingDecl.getType()) !=
-                    fir::unwrapRefType(namingType) ||
-                isBoxedTrivialReduction) {
-              TODO(currentLocation,
-                   "OpenMP user-defined reduction declaration was not "
-                   "materialized for this type");
+          // The user declaration takes precedence over the intrinsic for this
+          // type. A declaration listing several types (or several merged
+          // declarations) is handled the same way as the operator and named
+          // paths: the directive emits one op per type and the variable's
+          // type selects the matching per-type name below. A USE-associated
+          // shadowing reduction is found by FindSymbol as a use wrapper;
+          // naming from its ultimate (name, owner) below binds the source
+          // module's op, materialized on demand here for separate
+          // compilation, exactly as the named path does. A renamed shadowing
+          // intrinsic does not reach here: the renamed name resolves to the
+          // intrinsic rather than the user reduction, so semantics rejects
+          // the clause with a type-incompatibility error before lowering.
+          std::string opName = ReductionProcessor::getScopedUserReductionName(
+              converter, ultimate, namingType, isByRef);
+          mlir::ModuleOp module = builder.getModule();
+          auto existingDecl = module.lookupSymbol<OpType>(opName);
+          // Separate compilation: materialize the imported shadowing
+          // reduction on demand when its defining module is a mod file, then
+          // re-look it up (same-file ops already exist here).
+          if (!existingDecl && semaCtx && ultimate.owner().symbol() &&
+              ultimate.owner().symbol()->test(
+                  semantics::Symbol::Flag::ModFile)) {
+            Fortran::lower::materializeUserReduction(
+                converter, *semaCtx, ultimate, opName, namingType, isByRef);
+            existingDecl = module.lookupSymbol<OpType>(opName);
+          }
+          if (isBoxedTrivialElemReduction) {
+            // Trivial-element allocatable/pointer: synthesize the boxed op.
+            if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>(
+                    converter, semaCtx, ultimate, redType, currentLocation)) {
+              reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+                  builder.getContext(), boxedDecl.getSymName()));
+              ++idx;
+              continue;
             }
-            reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
-                builder.getContext(), existingDecl.getSymName()));
-            ++idx;
-            continue;
           }
+          if (!existingDecl ||
+              fir::unwrapRefType(existingDecl.getType()) !=
+                  fir::unwrapRefType(namingType) ||
+              isBoxedTrivialReduction) {
+            TODO(currentLocation,
+                 "OpenMP user-defined reduction declaration was not "
+                 "materialized for this type");
+          }
+          reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+              builder.getContext(), existingDecl.getSymName()));
+          ++idx;
+          continue;
         }
 
         redId = getReductionType(*reductionIntrinsic);

>From a0667d97e68e42db5e22e7a7517fefab8d23efd9 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Wed, 12 Aug 2026 15:27:01 +0100
Subject: [PATCH 02/18] [flang][OpenMP] Diagnose privatized array section
 reductions

Contiguous array sections can hit the same task and taskloop
base-symbol rebinding problem as array elements. Diagnose these
cases until reduction object identity is preserved through region
binding.

Check only symbols that become delayed-private block arguments so
eager taskloop privatization remains supported. Cover task and
taskloop section diagnostics.

This issue was previously reported here:
https://github.com/llvm/llvm-project/pull/215617#discussion_r3766204295

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 35 ++++++------
 ...ction-array-element-task-privatization.f90 | 53 ++++++++++++++++---
 2 files changed, 64 insertions(+), 24 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 280f71de33388..c2eddbd367a04 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -155,9 +155,9 @@ makeObjects(llvm::ArrayRef<const semantics::Symbol *> syms) {
   return objects;
 }
 
-static bool hasPrivatizedArrayElementReduction(
+static bool hasPrivatizedArrayReductionObject(
     llvm::ArrayRef<Object> reductionObjects,
-    const llvm::SetVector<const semantics::Symbol *> &privatizedSymbols) {
+    llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols) {
   for (const Object &object : reductionObjects) {
     if (!object.sym() || !object.ref())
       continue;
@@ -166,10 +166,7 @@ static bool hasPrivatizedArrayElementReduction(
     if (!dataRef)
       continue;
     const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
-    if (!arrayRef ||
-        llvm::any_of(arrayRef->subscript(), [](const auto &subscript) {
-          return std::holds_alternative<evaluate::Triplet>(subscript.u);
-        }))
+    if (!arrayRef)
       continue;
 
     const semantics::Symbol &ultimate = object.sym()->GetUltimate();
@@ -4288,10 +4285,10 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
                            /*useDelayedPrivatization=*/true, symTable);
   dsp.processStep1(&clauseOps);
 
-  if (hasPrivatizedArrayElementReduction(inReductionObjects,
-                                         dsp.getAllSymbolsToPrivatize()))
-    TODO(loc, "TASK construct with IN_REDUCTION of an array element whose "
-              "base array is privatized");
+  if (hasPrivatizedArrayReductionObject(inReductionObjects,
+                                        dsp.getDelayedPrivSymbols()))
+    TODO(loc, "TASK construct with IN_REDUCTION of an array element or section "
+              "whose base array is privatized");
 
   ObjectEntryBlockArgs taskArgs;
   taskArgs.priv.objects = makeObjects(dsp.getDelayedPrivSymbols());
@@ -4911,14 +4908,16 @@ static mlir::omp::TaskloopContextOp genStandaloneTaskloop(
                            enableDelayedPrivatization, symTable);
   dsp.processStep1(&taskloopClauseOps);
 
-  if (hasPrivatizedArrayElementReduction(inReductionObjects,
-                                         dsp.getAllSymbolsToPrivatize()))
-    TODO(loc, "TASKLOOP construct with IN_REDUCTION of an array element whose "
-              "base array is privatized");
-  if (hasPrivatizedArrayElementReduction(reductionObjects,
-                                         dsp.getAllSymbolsToPrivatize()))
-    TODO(loc, "TASKLOOP construct with REDUCTION of an array element whose "
-              "base array is privatized");
+  if (hasPrivatizedArrayReductionObject(inReductionObjects,
+                                        dsp.getDelayedPrivSymbols()))
+    TODO(loc,
+         "TASKLOOP construct with IN_REDUCTION of an array element or section "
+         "whose base array is privatized");
+  if (hasPrivatizedArrayReductionObject(reductionObjects,
+                                        dsp.getDelayedPrivSymbols()))
+    TODO(loc,
+         "TASKLOOP construct with REDUCTION of an array element or section "
+         "whose base array is privatized");
 
   mlir::omp::LoopNestOperands loopNestClauseOps;
   llvm::SmallVector<const semantics::Symbol *> iv;
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 770f309d1d160..bdce803fed4d5 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -5,14 +5,27 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION-SECTION
+! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-in-bbc.mlir %t/taskloop-in.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-in-fc1.mlir %t/taskloop-in.f90
+! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-section-bbc.mlir %t/taskloop-reduction-section.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-section-fc1.mlir %t/taskloop-reduction-section.f90
 
-! An array element in a task reduction and the implicitly firstprivate base
-! array are represented by separate block arguments. Reject these constructs
-! until lowering can bind references to the correct argument.
+! An array element or section in a task reduction and the implicitly
+! firstprivate base array are represented by separate block arguments. Reject
+! these constructs until lowering can bind references to the correct argument.
 
-! TASK: not yet implemented: TASK construct with IN_REDUCTION of an array element whose base array is privatized
-! TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element whose base array is privatized
-! TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element whose base array is privatized
+! TASK: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
+! TASK-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -24,6 +37,34 @@ subroutine task_reduction_element(a)
   !$omp end taskgroup
 end subroutine
 
+!--- task-section.f90
+subroutine task_reduction_section(a)
+  integer :: a(4)
+  !$omp taskgroup task_reduction(+: a(2:3))
+  !$omp task in_reduction(+: a(2:3))
+  a(2:3) = a(2:3) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
+!--- taskloop-in-section.f90
+subroutine taskloop_in_reduction_section(a, n)
+  integer :: a(4), n
+  !$omp taskloop in_reduction(+: a(2:3))
+  do i = 1, n
+    a(2:3) = a(2:3) + i
+  end do
+end subroutine
+
+!--- taskloop-reduction-section.f90
+subroutine taskloop_reduction_section(a, n)
+  integer :: a(4), n
+  !$omp taskloop reduction(+: a(2:3))
+  do i = 1, n
+    a(2:3) = a(2:3) + i
+  end do
+end subroutine
+
 !--- taskloop-in.f90
 subroutine taskloop_in_reduction_element(a, n)
   integer :: a(4), n

>From c9d1bffa6567c7b78c925f58cdfab45f80ebf4ee Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Fri, 14 Aug 2026 17:43:52 +0100
Subject: [PATCH 03/18] [flang][OpenMP] Preserve eager reduction diagnostics

Keep diagnosing taskloop array-element reductions when eager
privatization is selected. Restrict the relaxed eager behavior to array
sections while delayed privatization continues to diagnose both elements
and sections.

Extend the regression matrix to cover both taskloop reduction clause
forms in eager mode and retain positive eager array-section coverage.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 48 ++++++++++++++-----
 ...ction-array-element-task-privatization.f90 | 14 ++++--
 2 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index c2eddbd367a04..6693c9537aff4 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -157,7 +157,8 @@ makeObjects(llvm::ArrayRef<const semantics::Symbol *> syms) {
 
 static bool hasPrivatizedArrayReductionObject(
     llvm::ArrayRef<Object> reductionObjects,
-    llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols) {
+    llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols,
+    bool includeArraySections) {
   for (const Object &object : reductionObjects) {
     if (!object.sym() || !object.ref())
       continue;
@@ -166,7 +167,11 @@ static bool hasPrivatizedArrayReductionObject(
     if (!dataRef)
       continue;
     const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
-    if (!arrayRef)
+    if (!arrayRef ||
+        (!includeArraySections &&
+         llvm::any_of(arrayRef->subscript(), [](const auto &subscript) {
+           return std::holds_alternative<evaluate::Triplet>(subscript.u);
+         })))
       continue;
 
     const semantics::Symbol &ultimate = object.sym()->GetUltimate();
@@ -4286,7 +4291,8 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
   dsp.processStep1(&clauseOps);
 
   if (hasPrivatizedArrayReductionObject(inReductionObjects,
-                                        dsp.getDelayedPrivSymbols()))
+                                        dsp.getDelayedPrivSymbols(),
+                                        /*includeArraySections=*/true))
     TODO(loc, "TASK construct with IN_REDUCTION of an array element or section "
               "whose base array is privatized");
 
@@ -4908,16 +4914,32 @@ static mlir::omp::TaskloopContextOp genStandaloneTaskloop(
                            enableDelayedPrivatization, symTable);
   dsp.processStep1(&taskloopClauseOps);
 
-  if (hasPrivatizedArrayReductionObject(inReductionObjects,
-                                        dsp.getDelayedPrivSymbols()))
-    TODO(loc,
-         "TASKLOOP construct with IN_REDUCTION of an array element or section "
-         "whose base array is privatized");
-  if (hasPrivatizedArrayReductionObject(reductionObjects,
-                                        dsp.getDelayedPrivSymbols()))
-    TODO(loc,
-         "TASKLOOP construct with REDUCTION of an array element or section "
-         "whose base array is privatized");
+  llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols =
+      enableDelayedPrivatization ? dsp.getDelayedPrivSymbols()
+                                 : dsp.getAllSymbolsToPrivatize().getArrayRef();
+  if (hasPrivatizedArrayReductionObject(
+          inReductionObjects, privatizedSymbols,
+          /*includeArraySections=*/enableDelayedPrivatization)) {
+    if (enableDelayedPrivatization)
+      TODO(loc, "TASKLOOP construct with IN_REDUCTION of an array element or "
+                "section whose base array is privatized");
+    else
+      TODO(loc,
+           "TASKLOOP construct with IN_REDUCTION of an array element whose "
+           "base array is privatized");
+  }
+  if (hasPrivatizedArrayReductionObject(
+          reductionObjects, privatizedSymbols,
+          /*includeArraySections=*/enableDelayedPrivatization)) {
+    if (enableDelayedPrivatization)
+      TODO(loc,
+           "TASKLOOP construct with REDUCTION of an array element or section "
+           "whose base array is privatized");
+    else
+      TODO(loc,
+           "TASKLOOP construct with REDUCTION of an array element whose base "
+           "array is privatized");
+  }
 
   mlir::omp::LoopNestOperands loopNestClauseOps;
   llvm::SmallVector<const semantics::Symbol *> iv;
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index bdce803fed4d5..7cf176b1d64b2 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -11,10 +11,14 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION-SECTION
-! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-in-bbc.mlir %t/taskloop-in.f90
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-in-fc1.mlir %t/taskloop-in.f90
-! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-section-bbc.mlir %t/taskloop-reduction-section.f90
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-section-fc1.mlir %t/taskloop-reduction-section.f90
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-in.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-IN
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-in.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-IN
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION
+! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-in-section-bbc.mlir %t/taskloop-in-section.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-in-section-fc1.mlir %t/taskloop-in-section.f90
+! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-reduction-section-bbc.mlir %t/taskloop-reduction-section.f90
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-reduction-section-fc1.mlir %t/taskloop-reduction-section.f90
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -26,6 +30,8 @@
 ! TASK-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
+! EAGER-TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element whose base array is privatized
+! EAGER-TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element whose base array is privatized
 
 !--- task.f90
 subroutine task_reduction_element(a)

>From 105128779c4d4dabf346f8f1e4a9b76e66991295 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Mon, 17 Aug 2026 10:58:15 +0100
Subject: [PATCH 04/18] Diagnose eager array section reductions

Keep the taskloop reduction diagnostic for array sections when eager
privatization is selected. Eager lowering otherwise creates a reduction
for the whole base array instead of one reduction per section element.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 43 +++++--------------
 ...ction-array-element-task-privatization.f90 | 28 +++++++++---
 2 files changed, 33 insertions(+), 38 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 6693c9537aff4..969d671f16f14 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -157,8 +157,7 @@ makeObjects(llvm::ArrayRef<const semantics::Symbol *> syms) {
 
 static bool hasPrivatizedArrayReductionObject(
     llvm::ArrayRef<Object> reductionObjects,
-    llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols,
-    bool includeArraySections) {
+    llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols) {
   for (const Object &object : reductionObjects) {
     if (!object.sym() || !object.ref())
       continue;
@@ -167,11 +166,7 @@ static bool hasPrivatizedArrayReductionObject(
     if (!dataRef)
       continue;
     const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
-    if (!arrayRef ||
-        (!includeArraySections &&
-         llvm::any_of(arrayRef->subscript(), [](const auto &subscript) {
-           return std::holds_alternative<evaluate::Triplet>(subscript.u);
-         })))
+    if (!arrayRef)
       continue;
 
     const semantics::Symbol &ultimate = object.sym()->GetUltimate();
@@ -4291,8 +4286,7 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
   dsp.processStep1(&clauseOps);
 
   if (hasPrivatizedArrayReductionObject(inReductionObjects,
-                                        dsp.getDelayedPrivSymbols(),
-                                        /*includeArraySections=*/true))
+                                        dsp.getDelayedPrivSymbols()))
     TODO(loc, "TASK construct with IN_REDUCTION of an array element or section "
               "whose base array is privatized");
 
@@ -4917,29 +4911,14 @@ static mlir::omp::TaskloopContextOp genStandaloneTaskloop(
   llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols =
       enableDelayedPrivatization ? dsp.getDelayedPrivSymbols()
                                  : dsp.getAllSymbolsToPrivatize().getArrayRef();
-  if (hasPrivatizedArrayReductionObject(
-          inReductionObjects, privatizedSymbols,
-          /*includeArraySections=*/enableDelayedPrivatization)) {
-    if (enableDelayedPrivatization)
-      TODO(loc, "TASKLOOP construct with IN_REDUCTION of an array element or "
-                "section whose base array is privatized");
-    else
-      TODO(loc,
-           "TASKLOOP construct with IN_REDUCTION of an array element whose "
-           "base array is privatized");
-  }
-  if (hasPrivatizedArrayReductionObject(
-          reductionObjects, privatizedSymbols,
-          /*includeArraySections=*/enableDelayedPrivatization)) {
-    if (enableDelayedPrivatization)
-      TODO(loc,
-           "TASKLOOP construct with REDUCTION of an array element or section "
-           "whose base array is privatized");
-    else
-      TODO(loc,
-           "TASKLOOP construct with REDUCTION of an array element whose base "
-           "array is privatized");
-  }
+  if (hasPrivatizedArrayReductionObject(inReductionObjects, privatizedSymbols))
+    TODO(loc,
+         "TASKLOOP construct with IN_REDUCTION of an array element or section "
+         "whose base array is privatized");
+  if (hasPrivatizedArrayReductionObject(reductionObjects, privatizedSymbols))
+    TODO(loc,
+         "TASKLOOP construct with REDUCTION of an array element or section "
+         "whose base array is privatized");
 
   mlir::omp::LoopNestOperands loopNestClauseOps;
   llvm::SmallVector<const semantics::Symbol *> iv;
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 7cf176b1d64b2..fa9b6fbd38f1d 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -15,10 +15,12 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-in.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-IN
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION
-! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-in-section-bbc.mlir %t/taskloop-in-section.f90
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-in-section-fc1.mlir %t/taskloop-in-section.f90
-! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o %t/eager-reduction-section-bbc.mlir %t/taskloop-reduction-section.f90
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o %t/eager-reduction-section-fc1.mlir %t/taskloop-reduction-section.f90
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-IN-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-IN-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-udr-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-UDR-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-udr-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-UDR-SECTION
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -30,8 +32,11 @@
 ! TASK-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
-! EAGER-TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element whose base array is privatized
-! EAGER-TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element whose base array is privatized
+! EAGER-TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
+! EAGER-TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
+! EAGER-TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
+! EAGER-TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
+! EAGER-TASKLOOP-UDR-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -71,6 +76,17 @@ subroutine taskloop_reduction_section(a, n)
   end do
 end subroutine
 
+!--- taskloop-udr-section.f90
+subroutine taskloop_udr_section(a)
+  integer :: a(4), i
+  !$omp declare reduction(myred : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp taskloop reduction(myred : a(2:3))
+  do i = 1, 1
+    a(2:3) = a(2:3) + i
+  end do
+end subroutine
+
 !--- taskloop-in.f90
 subroutine taskloop_in_reduction_element(a, n)
   integer :: a(4), n

>From b19af380062dc2e720bb034bf0cab02fd3898ee9 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 18 Aug 2026 10:45:11 +0100
Subject: [PATCH 05/18] Lower full array reduction sections

A full-extent array section such as a(:) uses the same descriptor as its
base array during reduction lowering. Data-sharing analysis nevertheless
marked the base as implicitly firstprivate. This produced a duplicate binding
or triggered the array-section TODO diagnostic.

Recognize omitted-bound, unit-stride sections on reduction clauses. Omit the
redundant implicit firstprivate capture while keeping element and partial
section handling unchanged. Cover task and taskloop reductions, including
user-defined reductions and both taskloop privatization modes.

Assisted-by: Codex
---
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp | 47 ++++++++++
 flang/lib/Lower/OpenMP/DataSharingProcessor.h |  2 +
 ...-array-full-section-task-privatization.f90 | 87 +++++++++++++++++++
 3 files changed, 136 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90

diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 1d39c1a8d4b77..6aae152a977b4 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -13,6 +13,8 @@
 #include "DataSharingProcessor.h"
 
 #include "Utils.h"
+#include "flang/Evaluate/fold.h"
+#include "flang/Evaluate/tools.h"
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/PFTBuilder.h"
 #include "flang/Lower/Support/PrivateReductionUtils.h"
@@ -32,6 +34,29 @@
 #include "llvm/Frontend/OpenMP/OMP.h"
 #include <variant>
 
+namespace {
+static bool isWholeArraySection(const Fortran::lower::omp::Object &object) {
+  if (!object.ref())
+    return false;
+
+  std::optional<Fortran::evaluate::DataRef> dataRef =
+      Fortran::evaluate::ExtractDataRef(*object.ref());
+  if (!dataRef)
+    return false;
+
+  const auto *arrayRef = std::get_if<Fortran::evaluate::ArrayRef>(&dataRef->u);
+  if (!arrayRef)
+    return false;
+
+  return llvm::all_of(
+      arrayRef->subscript(), [](const Fortran::evaluate::Subscript &sub) {
+        const auto *triplet = std::get_if<Fortran::evaluate::Triplet>(&sub.u);
+        return triplet && !triplet->GetLower() && !triplet->GetUpper() &&
+               Fortran::evaluate::ToInt64(triplet->GetStride()) == 1;
+      });
+}
+} // namespace
+
 namespace Fortran {
 namespace lower {
 namespace omp {
@@ -264,6 +289,13 @@ void DataSharingProcessor::collectSymbolsForPrivatization() {
     return false;
   };
 
+  auto collectWholeArrayReductionSymbols = [&](const auto &reductionClause) {
+    const ObjectList &objects = std::get<ObjectList>(reductionClause.t);
+    for (const Object &object : objects)
+      if (object.sym() && isWholeArraySection(object))
+        wholeArrayReductionSymbols.insert(&object.sym()->GetUltimate());
+  };
+
   for (const omp::Clause &clause : clauses) {
     if (const auto &privateClause =
             std::get_if<omp::clause::Private>(&clause.u)) {
@@ -295,6 +327,15 @@ void DataSharingProcessor::collectSymbolsForPrivatization() {
       } else {
         collectOmpObjectListSymbol(objects, explicitlyPrivatizedSymbols);
       }
+    } else if (const auto *inReductionClause =
+                   std::get_if<omp::clause::InReduction>(&clause.u)) {
+      collectWholeArrayReductionSymbols(*inReductionClause);
+    } else if (const auto *reductionClause =
+                   std::get_if<omp::clause::Reduction>(&clause.u)) {
+      collectWholeArrayReductionSymbols(*reductionClause);
+    } else if (const auto *taskReductionClause =
+                   std::get_if<omp::clause::TaskReduction>(&clause.u)) {
+      collectWholeArrayReductionSymbols(*taskReductionClause);
     }
   }
 
@@ -538,6 +579,12 @@ void DataSharingProcessor::collectPrivatizedSymbols(
       return false;
 
     if (collectImplicit) {
+      // A full-extent section is lowered through the same descriptor as its
+      // base array. Do not create a second implicit firstprivate descriptor;
+      // the reduction region argument must be the binding used in the body.
+      if (wholeArrayReductionSymbols.contains(&sym->GetUltimate()))
+        return false;
+
       // If we're a combined construct with a target region, implicit
       // firstprivate captures, should only belong to the target region
       // and not be added/captured by later directives. Parallel regions
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.h b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
index 557584c21123f..dcb09c20c63f8 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.h
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
@@ -95,6 +95,8 @@ class DataSharingProcessor {
   llvm::SmallVector<mlir::Value> loopIVs;
   // Symbols in private, firstprivate, and/or lastprivate clauses.
   llvm::SetVector<const semantics::Symbol *> explicitlyPrivatizedSymbols;
+  // Base symbols of reduction objects written as full-extent sections.
+  llvm::SetVector<const semantics::Symbol *> wholeArrayReductionSymbols;
   llvm::SetVector<const semantics::Symbol *> defaultSymbols;
   llvm::SetVector<const semantics::Symbol *> allPrivatizedSymbols;
   llvm::SetVector<const semantics::Symbol *> conditionalLastPrivatizedSymbols;
diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
new file mode 100644
index 0000000000000..948f9f9d8793c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -0,0 +1,87 @@
+! RUN: split-file %s %t
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task.f90 | FileCheck %s --check-prefix=TASK --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
+
+! A full-extent section uses the same descriptor as its base array. Check that
+! it is bound only to the reduction argument, rather than also being captured
+! as an implicit firstprivate object.
+
+! TASK-LABEL: func.func @_QPtask_full_section
+! TASK: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[TASKGROUP_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK: %[[TASKGROUP_DECL:.*]]:2 = hlfir.declare %[[TASKGROUP_ARG]]
+! TASK: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 %[[TASKGROUP_DECL]]#0 -> %[[TASK_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK: %[[TASK_DECL:.*]]:2 = hlfir.declare %[[TASK_ARG]]
+! TASK: %[[TASK_BOX:.*]] = fir.load %[[TASK_DECL]]#0
+! TASK: %[[TASK_SECTION:.*]] = hlfir.designate %[[TASK_BOX]]
+! TASK: hlfir.elemental
+! TASK: %[[TASK_ELEMENT:.*]] = hlfir.designate %[[TASK_SECTION]]
+! TASK: %[[TASK_VALUE:.*]] = fir.load %[[TASK_ELEMENT]]
+! TASK: arith.addi %[[TASK_VALUE]]
+
+! TASKLOOP-LABEL: func.func @_QPtaskloop_in_full_section
+! TASKLOOP: omp.taskloop.context in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[IN_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASKLOOP: %[[IN_DECL:.*]]:2 = hlfir.declare %[[IN_ARG]]
+! TASKLOOP: %[[IN_BOX:.*]] = fir.load %[[IN_DECL]]#0
+! TASKLOOP: %[[IN_SECTION:.*]] = hlfir.designate %[[IN_BOX]]
+! TASKLOOP: hlfir.elemental
+! TASKLOOP: %[[IN_ELEMENT:.*]] = hlfir.designate %[[IN_SECTION]]
+! TASKLOOP: %[[IN_VALUE:.*]] = fir.load %[[IN_ELEMENT]]
+! TASKLOOP: arith.addi %[[IN_VALUE]]
+
+! TASKLOOP-LABEL: func.func @_QPtaskloop_reduction_full_section
+! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[RED_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASKLOOP: %[[RED_DECL:.*]]:2 = hlfir.declare %[[RED_ARG]]
+! TASKLOOP: %[[RED_BOX:.*]] = fir.load %[[RED_DECL]]#0
+! TASKLOOP: %[[RED_SECTION:.*]] = hlfir.designate %[[RED_BOX]]
+! TASKLOOP: hlfir.elemental
+! TASKLOOP: %[[RED_ELEMENT:.*]] = hlfir.designate %[[RED_SECTION]]
+! TASKLOOP: %[[RED_VALUE:.*]] = fir.load %[[RED_ELEMENT]]
+! TASKLOOP: arith.addi %[[RED_VALUE]]
+
+! TASKLOOP-LABEL: func.func @_QPtaskloop_udr_full_section
+! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @_QQFtaskloop_udr_full_sectionmyred_byref_box_4xi32 {{.*}} -> %[[UDR_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASKLOOP: %[[UDR_DECL:.*]]:2 = hlfir.declare %[[UDR_ARG]]
+! TASKLOOP: %[[UDR_BOX:.*]] = fir.load %[[UDR_DECL]]#0
+! TASKLOOP: %[[UDR_SECTION:.*]] = hlfir.designate %[[UDR_BOX]]
+! TASKLOOP: hlfir.elemental
+! TASKLOOP: %[[UDR_ELEMENT:.*]] = hlfir.designate %[[UDR_SECTION]]
+! TASKLOOP: %[[UDR_VALUE:.*]] = fir.load %[[UDR_ELEMENT]]
+! TASKLOOP: arith.addi %[[UDR_VALUE]]
+
+!--- task.f90
+subroutine task_full_section(a)
+  integer :: a(-2:1)
+  !$omp taskgroup task_reduction(+: a(:))
+  !$omp task in_reduction(+: a(:))
+  a(:) = a(:) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
+!--- taskloop.f90
+subroutine taskloop_in_full_section(a, n)
+  integer :: a(-2:1), n
+  !$omp taskloop in_reduction(+: a(:))
+  do i = 1, n
+    a(:) = a(:) + i
+  end do
+end subroutine
+
+subroutine taskloop_reduction_full_section(a, n)
+  integer :: a(-2:1), n
+  !$omp taskloop reduction(+: a(:))
+  do i = 1, n
+    a(:) = a(:) + i
+  end do
+end subroutine
+
+subroutine taskloop_udr_full_section(a)
+  integer :: a(-2:1), i
+  !$omp declare reduction(myred : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp taskloop reduction(myred : a(:))
+  do i = 1, 1
+    a(:) = a(:) + i
+  end do
+end subroutine

>From 00da64893b0fb05bd4003815fb7b0bf1a2bf069d Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 18 Aug 2026 11:33:41 +0100
Subject: [PATCH 06/18] Handle default DSA for full sections

Full-extent reduction sections use the base array descriptor, but the
data-sharing exception only covered ordinary implicit captures. A task
default(private) or default(firstprivate) therefore still created a
competing private descriptor and reached the task reduction TODO.

Apply the exception to default-selected private symbols as well, while
leaving explicit private clauses on their existing path. Cover both default
forms and verify that body expressions use the declared reduction argument.

Assisted-by: Codex
---
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp | 18 ++++++---
 ...-array-full-section-task-privatization.f90 | 40 +++++++++++++++++++
 2 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 6aae152a977b4..d5414b81e64f8 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -558,10 +558,13 @@ void DataSharingProcessor::collectPrivatizedSymbols(
     const llvm::SetVector<const semantics::Symbol *> &symbolsInNestedRegions,
     llvm::SetVector<const semantics::Symbol *> *symbols) {
   // Filter-out symbols that must not be privatized.
+  bool collectDefaultPrivate = false;
   bool collectImplicit = false;
   bool collectPreDetermined = false;
   bool collectIndirectRefs = !flag.has_value();
   if (!collectIndirectRefs) {
+    collectDefaultPrivate = *flag == semantics::Symbol::Flag::OmpPrivate ||
+                            *flag == semantics::Symbol::Flag::OmpFirstPrivate;
     collectImplicit = *flag == semantics::Symbol::Flag::OmpImplicit;
     collectPreDetermined = *flag == semantics::Symbol::Flag::OmpPreDetermined;
   }
@@ -578,13 +581,16 @@ void DataSharingProcessor::collectPrivatizedSymbols(
     if (sym->test(semantics::Symbol::Flag::OmpLinear) && !inTarget)
       return false;
 
-    if (collectImplicit) {
-      // A full-extent section is lowered through the same descriptor as its
-      // base array. Do not create a second implicit firstprivate descriptor;
-      // the reduction region argument must be the binding used in the body.
-      if (wholeArrayReductionSymbols.contains(&sym->GetUltimate()))
-        return false;
+    // A full-extent section is lowered through the same descriptor as its base
+    // array. Do not create a second private descriptor when privatization is
+    // selected implicitly or by a default clause; the reduction region
+    // argument must be the binding used in the body. Explicit private clauses
+    // are collected separately in collectSymbolsForPrivatization().
+    if ((collectImplicit || collectDefaultPrivate) &&
+        wholeArrayReductionSymbols.contains(&sym->GetUltimate()))
+      return false;
 
+    if (collectImplicit) {
       // If we're a combined construct with a target region, implicit
       // firstprivate captures, should only belong to the target region
       // and not be added/captured by later directives. Parallel regions
diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index 948f9f9d8793c..89f7868432dc3 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -1,5 +1,6 @@
 ! RUN: split-file %s %t
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task.f90 | FileCheck %s --check-prefix=TASK --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-default.f90 | FileCheck %s --check-prefix=TASK-DEFAULT --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate --implicit-check-not=Ea_private
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
 
@@ -19,6 +20,26 @@
 ! TASK: %[[TASK_VALUE:.*]] = fir.load %[[TASK_ELEMENT]]
 ! TASK: arith.addi %[[TASK_VALUE]]
 
+! TASK-DEFAULT-LABEL: func.func @_QPtask_default_firstprivate_full_section
+! TASK-DEFAULT: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[FIRSTPRIVATE_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-DEFAULT: %[[FIRSTPRIVATE_DECL:.*]]:2 = hlfir.declare %[[FIRSTPRIVATE_ARG]]
+! TASK-DEFAULT: %[[FIRSTPRIVATE_BOX:.*]] = fir.load %[[FIRSTPRIVATE_DECL]]#0
+! TASK-DEFAULT: %[[FIRSTPRIVATE_SECTION:.*]] = hlfir.designate %[[FIRSTPRIVATE_BOX]]
+! TASK-DEFAULT: hlfir.elemental
+! TASK-DEFAULT: %[[FIRSTPRIVATE_ELEMENT:.*]] = hlfir.designate %[[FIRSTPRIVATE_SECTION]]
+! TASK-DEFAULT: %[[FIRSTPRIVATE_VALUE:.*]] = fir.load %[[FIRSTPRIVATE_ELEMENT]]
+! TASK-DEFAULT: arith.addi %[[FIRSTPRIVATE_VALUE]]
+
+! TASK-DEFAULT-LABEL: func.func @_QPtask_default_private_full_section
+! TASK-DEFAULT: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[PRIVATE_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-DEFAULT: %[[PRIVATE_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_ARG]]
+! TASK-DEFAULT: %[[PRIVATE_BOX:.*]] = fir.load %[[PRIVATE_DECL]]#0
+! TASK-DEFAULT: %[[PRIVATE_SECTION:.*]] = hlfir.designate %[[PRIVATE_BOX]]
+! TASK-DEFAULT: hlfir.elemental
+! TASK-DEFAULT: %[[PRIVATE_ELEMENT:.*]] = hlfir.designate %[[PRIVATE_SECTION]]
+! TASK-DEFAULT: %[[PRIVATE_VALUE:.*]] = fir.load %[[PRIVATE_ELEMENT]]
+! TASK-DEFAULT: arith.addi %[[PRIVATE_VALUE]]
+
 ! TASKLOOP-LABEL: func.func @_QPtaskloop_in_full_section
 ! TASKLOOP: omp.taskloop.context in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[IN_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 ! TASKLOOP: %[[IN_DECL:.*]]:2 = hlfir.declare %[[IN_ARG]]
@@ -59,6 +80,25 @@ subroutine task_full_section(a)
   !$omp end taskgroup
 end subroutine
 
+!--- task-default.f90
+subroutine task_default_firstprivate_full_section(a)
+  integer :: a(-2:1)
+  !$omp taskgroup task_reduction(+: a(:))
+  !$omp task default(firstprivate) in_reduction(+: a(:))
+  a(:) = a(:) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
+subroutine task_default_private_full_section(a)
+  integer :: a(-2:1)
+  !$omp taskgroup task_reduction(+: a(:))
+  !$omp task default(private) in_reduction(+: a(:))
+  a(:) = a(:) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
 !--- taskloop.f90
 subroutine taskloop_in_full_section(a, n)
   integer :: a(-2:1), n

>From 0410e013711feb230f8dcfec3fc23ff2a4f30ee4 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 18 Aug 2026 11:54:24 +0100
Subject: [PATCH 07/18] Test multidimensional reduction sections

Add rank-two full and mixed array section task reductions. This protects the
all-dimensions whole-section classification from regressions while preserving
the TODO for partial sections.

Assisted-by: Codex
---
 ...ction-array-element-task-privatization.f90 | 13 ++++++++++++
 ...-array-full-section-task-privatization.f90 | 21 +++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index fa9b6fbd38f1d..3b498ebcd9cab 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -7,6 +7,8 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-rank-two-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-RANK-TWO-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-rank-two-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-RANK-TWO-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-REDUCTION-SECTION
@@ -30,6 +32,7 @@
 ! TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! TASK-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASK-RANK-TWO-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
@@ -58,6 +61,16 @@ subroutine task_reduction_section(a)
   !$omp end taskgroup
 end subroutine
 
+!--- task-rank-two-section.f90
+subroutine task_reduction_rank_two_section(a)
+  integer :: a(4, 4)
+  !$omp taskgroup task_reduction(+: a(:, 2))
+  !$omp task in_reduction(+: a(:, 2))
+  a(:, 2) = a(:, 2) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
 !--- taskloop-in-section.f90
 subroutine taskloop_in_reduction_section(a, n)
   integer :: a(4), n
diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index 89f7868432dc3..b1927ec049fe5 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -20,6 +20,18 @@
 ! TASK: %[[TASK_VALUE:.*]] = fir.load %[[TASK_ELEMENT]]
 ! TASK: arith.addi %[[TASK_VALUE]]
 
+! TASK-LABEL: func.func @_QPtask_rank_two_full_section
+! TASK: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4x4xi32 {{.*}} -> %[[RANK_TWO_TASKGROUP_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4x4xi32>>>)
+! TASK: %[[RANK_TWO_TASKGROUP_DECL:.*]]:2 = hlfir.declare %[[RANK_TWO_TASKGROUP_ARG]]
+! TASK: omp.task in_reduction(byref @add_reduction_byref_box_4x4xi32 %[[RANK_TWO_TASKGROUP_DECL]]#0 -> %[[RANK_TWO_TASK_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4x4xi32>>>)
+! TASK: %[[RANK_TWO_TASK_DECL:.*]]:2 = hlfir.declare %[[RANK_TWO_TASK_ARG]]
+! TASK: %[[RANK_TWO_TASK_BOX:.*]] = fir.load %[[RANK_TWO_TASK_DECL]]#0
+! TASK: %[[RANK_TWO_TASK_SECTION:.*]] = hlfir.designate %[[RANK_TWO_TASK_BOX]]
+! TASK: hlfir.elemental
+! TASK: %[[RANK_TWO_TASK_ELEMENT:.*]] = hlfir.designate %[[RANK_TWO_TASK_SECTION]]
+! TASK: %[[RANK_TWO_TASK_VALUE:.*]] = fir.load %[[RANK_TWO_TASK_ELEMENT]]
+! TASK: arith.addi %[[RANK_TWO_TASK_VALUE]]
+
 ! TASK-DEFAULT-LABEL: func.func @_QPtask_default_firstprivate_full_section
 ! TASK-DEFAULT: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[FIRSTPRIVATE_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 ! TASK-DEFAULT: %[[FIRSTPRIVATE_DECL:.*]]:2 = hlfir.declare %[[FIRSTPRIVATE_ARG]]
@@ -80,6 +92,15 @@ subroutine task_full_section(a)
   !$omp end taskgroup
 end subroutine
 
+subroutine task_rank_two_full_section(a)
+  integer :: a(-2:1, -1:2)
+  !$omp taskgroup task_reduction(+: a(:, :))
+  !$omp task in_reduction(+: a(:, :))
+  a(:, :) = a(:, :) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
 !--- task-default.f90
 subroutine task_default_firstprivate_full_section(a)
   integer :: a(-2:1)

>From 744d5743c66b3e5ccbc0919841d0b4727d546557 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 18 Aug 2026 17:44:33 +0100
Subject: [PATCH 08/18] Fix quoting

---
 .../reduction-array-full-section-task-privatization.f90   | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index b1927ec049fe5..5e417abf23d51 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -1,8 +1,8 @@
 ! RUN: split-file %s %t
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task.f90 | FileCheck %s --check-prefix=TASK --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-default.f90 | FileCheck %s --check-prefix=TASK-DEFAULT --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate --implicit-check-not=Ea_private
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not=not\ yet\ implemented --implicit-check-not=Ea_firstprivate
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task.f90 | FileCheck %s --check-prefix=TASK --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-default.f90 | FileCheck %s --check-prefix=TASK-DEFAULT --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate --implicit-check-not=Ea_private
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate
 
 ! A full-extent section uses the same descriptor as its base array. Check that
 ! it is bound only to the reduction argument, rather than also being captured

>From 1ae376b4925662681d7d8431171a43d44074d370 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Thu, 20 Aug 2026 15:22:25 +0100
Subject: [PATCH 09/18] Recognize explicit full array sections

Explicitly bounded sections may still cover their complete base array.
Compare constant section bounds with the declared bounds before treating
them as partial.

Assisted-by: Codex
---
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp | 25 -----------
 flang/lib/Lower/OpenMP/Utils.cpp              | 42 ++++++++++++++++++
 flang/lib/Lower/OpenMP/Utils.h                |  2 +
 ...-array-full-section-task-privatization.f90 | 43 +++++++++++++++++++
 4 files changed, 87 insertions(+), 25 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index d5414b81e64f8..670705d809a85 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -13,8 +13,6 @@
 #include "DataSharingProcessor.h"
 
 #include "Utils.h"
-#include "flang/Evaluate/fold.h"
-#include "flang/Evaluate/tools.h"
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/PFTBuilder.h"
 #include "flang/Lower/Support/PrivateReductionUtils.h"
@@ -34,29 +32,6 @@
 #include "llvm/Frontend/OpenMP/OMP.h"
 #include <variant>
 
-namespace {
-static bool isWholeArraySection(const Fortran::lower::omp::Object &object) {
-  if (!object.ref())
-    return false;
-
-  std::optional<Fortran::evaluate::DataRef> dataRef =
-      Fortran::evaluate::ExtractDataRef(*object.ref());
-  if (!dataRef)
-    return false;
-
-  const auto *arrayRef = std::get_if<Fortran::evaluate::ArrayRef>(&dataRef->u);
-  if (!arrayRef)
-    return false;
-
-  return llvm::all_of(
-      arrayRef->subscript(), [](const Fortran::evaluate::Subscript &sub) {
-        const auto *triplet = std::get_if<Fortran::evaluate::Triplet>(&sub.u);
-        return triplet && !triplet->GetLower() && !triplet->GetUpper() &&
-               Fortran::evaluate::ToInt64(triplet->GetStride()) == 1;
-      });
-}
-} // namespace
-
 namespace Fortran {
 namespace lower {
 namespace omp {
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 88ddf504bdd98..e522e28d31f58 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -73,6 +73,48 @@ llvm::cl::opt<bool> treatIndexAsSection(
 namespace Fortran {
 namespace lower {
 namespace omp {
+bool isWholeArraySection(const Object &object) {
+  if (!object.sym() || !object.ref())
+    return false;
+
+  std::optional<evaluate::DataRef> dataRef =
+      evaluate::ExtractDataRef(*object.ref());
+  if (!dataRef)
+    return false;
+
+  const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
+  if (!arrayRef)
+    return false;
+
+  const semantics::ArraySpec *shape = object.sym()->GetUltimate().GetShape();
+  if (!shape || shape->size() != arrayRef->subscript().size())
+    return false;
+
+  auto matchesDeclaredBound = [](const auto *sectionBound,
+                                 const auto &declaredBound) {
+    if (!sectionBound)
+      return true;
+    const auto &explicitDeclaredBound = declaredBound.GetExplicit();
+    if (!explicitDeclaredBound)
+      return false;
+    std::optional<std::int64_t> sectionValue = evaluate::ToInt64(*sectionBound);
+    std::optional<std::int64_t> declaredValue =
+        evaluate::ToInt64(*explicitDeclaredBound);
+    return sectionValue && declaredValue && sectionValue == declaredValue;
+  };
+
+  for (auto [subscript, declaredDimension] :
+       llvm::zip_equal(arrayRef->subscript(), *shape)) {
+    const auto *triplet = std::get_if<evaluate::Triplet>(&subscript.u);
+    if (!triplet || evaluate::ToInt64(triplet->GetStride()) != 1 ||
+        !matchesDeclaredBound(triplet->GetLower(),
+                              declaredDimension.lbound()) ||
+        !matchesDeclaredBound(triplet->GetUpper(), declaredDimension.ubound()))
+      return false;
+  }
+  return true;
+}
+
 bool requiresImplicitDefaultDeclareMapper(
     const semantics::DerivedTypeSpec &typeSpec) {
   // ISO C interoperable types (e.g., c_ptr, c_funptr) must always have implicit
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..f86ebf0e2966f 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -137,6 +137,8 @@ void generateMemberPlacementIndices(
 bool isMemberOrParentAllocatableOrPointer(
     const Object &object, Fortran::semantics::SemanticsContext &semaCtx);
 
+bool isWholeArraySection(const Object &object);
+
 mlir::Value createParentSymAndGenIntermediateMaps(
     mlir::Location clauseLocation, Fortran::lower::AbstractConverter &converter,
     semantics::SemanticsContext &semaCtx, lower::StatementContext &stmtCtx,
diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index 5e417abf23d51..75782389a989c 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -32,6 +32,10 @@
 ! TASK: %[[RANK_TWO_TASK_VALUE:.*]] = fir.load %[[RANK_TWO_TASK_ELEMENT]]
 ! TASK: arith.addi %[[RANK_TWO_TASK_VALUE]]
 
+! TASK-LABEL: func.func @_QPtask_explicit_full_section
+! TASK: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4xi32
+! TASK: omp.task in_reduction(byref @add_reduction_byref_box_4xi32
+
 ! TASK-DEFAULT-LABEL: func.func @_QPtask_default_firstprivate_full_section
 ! TASK-DEFAULT: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[FIRSTPRIVATE_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 ! TASK-DEFAULT: %[[FIRSTPRIVATE_DECL:.*]]:2 = hlfir.declare %[[FIRSTPRIVATE_ARG]]
@@ -82,6 +86,12 @@
 ! TASKLOOP: %[[UDR_VALUE:.*]] = fir.load %[[UDR_ELEMENT]]
 ! TASKLOOP: arith.addi %[[UDR_VALUE]]
 
+! TASKLOOP-LABEL: func.func @_QPtaskloop_explicit_full_section
+! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @add_reduction_byref_box_4xi32
+
+! TASKLOOP-LABEL: func.func @_QPtaskloop_rank_two_explicit_full_section
+! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @add_reduction_byref_box_4x4xi32
+
 !--- task.f90
 subroutine task_full_section(a)
   integer :: a(-2:1)
@@ -101,6 +111,15 @@ subroutine task_rank_two_full_section(a)
   !$omp end taskgroup
 end subroutine
 
+subroutine task_explicit_full_section(a)
+  integer :: a(-2:1)
+  !$omp taskgroup task_reduction(+: a(-2:1))
+  !$omp task in_reduction(+: a(-2:1))
+  a(-2:1) = a(-2:1) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
 !--- task-default.f90
 subroutine task_default_firstprivate_full_section(a)
   integer :: a(-2:1)
@@ -146,3 +165,27 @@ subroutine taskloop_udr_full_section(a)
     a(:) = a(:) + i
   end do
 end subroutine
+
+subroutine taskloop_explicit_full_section(a)
+  integer :: a(-2:1), i
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop reduction(+: a(-2:1))
+  do i = 1, 1
+    a(-2:1) = a(-2:1) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+subroutine taskloop_rank_two_explicit_full_section(a)
+  integer :: a(-2:1, -1:2), i
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop reduction(+: a(-2:1, -1:2))
+  do i = 1, 1
+    a(-2:1, -1:2) = a(-2:1, -1:2) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine

>From ea13241c880d2379c7f6cd2505f02aef69ec1054 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Thu, 20 Aug 2026 16:10:33 +0100
Subject: [PATCH 10/18] Recognize runtime full array sections

Recognize full sections whose explicit bounds fold to the base array's
bounds, including LBOUND and UBOUND results evaluated in the default
integer kind before conversion to the subscript kind.

Compare folded bounds by expression identity, not procedure-interface
equivalence, which can equate unrelated same-position dummy arguments.

Only the default integer kind is handled. Bounds using a nondefault
KIND= result are not recognized as full sections.

Assisted-by: Codex
---
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp |  2 +-
 flang/lib/Lower/OpenMP/Utils.cpp              | 34 +++++++++++--------
 flang/lib/Lower/OpenMP/Utils.h                |  4 ++-
 ...ction-array-element-task-privatization.f90 | 21 ++++++++++++
 ...-array-full-section-task-privatization.f90 | 17 ++++++++++
 5 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 670705d809a85..42669b2a7ec6d 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -267,7 +267,7 @@ void DataSharingProcessor::collectSymbolsForPrivatization() {
   auto collectWholeArrayReductionSymbols = [&](const auto &reductionClause) {
     const ObjectList &objects = std::get<ObjectList>(reductionClause.t);
     for (const Object &object : objects)
-      if (object.sym() && isWholeArraySection(object))
+      if (object.sym() && isWholeArraySection(object, semaCtx))
         wholeArrayReductionSymbols.insert(&object.sym()->GetUltimate());
   };
 
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index e522e28d31f58..7e1e4aa3b738b 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -14,6 +14,7 @@
 
 #include "ClauseFinder.h"
 #include "flang/Evaluate/fold.h"
+#include "flang/Evaluate/shape.h"
 #include "flang/Evaluate/tools.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
@@ -73,7 +74,8 @@ llvm::cl::opt<bool> treatIndexAsSection(
 namespace Fortran {
 namespace lower {
 namespace omp {
-bool isWholeArraySection(const Object &object) {
+bool isWholeArraySection(const Object &object,
+                         semantics::SemanticsContext &semaCtx) {
   if (!object.sym() || !object.ref())
     return false;
 
@@ -90,26 +92,30 @@ bool isWholeArraySection(const Object &object) {
   if (!shape || shape->size() != arrayRef->subscript().size())
     return false;
 
-  auto matchesDeclaredBound = [](const auto *sectionBound,
-                                 const auto &declaredBound) {
+  auto matchesBaseBound = [&](const auto *sectionBound,
+                              evaluate::MaybeExtentExpr baseBound) {
     if (!sectionBound)
       return true;
-    const auto &explicitDeclaredBound = declaredBound.GetExplicit();
-    if (!explicitDeclaredBound)
+    if (!baseBound)
       return false;
-    std::optional<std::int64_t> sectionValue = evaluate::ToInt64(*sectionBound);
-    std::optional<std::int64_t> declaredValue =
-        evaluate::ToInt64(*explicitDeclaredBound);
-    return sectionValue && declaredValue && sectionValue == declaredValue;
+    evaluate::ExtentExpr foldedSectionBound = evaluate::Fold(
+        semaCtx.foldingContext(), evaluate::ExtentExpr{*sectionBound});
+    evaluate::ExtentExpr foldedBaseBound =
+        evaluate::Fold(semaCtx.foldingContext(), std::move(*baseBound));
+    return evaluate::IsSameOrConvertOf(
+        evaluate::AsGenericExpr(std::move(foldedSectionBound)),
+        evaluate::AsGenericExpr(std::move(foldedBaseBound)));
   };
 
-  for (auto [subscript, declaredDimension] :
-       llvm::zip_equal(arrayRef->subscript(), *shape)) {
+  for (auto [dimension, subscript] : llvm::enumerate(arrayRef->subscript())) {
     const auto *triplet = std::get_if<evaluate::Triplet>(&subscript.u);
     if (!triplet || evaluate::ToInt64(triplet->GetStride()) != 1 ||
-        !matchesDeclaredBound(triplet->GetLower(),
-                              declaredDimension.lbound()) ||
-        !matchesDeclaredBound(triplet->GetUpper(), declaredDimension.ubound()))
+        !matchesBaseBound(triplet->GetLower(),
+                          evaluate::GetLBOUND(semaCtx.foldingContext(),
+                                              arrayRef->base(), dimension)) ||
+        !matchesBaseBound(triplet->GetUpper(),
+                          evaluate::GetUBOUND(semaCtx.foldingContext(),
+                                              arrayRef->base(), dimension)))
       return false;
   }
   return true;
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index f86ebf0e2966f..a90e854c7a352 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -29,6 +29,7 @@ class RecordType;
 namespace Fortran {
 
 namespace semantics {
+class SemanticsContext;
 class Symbol;
 namespace omp {
 class OmpVariantMatchContext;
@@ -137,7 +138,8 @@ void generateMemberPlacementIndices(
 bool isMemberOrParentAllocatableOrPointer(
     const Object &object, Fortran::semantics::SemanticsContext &semaCtx);
 
-bool isWholeArraySection(const Object &object);
+bool isWholeArraySection(const Object &object,
+                         semantics::SemanticsContext &semaCtx);
 
 mlir::Value createParentSymAndGenIntermediateMaps(
     mlir::Location clauseLocation, Fortran::lower::AbstractConverter &converter,
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 3b498ebcd9cab..323241c3a65a6 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -23,6 +23,8 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-reduction-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-REDUCTION-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-udr-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-UDR-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-udr-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-UDR-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-cross-scope-bounds.f90 2>&1 | FileCheck %s --check-prefix=TASK-CROSS-SCOPE-BOUNDS
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-cross-scope-bounds.f90 2>&1 | FileCheck %s --check-prefix=TASK-CROSS-SCOPE-BOUNDS
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -40,6 +42,7 @@
 ! EAGER-TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-UDR-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
+! TASK-CROSS-SCOPE-BOUNDS: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -117,3 +120,21 @@ subroutine taskloop_reduction_element(a, n)
     a(2) = a(2) + i
   end do
 end subroutine
+
+!--- task-cross-scope-bounds.f90
+subroutine task_cross_scope_bounds(n)
+  integer :: n
+  integer :: a(n)
+
+contains
+  subroutine inner(m)
+    integer :: m
+    !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+    !$omp& initializer(omp_priv = 1)
+    !$omp taskgroup task_reduction(+: a(1:m))
+    !$omp task in_reduction(+: a(1:m))
+    a(1:m) = a(1:m) + 1
+    !$omp end task
+    !$omp end taskgroup
+  end subroutine
+end subroutine
diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index 75782389a989c..d30a71de4debc 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -36,6 +36,14 @@
 ! TASK: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4xi32
 ! TASK: omp.task in_reduction(byref @add_reduction_byref_box_4xi32
 
+! TASK-LABEL: func.func @_QPtask_dynamic_full_section
+! TASK: omp.taskgroup task_reduction(byref @add_reduction_byref_box_Uxi32 {{.*}} -> %[[DYNAMIC_TASKGROUP_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<?xi32>>>)
+! TASK: %[[DYNAMIC_TASKGROUP_DECL:.*]]:2 = hlfir.declare %[[DYNAMIC_TASKGROUP_ARG]]
+! TASK: omp.task in_reduction(byref @add_reduction_byref_box_Uxi32 %[[DYNAMIC_TASKGROUP_DECL]]#0 -> %[[DYNAMIC_TASK_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<?xi32>>>)
+! TASK: %[[DYNAMIC_TASK_DECL:.*]]:2 = hlfir.declare %[[DYNAMIC_TASK_ARG]]
+! TASK: %[[DYNAMIC_TASK_BOX:.*]] = fir.load %[[DYNAMIC_TASK_DECL]]#0
+! TASK: hlfir.designate %[[DYNAMIC_TASK_BOX]]
+
 ! TASK-DEFAULT-LABEL: func.func @_QPtask_default_firstprivate_full_section
 ! TASK-DEFAULT: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[FIRSTPRIVATE_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 ! TASK-DEFAULT: %[[FIRSTPRIVATE_DECL:.*]]:2 = hlfir.declare %[[FIRSTPRIVATE_ARG]]
@@ -120,6 +128,15 @@ subroutine task_explicit_full_section(a)
   !$omp end taskgroup
 end subroutine
 
+subroutine task_dynamic_full_section(a)
+  integer :: a(:)
+  !$omp taskgroup task_reduction(+: a(lbound(a, 1):ubound(a, 1)))
+  !$omp task shared(a) in_reduction(+: a(lbound(a, 1):ubound(a, 1)))
+  a(lbound(a, 1):ubound(a, 1)) = a(lbound(a, 1):ubound(a, 1)) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
 !--- task-default.f90
 subroutine task_default_firstprivate_full_section(a)
   integer :: a(-2:1)

>From d07a0c476f2494496216c38c600c01f3043d0525 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Thu, 20 Aug 2026 15:24:04 +0100
Subject: [PATCH 11/18] Diagnose shared partial taskloop reductions

Partial array sections in taskloop reductions are lowered as whole-array
reductions when their base is shared. The existing diagnostic checks
only privatized bases, so explicit shared data bypasses it.

Diagnose partial sections independently of base data sharing while
continuing to accept full-extent sections. Cover reduction and
in_reduction clauses, including user-defined reductions, with eager and
delayed privatization.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 18 ++++++++-
 ...ction-array-element-task-privatization.f90 | 38 +++++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 969d671f16f14..bd7465710aded 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -179,6 +179,18 @@ static bool hasPrivatizedArrayReductionObject(
   return false;
 }
 
+static bool
+hasPartialArrayReductionObject(llvm::ArrayRef<Object> reductionObjects,
+                               semantics::SemanticsContext &semaCtx) {
+  for (const Object &object : reductionObjects) {
+    if (!object.ref() || isWholeArraySection(object, semaCtx))
+      continue;
+    if (evaluate::IsArraySection(*object.ref()))
+      return true;
+  }
+  return false;
+}
+
 /// Structure holding the information needed to create and bind entry block
 /// arguments associated to a single clause during OpenMP lowering.
 struct ObjectEntryBlockArgsEntry {
@@ -4919,7 +4931,11 @@ static mlir::omp::TaskloopContextOp genStandaloneTaskloop(
     TODO(loc,
          "TASKLOOP construct with REDUCTION of an array element or section "
          "whose base array is privatized");
-
+  if (hasPartialArrayReductionObject(inReductionObjects, semaCtx))
+    TODO(loc,
+         "TASKLOOP construct with IN_REDUCTION of a partial array section");
+  if (hasPartialArrayReductionObject(reductionObjects, semaCtx))
+    TODO(loc, "TASKLOOP construct with REDUCTION of a partial array section");
   mlir::omp::LoopNestOperands loopNestClauseOps;
   llvm::SmallVector<const semantics::Symbol *> iv;
   genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc,
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 323241c3a65a6..0bc80a6a113ec 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -25,6 +25,14 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-udr-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASKLOOP-UDR-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-cross-scope-bounds.f90 2>&1 | FileCheck %s --check-prefix=TASK-CROSS-SCOPE-BOUNDS
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-cross-scope-bounds.f90 2>&1 | FileCheck %s --check-prefix=TASK-CROSS-SCOPE-BOUNDS
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-udr-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-udr-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-udr-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-udr-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -43,6 +51,8 @@
 ! EAGER-TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-UDR-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! TASK-CROSS-SCOPE-BOUNDS: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASKLOOP-UDR-SHARED-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of a partial array section
+! TASKLOOP-IN-SHARED-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of a partial array section
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -103,6 +113,34 @@ subroutine taskloop_udr_section(a)
   end do
 end subroutine
 
+!--- taskloop-udr-shared-section.f90
+subroutine taskloop_udr_shared_section(a)
+  integer :: a(4), i
+  !$omp declare reduction(myred : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop reduction(myred : a(2:3))
+  do i = 1, 1
+    a(2:3) = a(2:3) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+!--- taskloop-in-shared-section.f90
+subroutine taskloop_in_reduction_shared_section(a)
+  integer :: a(4, 4), i
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop in_reduction(+: a(:, 2))
+  do i = 1, 1
+    a(:, 2) = a(:, 2) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
 !--- taskloop-in.f90
 subroutine taskloop_in_reduction_element(a, n)
   integer :: a(4), n

>From ca393811495a92c4eb048c478bf48714b25f432c Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Thu, 20 Aug 2026 15:33:07 +0100
Subject: [PATCH 12/18] Preserve supported array element reductions

Single element task reductions lower as whole-array reductions. Keep
those paths enabled when lowering succeeds.

Diagnose eager ordinary tasks and targets, where current lowering
otherwise produces invalid IR, and cover supported task, taskgroup, and
taskloop paths.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 28 ++++++-
 ...ction-array-element-task-privatization.f90 | 26 ++++++
 .../OpenMP/reduction-array-element-shared.f90 | 83 +++++++++++++++++++
 3 files changed, 136 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Lower/OpenMP/reduction-array-element-shared.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bd7465710aded..bdb6d42aae5c5 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -191,6 +191,25 @@ hasPartialArrayReductionObject(llvm::ArrayRef<Object> reductionObjects,
   return false;
 }
 
+static bool
+hasArrayElementReductionObject(llvm::ArrayRef<Object> reductionObjects) {
+  for (const Object &object : reductionObjects) {
+    if (!object.ref())
+      continue;
+    std::optional<evaluate::DataRef> dataRef =
+        evaluate::ExtractDataRef(*object.ref());
+    if (!dataRef)
+      continue;
+    const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
+    if (arrayRef && llvm::all_of(arrayRef->subscript(),
+                                 [](const evaluate::Subscript &subscript) {
+                                   return subscript.Rank() == 0;
+                                 }))
+      return true;
+  }
+  return false;
+}
+
 /// Structure holding the information needed to create and bind entry block
 /// arguments associated to a single clause during OpenMP lowering.
 struct ObjectEntryBlockArgsEntry {
@@ -4020,6 +4039,9 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
              "TARGET construct with IN_REDUCTION of a privatized variable");
     }
 
+  if (hasArrayElementReductionObject(inReductionObjects))
+    TODO(loc, "TARGET construct with IN_REDUCTION of an array element");
+
   // Collect symbols that have dynamic substring accesses
   llvm::SmallPtrSet<const semantics::Symbol *, 8> symbolsWithDynamicSubstring;
   collectSymbolsWithDynamicSubstring(semaCtx, eval,
@@ -4285,12 +4307,16 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
   genTaskClauses(converter, semaCtx, symTable, stmtCtx, item->clauses, loc,
                  clauseOps, inReductionObjects);
 
-  if (!enableDelayedPrivatization)
+  if (!enableDelayedPrivatization) {
+    if (hasArrayElementReductionObject(inReductionObjects))
+      TODO(loc, "TASK construct with IN_REDUCTION of an array element when "
+                "delayed privatization is disabled");
     return genOpWithBody<mlir::omp::TaskOp>(
         OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                           llvm::omp::Directive::OMPD_task)
             .setClauses(&item->clauses),
         queue, item, clauseOps);
+  }
 
   DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval,
                            lower::omp::isLastItemInQueue(item, queue),
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 0bc80a6a113ec..6e0067abcb5eb 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -33,6 +33,10 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/task-shared-element.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/task-shared-element.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -53,6 +57,8 @@
 ! TASK-CROSS-SCOPE-BOUNDS: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-UDR-SHARED-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of a partial array section
 ! TASKLOOP-IN-SHARED-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of a partial array section
+! EAGER-TASK-SHARED-ELEMENT: not yet implemented: TASK construct with IN_REDUCTION of an array element when delayed privatization is disabled
+! TARGET-ELEMENT: not yet implemented: TARGET construct with IN_REDUCTION of an array element
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -64,6 +70,26 @@ subroutine task_reduction_element(a)
   !$omp end taskgroup
 end subroutine
 
+!--- task-shared-element.f90
+subroutine task_in_reduction_shared_element(a)
+  integer :: a(4)
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp task in_reduction(+: a(2))
+  a(2) = a(2) + 1
+  !$omp end task
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+!--- target-element.f90
+subroutine target_in_reduction_element(a)
+  integer :: a(4)
+  !$omp target in_reduction(+: a(2)) map(tofrom: a)
+  a(2) = a(2) + 1
+  !$omp end target
+end subroutine
+
 !--- task-section.f90
 subroutine task_reduction_section(a)
   integer :: a(4)
diff --git a/flang/test/Lower/OpenMP/reduction-array-element-shared.f90 b/flang/test/Lower/OpenMP/reduction-array-element-shared.f90
new file mode 100644
index 0000000000000..c066453a93637
--- /dev/null
+++ b/flang/test/Lower/OpenMP/reduction-array-element-shared.f90
@@ -0,0 +1,83 @@
+! RUN: split-file %s %t
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task.f90 | FileCheck %s --check-prefix=TASK --implicit-check-not="not yet implemented"
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskgroup.f90 | FileCheck %s --check-prefix=TASKGROUP --implicit-check-not="not yet implemented"
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not="not yet implemented"
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not="not yet implemented"
+
+! A single-element reduction is lowered as a reduction of its whole base array
+! when that path is otherwise supported. Check that the generated reduction
+! arguments therefore use the base array descriptor.
+
+! TASK-LABEL: func.func @_QPtask_element
+! TASK: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[TASKGROUP_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK: %[[TASKGROUP_DECL:.*]]:2 = hlfir.declare %[[TASKGROUP_ARG]]
+! TASK: omp.task in_reduction(byref @add_reduction_byref_box_4xi32 %[[TASKGROUP_DECL]]#0 -> %[[TASK_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK: %[[TASK_DECL:.*]]:2 = hlfir.declare %[[TASK_ARG]]
+! TASK: %[[TASK_BOX:.*]] = fir.load %[[TASK_DECL]]#0
+! TASK: hlfir.designate %[[TASK_BOX]]
+
+! TASKGROUP-LABEL: func.func @_QPtaskgroup_element
+! TASKGROUP: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[TASKGROUP_ONLY_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASKGROUP: %[[TASKGROUP_ONLY_DECL:.*]]:2 = hlfir.declare %[[TASKGROUP_ONLY_ARG]]
+! TASKGROUP: %[[TASKGROUP_ONLY_BOX:.*]] = fir.load %[[TASKGROUP_ONLY_DECL]]#0
+! TASKGROUP: hlfir.designate %[[TASKGROUP_ONLY_BOX]]
+
+! TASKLOOP-LABEL: func.func @_QPtaskloop_in_element
+! TASKLOOP: omp.taskgroup task_reduction(byref @add_reduction_byref_box_4xi32 {{.*}} -> %[[TASKLOOP_GROUP_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASKLOOP: %[[TASKLOOP_GROUP_DECL:.*]]:2 = hlfir.declare %[[TASKLOOP_GROUP_ARG]]
+! TASKLOOP: omp.taskloop.context in_reduction(byref @add_reduction_byref_box_4xi32 %[[TASKLOOP_GROUP_DECL]]#0 -> %[[TASKLOOP_ARG:arg[0-9]+]] : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASKLOOP: %[[TASKLOOP_DECL:.*]]:2 = hlfir.declare %[[TASKLOOP_ARG]]
+! TASKLOOP: %[[TASKLOOP_BOX:.*]] = fir.load %[[TASKLOOP_DECL]]#0
+! TASKLOOP: hlfir.designate %[[TASKLOOP_BOX]]
+
+! TASKLOOP-LABEL: func.func @_QPtaskloop_reduction_element
+! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @add_reduction_byref_box_4xi32
+
+!--- task.f90
+subroutine task_element(a)
+  integer :: a(4)
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskgroup task_reduction(+: a(2))
+  !$omp task in_reduction(+: a(2))
+  a(2) = a(2) + 1
+  !$omp end task
+  !$omp end taskgroup
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+!--- taskgroup.f90
+subroutine taskgroup_element(a)
+  integer :: a(4)
+  !$omp taskgroup task_reduction(+: a(2))
+  a(2) = a(2) + 1
+  !$omp end taskgroup
+end subroutine
+
+!--- taskloop.f90
+subroutine taskloop_in_element(a)
+  integer :: a(4), i
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskgroup task_reduction(+: a(2))
+  !$omp taskloop in_reduction(+: a(2))
+  do i = 1, 1
+    a(2) = a(2) + i
+  end do
+  !$omp end taskgroup
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+subroutine taskloop_reduction_element(a)
+  integer :: a(4), i
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop reduction(+: a(2))
+  do i = 1, 1
+    a(2) = a(2) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine

>From 9264fcc433c1444f48c65652451afe89516ea3ef Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Thu, 20 Aug 2026 16:40:33 +0100
Subject: [PATCH 13/18] Diagnose eager task in_reduction

Eager task lowering creates in_reduction operands without corresponding
region arguments when delayed privatization is disabled. This produces
invalid MLIR.

Diagnose eager task in_reduction until lowering can bind its region
arguments. Move full-array-section coverage to the TODO test.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp                   |  4 ++++
 .../reduction-array-element-task-privatization.f90  | 13 +++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bdb6d42aae5c5..5a5a06409a576 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4311,6 +4311,10 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
     if (hasArrayElementReductionObject(inReductionObjects))
       TODO(loc, "TASK construct with IN_REDUCTION of an array element when "
                 "delayed privatization is disabled");
+    if (!inReductionObjects.empty())
+      TODO(loc, "TASK construct with IN_REDUCTION when delayed "
+                "privatization is disabled");
+
     return genOpWithBody<mlir::omp::TaskOp>(
         OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                           llvm::omp::Directive::OMPD_task)
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 6e0067abcb5eb..5badf02aee97a 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -35,6 +35,8 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-in-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-SHARED-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/task-shared-element.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-ELEMENT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/task-shared-element.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/task-shared-full-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-FULL-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/task-shared-full-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-FULL-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 
@@ -58,6 +60,7 @@
 ! TASKLOOP-UDR-SHARED-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of a partial array section
 ! TASKLOOP-IN-SHARED-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of a partial array section
 ! EAGER-TASK-SHARED-ELEMENT: not yet implemented: TASK construct with IN_REDUCTION of an array element when delayed privatization is disabled
+! EAGER-TASK-SHARED-FULL-SECTION: not yet implemented: TASK construct with IN_REDUCTION when delayed privatization is disabled
 ! TARGET-ELEMENT: not yet implemented: TARGET construct with IN_REDUCTION of an array element
 
 !--- task.f90
@@ -82,6 +85,16 @@ subroutine task_in_reduction_shared_element(a)
   !$omp end parallel
 end subroutine
 
+!--- task-shared-full-section.f90
+subroutine task_in_reduction_shared_full_section(a)
+  integer :: a(4)
+  !$omp taskgroup task_reduction(+: a(:))
+  !$omp task shared(a) in_reduction(+: a(:))
+  a(:) = a(:) + 1
+  !$omp end task
+  !$omp end taskgroup
+end subroutine
+
 !--- target-element.f90
 subroutine target_in_reduction_element(a)
   integer :: a(4)

>From a07254afec0e4c3b4eb112773c465adfba5ea205 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Mon, 24 Aug 2026 11:21:00 +0100
Subject: [PATCH 14/18] Diagnose UDR array element task reductions

Task reduction array elements currently lower through their whole-array
descriptor. User-defined initializers and combiners can therefore modify
elements outside the listed reduction item.

Diagnose these cases for task, taskgroup, and taskloop constructs while
keeping supported predefined reductions unchanged.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 129 ++++++++++++++++--
 ...ction-array-element-task-privatization.f90 |  83 +++++++++++
 2 files changed, 197 insertions(+), 15 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 5a5a06409a576..16b2b5d8da463 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -13,6 +13,7 @@
 #include "flang/Lower/OpenMP.h"
 
 #include "Atomic.h"
+#include "ClauseFinder.h"
 #include "ClauseProcessor.h"
 #include "DataSharingProcessor.h"
 #include "Decomposer.h"
@@ -191,23 +192,100 @@ hasPartialArrayReductionObject(llvm::ArrayRef<Object> reductionObjects,
   return false;
 }
 
+static bool isArrayElementReductionObject(const Object &object) {
+  return object.ref() && object.ref()->Rank() == 0 &&
+         evaluate::IsArrayElement(*object.ref(), /*intoSubstring=*/false);
+}
+
 static bool
 hasArrayElementReductionObject(llvm::ArrayRef<Object> reductionObjects) {
-  for (const Object &object : reductionObjects) {
-    if (!object.ref())
-      continue;
-    std::optional<evaluate::DataRef> dataRef =
-        evaluate::ExtractDataRef(*object.ref());
-    if (!dataRef)
-      continue;
-    const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u);
-    if (arrayRef && llvm::all_of(arrayRef->subscript(),
-                                 [](const evaluate::Subscript &subscript) {
-                                   return subscript.Rank() == 0;
-                                 }))
-      return true;
-  }
-  return false;
+  return llvm::any_of(reductionObjects, isArrayElementReductionObject);
+}
+
+static bool isUserDefinedReductionOperator(
+    const clause::ReductionOperator &reductionOperator, const Object &object,
+    lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx) {
+  const semantics::Symbol *objectSymbol = object.sym();
+  const semantics::DeclTypeSpec *objectType =
+      objectSymbol ? objectSymbol->GetUltimate().GetType() : nullptr;
+  if (!objectType)
+    return false;
+
+  return common::visit(
+      common::visitors{
+          [&](const clause::DefinedOperator &definedOperator) {
+            return common::visit(
+                common::visitors{
+                    [&](const clause::DefinedOperator::IntrinsicOperator &op) {
+                      using IntrinsicOperator =
+                          clause::DefinedOperator::IntrinsicOperator;
+                      switch (op) {
+                      case IntrinsicOperator::Add:
+                      case IntrinsicOperator::Multiply:
+                      case IntrinsicOperator::AND:
+                      case IntrinsicOperator::OR:
+                      case IntrinsicOperator::EQV:
+                      case IntrinsicOperator::NEQV:
+                        break;
+                      default:
+                        return false;
+                      }
+
+                      parser::CharBlock mangledName =
+                          semantics::omp::MangledIntrinsicOperatorReductionName(
+                              ReductionProcessor::toParserIntrinsicOperator(op),
+                              semaCtx);
+                      return semantics::omp::FindUserReductionSymbol(
+                                 converter.getCurrentScope(), mangledName,
+                                 objectType) != nullptr;
+                    },
+                    [&](const clause::DefinedOperator::DefinedOpName &op) {
+                      const semantics::Symbol *operatorSymbol = op.v.sym();
+                      return operatorSymbol &&
+                             semantics::omp::FindOperatorUserReductionSymbol(
+                                 converter.getCurrentScope(), *operatorSymbol,
+                                 objectType);
+                    },
+                },
+                definedOperator.u);
+          },
+          [&](const clause::ProcedureDesignator &procedureDesignator) {
+            const semantics::Symbol *symbol = procedureDesignator.v.sym();
+            return (symbol &&
+                    symbol->GetUltimate()
+                        .detailsIf<semantics::UserReductionDetails>()) ||
+                   ReductionProcessor::findUserDefinedReductionForIntrinsic(
+                       converter.getCurrentScope(), procedureDesignator,
+                       objectType) != nullptr;
+          },
+      },
+      reductionOperator.u);
+}
+
+template <typename ReductionClause>
+static bool
+hasUserDefinedArrayElementReduction(const List<Clause> &clauses,
+                                    lower::AbstractConverter &converter,
+                                    semantics::SemanticsContext &semaCtx) {
+  bool found = false;
+  ClauseFinder::findRepeatableClause<ReductionClause>(
+      clauses,
+      [&](const ReductionClause &reductionClause, const parser::CharBlock &) {
+        if (found)
+          return;
+
+        const auto &reductionOperators =
+            std::get<clause::ReductionOperatorList>(reductionClause.t);
+        assert(reductionOperators.size() == 1 &&
+               "expected one reduction operator");
+        const ObjectList &objects = std::get<ObjectList>(reductionClause.t);
+        found = llvm::any_of(objects, [&](const Object &object) {
+          return isArrayElementReductionObject(object) &&
+                 isUserDefinedReductionOperator(reductionOperators.front(),
+                                                object, converter, semaCtx);
+        });
+      });
+  return found;
 }
 
 /// Structure holding the information needed to create and bind entry block
@@ -4307,6 +4385,11 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
   genTaskClauses(converter, semaCtx, symTable, stmtCtx, item->clauses, loc,
                  clauseOps, inReductionObjects);
 
+  if (hasUserDefinedArrayElementReduction<clause::InReduction>(
+          item->clauses, converter, semaCtx))
+    TODO(loc, "TASK construct with IN_REDUCTION of an array element using a "
+              "user-defined reduction");
+
   if (!enableDelayedPrivatization) {
     if (hasArrayElementReductionObject(inReductionObjects))
       TODO(loc, "TASK construct with IN_REDUCTION of an array element when "
@@ -4358,6 +4441,12 @@ genTaskgroupOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
   genTaskgroupClauses(converter, semaCtx, item->clauses, loc, clauseOps,
                       taskReductionObjects);
 
+  if (hasUserDefinedArrayElementReduction<clause::TaskReduction>(
+          item->clauses, converter, semaCtx))
+    TODO(loc,
+         "TASKGROUP construct with TASK_REDUCTION of an array element using a "
+         "user-defined reduction");
+
   ObjectEntryBlockArgs taskgroupArgs;
   taskgroupArgs.taskReduction.objects = taskReductionObjects;
   taskgroupArgs.taskReduction.vars = clauseOps.taskReductionVars;
@@ -4950,6 +5039,16 @@ static mlir::omp::TaskloopContextOp genStandaloneTaskloop(
                            enableDelayedPrivatization, symTable);
   dsp.processStep1(&taskloopClauseOps);
 
+  if (hasUserDefinedArrayElementReduction<clause::InReduction>(
+          item->clauses, converter, semaCtx))
+    TODO(loc,
+         "TASKLOOP construct with IN_REDUCTION of an array element using a "
+         "user-defined reduction");
+  if (hasUserDefinedArrayElementReduction<clause::Reduction>(
+          item->clauses, converter, semaCtx))
+    TODO(loc, "TASKLOOP construct with REDUCTION of an array element using a "
+              "user-defined reduction");
+
   llvm::ArrayRef<const semantics::Symbol *> privatizedSymbols =
       enableDelayedPrivatization ? dsp.getDelayedPrivSymbols()
                                  : dsp.getAllSymbolsToPrivatize().getArrayRef();
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 5badf02aee97a..0ebbd4bc73c26 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -37,6 +37,18 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/task-shared-element.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-ELEMENT
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/task-shared-full-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-FULL-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/task-shared-full-section.f90 2>&1 | FileCheck %s --check-prefix=EAGER-TASK-SHARED-FULL-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASK-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASK-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskgroup-udr-element.f90 2>&1 | FileCheck %s --check-prefix=TASKGROUP-UDR-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskgroup-udr-element.f90 2>&1 | FileCheck %s --check-prefix=TASKGROUP-UDR-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 --enable-delayed-privatization=false -o - %t/taskloop-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-max-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-MAX-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-max-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-MAX-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-UDR-SHARED-ELEMENT
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 
@@ -61,6 +73,11 @@
 ! TASKLOOP-IN-SHARED-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of a partial array section
 ! EAGER-TASK-SHARED-ELEMENT: not yet implemented: TASK construct with IN_REDUCTION of an array element when delayed privatization is disabled
 ! EAGER-TASK-SHARED-FULL-SECTION: not yet implemented: TASK construct with IN_REDUCTION when delayed privatization is disabled
+! TASK-UDR-SHARED-ELEMENT: not yet implemented: TASK construct with IN_REDUCTION of an array element using a user-defined reduction
+! TASKGROUP-UDR-ELEMENT: not yet implemented: TASKGROUP construct with TASK_REDUCTION of an array element using a user-defined reduction
+! TASKLOOP-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with REDUCTION of an array element using a user-defined reduction
+! TASKLOOP-MAX-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with REDUCTION of an array element using a user-defined reduction
+! TASKLOOP-IN-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element using a user-defined reduction
 ! TARGET-ELEMENT: not yet implemented: TARGET construct with IN_REDUCTION of an array element
 
 !--- task.f90
@@ -95,6 +112,26 @@ subroutine task_in_reduction_shared_full_section(a)
   !$omp end taskgroup
 end subroutine
 
+!--- task-udr-shared-element.f90
+subroutine task_in_reduction_udr_shared_element(a)
+  integer :: a(4)
+  !$omp declare reduction(+: integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp task shared(a) in_reduction(+: a(2))
+  a(2) = a(2) + 1
+  !$omp end task
+end subroutine
+
+!--- taskgroup-udr-element.f90
+subroutine taskgroup_udr_element(a)
+  integer :: a(4)
+  !$omp declare reduction(+: integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp taskgroup task_reduction(+: a(2))
+  a(2) = a(2) + 1
+  !$omp end taskgroup
+end subroutine
+
 !--- target-element.f90
 subroutine target_in_reduction_element(a)
   integer :: a(4)
@@ -167,6 +204,52 @@ subroutine taskloop_udr_shared_section(a)
   !$omp end parallel
 end subroutine
 
+!--- taskloop-udr-shared-element.f90
+subroutine taskloop_udr_shared_element(a)
+  integer :: a(4), i
+  !$omp declare reduction(myred : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop reduction(myred : a(2))
+  do i = 1, 1
+    a(2) = a(2) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+!--- taskloop-max-udr-shared-element.f90
+subroutine taskloop_max_udr_shared_element(a)
+  integer :: a(4), i
+  intrinsic :: max
+  !$omp declare reduction(max : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop reduction(max : a(2))
+  do i = 1, 1
+    a(2) = a(2) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+!--- taskloop-in-udr-shared-element.f90
+subroutine taskloop_in_reduction_udr_shared_element(a)
+  integer :: a(4), i
+  !$omp declare reduction(+: integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel shared(a)
+  !$omp single
+  !$omp taskloop in_reduction(+: a(2))
+  do i = 1, 1
+    a(2) = a(2) + i
+  end do
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
 !--- taskloop-in-shared-section.f90
 subroutine taskloop_in_reduction_shared_section(a)
   integer :: a(4, 4), i

>From 313682565553fa03f770039b465e10ce6e39d037 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Mon, 24 Aug 2026 11:23:47 +0100
Subject: [PATCH 15/18] Diagnose partial task reduction sections

Partial array sections lower as whole-array reductions even when their
base is shared. Existing checks tied the diagnostic to privatization and
therefore missed ordinary task and taskgroup constructs.

Diagnose partial sections independently of their base data-sharing
attribute.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp               |  7 +++++++
 ...duction-array-element-task-privatization.f90 | 17 ++++++++++++++---
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 16b2b5d8da463..6ea45ff558af1 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4390,6 +4390,9 @@ genTaskOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
     TODO(loc, "TASK construct with IN_REDUCTION of an array element using a "
               "user-defined reduction");
 
+  if (hasPartialArrayReductionObject(inReductionObjects, semaCtx))
+    TODO(loc, "TASK construct with IN_REDUCTION of a partial array section");
+
   if (!enableDelayedPrivatization) {
     if (hasArrayElementReductionObject(inReductionObjects))
       TODO(loc, "TASK construct with IN_REDUCTION of an array element when "
@@ -4447,6 +4450,10 @@ genTaskgroupOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
          "TASKGROUP construct with TASK_REDUCTION of an array element using a "
          "user-defined reduction");
 
+  if (hasPartialArrayReductionObject(taskReductionObjects, semaCtx))
+    TODO(loc,
+         "TASKGROUP construct with TASK_REDUCTION of a partial array section");
+
   ObjectEntryBlockArgs taskgroupArgs;
   taskgroupArgs.taskReduction.objects = taskReductionObjects;
   taskgroupArgs.taskReduction.vars = clauseOps.taskReductionVars;
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 0ebbd4bc73c26..e8eaf00f274b7 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -49,6 +49,8 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-max-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-MAX-UDR-SHARED-ELEMENT
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-UDR-SHARED-ELEMENT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop-in-udr-shared-element.f90 2>&1 | FileCheck %s --check-prefix=TASKLOOP-IN-UDR-SHARED-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SHARED-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SHARED-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 
@@ -59,8 +61,8 @@
 ! TASK: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-REDUCTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
-! TASK-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
-! TASK-RANK-TWO-SECTION: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASK-SECTION: not yet implemented: TASKGROUP construct with TASK_REDUCTION of a partial array section
+! TASK-RANK-TWO-SECTION: not yet implemented: TASKGROUP construct with TASK_REDUCTION of a partial array section
 ! TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-IN: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
@@ -68,7 +70,7 @@
 ! EAGER-TASKLOOP-IN-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-REDUCTION-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
 ! EAGER-TASKLOOP-UDR-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of an array element or section whose base array is privatized
-! TASK-CROSS-SCOPE-BOUNDS: not yet implemented: TASK construct with IN_REDUCTION of an array element or section whose base array is privatized
+! TASK-CROSS-SCOPE-BOUNDS: not yet implemented: TASKGROUP construct with TASK_REDUCTION of a partial array section
 ! TASKLOOP-UDR-SHARED-SECTION: not yet implemented: TASKLOOP construct with REDUCTION of a partial array section
 ! TASKLOOP-IN-SHARED-SECTION: not yet implemented: TASKLOOP construct with IN_REDUCTION of a partial array section
 ! EAGER-TASK-SHARED-ELEMENT: not yet implemented: TASK construct with IN_REDUCTION of an array element when delayed privatization is disabled
@@ -78,6 +80,7 @@
 ! TASKLOOP-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with REDUCTION of an array element using a user-defined reduction
 ! TASKLOOP-MAX-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with REDUCTION of an array element using a user-defined reduction
 ! TASKLOOP-IN-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element using a user-defined reduction
+! TASK-SHARED-SECTION: not yet implemented: TASK construct with IN_REDUCTION of a partial array section
 ! TARGET-ELEMENT: not yet implemented: TARGET construct with IN_REDUCTION of an array element
 
 !--- task.f90
@@ -132,6 +135,14 @@ subroutine taskgroup_udr_element(a)
   !$omp end taskgroup
 end subroutine
 
+!--- task-shared-section.f90
+subroutine task_in_reduction_shared_section(a)
+  integer :: a(4)
+  !$omp task shared(a) in_reduction(+: a(2:3))
+  a(2:3) = a(2:3) + 1
+  !$omp end task
+end subroutine
+
 !--- target-element.f90
 subroutine target_in_reduction_element(a)
   integer :: a(4)

>From b49488c79d041ad63779ef6cdd5250d85de12dd7 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Mon, 24 Aug 2026 14:28:43 +0100
Subject: [PATCH 16/18] Diagnose partial task-modifier reductions

Task-modified reductions currently lower partial array sections through
their whole-array descriptor. A user-defined initializer or combiner can
therefore update elements outside the reduction item.

Diagnose partial sections on parallel and worksharing reduction-scoping
constructs while continuing to accept full-extent sections.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 18 ++++++
 ...ction-array-element-task-privatization.f90 | 57 +++++++++++++++++++
 ...-array-full-section-task-privatization.f90 | 57 +++++++++++++++++++
 3 files changed, 132 insertions(+)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 6ea45ff558af1..17fa2655a827e 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -192,6 +192,16 @@ hasPartialArrayReductionObject(llvm::ArrayRef<Object> reductionObjects,
   return false;
 }
 
+static void checkTaskModifierPartialArrayReduction(
+    mlir::Location loc, semantics::SemanticsContext &semaCtx,
+    mlir::omp::ReductionModifierAttr reductionMod,
+    llvm::ArrayRef<Object> reductionObjects) {
+  if (reductionMod &&
+      reductionMod.getValue() == mlir::omp::ReductionModifier::task &&
+      hasPartialArrayReductionObject(reductionObjects, semaCtx))
+    TODO(loc, "REDUCTION with TASK modifier of a partial array section");
+}
+
 static bool isArrayElementReductionObject(const Object &object) {
   return object.ref() && object.ref()->Rank() == 0 &&
          evaluate::IsArrayElement(*object.ref(), /*intoSubstring=*/false);
@@ -2513,6 +2523,8 @@ static void genParallelClauses(
 
   cp.processProcBind(clauseOps);
   cp.processReduction(loc, clauseOps, reductionObjects);
+  checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
+                                         reductionObjects);
 }
 
 static void genScanClauses(lower::AbstractConverter &converter,
@@ -2534,6 +2546,8 @@ genSectionsClauses(lower::AbstractConverter &converter,
   cp.processAllocate(clauseOps);
   cp.processNowait(clauseOps);
   cp.processReduction(loc, clauseOps, reductionObjects);
+  checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
+                                         reductionObjects);
   // TODO Support delayed privatization.
 }
 
@@ -2634,6 +2648,8 @@ static void genScopeClauses(lower::AbstractConverter &converter,
   cp.processAllocate(clauseOps);
   cp.processNowait(clauseOps);
   cp.processReduction(loc, clauseOps, reductionObjects);
+  checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
+                                         reductionObjects);
 }
 
 static void genSingleClauses(lower::AbstractConverter &converter,
@@ -2860,6 +2876,8 @@ static void genWsloopClauses(
   cp.processOrder(clauseOps);
   cp.processOrdered(clauseOps);
   cp.processReduction(loc, clauseOps, reductionObjects, reductionVarCache);
+  checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
+                                         reductionObjects);
   cp.processSchedule(stmtCtx, clauseOps);
   cp.processLinear(clauseOps);
 }
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index e8eaf00f274b7..8819d11b66c7c 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -53,6 +53,14 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-shared-section.f90 2>&1 | FileCheck %s --check-prefix=TASK-SHARED-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/target-element.f90 2>&1 | FileCheck %s --check-prefix=TARGET-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/parallel-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=PARALLEL-TASK-UDR-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/parallel-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=PARALLEL-TASK-UDR-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/sections-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=SECTIONS-TASK-UDR-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/sections-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=SECTIONS-TASK-UDR-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/scope-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=SCOPE-TASK-UDR-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/scope-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=SCOPE-TASK-UDR-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=DO-TASK-UDR-SECTION
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=DO-TASK-UDR-SECTION
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -82,6 +90,10 @@
 ! TASKLOOP-IN-UDR-SHARED-ELEMENT: not yet implemented: TASKLOOP construct with IN_REDUCTION of an array element using a user-defined reduction
 ! TASK-SHARED-SECTION: not yet implemented: TASK construct with IN_REDUCTION of a partial array section
 ! TARGET-ELEMENT: not yet implemented: TARGET construct with IN_REDUCTION of an array element
+! PARALLEL-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
+! SECTIONS-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
+! SCOPE-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
+! DO-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -309,3 +321,48 @@ subroutine inner(m)
     !$omp end taskgroup
   end subroutine
 end subroutine
+
+!--- parallel-task-udr-section.f90
+subroutine parallel_task_udr_section(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel reduction(task, + : a(2:3))
+  !$omp target map(tofrom: a) in_reduction(+ : a(2:3))
+  a(2:3) = a(2:3) + 1
+  !$omp end target
+  !$omp end parallel
+end subroutine
+
+!--- sections-task-udr-section.f90
+subroutine sections_task_udr_section(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp sections reduction(task, + : a(2:3))
+  !$omp section
+  a(2:3) = a(2:3) + 1
+  !$omp end sections
+end subroutine
+
+!--- scope-task-udr-section.f90
+subroutine scope_task_udr_section(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp scope reduction(task, + : a(2:3))
+  a(2:3) = a(2:3) + 1
+  !$omp end scope
+end subroutine
+
+!--- do-task-udr-section.f90
+subroutine do_task_udr_section(a)
+  integer :: a(4), i
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp do reduction(task, + : a(2:3))
+  do i = 1, 1
+    a(2:3) = a(2:3) + i
+  end do
+  !$omp end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index d30a71de4debc..ccaf4cc554316 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -3,6 +3,8 @@
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/task-default.f90 | FileCheck %s --check-prefix=TASK-DEFAULT --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate --implicit-check-not=Ea_private
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -mmlir --enable-delayed-privatization=false -o - %t/taskloop.f90 | FileCheck %s --check-prefix=TASKLOOP --implicit-check-not="not yet implemented" --implicit-check-not=Ea_firstprivate
+! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/task-modifier.f90 | FileCheck %s --check-prefix=TASK-MODIFIER --implicit-check-not="not yet implemented"
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/task-modifier.f90 | FileCheck %s --check-prefix=TASK-MODIFIER --implicit-check-not="not yet implemented"
 
 ! A full-extent section uses the same descriptor as its base array. Check that
 ! it is bound only to the reduction argument, rather than also being captured
@@ -100,6 +102,19 @@
 ! TASKLOOP-LABEL: func.func @_QPtaskloop_rank_two_explicit_full_section
 ! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @add_reduction_byref_box_4x4xi32
 
+! TASK-MODIFIER-LABEL: func.func @_QPparallel_task_udr_full_section
+! TASK-MODIFIER: omp.parallel reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-MODIFIER: omp.target {{.*}}in_reduction(byref @{{.*}} %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+
+! TASK-MODIFIER-LABEL: func.func @_QPsections_task_udr_full_section
+! TASK-MODIFIER: omp.sections reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+
+! TASK-MODIFIER-LABEL: func.func @_QPscope_task_udr_full_section
+! TASK-MODIFIER: omp.scope reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+
+! TASK-MODIFIER-LABEL: func.func @_QPdo_task_udr_full_section
+! TASK-MODIFIER: omp.wsloop {{.*}}reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+
 !--- task.f90
 subroutine task_full_section(a)
   integer :: a(-2:1)
@@ -206,3 +221,45 @@ subroutine taskloop_rank_two_explicit_full_section(a)
   !$omp end single
   !$omp end parallel
 end subroutine
+
+!--- task-modifier.f90
+subroutine parallel_task_udr_full_section(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel reduction(task, + : a(:))
+  !$omp target map(tofrom: a) in_reduction(+ : a(:))
+  a(:) = a(:) + 1
+  !$omp end target
+  !$omp end parallel
+end subroutine
+
+subroutine sections_task_udr_full_section(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp sections reduction(task, + : a(:))
+  !$omp section
+  a(:) = a(:) + 1
+  !$omp end sections
+end subroutine
+
+subroutine scope_task_udr_full_section(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp scope reduction(task, + : a(:))
+  a(:) = a(:) + 1
+  !$omp end scope
+end subroutine
+
+subroutine do_task_udr_full_section(a)
+  integer :: a(4), i
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp do reduction(task, + : a(:))
+  do i = 1, 1
+    a(:) = a(:) + i
+  end do
+  !$omp end do
+end subroutine

>From 211d8a67ee029926dc8ad211a15e0886f59378da Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 25 Aug 2026 12:26:06 +0100
Subject: [PATCH 17/18] Diagnose task UDR array elements

Task-modified reductions currently lower user-defined reductions on
array elements through the whole-array descriptor. Their initializers
and combiners can therefore update elements outside the reduction item.

Diagnose these reductions on parallel and worksharing reduction-scoping
constructs while continuing to accept predefined array-element
reductions.

Assisted-by: Codex
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 21 +++++++
 ...ction-array-element-task-privatization.f90 | 55 +++++++++++++++++++
 2 files changed, 76 insertions(+)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 17fa2655a827e..ef5f813c38ac3 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -298,6 +298,19 @@ hasUserDefinedArrayElementReduction(const List<Clause> &clauses,
   return found;
 }
 
+static void checkTaskModifierUserDefinedArrayElementReduction(
+    mlir::Location loc, lower::AbstractConverter &converter,
+    semantics::SemanticsContext &semaCtx,
+    mlir::omp::ReductionModifierAttr reductionMod,
+    const List<Clause> &clauses) {
+  if (reductionMod &&
+      reductionMod.getValue() == mlir::omp::ReductionModifier::task &&
+      hasUserDefinedArrayElementReduction<clause::Reduction>(clauses, converter,
+                                                             semaCtx))
+    TODO(loc, "REDUCTION with TASK modifier of an array element using a "
+              "user-defined reduction");
+}
+
 /// Structure holding the information needed to create and bind entry block
 /// arguments associated to a single clause during OpenMP lowering.
 struct ObjectEntryBlockArgsEntry {
@@ -2525,6 +2538,8 @@ static void genParallelClauses(
   cp.processReduction(loc, clauseOps, reductionObjects);
   checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
                                          reductionObjects);
+  checkTaskModifierUserDefinedArrayElementReduction(
+      loc, converter, semaCtx, clauseOps.reductionMod, clauses);
 }
 
 static void genScanClauses(lower::AbstractConverter &converter,
@@ -2548,6 +2563,8 @@ genSectionsClauses(lower::AbstractConverter &converter,
   cp.processReduction(loc, clauseOps, reductionObjects);
   checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
                                          reductionObjects);
+  checkTaskModifierUserDefinedArrayElementReduction(
+      loc, converter, semaCtx, clauseOps.reductionMod, clauses);
   // TODO Support delayed privatization.
 }
 
@@ -2650,6 +2667,8 @@ static void genScopeClauses(lower::AbstractConverter &converter,
   cp.processReduction(loc, clauseOps, reductionObjects);
   checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
                                          reductionObjects);
+  checkTaskModifierUserDefinedArrayElementReduction(
+      loc, converter, semaCtx, clauseOps.reductionMod, clauses);
 }
 
 static void genSingleClauses(lower::AbstractConverter &converter,
@@ -2878,6 +2897,8 @@ static void genWsloopClauses(
   cp.processReduction(loc, clauseOps, reductionObjects, reductionVarCache);
   checkTaskModifierPartialArrayReduction(loc, semaCtx, clauseOps.reductionMod,
                                          reductionObjects);
+  checkTaskModifierUserDefinedArrayElementReduction(
+      loc, converter, semaCtx, clauseOps.reductionMod, clauses);
   cp.processSchedule(stmtCtx, clauseOps);
   cp.processLinear(clauseOps);
 }
diff --git a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90 b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
index 8819d11b66c7c..021beabde9bea 100644
--- a/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/Todo/reduction-array-element-task-privatization.f90
@@ -61,6 +61,14 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/scope-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=SCOPE-TASK-UDR-SECTION
 ! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=DO-TASK-UDR-SECTION
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do-task-udr-section.f90 2>&1 | FileCheck %s --check-prefix=DO-TASK-UDR-SECTION
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/parallel-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=PARALLEL-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/parallel-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=PARALLEL-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/sections-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=SECTIONS-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/sections-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=SECTIONS-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/scope-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=SCOPE-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/scope-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=SCOPE-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=DO-TASK-UDR-ELEMENT
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do-task-udr-element.f90 2>&1 | FileCheck %s --check-prefix=DO-TASK-UDR-ELEMENT
 
 ! An array element or section in a task reduction and the implicitly
 ! firstprivate base array are represented by separate block arguments. Reject
@@ -94,6 +102,10 @@
 ! SECTIONS-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
 ! SCOPE-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
 ! DO-TASK-UDR-SECTION: not yet implemented: REDUCTION with TASK modifier of a partial array section
+! PARALLEL-TASK-UDR-ELEMENT: not yet implemented: REDUCTION with TASK modifier of an array element using a user-defined reduction
+! SECTIONS-TASK-UDR-ELEMENT: not yet implemented: REDUCTION with TASK modifier of an array element using a user-defined reduction
+! SCOPE-TASK-UDR-ELEMENT: not yet implemented: REDUCTION with TASK modifier of an array element using a user-defined reduction
+! DO-TASK-UDR-ELEMENT: not yet implemented: REDUCTION with TASK modifier of an array element using a user-defined reduction
 
 !--- task.f90
 subroutine task_reduction_element(a)
@@ -366,3 +378,46 @@ subroutine do_task_udr_section(a)
   end do
   !$omp end do
 end subroutine
+
+!--- parallel-task-udr-element.f90
+subroutine parallel_task_udr_element(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp parallel reduction(task, + : a(2))
+  a(2) = a(2) + 1
+  !$omp end parallel
+end subroutine
+
+!--- sections-task-udr-element.f90
+subroutine sections_task_udr_element(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp sections reduction(task, + : a(2))
+  !$omp section
+  a(2) = a(2) + 1
+  !$omp end sections
+end subroutine
+
+!--- scope-task-udr-element.f90
+subroutine scope_task_udr_element(a)
+  integer :: a(4)
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp scope reduction(task, + : a(2))
+  a(2) = a(2) + 1
+  !$omp end scope
+end subroutine
+
+!--- do-task-udr-element.f90
+subroutine do_task_udr_element(a)
+  integer :: a(4), i
+  !$omp declare reduction(+ : integer : omp_out = omp_out + omp_in) &
+  !$omp& initializer(omp_priv = 1)
+  !$omp do reduction(task, + : a(2))
+  do i = 1, 1
+    a(2) = a(2) + i
+  end do
+  !$omp end do
+end subroutine

>From db4b1b637b56a7e54d0e3c16be88d2a827f5a415 Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 25 Aug 2026 12:27:24 +0100
Subject: [PATCH 18/18] Strengthen task reduction tests

Task-modifier full-section tests accepted any reduction declaration and
could not distinguish user-defined reductions from built-in ones.

Check exact UDR symbols and add a predefined max array-element control.

Assisted-by: Codex
---
 ...-array-full-section-task-privatization.f90 | 20 ++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90 b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
index ccaf4cc554316..c6993e2b37f8d 100644
--- a/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
+++ b/flang/test/Lower/OpenMP/reduction-array-full-section-task-privatization.f90
@@ -103,17 +103,20 @@
 ! TASKLOOP: omp.taskloop.context {{.*}}reduction(byref @add_reduction_byref_box_4x4xi32
 
 ! TASK-MODIFIER-LABEL: func.func @_QPparallel_task_udr_full_section
-! TASK-MODIFIER: omp.parallel reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
-! TASK-MODIFIER: omp.target {{.*}}in_reduction(byref @{{.*}} %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-MODIFIER: omp.parallel reduction(mod: task, byref @"_QQFparallel_task_udr_full_sectionop.+_byref_box_4xi32" %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-MODIFIER: omp.target {{.*}}in_reduction(byref @"_QQFparallel_task_udr_full_sectionop.+_byref_box_4xi32" %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 
 ! TASK-MODIFIER-LABEL: func.func @_QPsections_task_udr_full_section
-! TASK-MODIFIER: omp.sections reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-MODIFIER: omp.sections reduction(mod: task, byref @"_QQFsections_task_udr_full_sectionop.+_byref_box_4xi32" %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 
 ! TASK-MODIFIER-LABEL: func.func @_QPscope_task_udr_full_section
-! TASK-MODIFIER: omp.scope reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-MODIFIER: omp.scope reduction(mod: task, byref @"_QQFscope_task_udr_full_sectionop.+_byref_box_4xi32" %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 
 ! TASK-MODIFIER-LABEL: func.func @_QPdo_task_udr_full_section
-! TASK-MODIFIER: omp.wsloop {{.*}}reduction(mod: task, byref @{{.*}} %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+! TASK-MODIFIER: omp.wsloop {{.*}}reduction(mod: task, byref @"_QQFdo_task_udr_full_sectionop.+_byref_box_4xi32" %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
+
+! TASK-MODIFIER-LABEL: func.func @_QPparallel_task_intrinsic_max_element
+! TASK-MODIFIER: omp.parallel reduction(mod: task, byref @max_byref_box_4xi32 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.array<4xi32>>>)
 
 !--- task.f90
 subroutine task_full_section(a)
@@ -263,3 +266,10 @@ subroutine do_task_udr_full_section(a)
   end do
   !$omp end do
 end subroutine
+
+subroutine parallel_task_intrinsic_max_element(a)
+  integer :: a(4)
+  !$omp parallel reduction(task, max : a(2))
+  a(2) = max(a(2), 1)
+  !$omp end parallel
+end subroutine



More information about the flang-commits mailing list