Files
TheRock/compiler/amd-llvm/flang/test/Lower/OpenACC/acc-cache.f90
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

843 lines
32 KiB
Fortran

! This test checks lowering of OpenACC cache directive.
! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
! CHECK-LABEL: acc.private.recipe @privatization_ref_i32 : !fir.ref<i32> init {
! CHECK-LABEL: func.func @_QPtest_cache_basic()
subroutine test_cache_basic()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n
!$acc cache(b)
a(i) = b(i)
end do
! CHECK: acc.loop
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "b"
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_basicEb"}
! Loop body uses the cached reference
! CHECK: %[[ELEM:.*]] = hlfir.designate %[[DECL]]#0 (%{{.*}}) : (!fir.ref<!fir.array<10xf32>>, i64) -> !fir.ref<f32>
! CHECK: %[[LOAD:.*]] = fir.load %[[ELEM]] : !fir.ref<f32>
! CHECK: hlfir.assign %[[LOAD]] to %{{.*}} : f32, !fir.ref<f32>
! Scope termination: acc.yield marks the end of the cache scope
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_readonly()
subroutine test_cache_readonly()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n
!$acc cache(readonly: b)
a(i) = b(i)
end do
! CHECK: acc.loop
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) -> !fir.ref<!fir.array<10xf32>> {modifiers = #acc<data_clause_modifier readonly>, name = "b"
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_readonlyEb"}
! Loop body uses the cached readonly reference
! CHECK: %[[ELEM:.*]] = hlfir.designate %[[DECL]]#0 (%{{.*}}) : (!fir.ref<!fir.array<10xf32>>, i64) -> !fir.ref<f32>
! CHECK: %[[LOAD:.*]] = fir.load %[[ELEM]] : !fir.ref<f32>
! CHECK: hlfir.assign %[[LOAD]] to %{{.*}} : f32, !fir.ref<f32>
! Scope termination
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_array_section()
! For b(2:5) with startIdx=1: lowerbound = 2-1 = 1, upperbound = 5-1 = 4, extent = 4
! This test includes an IF statement to verify cache scope with unstructured control flow
subroutine test_cache_array_section()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n
!$acc cache(b(2:5))
if (i > 2) then
a(i) = b(i)
end if
end do
! CHECK: acc.loop
! For b(2:5) with startIdx=1: lowerbound = 2-1 = 1, upperbound = 5-1 = 4
! CHECK: %[[C1:.*]] = arith.constant 1 : index
! CHECK: %[[LB:.*]] = arith.constant 1 : index
! CHECK: %[[UB:.*]] = arith.constant 4 : index
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[LB]] : index) upperbound(%[[UB]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "b
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_array_sectionEb"}
! Unstructured control flow: IF condition generates fir.if
! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %{{.*}}, %{{.*}} : i32
! CHECK: fir.if %[[CMP]] {
! Loop body uses the cached array section inside conditional
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.assign
! CHECK: }
! Scope termination: acc.yield terminates the cache scope for all control flow paths
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_multiple()
! This test includes IF-ELSE to verify cache scope with multiple control flow paths
subroutine test_cache_multiple()
integer, parameter :: n = 10
real, dimension(n) :: a, b, c
integer :: i
!$acc loop
do i = 1, n
!$acc cache(b, c)
if (i < 5) then
a(i) = b(i) + c(i)
else
a(i) = b(i) - c(i)
end if
end do
! CHECK: acc.loop
! CHECK: %[[CACHE_B:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "b"
! CHECK: %[[DECL_B:.*]]:2 = hlfir.declare %[[CACHE_B]](%{{.*}}) {uniq_name = "_QFtest_cache_multipleEb"}
! CHECK: %[[CACHE_C:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "c"
! CHECK: %[[DECL_C:.*]]:2 = hlfir.declare %[[CACHE_C]](%{{.*}}) {uniq_name = "_QFtest_cache_multipleEc"}
! Unstructured control flow: IF-ELSE generates fir.if with else region
! CHECK: %[[CMP:.*]] = arith.cmpi slt, %{{.*}}, %{{.*}} : i32
! CHECK: fir.if %[[CMP]] {
! Then branch: uses both cached references with addition
! CHECK: hlfir.designate %[[DECL_B]]#0
! CHECK: fir.load
! CHECK: hlfir.designate %[[DECL_C]]#0
! CHECK: fir.load
! CHECK: arith.addf
! CHECK: hlfir.assign
! CHECK: } else {
! Else branch: uses both cached references with subtraction
! CHECK: hlfir.designate %[[DECL_B]]#0
! CHECK: fir.load
! CHECK: hlfir.designate %[[DECL_C]]#0
! CHECK: fir.load
! CHECK: arith.subf
! CHECK: hlfir.assign
! CHECK: }
! Scope termination: both IF and ELSE paths use cache, then converge to yield
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_2d_array()
! For b(1:5, 1:5) with startIdx=1: each dimension has lowerbound = 1-1 = 0, upperbound = 5-1 = 4, extent = 5
subroutine test_cache_2d_array()
integer, parameter :: n = 10
real, dimension(n, n) :: a, b
integer :: i, j
!$acc loop
do i = 1, n
!$acc cache(b(1:5, 1:5))
do j = 1, n
a(i,j) = b(i,j)
end do
end do
! CHECK: acc.loop
! Dimension 1: lowerbound = 1 - startIdx = 0, upperbound = 5 - startIdx = 4
! CHECK: %[[C1:.*]] = arith.constant 1 : index
! CHECK: %[[C0:.*]] = arith.constant 0 : index
! CHECK: %[[C4:.*]] = arith.constant 4 : index
! CHECK: %[[BOUND1:.*]] = acc.bounds lowerbound(%[[C0]] : index) upperbound(%[[C4]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! Dimension 2: lowerbound = 0, upperbound = 4
! CHECK: %[[C0_2:.*]] = arith.constant 0 : index
! CHECK: %[[C4_2:.*]] = arith.constant 4 : index
! CHECK: %[[BOUND2:.*]] = acc.bounds lowerbound(%[[C0_2]] : index) upperbound(%[[C4_2]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10x10xf32>>) bounds(%[[BOUND1]], %[[BOUND2]]) -> !fir.ref<!fir.array<10x10xf32>> {{{.*}}name = "b
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_2d_arrayEb"}
! Nested loop uses the cached 2D array
! CHECK: fir.do_loop
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.assign
! Scope termination for acc.loop
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_loop_var()
! Test cache with loop variable dependent bounds: b(i:i+2)
subroutine test_cache_loop_var()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n-2
!$acc cache(b(i:i+2))
a(i) = b(i) + b(i+1) + b(i+2)
end do
! CHECK: acc.loop private({{.*}}) control(%[[IV:.*]] : i32) = ({{.*}}) to ({{.*}})
! The privatized iterator is declared and initialized from the loop control variable
! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_cache_loop_varEi"}
! CHECK: fir.store %[[IV]] to %[[I_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[C1:.*]] = arith.constant 1 : index
! Load iterator i for lowerbound computation
! CHECK: %[[I_LOAD1:.*]] = fir.load %[[I_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[I_CVT1:.*]] = fir.convert %[[I_LOAD1]] : (i32) -> i64
! CHECK: %[[I_IDX1:.*]] = fir.convert %[[I_CVT1]] : (i64) -> index
! Compute lowerbound = i - 1
! CHECK: %[[LB:.*]] = arith.subi %[[I_IDX1]], %[[C1]] : index
! Load iterator i again for upperbound computation (i+2)
! CHECK: %[[I_LOAD2:.*]] = fir.load %[[I_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[C2:.*]] = arith.constant 2 : i32
! CHECK: %[[I_PLUS_2:.*]] = arith.addi %[[I_LOAD2]], %[[C2]] : i32
! CHECK: %[[UB_CVT:.*]] = fir.convert %[[I_PLUS_2]] : (i32) -> i64
! CHECK: %[[UB_IDX:.*]] = fir.convert %[[UB_CVT]] : (i64) -> index
! Compute upperbound = (i+2) - 1
! CHECK: %[[UB:.*]] = arith.subi %[[UB_IDX]], %[[C1]] : index
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[LB]] : index) upperbound(%[[UB]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "b
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_loop_varEb"}
! Loop body uses the cached reference for b(i), b(i+1), b(i+2)
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: arith.addf
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: arith.addf
! CHECK: hlfir.assign
! Scope termination
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_2d_loop_vars()
! Test 2D cache with swapped loop variables inside nested loop: b(j:j+1, i:i+1)
subroutine test_cache_2d_loop_vars()
integer, parameter :: n = 10
real, dimension(n, n) :: a, b
integer :: i, j
!$acc loop
do i = 1, n-1
do j = 1, n-1
!$acc cache(b(j:j+1, i:i+1))
a(i,j) = b(j,i) + b(j+1,i+1)
end do
end do
! CHECK: acc.loop private({{.*}}) control(%[[I_IV:.*]] : i32) = ({{.*}}) to ({{.*}})
! Outer loop iterator i is stored to privatized variable
! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_cache_2d_loop_varsEi"}
! CHECK: fir.store %[[I_IV]] to %[[I_DECL]]#0 : !fir.ref<i32>
! Inner loop j (non-acc loop, fir.do_loop)
! CHECK: fir.do_loop %[[J_IV:.*]] = {{.*}} iter_args(%[[J_ITER:.*]] = {{.*}})
! Inner loop iterator j is stored to j variable
! CHECK: fir.store %[[J_ITER]] to %[[J_REF:.*]] : !fir.ref<i32>
! Dimension 1 bounds from j: lowerbound = j-1, upperbound = j
! CHECK: %[[BOUND1:.*]] = acc.bounds lowerbound(%{{.*}} : index) upperbound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) startIdx(%{{.*}} : index)
! Dimension 2 bounds from i: lowerbound = i-1, upperbound = i
! CHECK: %[[BOUND2:.*]] = acc.bounds lowerbound(%{{.*}} : index) upperbound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) startIdx(%{{.*}} : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10x10xf32>>) bounds(%[[BOUND1]], %[[BOUND2]]) -> !fir.ref<!fir.array<10x10xf32>> {{{.*}}name = "b
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_2d_loop_varsEb"}
! Loop body uses the cached 2D reference
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: arith.addf
! CHECK: hlfir.assign
! Inner loop continues within the cache scope
! CHECK: }
! Scope termination for acc.loop
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_single_element()
! Test cache with single element access: b(i)
! This test includes an EXIT statement to verify cache scope with early loop exit
subroutine test_cache_single_element()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n
!$acc cache(b(i))
a(i) = b(i)
if (a(i) > 100.0) exit
end do
! Unstructured loop with EXIT: acc.loop becomes unstructured with cf.br/cf.cond_br
! CHECK: acc.loop private({{.*}}) {
! The privatized iterator is declared
! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_cache_single_elementEi"}
! Loop control is done with cf.br/cf.cond_br in unstructured form
! CHECK: cf.br ^[[HEADER:.*]]
! CHECK: ^[[HEADER]]:
! CHECK: cf.cond_br %{{.*}}, ^[[BODY:.*]], ^[[EXIT:.*]]
! CHECK: ^[[BODY]]:
! CHECK: %[[C1:.*]] = arith.constant 1 : index
! Load iterator i for bounds computation
! CHECK: %[[I_LOAD:.*]] = fir.load %[[I_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[I_CVT1:.*]] = fir.convert %[[I_LOAD]] : (i32) -> i64
! CHECK: %[[I_IDX:.*]] = fir.convert %[[I_CVT1]] : (i64) -> index
! Compute lowerbound = i - 1 (single element: upperbound = lowerbound)
! CHECK: %[[LB:.*]] = arith.subi %[[I_IDX]], %[[C1]] : index
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[LB]] : index) upperbound(%[[LB]] : index) extent(%[[C1]] : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "b
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_single_elementEb"}
! Loop body uses the cached single element
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.assign
! Unstructured control flow: EXIT generates conditional branch
! CHECK: %[[CMP:.*]] = arith.cmpf ogt, %{{.*}}, %{{.*}} : f32
! CHECK: cf.cond_br %[[CMP]], ^[[EXIT_BB:.*]], ^[[CONT_BB:.*]]
! CHECK: ^[[EXIT_BB]]:
! Early exit path: branch to acc.yield
! CHECK: cf.br ^[[YIELD:.*]]
! CHECK: ^[[CONT_BB]]:
! Normal path: update iterator and loop back
! CHECK: cf.br ^[[HEADER]]
! CHECK: ^[[YIELD]]:
! Scope termination: acc.yield marks end of cache scope
! CHECK: acc.yield
! CHECK-NEXT: } attributes {{{.*}}unstructured}
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_mixed_bounds()
! Test cache with mixed constant and variable bounds: b(1:i)
! This test includes a CYCLE statement to verify cache scope with loop continuation
subroutine test_cache_mixed_bounds()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n
!$acc cache(b(1:i))
if (mod(i, 2) == 0) cycle
a(i) = b(i)
end do
! CHECK: acc.loop private({{.*}}) control(%[[IV:.*]] : i32) = ({{.*}}) to ({{.*}})
! The privatized iterator is declared and initialized
! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_cache_mixed_boundsEi"}
! CHECK: fir.store %[[IV]] to %[[I_DECL]]#0 : !fir.ref<i32>
! b(1:i): lower bound is constant 0 (1-1), upper bound is i-1
! CHECK: %[[C1:.*]] = arith.constant 1 : index
! CHECK: %[[C0:.*]] = arith.constant 0 : index
! Load iterator i for upperbound computation
! CHECK: %[[I_LOAD:.*]] = fir.load %[[I_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[I_CVT:.*]] = fir.convert %[[I_LOAD]] : (i32) -> i64
! CHECK: %[[I_IDX:.*]] = fir.convert %[[I_CVT]] : (i64) -> index
! Compute upperbound = i - 1
! CHECK: %[[UB:.*]] = arith.subi %[[I_IDX]], %[[C1]] : index
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[C0]] : index) upperbound(%[[UB]] : index) extent(%{{.*}} : index) stride(%[[C1]] : index) startIdx(%[[C1]] : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<10xf32>> {{{.*}}name = "b
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_mixed_boundsEb"}
! Unstructured control flow: CYCLE generates inverted fir.if (body executes when NOT cycling)
! CHECK: %[[MOD:.*]] = arith.remsi %{{.*}}, %{{.*}} : i32
! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[MOD]], %{{.*}} : i32
! CHECK: %[[TRUE:.*]] = arith.constant true
! CHECK: %[[NOT_CYCLE:.*]] = arith.xori %[[CMP]], %[[TRUE]] : i1
! CHECK: fir.if %[[NOT_CYCLE]] {
! Loop body uses the cached reference (only executed when not cycling)
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.assign
! CHECK: }
! Scope termination: acc.yield after the conditional
! CHECK: acc.yield
! CHECK-NEXT: }
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_nonunit_lb()
! Test cache with array that has non-1 lower bound: arr(10:20), cache(arr(15))
! This test includes SELECT CASE for multi-way unstructured control flow
subroutine test_cache_nonunit_lb()
integer :: arr(10:20)
integer :: i
!$acc loop
do i = 10, 20
!$acc cache(arr(15))
select case (mod(i, 3))
case (0)
arr(i) = i * 2
case (1)
arr(i) = i * 3
case default
arr(i) = i
end select
end do
! For arr(10:20), startIdx = 10, element 15 has lowerbound = 15 - 10 = 5
! CHECK: %[[C10:.*]] = arith.constant 10 : index
! Unstructured loop with SELECT CASE: acc.loop becomes unstructured
! CHECK: acc.loop private({{.*}}) {
! CHECK: cf.br ^[[HEADER:.*]]
! CHECK: ^[[HEADER]]:
! CHECK: cf.cond_br %{{.*}}, ^[[BODY:.*]], ^[[EXIT:.*]]
! CHECK: ^[[BODY]]:
! Compute lowerbound = 15 - startIdx = 15 - 10 = 5
! CHECK: %[[C1:.*]] = arith.constant 1 : index
! CHECK: %[[C15:.*]] = arith.constant 15 : index
! CHECK: %[[LB:.*]] = arith.subi %[[C15]], %{{.*}} : index
! Single element: upperbound equals lowerbound, startIdx = 10
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%[[LB]] : index) upperbound(%[[LB]] : index) extent(%[[C1]] : index) stride(%{{.*}} : index) startIdx(%{{.*}} : index) {strideInBytes = true}
! For non-unit lower bound arrays, acc.cache uses the box type from hlfir.declare
! CHECK: %[[CACHE:.*]] = acc.cache var(%{{.*}} : !fir.box<!fir.array<11xi32>>) bounds(%[[BOUND]]) -> !fir.box<!fir.array<11xi32>> {{{.*}}name = "arr
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]](%{{.*}}) {uniq_name = "_QFtest_cache_nonunit_lbEarr"}
! Unstructured control flow: SELECT CASE generates fir.select_case
! CHECK: %[[MOD:.*]] = arith.remsi %{{.*}}, %{{.*}} : i32
! CHECK: fir.select_case %[[MOD]] : i32 [#fir.point, %{{.*}}, ^[[CASE0:.*]], #fir.point, %{{.*}}, ^[[CASE1:.*]], unit, ^[[DEFAULT:.*]]]
! Case 0: i * 2
! CHECK: ^[[CASE0]]:
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: hlfir.assign
! CHECK: cf.br ^[[MERGE:.*]]
! Case 1: i * 3
! CHECK: ^[[CASE1]]:
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: hlfir.assign
! CHECK: cf.br ^[[MERGE]]
! Default case: i
! CHECK: ^[[DEFAULT]]:
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: hlfir.assign
! CHECK: cf.br ^[[MERGE]]
! All SELECT CASE branches converge, then loop back or exit
! CHECK: ^[[MERGE]]:
! CHECK: cf.br ^[[HEADER]]
! CHECK: ^[[EXIT]]:
! Scope termination: acc.yield marks end of cache scope
! CHECK: acc.yield
! CHECK-NEXT: } attributes {{{.*}}unstructured}
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_use_after_region()
! CHECK: %[[B_VAR:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "_QFtest_cache_use_after_regionEb"}
subroutine test_cache_use_after_region()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
!$acc loop
do i = 1, n
!$acc cache(b)
a(i) = b(i)
end do
! Use b after the cache region - should use original variable
a(1) = b(1) + 1.0
! CHECK: acc.loop
! CHECK: acc.cache varPtr(%[[B_VAR]]#0 : !fir.ref<!fir.array<10xf32>>)
! CHECK: acc.yield
! CHECK: }
! After loop: uses original b, not cached version
! CHECK: %[[B_ORIG:.*]] = hlfir.designate %[[B_VAR]]#0 (%{{.*}})
! CHECK: fir.load %[[B_ORIG]]
! CHECK: arith.addf
! CHECK: hlfir.assign
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_nested_scope()
! CHECK: %[[B_VAR:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "_QFtest_cache_nested_scopeEb"}
subroutine test_cache_nested_scope()
integer, parameter :: n = 10
real, dimension(n) :: a, b, c
integer :: i, j
!$acc loop
do i = 1, n
!$acc loop
do j = 1, n
!$acc cache(b(j))
c(j) = b(j)
end do
! Use b(i) in outer loop - should use original, not inner cache
a(i) = b(i)
end do
! Outer acc.loop
! CHECK: acc.loop
! Inner acc.loop with cache
! CHECK: acc.loop
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%[[B_VAR]]#0 : !fir.ref<!fir.array<10xf32>>) bounds(%{{.*}})
! CHECK: %[[CACHE_DECL:.*]]:2 = hlfir.declare %[[CACHE]]
! CHECK: hlfir.designate %[[CACHE_DECL]]#0
! CHECK: acc.yield
! CHECK: }
! After inner loop: uses original b, not cached
! CHECK: hlfir.designate %[[B_VAR]]#0
! CHECK: acc.yield
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_in_regular_loop()
! CHECK: %[[B_VAR:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "_QFtest_cache_in_regular_loopEb"}
subroutine test_cache_in_regular_loop()
integer, parameter :: n = 10
real, dimension(n) :: a, b
integer :: i
! Cache in regular DO loop (not acc loop)
do i = 1, n
!$acc cache(b(i))
a(i) = b(i)
end do
! CHECK: fir.do_loop
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%[[B_VAR]]#0 : !fir.ref<!fir.array<10xf32>>) bounds(%{{.*}})
! CHECK: %[[CACHE_DECL:.*]]:2 = hlfir.declare %[[CACHE]]
! CHECK: hlfir.designate %[[CACHE_DECL]]#0
! CHECK: fir.load
! CHECK: hlfir.assign
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_in_if
! CHECK: %[[B_VAR:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFtest_cache_in_ifEb"}
subroutine test_cache_in_if(a, b, cache)
integer, parameter :: n = 10
real, dimension(n) :: a, b
logical :: cache
integer :: i
!$acc loop
do i = 1, n
if (cache) then
!$acc cache(b)
end if
a(i) = b(i)
end do
! CHECK: acc.loop
! CHECK: fir.if
! CHECK: acc.cache varPtr(%[[B_VAR]]#0 : !fir.ref<!fir.array<10xf32>>)
! CHECK: }
! After IF: uses original b, not cached version
! CHECK: hlfir.designate %[[B_VAR]]#0
! CHECK: acc.yield
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_in_nested_do
! CHECK: %[[B_VAR:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "_QFtest_cache_in_nested_doEb"}
subroutine test_cache_in_nested_do()
integer, parameter :: n = 1000, m = 100, l = 100
real, dimension(n, m, l) :: a, b
integer :: i, j
!$acc loop
do i = 1, n
do j = 1, m, 2
!$acc cache(b(i,m,j))
end do
do j = 1, m, 2
a(i, m, :) = b(i, m, :)
end do
end do
! CHECK: acc.loop
! First inner DO loop with cache
! CHECK: fir.do_loop
! CHECK: acc.cache varPtr(%[[B_VAR]]#0 : !fir.ref<!fir.array<1000x100x100xf32>>) bounds
! Second inner DO loop: uses original b, not cached version
! CHECK: fir.do_loop
! CHECK: hlfir.designate %[[B_VAR]]#0
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_derived_type()
subroutine test_cache_derived_type()
type :: dt
real :: array(100)
end type
integer, parameter :: n = 100
type(dt) :: data
real :: a(n)
integer :: i
!$acc loop
do i = 5, n - 4
!$acc cache(data%array(i-4:i+4))
a(i) = data%array(i)
end do
! CHECK: acc.loop
! CHECK: %[[ARRAY_COORD:.*]] = hlfir.designate %{{.*}}{"array"} shape %{{.*}} : (!fir.ref<!fir.type<_QFtest_cache_derived_typeTdt{array:!fir.array<100xf32>}>>, !fir.shape<1>) -> !fir.ref<!fir.array<100xf32>>
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%{{.*}} : index) upperbound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) startIdx(%{{.*}} : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%[[ARRAY_COORD]] : !fir.ref<!fir.array<100xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<100xf32>> {name = "data%array(i-4_4:i+4_4)", structured = false}
! CHECK: acc.yield
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_derived_type_readonly()
subroutine test_cache_derived_type_readonly()
type :: dt
real :: array(100)
end type
integer, parameter :: n = 100
type(dt) :: data
real :: a(n)
integer :: i
!$acc loop
do i = 5, n - 4
!$acc cache(readonly: data%array(i-4:i+4))
a(i) = data%array(i)
end do
! CHECK: acc.loop
! CHECK: %[[ARRAY_COORD:.*]] = hlfir.designate %{{.*}}{"array"} shape %{{.*}} : (!fir.ref<!fir.type<_QFtest_cache_derived_type_readonlyTdt{array:!fir.array<100xf32>}>>, !fir.shape<1>) -> !fir.ref<!fir.array<100xf32>>
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%{{.*}} : index) upperbound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) startIdx(%{{.*}} : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%[[ARRAY_COORD]] : !fir.ref<!fir.array<100xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<100xf32>> {modifiers = #acc<data_clause_modifier readonly>, name = "data%array(i-4_4:i+4_4)", structured = false}
! CHECK: acc.yield
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_nested_derived_type()
subroutine test_cache_nested_derived_type()
type :: inner
real :: arr(50)
end type
type :: outer
type(inner) :: in
end type
integer, parameter :: n = 50
type(outer) :: obj
real :: a(n)
integer :: i
!$acc loop
do i = 1, n
!$acc cache(obj%in%arr(i))
a(i) = obj%in%arr(i)
end do
! CHECK: acc.loop
! CHECK: %[[IN_COORD:.*]] = hlfir.designate %{{.*}}{"in"} : (!fir.ref<!fir.type<_QFtest_cache_nested_derived_typeTouter{in:!fir.type<_QFtest_cache_nested_derived_typeTinner{arr:!fir.array<50xf32>}>}>>) -> !fir.ref<!fir.type<_QFtest_cache_nested_derived_typeTinner{arr:!fir.array<50xf32>}>>
! CHECK: %[[ARR_COORD:.*]] = hlfir.designate %[[IN_COORD]]{"arr"} shape %{{.*}} : (!fir.ref<!fir.type<_QFtest_cache_nested_derived_typeTinner{arr:!fir.array<50xf32>}>>, !fir.shape<1>) -> !fir.ref<!fir.array<50xf32>>
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound(%{{.*}} : index) upperbound(%{{.*}} : index) extent(%{{.*}} : index) stride(%{{.*}} : index) startIdx(%{{.*}} : index)
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%[[ARR_COORD]] : !fir.ref<!fir.array<50xf32>>) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<50xf32>> {name = "obj%in%arr(i)", structured = false}
! CHECK: acc.yield
end subroutine
! Test cache with allocatable component in combined construct
! CHECK-LABEL: func.func @_QPtest_cache_combined_allocatable(
subroutine test_cache_combined_allocatable(data, C, M)
type :: dt
real, dimension(:), allocatable :: A
end type
type(dt), intent(inout) :: data
real, dimension(:), intent(out) :: C
integer, intent(in) :: M
integer :: i
!$acc parallel loop gang vector copyin(data, data%A(-3:M+4)) copyout(C(1:M))
do i = 1, M
!$acc cache(data%A(i-4:i+4))
C(i) = data%A(i)
end do
! CHECK: acc.parallel {{.*}} {
! CHECK: acc.loop
! CHECK: acc.cache varPtr(%{{.*}}) bounds(%{{.*}}) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>> {name = "data%a(i-4_4:i+4_4)", structured = false}
! CHECK: hlfir.declare %{{.*}} {{{.*}}uniq_name = "data%a(i-4_4:i+4_4)"}
! CHECK: acc.yield
end subroutine
! Test cache with copy of whole struct (not explicit component copyin)
! CHECK-LABEL: func.func @_QPtest_cache_parallel_copy_struct(
subroutine test_cache_parallel_copy_struct(data, M)
type :: dt
real, dimension(:), allocatable :: A
end type
type(dt), intent(inout) :: data
integer, intent(in) :: M
real :: r
integer :: i
!$acc parallel loop copy(data)
do i = 1, M
!$acc cache(data%A(i))
r = data%A(i)
end do
! CHECK: acc.parallel {{.*}} {
! CHECK: acc.loop
! CHECK: acc.cache varPtr(%{{.*}}) bounds(%{{.*}}) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>> {name = "data%a(i)", structured = false}
! CHECK: hlfir.declare %{{.*}} {{{.*}}uniq_name = "data%a(i)"}
! CHECK: acc.yield
end subroutine
! Test cache with nested derived type in parallel loop with copyin
! CHECK-LABEL: func.func @_QPtest_cache_nested_parallel(
subroutine test_cache_nested_parallel(obj, N)
type :: inner
real, dimension(:), allocatable :: arr
end type
type :: outer
type(inner) :: in
end type
type(outer), intent(inout) :: obj
integer, intent(in) :: N
real :: r
integer :: i
!$acc parallel loop copyin(obj%in%arr(1:N))
do i = 1, N
!$acc cache(obj%in%arr(i))
r = obj%in%arr(i)
end do
! CHECK: acc.parallel {{.*}} {
! CHECK: acc.loop
! CHECK: acc.cache varPtr(%{{.*}}) bounds(%{{.*}}) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>> {name = "obj%in%arr(i)", structured = false}
! CHECK: hlfir.declare %{{.*}} {{{.*}}uniq_name = "obj%in%arr(i)"}
! CHECK: acc.yield
end subroutine
! Test cache with explicit shape component in parallel loop with copyin
! CHECK-LABEL: func.func @_QPtest_cache_explicit_shape_comp(
subroutine test_cache_explicit_shape_comp(data, C, M)
type :: dt
real, dimension(10) :: A
end type
type(dt), intent(inout) :: data
real, dimension(:), intent(out) :: C
integer, intent(in) :: M
integer :: i
!$acc parallel loop gang vector copyin(data, data%A(1:M)) copyout(C(1:M))
do i = 1, M
!$acc cache(data%A(i:i+4))
C(i) = data%A(i)
end do
! CHECK: acc.parallel {{.*}} {
! CHECK: acc.loop
! CHECK: acc.cache varPtr(%{{.*}}) bounds(%{{.*}}) -> !fir.ref<!fir.array<10xf32>> {name = "data%a(i:i+4_4)", structured = false}
! CHECK: hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "data%a(i:i+4_4)"}
! CHECK: acc.yield
end subroutine
! Test cache with temporary in designator bounds - verifies local statement context
! doesn't cause issues with temporary cleanup
! CHECK-LABEL: func.func @_QPtest_cache_temp_in_designator(
subroutine test_cache_temp_in_designator(data, a)
integer, parameter :: n = 100
real :: data(n)
real :: a(n)
integer :: i
!$acc loop
do i = 5, n - 4
!$acc cache(readonly: data(1:maxloc(a+a, dim=1)))
a(i) = data(i)
end do
! CHECK: acc.loop
! CHECK: %[[ELEMENTAL:.*]] = hlfir.elemental
! CHECK: %[[MAXLOC:.*]] = hlfir.maxloc %[[ELEMENTAL]]
! CHECK: %[[BOUND:.*]] = acc.bounds lowerbound({{.*}}) upperbound({{.*}})
! CHECK: %[[CACHE:.*]] = acc.cache varPtr(%{{.*}}) bounds(%[[BOUND]]) -> !fir.ref<!fir.array<100xf32>> {modifiers = #acc<data_clause_modifier readonly>, name = "data(1:maxloc(a+a,dim=1_4))", structured = false}
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[CACHE]]
! CHECK: hlfir.destroy %[[ELEMENTAL]]
! CHECK: hlfir.designate %[[DECL]]#0
! CHECK: acc.yield
end subroutine
subroutine full_array_cache()
integer :: k, j , kd, jd,y, x
real(8) :: tile(0:8,0:8)
!$acc parallel loop gang collapse(2)
do k = 1, kd
do j = 1, jd
!$acc cache(tile(:,:))
end do
end do
end subroutine
! CHECK-LABEL: func.func @_QPfull_array_cache()
! CHECK: acc.cache var(%{{.*}}) bounds(%{{.*}})
! Caching a section of an array with a non-unit lower bound produces a
! box-typed acc.cache. The cache descriptor's extents and strides are decided
! later by cache materialization, so the cached symbol must be re-declared with
! the ORIGINAL lower bound ONLY -- a fir.shift, never a fir.shape_shift. A
! fir.shape_shift would re-impose the host array's extents on the descriptor and
! defeat the materialized (possibly packed) layout.
subroutine test_cache_nonunit_lb_box()
integer :: arr(10:20)
integer :: i
!$acc loop
do i = 10, 20
!$acc cache(arr(12:18))
arr(i) = i
end do
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_nonunit_lb_box()
! The host array is declared with its full shape: lower bound 10, extent 11.
! CHECK: %[[LB:.*]] = arith.constant 10 : index
! CHECK: %[[EXT:.*]] = arith.constant 11 : index
! CHECK: %[[SHAPE:.*]] = fir.shape_shift %[[LB]], %[[EXT]] : (index, index) -> !fir.shapeshift<1>
! CHECK: %[[ARR:.*]]:2 = hlfir.declare %{{.*}}(%[[SHAPE]]) {uniq_name = "_QFtest_cache_nonunit_lb_boxEarr"} : (!fir.ref<!fir.array<11xi32>>, !fir.shapeshift<1>) -> (!fir.box<!fir.array<11xi32>>, !fir.ref<!fir.array<11xi32>>)
! CHECK: %[[CACHE:.*]] = acc.cache var(%[[ARR]]#0 : !fir.box<!fir.array<11xi32>>) bounds(%{{.*}}) -> !fir.box<!fir.array<11xi32>> {name = "arr(12:18)"
! The cached re-declaration reuses the SAME lower bound (10) with NO extent: a
! fir.shift on %[[LB]], never a fir.shape_shift.
! CHECK-NOT: fir.shape_shift
! CHECK: %[[SHIFT:.*]] = fir.shift %[[LB]] : (index) -> !fir.shift<1>
! CHECK: hlfir.declare %[[CACHE]](%[[SHIFT]]) {uniq_name = "_QFtest_cache_nonunit_lb_boxEarr"} : (!fir.box<!fir.array<11xi32>>, !fir.shift<1>) -> (!fir.box<!fir.array<11xi32>>, !fir.box<!fir.array<11xi32>>)
! Same property for an assumed-shape array (runtime descriptor): the cached
! re-declaration uses a multi-dimensional fir.shift (lower bounds only), so the
! rebox preserves the descriptor's extents/strides instead of reshaping them.
subroutine test_cache_assumed_shape_box(arr)
real(8) :: arr(0:, 0:)
integer :: i
!$acc loop
do i = 1, 10
!$acc cache(arr(3:8, 0:4))
arr(i, 0) = arr(i, 1)
end do
end subroutine
! CHECK-LABEL: func.func @_QPtest_cache_assumed_shape_box(
! Both declared lower bounds are 0 (arr(0:,0:)):
! CHECK: %[[C0A:.*]] = arith.constant 0 : i64
! CHECK: %[[LB0:.*]] = fir.convert %[[C0A]] : (i64) -> index
! CHECK: %[[C0B:.*]] = arith.constant 0 : i64
! CHECK: %[[LB1:.*]] = fir.convert %[[C0B]] : (i64) -> index
! CHECK: %[[ARR:.*]]:2 = hlfir.declare %arg0(%{{.*}}) dummy_scope %{{.*}} {{.*}}uniq_name = "_QFtest_cache_assumed_shape_boxEarr"}
! CHECK: %[[CACHE:.*]] = acc.cache var(%[[ARR]]#0 : !fir.box<!fir.array<?x?xf64>>) bounds(%{{.*}}, %{{.*}}) -> !fir.box<!fir.array<?x?xf64>> {name = "arr(3:8,0:4)"
! The cached re-declaration reuses the SAME two lower bounds (0, 0) with no
! extents: a fir.shift on %[[LB0]], %[[LB1]], never a fir.shape_shift.
! CHECK-NOT: fir.shape_shift
! CHECK: %[[SHIFT:.*]] = fir.shift %[[LB0]], %[[LB1]] : (index, index) -> !fir.shift<2>
! CHECK: hlfir.declare %[[CACHE]](%[[SHIFT]]) {uniq_name = "_QFtest_cache_assumed_shape_boxEarr"} : (!fir.box<!fir.array<?x?xf64>>, !fir.shift<2>) -> (!fir.box<!fir.array<?x?xf64>>, !fir.box<!fir.array<?x?xf64>>)