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 <[email protected]> Date: Thu, 10 Sep 2026 13:51:15 -0700 Subject: [PATCH 01/13] 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 <[email protected]> Date: Thu, 10 Sep 2026 14:03:43 -0700 Subject: [PATCH 02/13] 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 <[email protected]> Date: Thu, 10 Sep 2026 14:13:45 -0700 Subject: [PATCH 03/13] 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 <[email protected]> Date: Thu, 10 Sep 2026 14:15:43 -0700 Subject: [PATCH 04/13] 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 <[email protected]> Date: Thu, 10 Sep 2026 14:51:54 -0700 Subject: [PATCH 05/13] 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 <[email protected]> Date: Thu, 10 Sep 2026 14:56:13 -0700 Subject: [PATCH 06/13] 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>` >From c95375574457ec63821d0fb37ee547b768fb35d3 Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 09:13:39 -0700 Subject: [PATCH 07/13] add option --- clang/include/clang/Options/FlangOptions.td | 3 + flang/docs/OpenACC-extensions.md | 25 ++++---- flang/include/flang/Lower/LoweringOptions.def | 5 ++ flang/lib/Frontend/CompilerInvocation.cpp | 6 ++ flang/lib/Lower/OpenACC.cpp | 60 ++++++++----------- ...tprivate-derived-allocatable-component.f90 | 6 +- .../acc-firstprivate-derived-user-assign.f90 | 6 +- .../OpenACC/acc-firstprivate-derived.f90 | 6 +- .../test/Lower/OpenACC/acc-parallel-loop.f90 | 43 +++++++------ flang/test/Lower/OpenACC/acc-serial-loop.f90 | 13 ++-- 10 files changed, 94 insertions(+), 79 deletions(-) diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td index 8fd0d78a2659c..97c5cb3dbf007 100644 --- a/clang/include/clang/Options/FlangOptions.td +++ b/clang/include/clang/Options/FlangOptions.td @@ -199,6 +199,9 @@ defm openacc_default_none_scalars_strict : OptOutFC1FFlag<"openacc-default-none- defm openacc_multiple_names_in_routine : OptOutFC1FFlag<"openacc-multiple-names-in-routine", "Accept multiple names in OpenACC ROUTINE directive (extension)", "Do not accept multiple names in OpenACC ROUTINE directive">; +defm openacc_combined_loop_firstprivate : OptOutFC1FFlag<"openacc-combined-loop-firstprivate", + "Attach firstprivate on combined loop in addition to compute construct (extension)", + "Keep firstprivate only on the compute construct for combined loop">; defm prefer_intrinsic_module_use_association : OptOutFC1FFlag<"prefer-intrinsic-module-use-association", "Resolve a USE association conflict in favor of an intrinsic module generic (extension)", "Diagnose a USE association conflict with an intrinsic module generic">; diff --git a/flang/docs/OpenACC-extensions.md b/flang/docs/OpenACC-extensions.md index 41fa1513e33cb..9816db6dbc464 100644 --- a/flang/docs/OpenACC-extensions.md +++ b/flang/docs/OpenACC-extensions.md @@ -57,19 +57,24 @@ These extensions require no flag. this use (`-Wportability`, also enabled by `-pedantic`; suppress with `-Wno-portability`). -### Combined `parallel loop` firstprivate +## Extensions enabled by default -`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. +### `-fopenacc-combined-loop-firstprivate` — combined loop firstprivate -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. +`firstprivate` is a compute-construct clause, not a `loop` clause. On a +combined `parallel loop` or `serial loop`, Flang keeps the explicit clause on +the compute construct and also attaches an implicit `firstprivate` on the +associated `acc.loop` so each thread gets its own initialized copy. -## Extensions enabled by default +This applies to all types (scalars, arrays, derived types, etc.) and all +parallelism modes (`independent`, `seq`, `auto`), consistent with how `private` +and `reduction` are handled on combined constructs. `kernels loop` is not +affected because `kernels` cannot take `firstprivate`. Standalone `acc loop` +and non-combined `parallel` / `serial` with separate inner `acc loop` are not +affected. + +Disable with `-fno-openacc-combined-loop-firstprivate` to keep firstprivate +only on the compute construct (spec behavior). ### `-fopenacc-multiple-names-in-routine` — `!$acc routine(<name>[, <name>]*) <clause-list>` diff --git a/flang/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def index 61ccb2ac19bdd..2931a5edc56e9 100644 --- a/flang/include/flang/Lower/LoweringOptions.def +++ b/flang/include/flang/Lower/LoweringOptions.def @@ -97,5 +97,10 @@ ENUM_LOWERINGOPT(FPMaxminBehavior, Fortran::common::FPMaxminBehavior, 2, 0) /// 0 means no trapping. Bit values match IEEE_FLAG_TYPE encoding. ENUM_LOWERINGOPT(FPExceptionTraps, unsigned, 8, 0) +/// If true (default), attach an implicit firstprivate on the loop of a +/// combined construct (parallel loop, serial loop) in addition to the compute +/// construct. Controlled by -f[no-]openacc-combined-loop-firstprivate. +ENUM_LOWERINGOPT(OpenACCCombinedLoopFirstprivate, unsigned, 1, 1) + #undef LOWERINGOPT #undef ENUM_LOWERINGOPT diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index b76b38ff6497f..7189d9f06cd4a 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -1823,6 +1823,12 @@ bool CompilerInvocation::createFromArgs( invoc.loweringOpts.setRepackArraysWhole(arg->getValue() == llvm::StringRef{"whole"}); + // -f[no-]openacc-combined-loop-firstprivate + invoc.loweringOpts.setOpenACCCombinedLoopFirstprivate(args.hasFlag( + clang::options::OPT_fopenacc_combined_loop_firstprivate, + clang::options::OPT_fno_openacc_combined_loop_firstprivate, + /*default=*/true)); + if (auto *arg = args.getLastArg(clang::options::OPT_ffp_maxmin_behavior_EQ)) { auto value = Fortran::common::parseFPMaxminBehavior(arg->getValue()); invoc.getCodeGenOpts().setFPMaxminBehavior(value); diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp index fdca3c6f182df..5c23837d03471 100644 --- a/flang/lib/Lower/OpenACC.cpp +++ b/flang/lib/Lower/OpenACC.cpp @@ -1723,29 +1723,22 @@ 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. +// Attach an implicit firstprivate on a combined loop in addition to the +// compute clause. On a combined construct, private and reduction already +// apply to both the compute region and the loop; firstprivate should too. +// Controlled by -f[no-]openacc-combined-loop-firstprivate (default on). +// Applies to parallel loop and serial loop (kernels cannot take +// firstprivate), all types (scalars, arrays, derived, etc.), and all +// parallelism modes (independent, seq, auto). 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) + std::optional<mlir::acc::CombinedConstructsType> combinedConstructs) { + if (!combinedConstructs) return false; - if (!loopWillBeIndependent(converter, accClauseList, - llvm::acc::ACCD_parallel_loop)) + if (*combinedConstructs != mlir::acc::CombinedConstructsType::ParallelLoop && + *combinedConstructs != mlir::acc::CombinedConstructsType::SerialLoop) return false; - mlir::Value var = - converter.getSymbolAddress(getSymbolFromAccObject(accObject)); - return var && - mlir::acc::bitEnumContainsAny(mlir::acc::getTypeCategory(var), - mlir::acc::VariableTypeCategory::scalar); + return converter.getLoweringOptions().getOpenACCCombinedLoopFirstprivate(); } // Helper to visit Bounds of DO LOOP nest. @@ -2528,21 +2521,20 @@ static mlir::acc::LoopOp createLoopOp( } 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); - }); + // Duplicate firstprivate onto this combined 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. + if (shouldAttachFirstprivateOnCombinedLoop(converter, + combinedConstructs)) { + 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); + } } else if (const auto *reductionClause = std::get_if<Fortran::parser::AccClause::Reduction>( &clause.u)) { diff --git a/flang/test/Lower/OpenACC/acc-firstprivate-derived-allocatable-component.f90 b/flang/test/Lower/OpenACC/acc-firstprivate-derived-allocatable-component.f90 index 7a71981d1c915..3403ce5cc25af 100644 --- a/flang/test/Lower/OpenACC/acc-firstprivate-derived-allocatable-component.f90 +++ b/flang/test/Lower/OpenACC/acc-firstprivate-derived-allocatable-component.f90 @@ -51,12 +51,14 @@ subroutine test(a) ! CHECK: %[[VAL_9:.*]] = arith.constant 1 : i32 ! CHECK: %[[VAL_10:.*]] = fir.load %[[VAL_5]]#0 : !fir.ref<i32> ! CHECK: %[[VAL_11:.*]] = arith.constant 1 : i32 +! CHECK: %[[FP_A_LOOP:.*]] = acc.firstprivate varPtr({{.*}}) recipe(@firstprivatization_ref_rec__QMm_firstprivate_derived_alloc_compTpoint) implicit(true) name("a") ! CHECK: %[[VAL_12:.*]] = acc.private varPtr(%[[VAL_3]]#0 : !fir.ref<i32>) recipe(@privatization_ref_i32) implicit(true) name("i") -> !fir.ref<i32> -! CHECK: acc.loop combined(parallel) private(%[[VAL_12]] : !fir.ref<i32>) control(%[[VAL_14:.*]] : i32) = (%[[VAL_9]] : i32) to (%[[VAL_10]] : i32) step (%[[VAL_11]] : i32) { +! CHECK: acc.loop combined(parallel) firstprivate(%[[FP_A_LOOP]] : {{.*}}) private(%[[VAL_12]] : !fir.ref<i32>) control(%[[VAL_14:.*]] : i32) = (%[[VAL_9]] : i32) to (%[[VAL_10]] : i32) step (%[[VAL_11]] : i32) { +! CHECK: %[[VAL_8B:.*]]:2 = hlfir.declare %[[FP_A_LOOP]] ! CHECK: %[[VAL_13:.*]]:2 = hlfir.declare %[[VAL_12]] {uniq_name = "_QMm_firstprivate_derived_alloc_compFtestEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>) ! CHECK: fir.store %[[VAL_14]] to %[[VAL_13]]#0 : !fir.ref<i32> ! CHECK: %[[VAL_15:.*]] = arith.constant 1.000000e+00 : f32 -! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_8]]#0{"x"} {fortran_attrs = #fir.var_attrs<allocatable>} : (!fir.ref<!fir.type<_QMm_firstprivate_derived_alloc_compTpoint{x:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>> +! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_8B]]#0{"x"} {fortran_attrs = #fir.var_attrs<allocatable>} : (!fir.ref<!fir.type<_QMm_firstprivate_derived_alloc_compTpoint{x:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>> ! CHECK: %[[VAL_17:.*]] = fir.load %[[VAL_16]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>> ! CHECK: %[[VAL_18:.*]] = arith.constant 10 : index ! CHECK: %[[VAL_19:.*]] = hlfir.designate %[[VAL_17]] (%[[VAL_18]]) : (!fir.box<!fir.heap<!fir.array<?xf32>>>, index) -> !fir.ref<f32> diff --git a/flang/test/Lower/OpenACC/acc-firstprivate-derived-user-assign.f90 b/flang/test/Lower/OpenACC/acc-firstprivate-derived-user-assign.f90 index f92c2a5ad680a..edf7db534baa9 100644 --- a/flang/test/Lower/OpenACC/acc-firstprivate-derived-user-assign.f90 +++ b/flang/test/Lower/OpenACC/acc-firstprivate-derived-user-assign.f90 @@ -55,15 +55,17 @@ subroutine test() ! CHECK: %[[VAL_7:.*]] = acc.firstprivate varPtr(%[[VAL_2]]#0 : !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) recipe(@firstprivatization_ref_rec__QMm_firstprivate_derived_user_defTpoint) name("a") -> !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>> ! CHECK: acc.parallel combined(loop) firstprivate(%[[VAL_7]] : !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) { ! CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_7]] {uniq_name = "_QMm_firstprivate_derived_user_defFtestEa"} : (!fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) -> (!fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>, !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) +! CHECK: %[[FP_A_LOOP:.*]] = acc.firstprivate varPtr(%[[VAL_8]]#0 : !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) recipe(@firstprivatization_ref_rec__QMm_firstprivate_derived_user_defTpoint) implicit(true) name("a") -> !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>> ! CHECK: %[[VAL_9:.*]] = arith.constant 1 : i32 ! CHECK: %[[VAL_10:.*]] = fir.load %[[VAL_6]]#0 : !fir.ref<i32> ! CHECK: %[[VAL_11:.*]] = arith.constant 1 : i32 ! CHECK: %[[VAL_12:.*]] = acc.private varPtr(%[[VAL_4]]#0 : !fir.ref<i32>) recipe(@privatization_ref_i32) implicit(true) name("i") -> !fir.ref<i32> -! CHECK: acc.loop combined(parallel) private(%[[VAL_12]] : !fir.ref<i32>) control(%[[VAL_14:.*]] : i32) = (%[[VAL_9]] : i32) to (%[[VAL_10]] : i32) step (%[[VAL_11]] : i32) { +! CHECK: acc.loop combined(parallel) private(%[[VAL_12]] : !fir.ref<i32>) firstprivate(%[[FP_A_LOOP]] : !fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) control(%[[VAL_14:.*]] : i32) = (%[[VAL_9]] : i32) to (%[[VAL_10]] : i32) step (%[[VAL_11]] : i32) { +! CHECK: %[[VAL_8B:.*]]:2 = hlfir.declare %[[FP_A_LOOP]] {uniq_name = "_QMm_firstprivate_derived_user_defFtestEa"} ! CHECK: %[[VAL_13:.*]]:2 = hlfir.declare %[[VAL_12]] {uniq_name = "_QMm_firstprivate_derived_user_defFtestEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>) ! CHECK: fir.store %[[VAL_14]] to %[[VAL_13]]#0 : !fir.ref<i32> ! CHECK: %[[VAL_15:.*]] = arith.constant 1.000000e+00 : f32 -! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_8]]#0{"x"} : (!fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) -> !fir.ref<f32> +! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_8B]]#0{"x"} : (!fir.ref<!fir.type<_QMm_firstprivate_derived_user_defTpoint{x:f32,y:f32,z:f32}>>) -> !fir.ref<f32> ! CHECK: hlfir.assign %[[VAL_15]] to %[[VAL_16]] : f32, !fir.ref<f32> ! CHECK: acc.yield ! CHECK: } inclusiveUpperbound(array<i1: true>) independent diff --git a/flang/test/Lower/OpenACC/acc-firstprivate-derived.f90 b/flang/test/Lower/OpenACC/acc-firstprivate-derived.f90 index ffc24a07e757c..8aa8b3156f3b1 100644 --- a/flang/test/Lower/OpenACC/acc-firstprivate-derived.f90 +++ b/flang/test/Lower/OpenACC/acc-firstprivate-derived.f90 @@ -41,15 +41,17 @@ subroutine test() ! CHECK: %[[VAL_7:.*]] = acc.firstprivate varPtr(%[[VAL_2]]#0 : !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) recipe(@firstprivatization_ref_rec__QMm_firstprivate_derivedTpoint) name("a") -> !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>> ! CHECK: acc.parallel combined(loop) firstprivate(%[[VAL_7]] : !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) { ! CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_7]] {uniq_name = "_QMm_firstprivate_derivedFtestEa"} : (!fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) -> (!fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>, !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) +! CHECK: %[[FP_A_LOOP:.*]] = acc.firstprivate varPtr(%[[VAL_8]]#0 : !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) recipe(@firstprivatization_ref_rec__QMm_firstprivate_derivedTpoint) implicit(true) name("a") -> !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>> ! CHECK: %[[VAL_9:.*]] = arith.constant 1 : i32 ! CHECK: %[[VAL_10:.*]] = fir.load %[[VAL_6]]#0 : !fir.ref<i32> ! CHECK: %[[VAL_11:.*]] = arith.constant 1 : i32 ! CHECK: %[[VAL_12:.*]] = acc.private varPtr(%[[VAL_4]]#0 : !fir.ref<i32>) recipe(@privatization_ref_i32) implicit(true) name("i") -> !fir.ref<i32> -! CHECK: acc.loop combined(parallel) private(%[[VAL_12]] : !fir.ref<i32>) control(%[[VAL_14:.*]] : i32) = (%[[VAL_9]] : i32) to (%[[VAL_10]] : i32) step (%[[VAL_11]] : i32) { +! CHECK: acc.loop combined(parallel) private(%[[VAL_12]] : !fir.ref<i32>) firstprivate(%[[FP_A_LOOP]] : !fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) control(%[[VAL_14:.*]] : i32) = (%[[VAL_9]] : i32) to (%[[VAL_10]] : i32) step (%[[VAL_11]] : i32) { +! CHECK: %[[VAL_8B:.*]]:2 = hlfir.declare %[[FP_A_LOOP]] {uniq_name = "_QMm_firstprivate_derivedFtestEa"} ! CHECK: %[[VAL_13:.*]]:2 = hlfir.declare %[[VAL_12]] {uniq_name = "_QMm_firstprivate_derivedFtestEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>) ! CHECK: fir.store %[[VAL_14]] to %[[VAL_13]]#0 : !fir.ref<i32> ! CHECK: %[[VAL_15:.*]] = arith.constant 1.000000e+00 : f32 -! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_8]]#0{"x"} : (!fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) -> !fir.ref<f32> +! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_8B]]#0{"x"} : (!fir.ref<!fir.type<_QMm_firstprivate_derivedTpoint{x:f32,y:f32,z:f32}>>) -> !fir.ref<f32> ! CHECK: hlfir.assign %[[VAL_15]] to %[[VAL_16]] : f32, !fir.ref<f32> ! CHECK: acc.yield ! CHECK: } inclusiveUpperbound(array<i1: true>) independent diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 index 54f7be328dd5f..5ed965bf802d5 100644 --- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 +++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 @@ -672,9 +672,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, sections, derived/character/boxed types, seq -! and auto loops, and non-combined loops are not. +! Combined parallel loop firstprivate: copies are also attached on the +! acc.loop, consistent with private and reduction on combined constructs. subroutine acc_parallel_loop_firstprivate_scalar integer :: i, n, v real :: a(10) @@ -707,8 +706,8 @@ subroutine acc_parallel_loop_firstprivate_seq ! 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: %[[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>) seq subroutine acc_parallel_loop_firstprivate_auto @@ -725,8 +724,8 @@ subroutine acc_parallel_loop_firstprivate_auto ! 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: %[[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>) auto_ subroutine acc_parallel_loop_firstprivate_mixed @@ -745,8 +744,8 @@ subroutine acc_parallel_loop_firstprivate_mixed ! 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>) +! CHECK: %[[FP_B_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.array<10xf32>>) recipe({{.*}}) implicit(true) name("b") -> !fir.ref<!fir.array<10xf32>> +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]], %[[FP_B_LOOP]] : !fir.ref<i32>, !fir.ref<!fir.array<10xf32>>) subroutine acc_parallel_firstprivate_sibling_loops integer :: i, j, n, v @@ -970,7 +969,7 @@ subroutine acc_parallel_loop_firstprivate_with_reduction ! 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. +! Character gets loop firstprivate too. subroutine acc_parallel_loop_firstprivate_character integer :: i, n character(len=8) :: s @@ -986,10 +985,10 @@ subroutine acc_parallel_loop_firstprivate_character ! 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) +! CHECK: %[[FP_S_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.char<1,8>>) recipe({{.*}}) implicit(true) name("s") -> !fir.ref<!fir.char<1,8>> +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_S_LOOP]] : !fir.ref<!fir.char<1,8>>) -! Derived type is composite, not scalar. +! Derived type gets loop firstprivate too. subroutine acc_parallel_loop_firstprivate_derived type t integer :: x @@ -1008,10 +1007,10 @@ subroutine acc_parallel_loop_firstprivate_derived ! 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) +! CHECK: acc.firstprivate varPtr({{.*}}) recipe({{.*}}) implicit(true) name("p") +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate -! Pointer and allocatable scalars are boxed, so they stay compute-only. +! Pointer and allocatable get loop firstprivate too. subroutine acc_parallel_loop_firstprivate_pointer integer, pointer :: p integer, target :: t @@ -1029,8 +1028,8 @@ subroutine acc_parallel_loop_firstprivate_pointer ! 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) +! CHECK: %[[FP_P_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.box<!fir.ptr<i32>>>) recipe({{.*}}) implicit(true) name("p") -> !fir.ref<!fir.box<!fir.ptr<i32>>> +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_P_LOOP]] : !fir.ref<!fir.box<!fir.ptr<i32>>>) subroutine acc_parallel_loop_firstprivate_allocatable integer, allocatable :: v @@ -1048,8 +1047,8 @@ subroutine acc_parallel_loop_firstprivate_allocatable ! 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) +! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.box<!fir.heap<i32>>>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<!fir.box<!fir.heap<i32>>> +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<!fir.box<!fir.heap<i32>>>) subroutine acc_parallel_loop_firstprivate_section integer :: i, n @@ -1064,8 +1063,8 @@ subroutine acc_parallel_loop_firstprivate_section ! 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) +! CHECK: %[[FP_B_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.array<10xf32>>) bounds({{.*}}) recipe({{.*}}) implicit(true) name("b(1:5)") -> !fir.ref<!fir.array<10xf32>> +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_B_LOOP]] : !fir.ref<!fir.array<10xf32>>) ! Non-combined parallel with a single loop is left to a later pass. subroutine acc_parallel_firstprivate_one_loop diff --git a/flang/test/Lower/OpenACC/acc-serial-loop.f90 b/flang/test/Lower/OpenACC/acc-serial-loop.f90 index 2a2054f6fb805..52db45dbd2dfd 100644 --- a/flang/test/Lower/OpenACC/acc-serial-loop.f90 +++ b/flang/test/Lower/OpenACC/acc-serial-loop.f90 @@ -602,8 +602,7 @@ subroutine acc_serial_loop end subroutine acc_serial_loop -! serial loop defaults to seq, so scalar firstprivate stays on the compute -! construct only. +! serial loop now gets loop firstprivate too (consistent with private/reduction). subroutine acc_serial_loop_firstprivate_scalar integer :: i, n, v real :: a(10) @@ -618,10 +617,10 @@ subroutine acc_serial_loop_firstprivate_scalar ! 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) +! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32> +! CHECK: acc.loop combined(serial) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>) -! serial loop is not combined parallel, even with independent. +! serial loop with independent also gets loop firstprivate. subroutine acc_serial_loop_firstprivate_independent integer :: i, n, v real :: a(10) @@ -636,6 +635,6 @@ subroutine acc_serial_loop_firstprivate_independent ! 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: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32> +! CHECK: acc.loop combined(serial) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>) ! CHECK: } inclusiveUpperbound(array<i1: true>) independent >From 4bf1d69aff7b4738c334f07f275b9ebb5e2a1580 Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 09:18:51 -0700 Subject: [PATCH 08/13] format --- flang/lib/Frontend/CompilerInvocation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index 7189d9f06cd4a..36732cd7fadcc 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -1824,10 +1824,10 @@ bool CompilerInvocation::createFromArgs( llvm::StringRef{"whole"}); // -f[no-]openacc-combined-loop-firstprivate - invoc.loweringOpts.setOpenACCCombinedLoopFirstprivate(args.hasFlag( - clang::options::OPT_fopenacc_combined_loop_firstprivate, - clang::options::OPT_fno_openacc_combined_loop_firstprivate, - /*default=*/true)); + invoc.loweringOpts.setOpenACCCombinedLoopFirstprivate( + args.hasFlag(clang::options::OPT_fopenacc_combined_loop_firstprivate, + clang::options::OPT_fno_openacc_combined_loop_firstprivate, + /*default=*/true)); if (auto *arg = args.getLastArg(clang::options::OPT_ffp_maxmin_behavior_EQ)) { auto value = Fortran::common::parseFPMaxminBehavior(arg->getValue()); >From b195d7f807b0b332fdf3fe76b1908ccd0641ac18 Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 09:22:03 -0700 Subject: [PATCH 09/13] add more tests --- .../test/Lower/OpenACC/acc-parallel-loop.f90 | 33 +++++++++++++++++ flang/test/Lower/OpenACC/acc-serial-loop.f90 | 36 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 index 5ed965bf802d5..34c5bac67e554 100644 --- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 +++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 @@ -728,6 +728,23 @@ subroutine acc_parallel_loop_firstprivate_auto ! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>) ! CHECK: } inclusiveUpperbound(array<i1: true>) auto_ +! Not only scalars: array gets loop firstprivate too. +subroutine acc_parallel_loop_firstprivate_array + integer :: i, n + real :: b(10) + n = 10 + !$acc parallel loop firstprivate(b) + do i = 1, n + b(i) = 1.0 + end do +end subroutine + +! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_array +! 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_B]] : !fir.ref<!fir.array<10xf32>>) +! CHECK: %[[FP_B_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.array<10xf32>>) recipe({{.*}}) implicit(true) name("b") -> !fir.ref<!fir.array<10xf32>> +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate(%[[FP_B_LOOP]] : !fir.ref<!fir.array<10xf32>>) + subroutine acc_parallel_loop_firstprivate_mixed integer :: i, n, v real :: b(10) @@ -951,6 +968,22 @@ subroutine acc_parallel_loop_firstprivate_dummy(v) ! 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>) +! Not only scalars: assumed-shape array dummy (boxed array). +subroutine acc_parallel_loop_firstprivate_assumed_shape(b) + real :: b(:) + integer :: i + !$acc parallel loop firstprivate(b) + do i = 1, size(b) + b(i) = 1.0 + end do +end subroutine + +! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_assumed_shape +! CHECK: acc.firstprivate varPtr({{.*}}) recipe({{.*}}) name("b") +! CHECK: acc.parallel combined(loop) {{.*}}firstprivate +! CHECK: acc.firstprivate varPtr({{.*}}) recipe({{.*}}) implicit(true) name("b") +! CHECK: acc.loop combined(parallel) {{.*}}firstprivate + subroutine acc_parallel_loop_firstprivate_with_reduction integer :: i, n, v, r n = 10 diff --git a/flang/test/Lower/OpenACC/acc-serial-loop.f90 b/flang/test/Lower/OpenACC/acc-serial-loop.f90 index 52db45dbd2dfd..fa6672762b6ee 100644 --- a/flang/test/Lower/OpenACC/acc-serial-loop.f90 +++ b/flang/test/Lower/OpenACC/acc-serial-loop.f90 @@ -638,3 +638,39 @@ subroutine acc_serial_loop_firstprivate_independent ! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32> ! CHECK: acc.loop combined(serial) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>) ! CHECK: } inclusiveUpperbound(array<i1: true>) independent + +! Not only scalars on serial loop: array gets loop firstprivate. +subroutine acc_serial_loop_firstprivate_array + integer :: i, n + real :: b(10) + n = 10 + !$acc serial loop firstprivate(b) + do i = 1, n + b(i) = 1.0 + end do +end subroutine + +! CHECK-LABEL: func.func @_QPacc_serial_loop_firstprivate_array +! CHECK: %[[FP_B:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) recipe({{.*}}) name("b") -> !fir.ref<!fir.array<10xf32>> +! CHECK: acc.serial combined(loop) {{.*}}firstprivate(%[[FP_B]] : !fir.ref<!fir.array<10xf32>>) +! CHECK: %[[FP_B_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<!fir.array<10xf32>>) recipe({{.*}}) implicit(true) name("b") -> !fir.ref<!fir.array<10xf32>> +! CHECK: acc.loop combined(serial) {{.*}}firstprivate(%[[FP_B_LOOP]] : !fir.ref<!fir.array<10xf32>>) + +! Not only independent on serial loop: seq still gets loop firstprivate. +subroutine acc_serial_loop_firstprivate_seq + integer :: i, n, v + real :: a(10) + n = 10 + v = 7 + !$acc serial loop seq firstprivate(v) + do i = 1, n + a(i) = v + end do +end subroutine + +! CHECK-LABEL: func.func @_QPacc_serial_loop_firstprivate_seq +! 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: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr({{.*}} : !fir.ref<i32>) recipe({{.*}}) implicit(true) name("v") -> !fir.ref<i32> +! CHECK: acc.loop combined(serial) {{.*}}firstprivate(%[[FP_V_LOOP]] : !fir.ref<i32>) +! CHECK: } inclusiveUpperbound(array<i1: true>) seq >From daccaa211a0c1d4d9819fb6d236e85eb75a3ff6c Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 09:24:36 -0700 Subject: [PATCH 10/13] format --- flang/test/Lower/OpenACC/acc-parallel-loop.f90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 index 34c5bac67e554..63ec22da62fd0 100644 --- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 +++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 @@ -979,9 +979,9 @@ subroutine acc_parallel_loop_firstprivate_assumed_shape(b) end subroutine ! CHECK-LABEL: func.func @_QPacc_parallel_loop_firstprivate_assumed_shape -! CHECK: acc.firstprivate varPtr({{.*}}) recipe({{.*}}) name("b") +! CHECK: acc.firstprivate var({{.*}}) recipe({{.*}}) name("b") ! CHECK: acc.parallel combined(loop) {{.*}}firstprivate -! CHECK: acc.firstprivate varPtr({{.*}}) recipe({{.*}}) implicit(true) name("b") +! CHECK: acc.firstprivate var({{.*}}) recipe({{.*}}) implicit(true) name("b") ! CHECK: acc.loop combined(parallel) {{.*}}firstprivate subroutine acc_parallel_loop_firstprivate_with_reduction >From 79275943b2800e80ed104a1fc178a67c175e4e95 Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 09:31:19 -0700 Subject: [PATCH 11/13] tweak --- flang/include/flang/Lower/LoweringOptions.def | 5 ----- flang/include/flang/Support/Fortran-features.h | 5 +++-- flang/lib/Frontend/CompilerInvocation.cpp | 13 +++++++------ flang/lib/Lower/OpenACC.cpp | 3 ++- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/flang/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def index 2931a5edc56e9..61ccb2ac19bdd 100644 --- a/flang/include/flang/Lower/LoweringOptions.def +++ b/flang/include/flang/Lower/LoweringOptions.def @@ -97,10 +97,5 @@ ENUM_LOWERINGOPT(FPMaxminBehavior, Fortran::common::FPMaxminBehavior, 2, 0) /// 0 means no trapping. Bit values match IEEE_FLAG_TYPE encoding. ENUM_LOWERINGOPT(FPExceptionTraps, unsigned, 8, 0) -/// If true (default), attach an implicit firstprivate on the loop of a -/// combined construct (parallel loop, serial loop) in addition to the compute -/// construct. Controlled by -f[no-]openacc-combined-loop-firstprivate. -ENUM_LOWERINGOPT(OpenACCCombinedLoopFirstprivate, unsigned, 1, 1) - #undef LOWERINGOPT #undef ENUM_LOWERINGOPT diff --git a/flang/include/flang/Support/Fortran-features.h b/flang/include/flang/Support/Fortran-features.h index 4b2ff0a227f61..6aea48b536bfd 100644 --- a/flang/include/flang/Support/Fortran-features.h +++ b/flang/include/flang/Support/Fortran-features.h @@ -61,8 +61,9 @@ ENUM_CLASS(LanguageFeature, BackslashEscapes, OldDebugLines, MultipleProgramUnitsOnSameLine, AllocatedForAssociated, OpenMPThreadprivateEquivalence, RelaxedCLocChecks, CudaPinned, OpenAccDefaultNoneScalarsStrict, OpenACCMultipleNamesInRoutine, - EnumerationType, CUDAInit, PreferIntrinsicModuleUseAssociation, - MultipleCommonBlockInit, OutOfBoundsSubscripts) + OpenACCCombinedLoopFirstprivate, EnumerationType, CUDAInit, + PreferIntrinsicModuleUseAssociation, MultipleCommonBlockInit, + OutOfBoundsSubscripts) // Portability and suspicious usage warnings ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable, diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index 36732cd7fadcc..e4f42173107ae 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -950,6 +950,13 @@ static bool parseFrontendArgs(FrontendOptions &opts, llvm::opt::ArgList &args, clang::options::OPT_fno_openacc_multiple_names_in_routine, true)); + // -f{no-}openacc-combined-loop-firstprivate + opts.features.Enable( + Fortran::common::LanguageFeature::OpenACCCombinedLoopFirstprivate, + args.hasFlag(clang::options::OPT_fopenacc_combined_loop_firstprivate, + clang::options::OPT_fno_openacc_combined_loop_firstprivate, + true)); + // -f{no-}prefer-intrinsic-module-use-association if (const auto *arg = args.getLastArg( clang::options::OPT_fprefer_intrinsic_module_use_association, @@ -1823,12 +1830,6 @@ bool CompilerInvocation::createFromArgs( invoc.loweringOpts.setRepackArraysWhole(arg->getValue() == llvm::StringRef{"whole"}); - // -f[no-]openacc-combined-loop-firstprivate - invoc.loweringOpts.setOpenACCCombinedLoopFirstprivate( - args.hasFlag(clang::options::OPT_fopenacc_combined_loop_firstprivate, - clang::options::OPT_fno_openacc_combined_loop_firstprivate, - /*default=*/true)); - if (auto *arg = args.getLastArg(clang::options::OPT_ffp_maxmin_behavior_EQ)) { auto value = Fortran::common::parseFPMaxminBehavior(arg->getValue()); invoc.getCodeGenOpts().setFPMaxminBehavior(value); diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp index 5c23837d03471..d6ccaa312dbf5 100644 --- a/flang/lib/Lower/OpenACC.cpp +++ b/flang/lib/Lower/OpenACC.cpp @@ -1738,7 +1738,8 @@ static bool shouldAttachFirstprivateOnCombinedLoop( if (*combinedConstructs != mlir::acc::CombinedConstructsType::ParallelLoop && *combinedConstructs != mlir::acc::CombinedConstructsType::SerialLoop) return false; - return converter.getLoweringOptions().getOpenACCCombinedLoopFirstprivate(); + return converter.getFoldingContext().languageFeatures().IsEnabled( + Fortran::common::LanguageFeature::OpenACCCombinedLoopFirstprivate); } // Helper to visit Bounds of DO LOOP nest. >From f0f22b92b3d6238f61645ba3604f5378b6ff09ac Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 10:13:39 -0700 Subject: [PATCH 12/13] tweak --- flang/include/flang/Lower/LoweringOptions.def | 5 +++++ flang/include/flang/Support/Fortran-features.h | 5 ++--- flang/lib/Frontend/CompilerInvocation.cpp | 13 ++++++------- flang/lib/Lower/OpenACC.cpp | 3 +-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/flang/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def index 61ccb2ac19bdd..2931a5edc56e9 100644 --- a/flang/include/flang/Lower/LoweringOptions.def +++ b/flang/include/flang/Lower/LoweringOptions.def @@ -97,5 +97,10 @@ ENUM_LOWERINGOPT(FPMaxminBehavior, Fortran::common::FPMaxminBehavior, 2, 0) /// 0 means no trapping. Bit values match IEEE_FLAG_TYPE encoding. ENUM_LOWERINGOPT(FPExceptionTraps, unsigned, 8, 0) +/// If true (default), attach an implicit firstprivate on the loop of a +/// combined construct (parallel loop, serial loop) in addition to the compute +/// construct. Controlled by -f[no-]openacc-combined-loop-firstprivate. +ENUM_LOWERINGOPT(OpenACCCombinedLoopFirstprivate, unsigned, 1, 1) + #undef LOWERINGOPT #undef ENUM_LOWERINGOPT diff --git a/flang/include/flang/Support/Fortran-features.h b/flang/include/flang/Support/Fortran-features.h index 6aea48b536bfd..4b2ff0a227f61 100644 --- a/flang/include/flang/Support/Fortran-features.h +++ b/flang/include/flang/Support/Fortran-features.h @@ -61,9 +61,8 @@ ENUM_CLASS(LanguageFeature, BackslashEscapes, OldDebugLines, MultipleProgramUnitsOnSameLine, AllocatedForAssociated, OpenMPThreadprivateEquivalence, RelaxedCLocChecks, CudaPinned, OpenAccDefaultNoneScalarsStrict, OpenACCMultipleNamesInRoutine, - OpenACCCombinedLoopFirstprivate, EnumerationType, CUDAInit, - PreferIntrinsicModuleUseAssociation, MultipleCommonBlockInit, - OutOfBoundsSubscripts) + EnumerationType, CUDAInit, PreferIntrinsicModuleUseAssociation, + MultipleCommonBlockInit, OutOfBoundsSubscripts) // Portability and suspicious usage warnings ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable, diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index e4f42173107ae..36732cd7fadcc 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -950,13 +950,6 @@ static bool parseFrontendArgs(FrontendOptions &opts, llvm::opt::ArgList &args, clang::options::OPT_fno_openacc_multiple_names_in_routine, true)); - // -f{no-}openacc-combined-loop-firstprivate - opts.features.Enable( - Fortran::common::LanguageFeature::OpenACCCombinedLoopFirstprivate, - args.hasFlag(clang::options::OPT_fopenacc_combined_loop_firstprivate, - clang::options::OPT_fno_openacc_combined_loop_firstprivate, - true)); - // -f{no-}prefer-intrinsic-module-use-association if (const auto *arg = args.getLastArg( clang::options::OPT_fprefer_intrinsic_module_use_association, @@ -1830,6 +1823,12 @@ bool CompilerInvocation::createFromArgs( invoc.loweringOpts.setRepackArraysWhole(arg->getValue() == llvm::StringRef{"whole"}); + // -f[no-]openacc-combined-loop-firstprivate + invoc.loweringOpts.setOpenACCCombinedLoopFirstprivate( + args.hasFlag(clang::options::OPT_fopenacc_combined_loop_firstprivate, + clang::options::OPT_fno_openacc_combined_loop_firstprivate, + /*default=*/true)); + if (auto *arg = args.getLastArg(clang::options::OPT_ffp_maxmin_behavior_EQ)) { auto value = Fortran::common::parseFPMaxminBehavior(arg->getValue()); invoc.getCodeGenOpts().setFPMaxminBehavior(value); diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp index d6ccaa312dbf5..5c23837d03471 100644 --- a/flang/lib/Lower/OpenACC.cpp +++ b/flang/lib/Lower/OpenACC.cpp @@ -1738,8 +1738,7 @@ static bool shouldAttachFirstprivateOnCombinedLoop( if (*combinedConstructs != mlir::acc::CombinedConstructsType::ParallelLoop && *combinedConstructs != mlir::acc::CombinedConstructsType::SerialLoop) return false; - return converter.getFoldingContext().languageFeatures().IsEnabled( - Fortran::common::LanguageFeature::OpenACCCombinedLoopFirstprivate); + return converter.getLoweringOptions().getOpenACCCombinedLoopFirstprivate(); } // Helper to visit Bounds of DO LOOP nest. >From a4bb994c3b51e3f1ee03cf9a14ec463afd43bada Mon Sep 17 00:00:00 2001 From: Susan Tan <[email protected]> Date: Mon, 14 Sep 2026 13:49:18 -0700 Subject: [PATCH 13/13] add tests --- .../acc-combined-loop-firstprivate-flag.f90 | 55 +++++++++++++++++++ .../test/Lower/OpenACC/acc-parallel-loop.f90 | 5 +- 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 flang/test/Lower/OpenACC/acc-combined-loop-firstprivate-flag.f90 diff --git a/flang/test/Lower/OpenACC/acc-combined-loop-firstprivate-flag.f90 b/flang/test/Lower/OpenACC/acc-combined-loop-firstprivate-flag.f90 new file mode 100644 index 0000000000000..ff5594d87a7ec --- /dev/null +++ b/flang/test/Lower/OpenACC/acc-combined-loop-firstprivate-flag.f90 @@ -0,0 +1,55 @@ +! Test that -fno-openacc-combined-loop-firstprivate keeps firstprivate on the +! compute construct only, with no implicit loop firstprivate. + +! RUN: %flang_fc1 -fopenacc -fno-openacc-combined-loop-firstprivate -emit-hlfir %s -o - | FileCheck %s + +subroutine flag_off_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 @_QPflag_off_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-NOT: acc.firstprivate {{.*}} implicit(true) +! CHECK: acc.loop combined(parallel) +! CHECK: } inclusiveUpperbound(array<i1: true>) independent + +subroutine flag_off_array + integer :: i, n + real :: b(10) + n = 10 + !$acc parallel loop firstprivate(b) + do i = 1, n + b(i) = 1.0 + end do +end subroutine + +! CHECK-LABEL: func.func @_QPflag_off_array +! CHECK: acc.firstprivate varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) recipe({{.*}}) name("b") +! CHECK: acc.parallel combined(loop) {{.*}}firstprivate +! CHECK-NOT: acc.firstprivate {{.*}} implicit(true) +! CHECK: acc.loop combined(parallel) + +subroutine flag_off_serial_loop + 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 @_QPflag_off_serial_loop +! CHECK: acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe({{.*}}) name("v") +! CHECK: acc.serial combined(loop) {{.*}}firstprivate +! CHECK-NOT: acc.firstprivate {{.*}} implicit(true) +! CHECK: acc.loop combined(serial) diff --git a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 index 63ec22da62fd0..1ed0c25d663ab 100644 --- a/flang/test/Lower/OpenACC/acc-parallel-loop.f90 +++ b/flang/test/Lower/OpenACC/acc-parallel-loop.f90 @@ -686,9 +686,10 @@ subroutine acc_parallel_loop_firstprivate_scalar 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: %[[FP_V:.*]] = acc.firstprivate varPtr(%{{.*}} : !fir.ref<i32>) recipe(@firstprivatization_ref_i32) 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: %[[DECL_V:.*]]:2 = hlfir.declare %[[FP_V]] {uniq_name = {{.*}}} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>) +! CHECK: %[[FP_V_LOOP:.*]] = acc.firstprivate varPtr(%[[DECL_V]]#0 : !fir.ref<i32>) recipe(@firstprivatization_ref_i32) 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
