// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-initialize-fir-analyses,acc-implicit-data)" -split-input-file | FileCheck %s // ----- func.func @test_fir_scalar_in_serial() { %livein = fir.alloca i64 {bindc_name = "scalarvar"} acc.serial { %load = fir.load %livein : !fir.ref acc.yield } return } // CHECK: acc.firstprivate varPtr({{.*}} : !fir.ref) recipe({{.*}}) -> !fir.ref {implicit = true, name = "scalarvar"} // ----- func.func @test_fir_scalar_in_parallel() { %livein = fir.alloca f32 {bindc_name = "scalarvar"} acc.parallel { %load = fir.load %livein : !fir.ref acc.yield } return } // CHECK: acc.firstprivate varPtr({{.*}} : !fir.ref) recipe({{.*}}) -> !fir.ref {implicit = true, name = "scalarvar"} // ----- func.func @test_fir_scalar_in_kernels() { %livein = fir.alloca f64 {bindc_name = "scalarvar"} acc.kernels { %load = fir.load %livein : !fir.ref acc.terminator } return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref) -> !fir.ref {dataClause = #acc, implicit = true, name = "scalarvar"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref) to varPtr({{.*}} : !fir.ref) {dataClause = #acc, implicit = true, name = "scalarvar"} // ----- func.func @test_fir_scalar_in_parallel_defaultnone() { %livein = fir.alloca f32 {bindc_name = "scalarvar"} acc.parallel { %load = fir.load %livein : !fir.ref acc.yield } attributes {defaultAttr = #acc} return } // CHECK-NOT: acc.firstprivate // ----- func.func @test_fir_scalar_in_kernels_defaultnone() { %livein = fir.alloca f64 {bindc_name = "scalarvar"} acc.kernels { %load = fir.load %livein : !fir.ref acc.terminator } attributes {defaultAttr = #acc} return } // CHECK-NOT: acc.copyin // ----- func.func @test_fir_derivedtype_in_parallel() { %livein = fir.alloca !fir.type<_QFTaggr{field:f32}> {bindc_name = "aggrvar"} acc.parallel { %load = fir.load %livein : !fir.ref> acc.yield } return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref>) -> !fir.ref> {dataClause = #acc, implicit = true, name = "aggrvar"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref>) to varPtr({{.*}} : !fir.ref>) {dataClause = #acc, implicit = true, name = "aggrvar"} // ----- func.func @test_fir_derivedtype_in_kernels() { %livein = fir.alloca !fir.type<_QFTaggr{field:f32}> {bindc_name = "aggrvar"} acc.kernels { %load = fir.load %livein : !fir.ref> acc.terminator } return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref>) -> !fir.ref> {dataClause = #acc, implicit = true, name = "aggrvar"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref>) to varPtr({{.*}} : !fir.ref>) {dataClause = #acc, implicit = true, name = "aggrvar"} // ----- func.func @test_fir_array_in_parallel() { %livein = fir.alloca !fir.array<10xf32> {bindc_name = "arrayvar"} acc.parallel { %load = fir.load %livein : !fir.ref> acc.yield } return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref>) -> !fir.ref> {dataClause = #acc, implicit = true, name = "arrayvar"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref>) to varPtr({{.*}} : !fir.ref>) {dataClause = #acc, implicit = true, name = "arrayvar"} // ----- func.func @test_fir_array_in_kernels() { %livein = fir.alloca !fir.array<10xf32> {bindc_name = "arrayvar"} acc.kernels { %load = fir.load %livein : !fir.ref> acc.terminator } return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref>) -> !fir.ref> {dataClause = #acc, implicit = true, name = "arrayvar"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref>) to varPtr({{.*}} : !fir.ref>) {dataClause = #acc, implicit = true, name = "arrayvar"} // ----- func.func @test_fir_derivedtype_in_parallel_defaultpresent() { %livein = fir.alloca !fir.type<_QFTaggr{field:f32}> {bindc_name = "aggrvar"} acc.parallel { %load = fir.load %livein : !fir.ref> acc.yield } attributes {defaultAttr = #acc} return } // CHECK: %[[PRESENT:.*]] = acc.present varPtr({{.*}} : !fir.ref>) -> !fir.ref> {acc.from_default, implicit = true, name = "aggrvar"} // CHECK: acc.delete accPtr(%[[PRESENT]] : !fir.ref>) {dataClause = #acc, implicit = true, name = "aggrvar"} // ----- func.func @test_fir_derivedtype_in_kernels_defaultpresent() { %livein = fir.alloca !fir.type<_QFTaggr{field:f32}> {bindc_name = "aggrvar"} acc.kernels { %load = fir.load %livein : !fir.ref> acc.terminator } attributes {defaultAttr = #acc} return } // CHECK: %[[PRESENT:.*]] = acc.present varPtr({{.*}} : !fir.ref>) -> !fir.ref> {acc.from_default, implicit = true, name = "aggrvar"} // CHECK: acc.delete accPtr(%[[PRESENT]] : !fir.ref>) {dataClause = #acc, implicit = true, name = "aggrvar"} // ----- func.func @test_fir_array_in_parallel_defaultpresent() { %livein = fir.alloca !fir.array<10xf32> {bindc_name = "arrayvar"} acc.parallel { %load = fir.load %livein : !fir.ref> acc.yield } attributes {defaultAttr = #acc} return } // CHECK: %[[PRESENT:.*]] = acc.present varPtr({{.*}} : !fir.ref>) -> !fir.ref> {acc.from_default, implicit = true, name = "arrayvar"} // CHECK: acc.delete accPtr(%[[PRESENT]] : !fir.ref>) {dataClause = #acc, implicit = true, name = "arrayvar"} // ----- func.func @test_fir_array_in_kernels_defaultpresent() { %livein = fir.alloca !fir.array<10xf32> {bindc_name = "arrayvar"} acc.kernels { %load = fir.load %livein : !fir.ref> acc.terminator } attributes {defaultAttr = #acc} return } // CHECK: %[[PRESENT:.*]] = acc.present varPtr({{.*}} : !fir.ref>) -> !fir.ref> {acc.from_default, implicit = true, name = "arrayvar"} // CHECK: acc.delete accPtr(%[[PRESENT]] : !fir.ref>) {dataClause = #acc, implicit = true, name = "arrayvar"} // ----- func.func @test_fir_scalar_in_parallel_defaultpresent() { %livein = fir.alloca f32 {bindc_name = "scalarvar"} acc.parallel { %load = fir.load %livein : !fir.ref acc.yield } attributes {defaultAttr = #acc} return } // CHECK: acc.firstprivate varPtr({{.*}} : !fir.ref) recipe({{.*}}) -> !fir.ref {implicit = true, name = "scalarvar"} // ----- func.func @test_fir_scalar_in_kernels_defaultpresent() { %livein = fir.alloca f64 {bindc_name = "scalarvar"} acc.kernels { %load = fir.load %livein : !fir.ref acc.terminator } attributes {defaultAttr = #acc} return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref) -> !fir.ref {dataClause = #acc, implicit = true, name = "scalarvar"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref) to varPtr({{.*}} : !fir.ref) {dataClause = #acc, implicit = true, name = "scalarvar"} // ----- func.func @test_fir_box_ref() { %livein = fir.alloca !fir.box> {bindc_name = "descriptor"} acc.parallel { %load = fir.load %livein : !fir.ref>> acc.yield } return } // CHECK: %[[COPYIN:.*]] = acc.copyin varPtr({{.*}} : !fir.ref>>) -> !fir.ref>> {dataClause = #acc, implicit = true, name = "descriptor"} // CHECK: acc.copyout accPtr(%[[COPYIN]] : !fir.ref>>) to varPtr({{.*}} : !fir.ref>>) {dataClause = #acc, implicit = true, name = "descriptor"} // ----- func.func @test_fir_box_val() { %desc = fir.alloca !fir.box> {bindc_name = "descriptor"} %livein = fir.load %desc : !fir.ref>> acc.parallel { %addr = fir.box_addr %livein : (!fir.box>) -> !fir.ref> acc.yield } return } // CHECK: %[[COPYIN:.*]] = acc.copyin var({{.*}} : !fir.box>) -> !fir.box> {dataClause = #acc, implicit = true, name = "descriptor"} // CHECK: acc.copyout accVar(%[[COPYIN]] : !fir.box>) to var({{.*}} : !fir.box>) {dataClause = #acc, implicit = true, name = "descriptor"} // ----- // This test has an explicit data clause for the box - but the pointer held // inside the box is used in the region instead of the box itself. Test that // implicit present is actually used. func.func @test_explicit_box_implicit_ptr() { %c1 = arith.constant 1 : index %c10 = arith.constant 10 : index %arr = fir.alloca !fir.array<10xf32> {bindc_name = "aa"} %shape = fir.shape %c10 : (index) -> !fir.shape<1> %arr_decl = fir.declare %arr(%shape) {uniq_name = "aa"} : (!fir.ref>, !fir.shape<1>) -> !fir.ref> %box = fir.embox %arr_decl(%shape) : (!fir.ref>, !fir.shape<1>) -> !fir.box> %copyin = acc.copyin var(%box : !fir.box>) -> !fir.box> {dataClause = #acc, name = "aa"} acc.serial dataOperands(%copyin : !fir.box>) { // Use the pointer, not the box %elem = fir.array_coor %arr_decl(%shape) %c1 : (!fir.ref>, !fir.shape<1>, index) -> !fir.ref acc.yield } acc.copyout accVar(%copyin : !fir.box>) to var(%box : !fir.box>) {dataClause = #acc, name = "aa"} return } // CHECK: acc.present varPtr(%{{.*}} : !fir.ref>){{.*}}-> !fir.ref> {implicit = true, name = "aa"} // ----- // This test uses an explicit-shape array with no data clause - it also has // an optimization where the pointer is used instead of the boxed entity. // It tests that the implicit data pass is able to recover the size despite // it not being encoded in the FIR type. // It was generated from the following Fortran source: // subroutine array(aa,nn) // integer :: nn // real :: aa(10:nn) // !$acc kernels loop // do ii = 10, nn // aa(ii) = ii // end do // !$acc end kernels // end subroutine func.func @_QParray(%arg0: !fir.ref> {fir.bindc_name = "aa"}, %arg1: !fir.ref {fir.bindc_name = "nn"}) { %c0 = arith.constant 0 : index %c1 = arith.constant 1 : index %c10_i64 = arith.constant 10 : i64 %0 = fir.dummy_scope : !fir.dscope %1 = fir.declare %arg1 dummy_scope %0 {uniq_name = "_QFarrayEnn"} : (!fir.ref, !fir.dscope) -> !fir.ref %4 = fir.convert %c10_i64 : (i64) -> index %5 = fir.load %1 : !fir.ref %6 = fir.convert %5 : (i32) -> i64 %7 = fir.convert %6 : (i64) -> index %8 = arith.subi %7, %4 : index %9 = arith.addi %8, %c1 : index %10 = arith.cmpi sgt, %9, %c0 : index %11 = arith.select %10, %9, %c0 : index %12 = fir.shape_shift %4, %11 : (index, index) -> !fir.shapeshift<1> %13 = fir.declare %arg0(%12) dummy_scope %0 {uniq_name = "_QFarrayEaa"} : (!fir.ref>, !fir.shapeshift<1>, !fir.dscope) -> !fir.ref> acc.kernels { %elem = fir.array_coor %13(%12) %4 : (!fir.ref>, !fir.shapeshift<1>, index) -> !fir.ref acc.terminator } return } // This tries to confirm that the acc.bounds operation is as expected. // Effectively the extent needs to be max(0, nn), stride needs to be 1, // adjusted lowerbound is 0, and actual language start index is 10. // CHECK: %[[NN:.*]] = fir.declare %{{.*}} dummy_scope %{{.*}} {uniq_name = "_QFarrayEnn"} : (!fir.ref, !fir.dscope) -> !fir.ref // CHECK: %[[C10:.*]] = fir.convert %c10{{.*}} : (i64) -> index // CHECK: %[[LOADEDNN:.*]] = fir.load %[[NN]] : !fir.ref // CHECK: %[[CAST1:.*]] = fir.convert %[[LOADEDNN]] : (i32) -> i64 // CHECK: %[[CAST2:.*]] = fir.convert %[[CAST1]] : (i64) -> index // CHECK: %[[SUBI:.*]] = arith.subi %[[CAST2]], %[[C10]] : index // CHECK: %[[ADDI:.*]] = arith.addi %[[SUBI]], %c1{{.*}} : index // CHECK: %[[CMPI:.*]] = arith.cmpi sgt, %[[ADDI]], %c0{{.*}} : index // CHECK: %[[SELECT:.*]] = arith.select %[[CMPI]], %[[ADDI]], %c0{{.*}} : index // CHECK: %[[BOUNDS:.*]] = acc.bounds lowerbound(%c0{{.*}} : index) upperbound(%{{.*}} : index) extent(%[[SELECT]] : index) stride(%c1{{.*}} : index) startIdx(%[[C10]] : index) // CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref>) bounds(%[[BOUNDS]]) -> !fir.ref> {dataClause = #acc, implicit = true, name = "aa"} // ----- // Test to confirm that a copyin clause is not implicitly generated for deviceptr symbol. func.func @test_deviceptr_no_implicit_copy() { %c10 = arith.constant 10 : index %arr = fir.alloca !fir.array<10xf64> {bindc_name = "a"} %shape = fir.shape %c10 : (index) -> !fir.shape<1> %arr_box = fir.embox %arr(%shape) : (!fir.ref>, !fir.shape<1>) -> !fir.box> %devptr = acc.deviceptr var(%arr_box : !fir.box>) -> !fir.box> {name = "a"} acc.parallel dataOperands(%devptr : !fir.box>) { %elem = fir.box_addr %arr_box : (!fir.box>) -> !fir.ref> acc.yield } return } // CHECK-NOT: acc.copyin // CHECK: acc.deviceptr // ----- // Test that acc.declare with deviceptr doesn't generate implicit copyin func.func @test_acc_declare_deviceptr() { %c10 = arith.constant 10 : index %arr = fir.alloca !fir.array<10xf64> {bindc_name = "a"} %shape = fir.shape %c10 : (index) -> !fir.shape<1> %arr_box = fir.embox %arr(%shape) : (!fir.ref>, !fir.shape<1>) -> !fir.box> %devptr = acc.deviceptr var(%arr_box : !fir.box>) -> !fir.box> {name = "a"} %token = acc.declare_enter dataOperands(%devptr : !fir.box>) acc.parallel { %elem = fir.box_addr %arr_box : (!fir.box>) -> !fir.ref> acc.yield } acc.declare_exit token(%token) return } // CHECK-LABEL: func.func @test_acc_declare_deviceptr // CHECK: acc.deviceptr // CHECK-NOT: acc.copyin // CHECK: acc.deviceptr // ----- // Test that implicit deviceptr is generated for a symbol with CUDA device attribute func.func @test_cuda_device_implicit_deviceptr() { %0 = fir.dummy_scope : !fir.dscope %1 = cuf.alloc !fir.box>> {bindc_name = "a", data_attr = #cuf.cuda, uniq_name = "_QFEa"} -> !fir.ref>>> %2 = fir.zero_bits !fir.heap> %c0 = arith.constant 0 : index %3 = fir.shape %c0 : (index) -> !fir.shape<1> %4 = fir.embox %2(%3) {allocator_idx = 2 : i32} : (!fir.heap>, !fir.shape<1>) -> !fir.box>> fir.store %4 to %1 : !fir.ref>>> %5:2 = hlfir.declare %1 {data_attr = #cuf.cuda, fortran_attrs = #fir.var_attrs, uniq_name = "_QFEa"} : (!fir.ref>>>) -> (!fir.ref>>>, !fir.ref>>>) %c1 = arith.constant 1 : index %c16_i32 = arith.constant 16 : i32 %c0_i32 = arith.constant 0 : i32 %6 = fir.convert %5#0 : (!fir.ref>>>) -> !fir.ref> %7 = fir.convert %c1 : (index) -> i64 %8 = fir.convert %c16_i32 : (i32) -> i64 fir.call @_FortranAAllocatableSetBounds(%6, %c0_i32, %7, %8) fastmath : (!fir.ref>, i32, i64, i64) -> () %9 = cuf.allocate %5#0 : !fir.ref>>> {data_attr = #cuf.cuda} -> i32 acc.serial { %cst = arith.constant 1.000000e+02 : f32 %10 = fir.load %5#0 : !fir.ref>>> %c5 = arith.constant 5 : index %11 = hlfir.designate %10 (%c5) : (!fir.box>>, index) -> !fir.ref hlfir.assign %cst to %11 : f32, !fir.ref acc.yield } return } func.func private @_FortranAAllocatableSetBounds(!fir.ref>, i32, i64, i64) -> () // CHECK-LABEL: func.func @test_cuda_device_implicit_deviceptr // CHECK-NOT: acc.copyin // CHECK: acc.deviceptr // CHECK-NOT: acc.copyout // ----- // Test argument mapped with deviceptr but used not via data mapping. func.func @test_fir_declare_deviceptr_arg_in_parallel(%arg0: !fir.ref>) { %c10 = arith.constant 10 : index %shape = fir.shape %c10 : (index) -> !fir.shape<1> %arr_decl = fir.declare %arg0(%shape) {acc.declare = #acc.declare, uniq_name = "_QFtestEa"} : (!fir.ref>, !fir.shape<1>) -> !fir.ref> %arr_box = fir.embox %arr_decl(%shape) : (!fir.ref>, !fir.shape<1>) -> !fir.box> %devptr = acc.deviceptr var(%arr_box : !fir.box>) -> !fir.box> {name = "a"} %token = acc.declare_enter dataOperands(%devptr : !fir.box>) acc.parallel { %addr = fir.box_addr %arr_box : (!fir.box>) -> !fir.ref> %elem = fir.array_coor %arr_decl(%shape) %c10 : (!fir.ref>, !fir.shape<1>, index) -> !fir.ref acc.yield } acc.declare_exit token(%token) dataOperands(%devptr : !fir.box>) return } // CHECK-LABEL: func.func @test_fir_declare_deviceptr_arg_in_parallel // CHECK: %[[DECL:.*]] = fir.declare %{{.*}}{{.*}}{acc.declare = #acc.declare{{.*}} // CHECK: %[[BOX:.*]] = fir.embox %[[DECL]] // CHECK: %[[DEVPTR:.*]] = acc.deviceptr var(%[[BOX]] : !fir.box>) -> !fir.box> {name = "a"} // CHECK: %[[TOKEN:.*]] = acc.declare_enter dataOperands(%[[DEVPTR]] : !fir.box>) // CHECK: %[[IMPLICIT_BOX:.*]] = acc.deviceptr var(%[[BOX]] : !fir.box>) -> !fir.box> {implicit = true, name = "a"} // CHECK: %[[IMPLICIT_REF:.*]] = acc.deviceptr varPtr(%[[DECL]] : !fir.ref>) -> !fir.ref> {implicit = true, name = "a"} // CHECK: acc.parallel dataOperands(%[[IMPLICIT_BOX]], %[[IMPLICIT_REF]] : !fir.box>, !fir.ref>) { // CHECK: fir.box_addr %[[IMPLICIT_BOX]] : (!fir.box>) -> !fir.ref> // CHECK: fir.array_coor %[[IMPLICIT_REF]] // CHECK: acc.declare_exit token(%[[TOKEN]]) dataOperands(%[[DEVPTR]] : !fir.box>) // ----- // Test that acc.serial inside acc.data deviceptr generates implicit deviceptr // (not copyin) when the deviceptr clause variable is derived from the ref used // by the serial construct (here, via fir.embox wrapping the declared ref). // This pattern arises when a subroutine containing: // !$acc data deviceptr(b) ← deviceptr operates on embox(b) (a box type) // !$acc serial // ... uses b ... ← serial uses b directly (a ref type) // !$acc end serial // !$acc end data // is inlined into the caller. After inlining, the deviceptr's box and the // serial's ref are different SSA values with different types, so alias // analysis returns NoAlias. The pass must recognize that the deviceptr's // variable is derived from the ref and generate an implicit deviceptr clause // instead of falling back to copyin/copyout. func.func @test_serial_inside_data_deviceptr_embox() { %c10 = arith.constant 10 : index %c1 = arith.constant 1 : index %arr = fir.alloca !fir.array<10xf32> {bindc_name = "b"} %shape = fir.shape %c10 : (index) -> !fir.shape<1> %arr_decl = fir.declare %arr(%shape) {uniq_name = "b"} : (!fir.ref>, !fir.shape<1>) -> !fir.ref> %box = fir.embox %arr_decl(%shape) : (!fir.ref>, !fir.shape<1>) -> !fir.box> %devptr = acc.deviceptr var(%box : !fir.box>) -> !fir.box> {name = "b(1:10)"} acc.data dataOperands(%devptr : !fir.box>) { acc.serial { %elem = fir.array_coor %arr_decl(%shape) %c1 : (!fir.ref>, !fir.shape<1>, index) -> !fir.ref %val = fir.load %elem : !fir.ref acc.yield } acc.terminator } return } // CHECK-LABEL: func.func @test_serial_inside_data_deviceptr_embox // CHECK: acc.deviceptr var({{.*}} : !fir.box>) -> !fir.box> {name = "b(1:10)"} // CHECK: acc.data // CHECK: acc.deviceptr varPtr({{.*}} : !fir.ref>) -> !fir.ref> {implicit = true, name = "b"} // CHECK: acc.serial dataOperands({{.*}} : !fir.ref>) // CHECK-NOT: acc.copyin // CHECK-NOT: acc.copyout