[flang-commits] [flang] [flang][openacc] Attach firstprivate scalar onto combined parallel loop (PR #222777)
Susan Tan ス-ザン タン via flang-commits
flang-commits at lists.llvm.org
Thu Sep 10 14:56:29 PDT 2026
https://github.com/SusanTan updated https://github.com/llvm/llvm-project/pull/222777
>From 85b75b94bd515b5ae3f28c00515948559dd93d03 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 10 Sep 2026 13:51:15 -0700
Subject: [PATCH 1/6] impl
---
flang/lib/Lower/OpenACC.cpp | 68 +++++++++---
.../test/Lower/OpenACC/acc-parallel-loop.f90 | 100 ++++++++++++++++++
flang/test/Lower/OpenACC/acc-private.f90 | 3 +-
flang/test/Lower/OpenACC/acc-serial-loop.f90 | 19 ++++
4 files changed, 177 insertions(+), 13 deletions(-)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index d883478e49375..4f350de1b6a8c 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -1722,6 +1722,31 @@ loopWillBeIndependent(Fortran::lower::AbstractConverter &converter,
}
}
+// Attach an implicit firstprivate on this combined loop in addition to the
+// compute clause (firstprivate is not a loop clause in the spec) when:
+// 1. Combined `parallel loop` — the region is that one loop. Not `serial
+// loop` (seq by default), `kernels loop`, or a standalone `acc loop`.
+// 2. Independent (the `parallel loop` default). Not `seq`/`auto`, which
+// must keep a carried firstprivate.
+// 3. Scalar. Nested loops reuse this copy via remap; they get no clause.
+static bool shouldAttachFirstprivateOnCombinedLoop(
+ Fortran::lower::AbstractConverter &converter,
+ const Fortran::parser::AccClauseList &accClauseList,
+ std::optional<mlir::acc::CombinedConstructsType> combinedConstructs,
+ const Fortran::parser::AccObject &accObject) {
+ if (!combinedConstructs ||
+ *combinedConstructs != mlir::acc::CombinedConstructsType::ParallelLoop)
+ return false;
+ if (!loopWillBeIndependent(converter, accClauseList,
+ llvm::acc::ACCD_parallel_loop))
+ return false;
+ mlir::Value var =
+ converter.getSymbolAddress(getSymbolFromAccObject(accObject));
+ return var &&
+ mlir::acc::bitEnumContainsAny(mlir::acc::getTypeCategory(var),
+ mlir::acc::VariableTypeCategory::scalar);
+}
+
// Helper to visit Bounds of DO LOOP nest.
//
// When `markInnerCollapsed` is true (the default), inner DOs that are absorbed
@@ -2209,6 +2234,7 @@ buildACCLoopOp(Fortran::lower::AbstractConverter &converter,
const Fortran::parser::DoConstruct &outerDoConstruct,
Fortran::lower::pft::Evaluation &eval,
llvm::SmallVector<mlir::Value> &privateOperands,
+ llvm::SmallVector<mlir::Value> &firstprivateOperands,
AccDataMap &dataMap,
llvm::SmallVector<mlir::Value> &gangOperands,
llvm::SmallVector<mlir::Value> &workerNumOperands,
@@ -2227,7 +2253,6 @@ buildACCLoopOp(Fortran::lower::AbstractConverter &converter,
llvm::SmallVector<bool> inclusiveBounds;
llvm::SmallVector<mlir::Location> locs;
llvm::SmallVector<mlir::Value> lowerbounds, upperbounds, steps;
- llvm::SmallVector<mlir::Value> firstprivateOperands;
llvm::SmallVector<
std::pair<Fortran::semantics::SymbolRef, Fortran::semantics::SymbolRef>>
localSymPairs;
@@ -2369,8 +2394,8 @@ static mlir::acc::LoopOp createLoopOp(
std::nullopt) {
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
llvm::SmallVector<mlir::Value> tileOperands, privateOperands,
- reductionOperands, cacheOperands, vectorOperands, workerNumOperands,
- gangOperands;
+ firstprivateOperands, reductionOperands, cacheOperands, vectorOperands,
+ workerNumOperands, gangOperands;
llvm::SmallVector<int32_t> tileOperandsSegments, gangOperandsSegments;
llvm::SmallVector<int64_t> collapseValues;
@@ -2499,6 +2524,24 @@ static mlir::acc::LoopOp createLoopOp(
/*structured=*/true, /*implicit=*/false,
/*async=*/{}, /*asyncDeviceTypes=*/{}, /*asyncOnlyDeviceTypes=*/{},
/*setDeclareAttr=*/false, &dataMap);
+ } else if (const auto *firstprivateClause =
+ std::get_if<Fortran::parser::AccClause::Firstprivate>(
+ &clause.u)) {
+ // Duplicate scalar firstprivate onto this combined independent loop.
+ // The compute construct already has the user-facing firstprivate (host
+ // seed). After that remap, getSymbolAddress is the compute copy, so the
+ // loop clause's varPtr chains from it. implicit=true: firstprivate is
+ // not a loop clause in the spec.
+ genDataOperandOperations<mlir::acc::FirstprivateOp>(
+ firstprivateClause->v, converter, semanticsContext, stmtCtx,
+ firstprivateOperands, mlir::acc::DataClause::acc_firstprivate,
+ /*structured=*/true, /*implicit=*/true,
+ /*async=*/{}, /*asyncDeviceTypes=*/{}, /*asyncOnlyDeviceTypes=*/{},
+ /*setDeclareAttr=*/false, &dataMap,
+ /*filter=*/[&](const Fortran::parser::AccObject &obj) {
+ return shouldAttachFirstprivateOnCombinedLoop(
+ converter, accClauseList, combinedConstructs, obj);
+ });
} else if (const auto *reductionClause =
std::get_if<Fortran::parser::AccClause::Reduction>(
&clause.u)) {
@@ -2572,9 +2615,10 @@ static mlir::acc::LoopOp createLoopOp(
auto loopOp = buildACCLoopOp(
converter, currentLocation, semanticsContext, stmtCtx, outerDoConstruct,
- eval, privateOperands, dataMap, gangOperands, workerNumOperands,
- vectorOperands, tileOperands, cacheOperands, reductionOperands, retTy,
- yieldValue, loopsToProcess, /*hasDirective=*/true);
+ eval, privateOperands, firstprivateOperands, dataMap, gangOperands,
+ workerNumOperands, vectorOperands, tileOperands, cacheOperands,
+ reductionOperands, retTy, yieldValue, loopsToProcess,
+ /*hasDirective=*/true);
if (!gangDeviceTypes.empty())
loopOp.setGangAttr(builder.getArrayAttr(gangDeviceTypes));
@@ -5555,9 +5599,9 @@ mlir::Operation *Fortran::lower::genOpenACCLoopFromDoConstruct(
// Prepare empty operand vectors since there are no associated `acc loop`
// clauses with the Fortran do loops being handled here.
- llvm::SmallVector<mlir::Value> privateOperands, gangOperands,
- workerNumOperands, vectorOperands, tileOperands, cacheOperands,
- reductionOperands;
+ llvm::SmallVector<mlir::Value> privateOperands, firstprivateOperands,
+ gangOperands, workerNumOperands, vectorOperands, tileOperands,
+ cacheOperands, reductionOperands;
llvm::SmallVector<mlir::Type> retTy;
AccDataMap dataMap;
mlir::Value yieldValue;
@@ -5568,9 +5612,9 @@ mlir::Operation *Fortran::lower::genOpenACCLoopFromDoConstruct(
Fortran::lower::StatementContext stmtCtx;
auto loopOp = buildACCLoopOp(
converter, converter.getCurrentLocation(), semanticsContext, stmtCtx,
- doConstruct, eval, privateOperands, dataMap, gangOperands,
- workerNumOperands, vectorOperands, tileOperands, cacheOperands,
- reductionOperands, retTy, yieldValue, loopsToProcess,
+ doConstruct, eval, privateOperands, firstprivateOperands, dataMap,
+ gangOperands, workerNumOperands, vectorOperands, tileOperands,
+ cacheOperands, reductionOperands, retTy, yieldValue, loopsToProcess,
/*hasDirective=*/false);
// Normal do loops which are not annotated with `acc loop` should be
diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
index 8b163593fede7..6017aec832b6b 100644
--- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90
+++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
@@ -453,6 +453,7 @@ subroutine acc_parallel_loop
! CHECK: %[[ACC_PRIVATE_B:.*]] = acc.firstprivate varPtr(%[[DECLB]]#0 : !fir.ref<!fir.array<10xf32>>) recipe(@firstprivatization_ref_10xf32) name("b") -> !fir.ref<!fir.array<10xf32>>
! CHECK: acc.parallel {{.*}} firstprivate(%[[ACC_PRIVATE_B]] : !fir.ref<!fir.array<10xf32>>) {
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: %[[ACC_PRIVATE_A:.*]] = acc.private varPtr(%[[DECLA]]#0 : !fir.ref<!fir.array<10xf32>>) recipe(@privatization_ref_10xf32) name("a") -> !fir.ref<!fir.array<10xf32>>
! CHECK: acc.loop {{.*}} private(%[[ACC_PRIVATE_A]]{{.*}} : !fir.ref<!fir.array<10xf32>>{{.*}})
! CHECK-NOT: fir.do_loop
@@ -671,3 +672,102 @@ subroutine acc_parallel_loop
! CHECK-NOT: fir.do_loop
end subroutine acc_parallel_loop
+
+! Combined parallel loop firstprivate: scalar copies are also attached on the
+! independent acc.loop; arrays, seq loops, and non-combined sibling loops are
+! not.
+subroutine acc_parallel_loop_firstprivate_scalar
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_scalar
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+! CHECK: } inclusiveUpperbound(array<i1: true>) independent
+
+subroutine acc_parallel_loop_firstprivate_seq
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop seq firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_seq
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+! CHECK: } inclusiveUpperbound(array<i1: true>) seq
+
+subroutine acc_parallel_loop_firstprivate_auto
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop auto firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_auto
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+! CHECK: } inclusiveUpperbound(array<i1: true>) auto_
+
+subroutine acc_parallel_loop_firstprivate_mixed
+ integer :: i, n, v
+ real :: b(10)
+ n = 10
+ v = 7
+ !$acc parallel loop firstprivate(v, b)
+ do i = 1, n
+ b(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_mixed
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) recipe({{.*}}) name("b") -> !fir.ref<!fir.array<10xf32>>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]], %[[FP_B]] : !fir.ref<i32>, !fir.ref<!fir.array<10xf32>>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK-NOT: implicit(true) name("b")
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+
+subroutine acc_parallel_firstprivate_sibling_loops
+ integer :: i, j, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel firstprivate(v)
+ !$acc loop
+ do i = 1, n
+ a(i) = v
+ end do
+ !$acc loop
+ do j = 1, n
+ a(j) = v
+ end do
+ !$acc end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_firstprivate_sibling_loops
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
diff --git a/flang/test/Lower/OpenACC/acc-private.f90 b/flang/test/Lower/OpenACC/acc-private.f90
index fdebdc2ed6f2a..aa5ebc5e3a5d2 100644
--- a/flang/test/Lower/OpenACC/acc-private.f90
+++ b/flang/test/Lower/OpenACC/acc-private.f90
@@ -425,7 +425,8 @@ program acc_private
! CHECK: %[[FP_C:.*]] = acc.firstprivate varPtr(%[[DECLC]]#0 : !fir.ref<i32>) recipe(@firstprivatization_ref_i32) name("c") -> !fir.ref<i32>
! CHECK: acc.parallel {{.*}} firstprivate(%[[FP_C]] : !fir.ref<i32>)
-! CHECK: acc.yield
+! CHECK: %[[FP_C_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe(@firstprivatization_ref_i32) implicit(true) name("c") -> !fir.ref<i32>
+! CHECK: acc.loop {{.*}}firstprivate(%[[FP_C_LOOP]] : !fir.ref<i32>)
!$acc parallel loop firstprivate(b)
DO i = 1, n
diff --git a/flang/test/Lower/OpenACC/acc-serial-loop.f90 b/flang/test/Lower/OpenACC/acc-serial-loop.f90
index 46ab7b3d37302..703cfe79ee28e 100644
--- a/flang/test/Lower/OpenACC/acc-serial-loop.f90
+++ b/flang/test/Lower/OpenACC/acc-serial-loop.f90
@@ -601,3 +601,22 @@ subroutine acc_serial_loop
! CHECK: acc.copyout accPtr(%[[COPYINREDI]] : !fir.ref<i32>) to varPtr(%{{.*}} : !fir.ref<i32>) dataClause(acc_reduction) implicit(true) name("reduction_i")
end subroutine acc_serial_loop
+
+! serial loop defaults to seq, so scalar firstprivate stays on the compute
+! construct only.
+subroutine acc_serial_loop_firstprivate_scalar
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc serial loop firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_serial_loop_firstprivate_scalar
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.serial combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(serial)
>From b69946918f79b987c52ef800de0d7c379dc28fcc Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 10 Sep 2026 14:03:43 -0700
Subject: [PATCH 2/6] add tests
---
.../test/Lower/OpenACC/acc-parallel-loop.f90 | 284 +++++++++++++++++-
flang/test/Lower/OpenACC/acc-private.f90 | 9 +
flang/test/Lower/OpenACC/acc-serial-loop.f90 | 19 ++
3 files changed, 310 insertions(+), 2 deletions(-)
diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
index 6017aec832b6b..8c98e17628b48 100644
--- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90
+++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
@@ -674,8 +674,8 @@ subroutine acc_parallel_loop
end subroutine acc_parallel_loop
! Combined parallel loop firstprivate: scalar copies are also attached on the
-! independent acc.loop; arrays, seq loops, and non-combined sibling loops are
-! not.
+! independent acc.loop. Arrays, sections, derived/character/boxed types, seq
+! and auto loops, and non-combined loops are not.
subroutine acc_parallel_loop_firstprivate_scalar
integer :: i, n, v
real :: a(10)
@@ -771,3 +771,283 @@ subroutine acc_parallel_firstprivate_sibling_loops
! CHECK: acc.parallel {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: acc.loop
+
+! Explicit independent is the same as the parallel loop default.
+subroutine acc_parallel_loop_firstprivate_independent
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop independent firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_independent
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+! CHECK: } inclusiveUpperbound(array<i1: true>) independent
+
+! gang does not change independent, so the scalar still copies onto the loop.
+subroutine acc_parallel_loop_firstprivate_gang
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop gang firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_gang
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+
+! collapse is still one acc.loop; attach the scalar once.
+subroutine acc_parallel_loop_firstprivate_collapse
+ integer :: i, j, n, v
+ real :: a(10, 10)
+ n = 10
+ v = 7
+ !$acc parallel loop collapse(2) firstprivate(v)
+ do i = 1, n
+ do j = 1, n
+ a(i, j) = v
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_collapse
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+! CHECK: collapse([2])
+
+! Inner standalone loop reuses the outer copy; no extra firstprivate.
+subroutine acc_parallel_loop_firstprivate_nested
+ integer :: i, j, n, v
+ real :: a(10, 10)
+ n = 10
+ v = 7
+ !$acc parallel loop firstprivate(v)
+ do i = 1, n
+ !$acc loop
+ do j = 1, n
+ a(i, j) = v
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_nested
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
+
+subroutine acc_parallel_loop_firstprivate_two_scalars
+ integer :: i, n, v, w
+ real :: a(10)
+ n = 10
+ v = 7
+ w = 3
+ !$acc parallel loop firstprivate(v, w)
+ do i = 1, n
+ a(i) = v + w
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_two_scalars
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: %[[FP_W:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("w") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]], %[[FP_W]] : !fir.ref<i32>, !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: %[[FP_W_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("w") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]], %[[FP_W_LOOP]] : !fir.ref<i32>, !fir.ref<i32>)
+
+subroutine acc_parallel_loop_firstprivate_scalar_kinds
+ integer :: i, n
+ real :: x
+ logical :: l
+ complex :: z
+ real :: a(10)
+ n = 10
+ x = 1.0
+ l = .true.
+ z = (1.0, 0.0)
+ !$acc parallel loop firstprivate(x, l, z)
+ do i = 1, n
+ a(i) = x
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_scalar_kinds
+! CHECK: %[[FP_X:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<f32>) recipe({{.*}}) name("x") -> !fir.ref<f32>
+! CHECK: %[[FP_L:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.logical<4>>) recipe({{.*}}) name("l") -> !fir.ref<!fir.logical<4>>
+! CHECK: %[[FP_Z:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<complex<f32>>) recipe({{.*}}) name("z") -> !fir.ref<complex<f32>>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_X]], %[[FP_L]], %[[FP_Z]] : !fir.ref<f32>, !fir.ref<!fir.logical<4>>, !fir.ref<complex<f32>>)
+! CHECK: %[[FP_X_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<f32>) recipe({{.*}}) implicit(true) name("x") -> !fir.ref<f32>
+! CHECK: %[[FP_L_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.logical<4>>) recipe({{.*}}) implicit(true) name("l") -> !fir.ref<!fir.logical<4>>
+! CHECK: %[[FP_Z_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<complex<f32>>) recipe({{.*}}) implicit(true) name("z") -> !fir.ref<complex<f32>>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_X_LOOP]], %[[FP_L_LOOP]], %[[FP_Z_LOOP]] : !fir.ref<f32>, !fir.ref<!fir.logical<4>>, !fir.ref<complex<f32>>)
+
+subroutine acc_parallel_loop_firstprivate_dummy(v)
+ integer :: v
+ integer :: i, n
+ real :: a(10)
+ n = 10
+ !$acc parallel loop firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_dummy
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>)
+
+subroutine acc_parallel_loop_firstprivate_with_reduction
+ integer :: i, n, v, r
+ n = 10
+ v = 7
+ r = 0
+ !$acc parallel loop firstprivate(v) reduction(+:r)
+ do i = 1, n
+ r = r + v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_with_reduction
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32>
+! CHECK: %[[RED_R:.*]] = acc.reduction varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) name("r") -> !fir.ref<i32>
+! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>) reduction(%[[RED_R]] : !fir.ref<i32>)
+
+! Character is not a scalar type category.
+subroutine acc_parallel_loop_firstprivate_character
+ integer :: i, n
+ character(len=8) :: s
+ real :: a(10)
+ n = 10
+ s = 'xxxxxxxx'
+ !$acc parallel loop firstprivate(s)
+ do i = 1, n
+ a(i) = 1.0
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_character
+! CHECK: %[[FP_S:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.char<1,8>>) recipe({{.*}}) name("s") -> !fir.ref<!fir.char<1,8>>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_S]] : !fir.ref<!fir.char<1,8>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+
+! Derived type is composite, not scalar.
+subroutine acc_parallel_loop_firstprivate_derived
+ type t
+ integer :: x
+ end type
+ type(t) :: p
+ integer :: i, n
+ real :: a(10)
+ n = 10
+ p%x = 1
+ !$acc parallel loop firstprivate(p)
+ do i = 1, n
+ a(i) = p%x
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_derived
+! CHECK: acc.firstprivate varPtr({{.*}}) recipe({{.*}}) name("p")
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+
+! Pointer and allocatable scalars are boxed, so they stay compute-only.
+subroutine acc_parallel_loop_firstprivate_pointer
+ integer, pointer :: p
+ integer, target :: t
+ integer :: i, n
+ real :: a(10)
+ n = 10
+ t = 7
+ p => t
+ !$acc parallel loop firstprivate(p)
+ do i = 1, n
+ a(i) = p
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_pointer
+! CHECK: %[[FP_P:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<i32>>>) recipe({{.*}}) name("p") -> !fir.ref<!fir.box<!fir.ptr<i32>>>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_P]] : !fir.ref<!fir.box<!fir.ptr<i32>>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+
+subroutine acc_parallel_loop_firstprivate_allocatable
+ integer, allocatable :: v
+ integer :: i, n
+ real :: a(10)
+ n = 10
+ allocate(v)
+ v = 7
+ !$acc parallel loop firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_allocatable
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<i32>>>) recipe({{.*}}) name("v") -> !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<!fir.box<!fir.heap<i32>>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+
+subroutine acc_parallel_loop_firstprivate_section
+ integer :: i, n
+ real :: b(10)
+ n = 10
+ !$acc parallel loop firstprivate(b(1:5))
+ do i = 1, n
+ b(1) = i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_section
+! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) bounds({{.*}}) recipe({{.*}}) name("b(1:5)") -> !fir.ref<!fir.array<10xf32>>
+! CHECK: acc.parallel combined(loop) {{.*}}firstprivate(%[[FP_B]] : !fir.ref<!fir.array<10xf32>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(parallel)
+
+! Non-combined parallel with a single loop is left to a later pass.
+subroutine acc_parallel_firstprivate_one_loop
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel firstprivate(v)
+ !$acc loop
+ do i = 1, n
+ a(i) = v
+ end do
+ !$acc end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_firstprivate_one_loop
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.parallel {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
diff --git a/flang/test/Lower/OpenACC/acc-private.f90 b/flang/test/Lower/OpenACC/acc-private.f90
index aa5ebc5e3a5d2..5876a4cff7b1e 100644
--- a/flang/test/Lower/OpenACC/acc-private.f90
+++ b/flang/test/Lower/OpenACC/acc-private.f90
@@ -436,6 +436,7 @@ program acc_private
! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%[[DECLB]]#0 : !fir.ref<!fir.array<100xf32>>) recipe(@firstprivatization_ref_100xf32) name("b") -> !fir.ref<!fir.array<100xf32>>
! CHECK: acc.parallel {{.*}} firstprivate(%[[FP_B]] : !fir.ref<!fir.array<100xf32>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: acc.yield
!$acc parallel loop firstprivate(b(51:100))
@@ -450,6 +451,8 @@ program acc_private
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[LB]] : index) upperbound(%[[UB]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%[[DECLB]]#0 : !fir.ref<!fir.array<100xf32>>) bounds(%[[BOUND]]) recipe(@firstprivatization_section_lb50.ub99_ref_100xf32) name("b(51:100)") -> !fir.ref<!fir.array<100xf32>>
! CHECK: acc.parallel {{.*}} firstprivate(%[[FP_B]] : !fir.ref<!fir.array<100xf32>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
end program
@@ -563,6 +566,8 @@ subroutine acc_firstprivate_assumed_shape(a, n)
! CHECK-LABEL: func.func @_QPacc_firstprivate_assumed_shape
! CHECK: %[[FIRSTPRIVATE_A:.*]] = acc.firstprivate var(%{{.*}} : !fir.box<!fir.array<?xi32>>) recipe(@firstprivatization_box_Uxi32) name("a") -> !fir.box<!fir.array<?xi32>>
! CHECK: acc.parallel {{.*}}firstprivate(%[[FIRSTPRIVATE_A]] : !fir.box<!fir.array<?xi32>>) {
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
subroutine acc_firstprivate_assumed_shape_with_section(a, n)
integer :: a(:), i, n
@@ -576,6 +581,8 @@ subroutine acc_firstprivate_assumed_shape_with_section(a, n)
! CHECK-LABEL: func.func @_QPacc_firstprivate_assumed_shape_with_section
! CHECK: %[[FIRSTPRIVATE_A:.*]] = acc.firstprivate var(%{{.*}} : !fir.box<!fir.array<?xi32>>) bounds(%{{.*}}) recipe(@firstprivatization_section_lb4.ub9_box_Uxi32) name("a(5:10)") -> !fir.box<!fir.array<?xi32>>
! CHECK: acc.parallel {{.*}}firstprivate(%[[FIRSTPRIVATE_A]] : !fir.box<!fir.array<?xi32>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
subroutine acc_firstprivate_dynamic_extent(a, n)
integer :: n, i
@@ -590,6 +597,8 @@ subroutine acc_firstprivate_dynamic_extent(a, n)
! CHECK-LABEL: func.func @_QPacc_firstprivate_dynamic_extent
! CHECK: %[[FIRSTPRIVATE_A:.*]] = acc.firstprivate var(%{{.*}} : !fir.box<!fir.array<?x?x2xi32>>) recipe(@firstprivatization_box_UxUx2xi32) name("a") -> !fir.box<!fir.array<?x?x2xi32>>
! CHECK: acc.parallel {{.*}}firstprivate(%[[FIRSTPRIVATE_A]] : !fir.box<!fir.array<?x?x2xi32>>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop
module acc_declare_equivalent
integer, parameter :: n = 10
diff --git a/flang/test/Lower/OpenACC/acc-serial-loop.f90 b/flang/test/Lower/OpenACC/acc-serial-loop.f90
index 703cfe79ee28e..2a2054f6fb805 100644
--- a/flang/test/Lower/OpenACC/acc-serial-loop.f90
+++ b/flang/test/Lower/OpenACC/acc-serial-loop.f90
@@ -620,3 +620,22 @@ subroutine acc_serial_loop_firstprivate_scalar
! CHECK: acc.serial combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: acc.loop combined(serial)
+
+! serial loop is not combined parallel, even with independent.
+subroutine acc_serial_loop_firstprivate_independent
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc serial loop independent firstprivate(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_serial_loop_firstprivate_independent
+! CHECK: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") -> !fir.ref<i32>
+! CHECK: acc.serial combined(loop) {{.*}}firstprivate(%[[FP_V]] : !fir.ref<i32>)
+! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
+! CHECK: acc.loop combined(serial)
+! CHECK: } inclusiveUpperbound(array<i1: true>) independent
>From d679b1662b5498a786cc2cd2ae1263cb6312e1b9 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 10 Sep 2026 14:13:45 -0700
Subject: [PATCH 3/6] tweak
---
flang/test/Lower/OpenACC/acc-parallel-loop.f90 | 1 -
1 file changed, 1 deletion(-)
diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
index 8c98e17628b48..cd3717bffccb9 100644
--- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90
+++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
@@ -453,7 +453,6 @@ subroutine acc_parallel_loop
! CHECK: %[[ACC_PRIVATE_B:.*]] = acc.firstprivate varPtr(%[[DECLB]]#0 : !fir.ref<!fir.array<10xf32>>) recipe(@firstprivatization_ref_10xf32) name("b") -> !fir.ref<!fir.array<10xf32>>
! CHECK: acc.parallel {{.*}} firstprivate(%[[ACC_PRIVATE_B]] : !fir.ref<!fir.array<10xf32>>) {
-! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: %[[ACC_PRIVATE_A:.*]] = acc.private varPtr(%[[DECLA]]#0 : !fir.ref<!fir.array<10xf32>>) recipe(@privatization_ref_10xf32) name("a") -> !fir.ref<!fir.array<10xf32>>
! CHECK: acc.loop {{.*}} private(%[[ACC_PRIVATE_A]]{{.*}} : !fir.ref<!fir.array<10xf32>>{{.*}})
! CHECK-NOT: fir.do_loop
>From 14768db9df03ad2c6b22c66c67cbbfba69af7b10 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 10 Sep 2026 14:15:43 -0700
Subject: [PATCH 4/6] tweak
---
flang/test/Lower/OpenACC/acc-private.f90 | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/flang/test/Lower/OpenACC/acc-private.f90 b/flang/test/Lower/OpenACC/acc-private.f90
index 5876a4cff7b1e..aa5ebc5e3a5d2 100644
--- a/flang/test/Lower/OpenACC/acc-private.f90
+++ b/flang/test/Lower/OpenACC/acc-private.f90
@@ -436,7 +436,6 @@ program acc_private
! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%[[DECLB]]#0 : !fir.ref<!fir.array<100xf32>>) recipe(@firstprivatization_ref_100xf32) name("b") -> !fir.ref<!fir.array<100xf32>>
! CHECK: acc.parallel {{.*}} firstprivate(%[[FP_B]] : !fir.ref<!fir.array<100xf32>>)
-! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: acc.yield
!$acc parallel loop firstprivate(b(51:100))
@@ -451,8 +450,6 @@ program acc_private
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[LB]] : index) upperbound(%[[UB]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%[[DECLB]]#0 : !fir.ref<!fir.array<100xf32>>) bounds(%[[BOUND]]) recipe(@firstprivatization_section_lb50.ub99_ref_100xf32) name("b(51:100)") -> !fir.ref<!fir.array<100xf32>>
! CHECK: acc.parallel {{.*}} firstprivate(%[[FP_B]] : !fir.ref<!fir.array<100xf32>>)
-! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
-! CHECK: acc.loop
end program
@@ -566,8 +563,6 @@ subroutine acc_firstprivate_assumed_shape(a, n)
! CHECK-LABEL: func.func @_QPacc_firstprivate_assumed_shape
! CHECK: %[[FIRSTPRIVATE_A:.*]] = acc.firstprivate var(%{{.*}} : !fir.box<!fir.array<?xi32>>) recipe(@firstprivatization_box_Uxi32) name("a") -> !fir.box<!fir.array<?xi32>>
! CHECK: acc.parallel {{.*}}firstprivate(%[[FIRSTPRIVATE_A]] : !fir.box<!fir.array<?xi32>>) {
-! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
-! CHECK: acc.loop
subroutine acc_firstprivate_assumed_shape_with_section(a, n)
integer :: a(:), i, n
@@ -581,8 +576,6 @@ subroutine acc_firstprivate_assumed_shape_with_section(a, n)
! CHECK-LABEL: func.func @_QPacc_firstprivate_assumed_shape_with_section
! CHECK: %[[FIRSTPRIVATE_A:.*]] = acc.firstprivate var(%{{.*}} : !fir.box<!fir.array<?xi32>>) bounds(%{{.*}}) recipe(@firstprivatization_section_lb4.ub9_box_Uxi32) name("a(5:10)") -> !fir.box<!fir.array<?xi32>>
! CHECK: acc.parallel {{.*}}firstprivate(%[[FIRSTPRIVATE_A]] : !fir.box<!fir.array<?xi32>>)
-! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
-! CHECK: acc.loop
subroutine acc_firstprivate_dynamic_extent(a, n)
integer :: n, i
@@ -597,8 +590,6 @@ subroutine acc_firstprivate_dynamic_extent(a, n)
! CHECK-LABEL: func.func @_QPacc_firstprivate_dynamic_extent
! CHECK: %[[FIRSTPRIVATE_A:.*]] = acc.firstprivate var(%{{.*}} : !fir.box<!fir.array<?x?x2xi32>>) recipe(@firstprivatization_box_UxUx2xi32) name("a") -> !fir.box<!fir.array<?x?x2xi32>>
! CHECK: acc.parallel {{.*}}firstprivate(%[[FIRSTPRIVATE_A]] : !fir.box<!fir.array<?x?x2xi32>>)
-! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
-! CHECK: acc.loop
module acc_declare_equivalent
integer, parameter :: n = 10
>From 6468a19366fd80859636c89da37689dc755761c2 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 10 Sep 2026 14:51:54 -0700
Subject: [PATCH 5/6] add implicit tests
---
.../test/Lower/OpenACC/acc-parallel-loop.f90 | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
index cd3717bffccb9..54f7be328dd5f 100644
--- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90
+++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90
@@ -771,6 +771,42 @@ subroutine acc_parallel_firstprivate_sibling_loops
! CHECK-NOT: acc.firstprivate {{.*}} implicit(true)
! CHECK: acc.loop
+! Scalars used without an explicit firstprivate are implicitly firstprivate
+! on the compute construct later. Do not emit a loop firstprivate here.
+subroutine acc_parallel_loop_no_explicit_firstprivate
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_no_explicit_firstprivate
+! CHECK: acc.parallel combined(loop) {
+! CHECK-NOT: acc.firstprivate
+! CHECK: acc.loop combined(parallel)
+
+! copyin is explicit but not firstprivate, so the loop still gets no copy.
+subroutine acc_parallel_loop_copyin_scalar
+ integer :: i, n, v
+ real :: a(10)
+ n = 10
+ v = 7
+ !$acc parallel loop copyin(v)
+ do i = 1, n
+ a(i) = v
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_copyin_scalar
+! CHECK: acc.copyin {{.*}} name("v")
+! CHECK: acc.parallel combined(loop) {{.*}}dataOperands
+! CHECK-NOT: acc.firstprivate
+! CHECK: acc.loop combined(parallel)
+
! Explicit independent is the same as the parallel loop default.
subroutine acc_parallel_loop_firstprivate_independent
integer :: i, n, v
>From 958856b53b334f4e626bf979052aa26b056526e3 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 10 Sep 2026 14:56:13 -0700
Subject: [PATCH 6/6] add description
---
flang/docs/OpenACC-extensions.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/flang/docs/OpenACC-extensions.md b/flang/docs/OpenACC-extensions.md
index c22f00efec758..41fa1513e33cb 100644
--- a/flang/docs/OpenACC-extensions.md
+++ b/flang/docs/OpenACC-extensions.md
@@ -57,6 +57,18 @@ These extensions require no flag.
this use (`-Wportability`, also enabled by `-pedantic`; suppress with
`-Wno-portability`).
+### Combined `parallel loop` firstprivate
+
+`firstprivate` is a compute-construct clause, not a `loop` clause. On a
+combined `parallel loop`, Flang keeps the explicit clause on the compute
+construct and also attaches an implicit `firstprivate` on the associated
+`acc.loop` so independent iterations do not share that copy.
+
+This applies only to OpenACC scalars on an independent combined
+`parallel loop`. It is not applied for `serial loop`, `kernels loop`,
+standalone `acc loop`, `seq`/`auto`, non-scalars, nested or collapsed inner
+loops, or scalars that are only implicitly firstprivate.
+
## Extensions enabled by default
### `-fopenacc-multiple-names-in-routine` — `!$acc routine(<name>[, <name>]*) <clause-list>`
More information about the flang-commits
mailing list