blob: f5c8b300d6909bcd447fb5213c8d002713481fee [file] [edit]
// RUN: fir-opt -allow-unregistered-dialect --inline-elementals %s | FileCheck %s
// Test inlining of hlfir.elemental into its hlfir.apply site when the
// elemental has more than two users.
// Test successful inlining with relaxed inlining.
func.func @test_safe_loop_inlining(%arg0: !fir.ref<!fir.array<10xf32>>, %i: index) {
%c1 = arith.constant 1 : index
%c10 = arith.constant 10 : index
%shape = fir.shape %c10 : (index) -> !fir.shape<1>
// Elemental Mask.
%elem = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%idx: index):
%addr = fir.coordinate_of %arg0, %idx : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
%val = fir.load %addr : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// User 1 - hlfir.apply inside nested loops (The target for inlining).
fir.do_loop %arg1 = %c1 to %c10 step %c1 {
fir.do_loop %arg2 = %c1 to %c10 step %c1 {
%res = hlfir.apply %elem, %arg2 : (!hlfir.expr<10xf32>, index) -> f32
%dummy = fir.alloca f32
fir.store %res to %dummy : !fir.ref<f32>
}
}
// User 2 - Associate (Simulating a shared mask use-case).
%temp:3 = hlfir.associate %elem(%shape) : (!hlfir.expr<10xf32>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>, i1)
// User 3 - Destroy
hlfir.destroy %elem : !hlfir.expr<10xf32>
hlfir.end_associate %temp#0, %temp#2 : !fir.ref<!fir.array<10xf32>>, i1
return
}
// CHECK-LABEL: func.func @test_safe_loop_inlining
// CHECK: %[[ELEM:.*]] = hlfir.elemental
// CHECK: fir.do_loop
// CHECK: fir.do_loop %[[INNER_IDX:.*]] =
// CHECK: %[[ADDR:.*]] = fir.coordinate_of %arg0, %[[INNER_IDX]]
// CHECK: %[[VAL:.*]] = fir.load %[[ADDR]]
// CHECK-NOT: hlfir.apply
// CHECK: hlfir.associate %[[ELEM]]
// This verifies that even when an elemental is inlined into an apply site,
// the destroy operation is preserved because another user (hlfir.associate)
// still requires the array buffer.
// CHECK: hlfir.destroy %[[ELEM]]
// Test blocking of incorrect inlining because of alias conflict.
func.func @test_unsafe_loop_alias_conflict(%arg0: !fir.ref<!fir.array<10xf32>>, %new_val: f32) {
%c1 = arith.constant 1 : index
%c10 = arith.constant 10 : index
%shape = fir.shape %c10 : (index) -> !fir.shape<1>
// Elemental depends on the values in %arg0 (Producer).
%elem = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%idx: index):
%addr = fir.coordinate_of %arg0, %idx : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
%val = fir.load %addr : !fir.ref<f32>
hlfir.yield_element %val : f32
}
fir.do_loop %arg1 = %c1 to %c10 step %c1 {
// We modify the array that the elemental needs to read from.
// Inlining the elemental here would see the new value.
%write_addr = fir.coordinate_of %arg0, %arg1 : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
fir.store %new_val to %write_addr : !fir.ref<f32>
// Target for inlining.
%res = hlfir.apply %elem, %arg1 : (!hlfir.expr<10xf32>, index) -> f32
%dummy = fir.alloca f32
fir.store %res to %dummy : !fir.ref<f32>
}
hlfir.destroy %elem : !hlfir.expr<10xf32>
return
}
// CHECK-LABEL: func.func @test_unsafe_loop_alias_conflict
// Elemental should not be inlined, check presence of elemental and apply.
// CHECK: %[[ELEM:.*]] = hlfir.elemental
// CHECK: fir.do_loop
// CHECK: fir.store
// CHECK: %[[APPLIED:.*]] = hlfir.apply %[[ELEM]]
// CHECK: fir.store %[[APPLIED]]
// Inlined code (coordinate_of/load) should not appear inside the loop.
// CHECK-NOT: fir.coordinate_of %arg0, %arg1
// Check successful inlining where 2-d hlfir.elemental survives because the
// 'associate' op is still using it.
func.func @test_inlining_use_mask(%arg0: !fir.box<!fir.array<?x?xi32>>, %arg1: !fir.ref<i32>) {
%c1 = arith.constant 1 : index
%c10 = arith.constant 10 : index
%shape = fir.shape %c10, %c10 : (index, index) -> !fir.shape<2>
// Elemental Mask.
%mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<10x10x!fir.logical<4>> {
^bb0(%i: index, %j: index):
%val = hlfir.designate %arg0 (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
%load = fir.load %val : !fir.ref<i32>
%ref = fir.load %arg1 : !fir.ref<i32>
%cmp = arith.cmpi eq, %load, %ref : i32
%res = fir.convert %cmp : (i1) -> !fir.logical<4>
hlfir.yield_element %res : !fir.logical<4>
}
// Extra User - This keeps the elemental alive even after inlining the apply
// site. Total uses = 3 (associate, apply, destroy).
%extra:3 = hlfir.associate %mask : (!hlfir.expr<10x10x!fir.logical<4>>) -> (!fir.box<!fir.array<10x10x!fir.logical<4>>>, !fir.ref<!fir.array<10x10x!fir.logical<4>>>, i1)
// Apply inside a loop
fir.do_loop %arg2 = %c1 to %c10 step %c1 {
fir.do_loop %arg3 = %c1 to %c10 step %c1 {
%applied = hlfir.apply %mask, %arg3, %arg2 : (!hlfir.expr<10x10x!fir.logical<4>>, index, index) -> !fir.logical<4>
%dummy_ref = fir.alloca !fir.logical<4>
fir.store %applied to %dummy_ref : !fir.ref<!fir.logical<4>>
}
}
hlfir.end_associate %extra#0, %extra#2 : !fir.box<!fir.array<10x10x!fir.logical<4>>>, i1
hlfir.destroy %mask : !hlfir.expr<10x10x!fir.logical<4>>
return
}
// CHECK-LABEL: func.func @test_inlining_use_mask
// CHECK: %[[MASK:.*]] = hlfir.elemental
// CHECK: hlfir.associate %[[MASK]]
// CHECK: fir.do_loop
// CHECK: fir.do_loop
// CHECK-NOT: hlfir.apply
// CHECK: %[[VAL:.*]] = hlfir.designate %arg0
// CHECK: %[[LOAD:.*]] = fir.load %[[VAL]]
// CHECK: %[[REF:.*]] = fir.load %arg1
// CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[LOAD]], %[[REF]]
// CHECK: %[[RES:.*]] = fir.convert %[[CMP]]
// CHECK: fir.store %[[RES]]
// CHECK: hlfir.destroy %[[MASK]]
// Check elemental and destroy are successfully removed even when
// the expression is passed through metadata operations (fir.convert)
// and used in nested loops.
func.func @test_inlining_elemental_cleanup(%arg0: !fir.box<!fir.array<?x?xi32>>, %arg1: !fir.ref<i32>) {
%c1 = arith.constant 1 : index
%c10 = arith.constant 10 : index
%shape = fir.shape %c10, %c10 : (index, index) -> !fir.shape<2>
// Elemental Mask.
%mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<10x10x!fir.logical<4>> {
^bb0(%i: index, %j: index):
%val = hlfir.designate %arg0 (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
%load = fir.load %val : !fir.ref<i32>
%ref = fir.load %arg1 : !fir.ref<i32>
%cmp = arith.cmpi eq, %load, %ref : i32
%res = fir.convert %cmp : (i1) -> !fir.logical<4>
hlfir.yield_element %res : !fir.logical<4>
}
%extra = fir.convert %mask : (!hlfir.expr<10x10x!fir.logical<4>>) -> !hlfir.expr<10x10x!fir.logical<4>>
// Apply Site.
fir.do_loop %arg2 = %c1 to %c10 step %c1 {
fir.do_loop %arg3 = %c1 to %c10 step %c1 {
%applied = hlfir.apply %mask, %arg3, %arg2 : (!hlfir.expr<10x10x!fir.logical<4>>, index, index) -> !fir.logical<4>
%dummy_ref = fir.alloca !fir.logical<4>
fir.store %applied to %dummy_ref : !fir.ref<!fir.logical<4>>
}
}
hlfir.destroy %mask : !hlfir.expr<10x10x!fir.logical<4>>
return
}
// CHECK-LABEL: func.func @test_inlining_elemental_cleanup
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: fir.convert
// CHECK: fir.do_loop
// CHECK: fir.do_loop
// CHECK-NOT: hlfir.apply
// CHECK: arith.cmpi eq
// CHECK-NOT: hlfir.destroy
// Check that inlining is blocked when there is more than one hlfir.apply
// site for the same elemental.
func.func @test_multi_apply_no_inlining(%arg0: !hlfir.expr<?xi32>, %target: i32, %shape: !fir.shape<1>) -> (i1, i1) {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
// Producer (Elemental).
%mask = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
^bb0(%i: index):
%val = hlfir.apply %arg0, %i : (!hlfir.expr<?xi32>, index) -> i32
%cmp = arith.cmpi eq, %val, %target : i32
%log = fir.convert %cmp : (i1) -> !fir.logical<4>
hlfir.yield_element %log : !fir.logical<4>
}
// First Apply Site.
%apply1 = hlfir.apply %mask, %c1 : (!hlfir.expr<?x!fir.logical<4>>, index) -> !fir.logical<4>
%cond1 = fir.convert %apply1 : (!fir.logical<4>) -> i1
// Second Apply Site.
%apply2 = hlfir.apply %mask, %c2 : (!hlfir.expr<?x!fir.logical<4>>, index) -> !fir.logical<4>
%cond2 = fir.convert %apply2 : (!fir.logical<4>) -> i1
// Destroy.
hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
return %cond1, %cond2 : i1, i1
}
// CHECK-LABEL: func.func @test_multi_apply_no_inlining(
// CHECK-SAME: %[[ARG0:.*]]: !hlfir.expr<?xi32>, %[[TARGET:.*]]: i32, %[[SHAPE:.*]]: !fir.shape<1>)
// CHECK: %[[MASK:.*]] = hlfir.elemental %[[SHAPE]]
// CHECK: %[[A1:.*]] = hlfir.apply %[[MASK]], %{{.*}}
// CHECK: %[[A2:.*]] = hlfir.apply %[[MASK]], %{{.*}}
// CHECK: hlfir.destroy %[[MASK]]
// Check global store blocks inlining.
fir.global @mask_storage : !hlfir.expr<10x10x!fir.logical<4>>
func.func @test_nested_elemental(%arg0: !fir.box<!fir.array<?x?xi32>>, %arg1: !fir.ref<i32>) {
%c1 = arith.constant 1 : index
%c10 = arith.constant 10 : index
%shape = fir.shape %c10, %c10 : (index, index) -> !fir.shape<2>
// The Elemental Mask (b * c).
%mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<10x10x!fir.logical<4>> {
^bb0(%i: index, %j: index):
%val = hlfir.designate %arg0 (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
%load = fir.load %val : !fir.ref<i32>
%ref = fir.load %arg1 : !fir.ref<i32>
%cmp = arith.cmpi eq, %load, %ref : i32
%res = fir.convert %cmp : (i1) -> !fir.logical<4>
hlfir.yield_element %res : !fir.logical<4>
}
// Total users - 1. fir.store, 2. hlfir.apply, 3. hlfir.destroy.
%ptr = fir.address_of(@mask_storage) : !fir.ref<!hlfir.expr<10x10x!fir.logical<4>>>
fir.store %mask to %ptr : !fir.ref<!hlfir.expr<10x10x!fir.logical<4>>>
// Target loop using the mask.
fir.do_loop %arg2 = %c1 to %c10 step %c1 {
fir.do_loop %arg3 = %c1 to %c10 step %c1 {
// CHECK-NOT: hlfir.apply
%applied = hlfir.apply %mask, %arg3, %arg2 : (!hlfir.expr<10x10x!fir.logical<4>>, index, index) -> !fir.logical<4>
%dummy_ref = fir.alloca !fir.logical<4>
fir.store %applied to %dummy_ref : !fir.ref<!fir.logical<4>>
}
}
hlfir.destroy %mask : !hlfir.expr<10x10x!fir.logical<4>>
return
}
// CHECK-LABEL: func.func @test_nested_elemental
// CHECK: %[[MASK:.*]] = hlfir.elemental
// CHECK: %[[PTR:.*]] = fir.address_of(@mask_storage)
// CHECK: fir.store %[[MASK]] to %[[PTR]]
// Apply site not inlined.
// CHECK: fir.do_loop
// CHECK: fir.do_loop
// CHECK: %[[VAL:.*]] = hlfir.apply %[[MASK]]
// CHECK: fir.store %[[VAL]]
// The designate/load should only be inside the elemental.
// CHECK-NOT: hlfir.designate
// Inlining into a single hlfir.apply (relaxed inlining).
// a = (b * c)[1]
func.func @test_scalar_apply_inlining_safe(%b: !fir.ref<!fir.array<10xf32>>, %c1: index) {
%c10 = arith.constant 10 : index
%shape = fir.shape %c10 : (index) -> !fir.shape<1>
// Producer(1D Elemental).
%prod = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%i: index):
%b_addr = fir.coordinate_of %b, %i : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
%b_val = fir.load %b_addr : !fir.ref<f32>
%res = arith.addf %b_val, %b_val : f32
hlfir.yield_element %res : f32
}
// Scalar Apply (Target for inlining).
%scalar_val = hlfir.apply %prod, %c1 : (!hlfir.expr<10xf32>, index) -> f32
%temp:3 = hlfir.associate %prod(%shape) : (!hlfir.expr<10xf32>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>, i1)
hlfir.destroy %prod : !hlfir.expr<10xf32>
// Use the result.
%dummy = fir.alloca f32
fir.store %scalar_val to %dummy : !fir.ref<f32>
hlfir.end_associate %temp#0, %temp#2 : !fir.ref<!fir.array<10xf32>>, i1
return
}
// CHECK-LABEL: func.func @test_scalar_apply_inlining_safe
// CHECK: %[[ELEM:.*]] = hlfir.elemental
// CHECK: hlfir.yield_element
// CHECK: %[[ADDR:.*]] = fir.coordinate_of %arg0, %arg1
// CHECK: %[[VAL:.*]] = fir.load %[[ADDR]]
// CHECK: arith.addf %[[VAL]], %[[VAL]]
// CHECK-NOT: hlfir.apply
// CHECK: hlfir.associate %[[ELEM]]
// CHECK: hlfir.destroy %[[ELEM]]
// Check long chains of elementals.
// subroutine reproducer(a)
// real, dimension(:) :: a
// a = sqrt(a * (a - 1))
// end subroutine
func.func @_QPreproducer(%arg0: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "a"}) {
%c0 = arith.constant 0 : index
%f1 = arith.constant 1.0 : f32
%0:2 = hlfir.declare %arg0 {uniq_name = "_QFreproducerEa"} : (!fir.box<!fir.array<?xf32>>) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
%1:3 = fir.box_dims %0#0, %c0 : (!fir.box<!fir.array<?xf32>>, index) -> (index, index, index)
%2 = fir.shape %1#1 : (index) -> !fir.shape<1>
// tmp1 = a - 1.
%tmp1 = hlfir.elemental %2 unordered : (!fir.shape<1>) -> !hlfir.expr<?xf32> {
^bb0(%i: index):
%a_ref = hlfir.designate %0#0 (%i) : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
%a_val = fir.load %a_ref : !fir.ref<f32>
%sub = arith.subf %a_val, %f1 : f32
hlfir.yield_element %sub : f32
}
%dummy = hlfir.no_reassoc %tmp1 : !hlfir.expr<?xf32>
// tmp2 = a * tmp1.
%tmp2 = hlfir.elemental %2 unordered : (!fir.shape<1>) -> !hlfir.expr<?xf32> {
^bb0(%j: index):
%a_ref_2 = hlfir.designate %0#0 (%j) : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
%t1_val = hlfir.apply %tmp1, %j : (!hlfir.expr<?xf32>, index) -> f32
%a_val_2 = fir.load %a_ref_2 : !fir.ref<f32>
%mul = arith.mulf %a_val_2, %t1_val : f32
hlfir.yield_element %mul : f32
}
// tmp3 = sqrt(tmp2).
%tmp3 = hlfir.elemental %2 unordered : (!fir.shape<1>) -> !hlfir.expr<?xf32> {
^bb0(%k: index):
%t2_val = hlfir.apply %tmp2, %k : (!hlfir.expr<?xf32>, index) -> f32
%res = math.sqrt %t2_val : f32
hlfir.yield_element %res : f32
}
// Final assignment.
hlfir.assign %tmp3 to %0#0 : !hlfir.expr<?xf32>, !fir.box<!fir.array<?xf32>>
hlfir.destroy %tmp3 : !hlfir.expr<?xf32>
hlfir.destroy %tmp2 : !hlfir.expr<?xf32>
hlfir.destroy %dummy: !hlfir.expr<?xf32>
hlfir.destroy %tmp1 : !hlfir.expr<?xf32>
return
}
// CHECK-LABEL: func.func @_QPreproducer
// CHECK: %[[TMP1:.*]] = hlfir.elemental
// CHECK: hlfir.no_reassoc %[[TMP1]]
// CHECK-NOT: hlfir.apply
// CHECK-DAG: arith.subf
// CHECK-DAG: arith.mulf
// CHECK-DAG: math.sqrt
// CHECK: hlfir.assign
// CHECK: hlfir.destroy %[[TMP1]]
// Check that the ordered elemental is not inlined into another:
// a = b + c + d (where b + c is ordered)
func.func private @persistent_user(!hlfir.expr<?xf32>)
func.func @test_noinline_ordered(%arg0: !hlfir.expr<?xf32>, %arg1: !hlfir.expr<?xf32>, %shape: !fir.shape<1>) {
%c1 = arith.constant 1 : index
// Producer (b + c) - ordered
%el_a = hlfir.elemental %shape {ordered = true} : (!fir.shape<1>) -> !hlfir.expr<?xf32> {
^bb0(%i: index):
%0 = hlfir.apply %arg0, %i : (!hlfir.expr<?xf32>, index) -> f32
%1 = hlfir.apply %arg1, %i : (!hlfir.expr<?xf32>, index) -> f32
%sum = arith.addf %0, %1 : f32
hlfir.yield_element %sum : f32
}
fir.call @persistent_user(%el_a) : (!hlfir.expr<?xf32>) -> ()
// Consumer (el_a + d)
%el_b = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<?xf32> {
^bb0(%j: index):
// This apply must remain because el_a is ordered.
%a_val = hlfir.apply %el_a, %j : (!hlfir.expr<?xf32>, index) -> f32
%total = arith.addf %a_val, %a_val : f32
hlfir.yield_element %total : f32
}
hlfir.destroy %el_a : !hlfir.expr<?xf32>
%val = hlfir.apply %el_b, %c1 : (!hlfir.expr<?xf32>, index) -> f32
hlfir.destroy %el_b : !hlfir.expr<?xf32>
return
}
// CHECK-LABEL: func.func @test_noinline_ordered
// CHECK: %[[PRODUCER:.*]] = hlfir.elemental %{{.*}} {ordered = true}
// CHECK: fir.call @persistent_user(%[[PRODUCER]])
// CHECK: %[[CONSUMER:.*]] = hlfir.elemental
// CHECK: hlfir.apply %[[PRODUCER]], %{{.*}}
// CHECK: hlfir.destroy %[[PRODUCER]]
// Check that the elemental is not inlined, because its array result
// must be finalized.
func.func @test_noinline_due_to_finalization(%arg0: !fir.box<!fir.array<?x!fir.type<_QMtypesTt1{x:f32}>>>, %shape: !fir.shape<1>) {
%c1 = arith.constant 1 : index
// Producer - Derived-type Elemental.
%el = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<?x!fir.type<_QMtypesTt1{x:f32}>> {
^bb0(%i: index):
%res = fir.alloca !fir.type<_QMtypesTt1{x:f32}>
%ld = fir.load %res : !fir.ref<!fir.type<_QMtypesTt1{x:f32}>>
hlfir.yield_element %ld : !fir.type<_QMtypesTt1{x:f32}>
}
fir.call @persistent_user(%el) : (!hlfir.expr<?x!fir.type<_QMtypesTt1{x:f32}>>) -> ()
// This apply must remain because the elemental result requires finalization.
%res_ptr = fir.alloca !fir.type<_QMtypesTt1{x:f32}>
%apply = hlfir.apply %el, %c1 : (!hlfir.expr<?x!fir.type<_QMtypesTt1{x:f32}>>, index) -> !hlfir.expr<!fir.type<_QMtypesTt1{x:f32}>>
hlfir.assign %apply to %res_ptr : !hlfir.expr<!fir.type<_QMtypesTt1{x:f32}>>, !fir.ref<!fir.type<_QMtypesTt1{x:f32}>>
// Destroy with 'finalize' keyword, hlfir::elementalOpMustProduceTemp becomes
// true.
hlfir.destroy %el finalize : !hlfir.expr<?x!fir.type<_QMtypesTt1{x:f32}>>
return
}
// CHECK-LABEL: func.func @test_noinline_due_to_finalization
// CHECK: %[[EL:.*]] = hlfir.elemental
// CHECK: fir.call @persistent_user(%[[EL]])
// CHECK: %[[APPLY:.*]] = hlfir.apply %[[EL]], %{{.*}}
// CHECK: hlfir.destroy %[[EL]] finalize
// Test conflicting writes hidden within nested regions (like fir.if) between
// producer and the apply site.
func.func @test_nested_region_conflict(%arg0: !fir.ref<f32>, %cond: i1) {
%c1 = arith.constant 1 : index
%shape = fir.shape %c1 : (index) -> !fir.shape<1>
// Producer.
%elem = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%val = fir.load %arg0 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// Nested region (fir.if) containing a store.
// This tests if your walk(func) correctly sees into the 'then' block.
fir.if %cond {
%new_val = arith.constant 3.0 : f32
fir.store %new_val to %arg0 : !fir.ref<f32>
} else {
// Distractor op
fir.no_reassoc %cond : i1
}
// Apply Site.
%res = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%j: index):
%val = hlfir.apply %elem, %j : (!hlfir.expr<1xf32>, index) -> f32
hlfir.yield_element %val : f32
}
hlfir.destroy %elem : !hlfir.expr<1xf32>
return
}
// CHECK-LABEL: func.func @test_nested_region_conflict
// CHECK: %[[ELEM:.*]] = hlfir.elemental
// CHECK: fir.if %{{.*}} {
// CHECK: fir.store
// CHECK: }
// CHECK: hlfir.elemental
// CHECK: hlfir.apply %[[ELEM]]
// CHECK: hlfir.destroy %[[ELEM]]
// Checks tracking the elemntal result through block arguments to find the
// hlfir.apply site across a branch, fir.store in the intervening block (^bb1)
// blocks the inlining. It was getting inlined with relaxed inlining patch.
func.func @test_cross_block_conflict(%arg0: !fir.ref<f32>, %shape: !fir.shape<1>) {
// Producer in Entry Block.
%elemental = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%val = fir.load %arg0 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// Pass value to block argument to maintain dataflow for the worklist.
cf.br ^bb1(%elemental : !hlfir.expr<1xf32>)
^bb1(%block_arg: !hlfir.expr<1xf32>):
// Conflicting Write.
// This store between producer and apply site across blocks.
%new_val = arith.constant 2.0 : f32
fir.store %new_val to %arg0 : !fir.ref<f32>
// Apply Site.
%res = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%j: index):
%val = hlfir.apply %block_arg, %j : (!hlfir.expr<1xf32>, index) -> f32
hlfir.yield_element %val : f32
}
hlfir.destroy %elemental : !hlfir.expr<1xf32>
return
}
// CHECK-LABEL: func.func @test_cross_block_conflict
// CHECK: %[[ELM:.*]] = hlfir.elemental
// CHECK: cf.br ^bb1(%[[ELM]] : !hlfir.expr<1xf32>)
// CHECK: ^bb1(%[[BARG:.*]]: !hlfir.expr<1xf32>):
// CHECK: fir.store {{.*}} to %arg0
// CHECK: hlfir.elemental
// CHECK: hlfir.apply %[[BARG]]
// CHECK: hlfir.destroy %[[ELM]]
// External impure procedure that might modify %arg0.
func.func private @impure_side_effect()
func.func @test_impure_call_conflict(%arg0: !fir.ref<f32>, %shape: !fir.shape<1>) {
// Reads from %arg0.
%elemental = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%val = fir.load %arg0 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// Acts as a memory barrier.
fir.call @impure_side_effect() : () -> ()
// Apply Site.
%res = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%j: index):
%val = hlfir.apply %elemental, %j : (!hlfir.expr<1xf32>, index) -> f32
hlfir.yield_element %val : f32
}
hlfir.destroy %elemental : !hlfir.expr<1xf32>
return
}
// CHECK-LABEL: func.func @test_impure_call_conflict
// CHECK: %[[ELEM:.*]] = hlfir.elemental
// CHECK: fir.call @impure_side_effect()
// CHECK: hlfir.apply %[[ELEM]]
// CHECK: hlfir.destroy %[[ELEM]]
// Check conflicting write to the same memory buffer read by the elemental
// producer in loop body blocks inlining.
func.func @test_memory_dependency_with_designate(%arg0: !fir.ref<!fir.array<10xf32>>, %shape: !fir.shape<1>) {
%c1 = arith.constant 1 : index
%c5 = arith.constant 5 : index
%val = arith.constant 2.0 : f32
// Reads from the whole array %arg0.
%elem = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%i: index):
%addr = fir.coordinate_of %arg0, %i : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
%load = fir.load %addr : !fir.ref<f32>
hlfir.yield_element %load : f32
}
// Modifies one element of the same array, partial write to the same base
// buffer.
%specific_addr = hlfir.designate %arg0 (%c1) : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
fir.store %val to %specific_addr : !fir.ref<f32>
// Apply Site.
%res = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%j: index):
%applied = hlfir.apply %elem, %j : (!hlfir.expr<10xf32>, index) -> f32
hlfir.yield_element %applied : f32
}
hlfir.destroy %elem : !hlfir.expr<10xf32>
return
}
// CHECK-LABEL: func.func @test_memory_dependency_with_designate
// CHECK: %[[ELEM:.*]] = hlfir.elemental {{.*}} unordered
// CHECK: fir.store {{.*}} to %{{.*}}
// CHECK: hlfir.elemental
// CHECK: hlfir.apply %[[ELEM]]
// CHECK: hlfir.destroy %[[ELEM]]
// Inlining is blocked because an unknown op uses %arg0.
func.func @test_unknown_op_fallback(%arg0: !fir.ref<f32>) -> f32 {
%c1 = arith.constant 1 : index
%f1 = arith.constant 1.0 : f32
%shape = fir.shape %c1 : (index) -> !fir.shape<1>
%elemental = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
"unknown.op"(%arg0) : (!fir.ref<f32>) -> ()
%val = fir.load %arg0 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
fir.store %f1 to %arg0 : !fir.ref<f32>
%apply = hlfir.apply %elemental, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %elemental : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_unknown_op_fallback
// CHECK: hlfir.elemental
// CHECK: hlfir.apply
// Inlining is blocked because the walker found a dependency inside a region.
func.func @test_nested_dependency(%arg0: !fir.ref<f32>, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%f1 = arith.constant 1.0 : f32
%shape = fir.shape %c1 : (index) -> !fir.shape<1>
%elemental = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%res = fir.if %cond -> f32 {
%val = fir.load %arg0 : !fir.ref<f32>
fir.result %val : f32
} else {
%c0 = arith.constant 0.0 : f32
fir.result %c0 : f32
}
hlfir.yield_element %res : f32
}
fir.store %f1 to %arg0 : !fir.ref<f32>
%apply = hlfir.apply %elemental, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %elemental : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_nested_dependency
// CHECK: hlfir.elemental
// CHECK: hlfir.apply
// Check if fir.box dependency are captured in fail-safe logic.
func.func @test_box_dependency(%arg0: !fir.box<!fir.array<?xf32>>) -> f32 {
%c1 = arith.constant 1 : index
%f0 = arith.constant 0.0 : f32
%shape = fir.shape %c1 : (index) -> !fir.shape<1>
%elemental = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
// 'unknown.op' uses the box. The fail-safe should capture %arg0 dependency.
"unknown.op"(%arg0) : (!fir.box<!fir.array<?xf32>>) -> ()
%addr_in = hlfir.designate %arg0 (%i) : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
%val = fir.load %addr_in : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// Inlining blocked as %arg0 was captured.
%addr_out = hlfir.designate %arg0 (%c1) : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
fir.store %f0 to %addr_out : !fir.ref<f32>
%apply = hlfir.apply %elemental, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %elemental : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_box_dependency
// CHECK: hlfir.elemental
// CHECK: hlfir.apply
// Check if .Default walker correctly enters a region (fir.if) and identifies
// metadata (fir.convert). It then feeds the result back to the worklist,
// allowing analysis to find the nested hlfir.apply.
func.func @test_nested_metadata_feedback(%arg0: !fir.shape<1>, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%c0 = arith.constant 5.0 : f32
hlfir.yield_element %c0 : f32
}
%res = fir.if %cond -> f32 {
%conv1 = fir.convert %el : (!hlfir.expr<1xf32>) -> !hlfir.expr<1xf32>
%conv2 = fir.convert %conv1 : (!hlfir.expr<1xf32>) -> !hlfir.expr<1xf32>
%apply = hlfir.apply %conv2, %c1 : (!hlfir.expr<1xf32>, index) -> f32
fir.result %apply : f32
} else {
%f0 = arith.constant 0.0 : f32
fir.result %f0 : f32
}
hlfir.destroy %el : !hlfir.expr<1xf32>
return %res : f32
}
// CHECK-LABEL: func.func @test_nested_metadata_feedback
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK: %[[VAL:.*]] = arith.constant 5.000000e+00 : f32
// CHECK: fir.if
// CHECK: fir.result %{{.*}} : f32
// CHECK: else
// CHECK: fir.result %{{.*}} : f32
// Check if analysis can find an hlfir.apply nested inside a fir.if
// when other operations like hlfir.declare are present in the
// same region. It ensures the .Default walker doesn't get stuck.
func.func @test_nested_declare_results(%arg0: !fir.shape<1>, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%cst = arith.constant 1.0 : f32
hlfir.yield_element %cst : f32
}
%res = fir.if %cond -> f32 {
// Metadata ops present in the region.
%mem = fir.alloca !hlfir.expr<1xf32>
%v:2 = hlfir.declare %mem {uniq_name = "test_var"} : (!fir.ref<!hlfir.expr<1xf32>>) -> (!fir.ref<!hlfir.expr<1xf32>>, !fir.ref<!hlfir.expr<1xf32>>)
// The apply using the elemental.
%apply = hlfir.apply %el, %c1 : (!hlfir.expr<1xf32>, index) -> f32
fir.result %apply : f32
} else {
%f0 = arith.constant 0.0 : f32
fir.result %f0 : f32
}
hlfir.destroy %el : !hlfir.expr<1xf32>
return %res : f32
}
// CHECK-LABEL: func.func @test_nested_declare_results
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK: %[[VAL:.*]] = arith.constant 1.000000e+00 : f32
// CHECK: fir.if
// CHECK: fir.result %{{.*}} : f32
// CHECK: else
// CHECK: fir.result %{{.*}} : f32
// Check fail-safe logic in getReadDependencies.
// The 'test.unknown_read' operation has no memory interface, so the pass
// should conservatively capture its operand (%mem) as a dependency.
// No inlining as there is a fir.store to %mem before the apply.
func.func @test_unspecified_read_fallback(%mem: !fir.ref<f32>) -> f32 {
%c1 = arith.constant 1 : index
%shape = fir.shape %c1 : (index) -> !fir.shape<1>
%el = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
"test.unknown_read"(%mem) : (!fir.ref<f32>) -> ()
%val = fir.load %mem : !fir.ref<f32>
hlfir.yield_element %val : f32
}
%f1 = arith.constant 1.0 : f32
fir.store %f1 to %mem : !fir.ref<f32>
%apply = hlfir.apply %el, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_unspecified_read_fallback
// CHECK: %[[EL:.*]] = hlfir.elemental
// CHECK: fir.store
// CHECK: %[[RES:.*]] = hlfir.apply %[[EL]]
// CHECK: hlfir.destroy %[[EL]]
// CHECK: return %[[RES]]
// Checks recursive analysis of the .Default walker. It identifies an
// hlfir.apply nested inside a fir.if block, skipping the container itself
// and successfully inlining the elemental body into the nested site.
func.func @test_minimal_nested(%arg0: !fir.shape<1>, %cond: i1) {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%c0 = arith.constant 0.0 : f32
hlfir.yield_element %c0 : f32
}
fir.if %cond {
%apply = hlfir.apply %el, %c1 : (!hlfir.expr<1xf32>, index) -> f32
"test.sink"(%apply) : (f32) -> ()
}
hlfir.destroy %el : !hlfir.expr<1xf32>
return
}
// CHECK-LABEL: func.func @test_minimal_nested
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK: arith.constant 0.000000e+00 : f32
// CHECK: "test.sink"
// Check .Default case identify unknown region-based operations as other users.
// The pass should inline the hlfir.apply but should preserve the
// hlfir.elemental and hlfir.destroy because the buffer is still required by
// unknown op.
func.func @test_unknown_region_consumer(%arg0: !fir.shape<1>) {
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%c0 = arith.constant 0.0 : f32
hlfir.yield_element %c0 : f32
}
// An operation with regions that can't be recognized as a metadata/apply op.
"unknown.region_op"(%el) ({
^bb0(%arg1: !hlfir.expr<1xf32>):
"some.inner.op"() : () -> ()
}) : (!hlfir.expr<1xf32>) -> ()
%c1 = arith.constant 1 : index
%apply = hlfir.apply %el, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return
}
// CHECK-LABEL: func.func @test_unknown_region_consumer
// CHECK: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK: hlfir.destroy
// Check recursive metadata (Walker -> Worklist -> Walker).
// hlfir.apply nested within multiple layers of control flow (nested fir.if).
// .Default walker and main worklist can hand off the elemental result across
// region boundaries many times.
func.func @test_deep_hybrid_nesting(%arg0: !fir.shape<1>, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%c0 = arith.constant 42.0 : f32
hlfir.yield_element %c0 : f32
}
%res = fir.if %cond -> f32 {
// .Default walker finds this, feeds back to worklist
%conv = fir.convert %el : (!hlfir.expr<1xf32>) -> !hlfir.expr<1xf32>
// Worklist finds this nested if, .Default walker runs again.
%inner = fir.if %cond -> f32 {
%apply = hlfir.apply %conv, %c1 : (!hlfir.expr<1xf32>, index) -> f32
fir.result %apply : f32
} else {
%f0 = arith.constant 0.0 : f32
fir.result %f0 : f32
}
fir.result %inner : f32
} else {
%f0 = arith.constant 0.0 : f32
fir.result %f0 : f32
}
hlfir.destroy %el : !hlfir.expr<1xf32>
return %res : f32
}
// CHECK-LABEL: func.func @test_deep_hybrid_nesting
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK: arith.constant 4.200000e+01 : f32
// Check conservative fail-safe in getReadDependencies. When an unknown
// operation is encountered, all of its reference-type operands are captured
// as dependencies. Inlining is blocked because of any of these captured
// values.
func.func @test_multi_operand_fallback(%mem1: !fir.ref<f32>, %mem2: !fir.ref<f32>) -> f32 {
%c1 = arith.constant 1 : index
%shape = fir.shape %c1 : (index) -> !fir.shape<1>
%el = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
// Unknown op with two reference operands.
"unknown.op"(%mem1, %mem2) : (!fir.ref<f32>, !fir.ref<f32>) -> ()
%val = fir.load %mem1 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// Write to the second operand (%mem2).
// In case of correctly captured all operands of the unknown op,
// inlining of the elemental should be blocked due to write to %mem2.
%f1 = arith.constant 1.0 : f32
fir.store %f1 to %mem2 : !fir.ref<f32>
%apply = hlfir.apply %el, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_multi_operand_fallback
// CHECK: hlfir.elemental
// CHECK: hlfir.apply
// Check fir::ResultOp mapping logic in the TypeSwitch - analysis can track
// the elemental result as it is yielded out of a fir.do_loop, correctly maps
// iter_arg to the loop result to find hlfir.apply site.
func.func @test_loop_carried_result(%arg0: !fir.shape<1>, %n: index) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%cst = arith.constant 7.0 : f32
hlfir.yield_element %cst : f32
}
// The elemental result is passed through a loop result.
%loop_res = fir.do_loop %i = %c1 to %n step %c1 iter_args(%arg = %el) -> (!hlfir.expr<1xf32>) {
fir.result %arg : !hlfir.expr<1xf32>
}
// The apply uses the result of the loop.
// TypeSwitch for fir::ResultOp should map %arg back to %loop_res.
%apply = hlfir.apply %loop_res, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_loop_carried_result
// CHECK-NOT: hlfir.elemental
// CHECK: arith.constant 7.0
// Check if worklist successfully traverses block boundaries.
// BranchOpInterface logic correctly maps the elemental result to the
// successor block's argument, allowing the pass to find hlfir.apply in
// a separate basic block.
func.func @test_cross_block_dataflow(%arg0: !fir.shape<1>, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%cst = arith.constant 3.14 : f32
hlfir.yield_element %cst : f32
}
// Value is passed as a block argument to bb1.
cf.cond_br %cond, ^bb1(%el : !hlfir.expr<1xf32>), ^bb2
^bb1(%block_arg: !hlfir.expr<1xf32>):
// The worklist must map %el to %block_arg via BranchOpInterface
%apply = hlfir.apply %block_arg, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return %apply : f32
^bb2:
%f0 = arith.constant 0.0 : f32
return %f0 : f32
}
// CHECK-LABEL: func.func @test_cross_block_dataflow
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK-NOT: hlfir.destroy
// CHECK: arith.constant 3.14
// This test verifiess following the elemental result through a loop exit.
// By mapping the fir.result operand to the parent operation's result,
// the worklist can trace the value from inside the loop to an hlfir.apply
// site located outside the loop.
func.func @test_loop_exit_dataflow(%arg0: !fir.shape<1>, %n: index) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%cst = arith.constant 1.23 : f32
hlfir.yield_element %cst : f32
}
// The elemental result exits the loop via fir.result.
%loop_res = fir.do_loop %i = %c1 to %n step %c1 iter_args(%arg = %el) -> (!hlfir.expr<1xf32>) {
fir.result %arg : !hlfir.expr<1xf32>
}
// The apply site is outside the loop, using the loop's result.
%apply = hlfir.apply %loop_res, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_loop_exit_dataflow
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK-NOT: hlfir.destroy
// CHECK: arith.constant 1.23
// Check if worklist can follow a result through nested control flow
// (if inside a loop) and out of the loop result itself.
func.func @test_complex_nesting_valid(%arg0: !fir.shape<1>, %n: index, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%cst = arith.constant 100.0 : f32
hlfir.yield_element %cst : f32
}
// Value enters the loop via iter_args.
%loop = fir.do_loop %i = %c1 to %n step %c1 iter_args(%arg = %el) -> (!hlfir.expr<1xf32>) {
// Nested structured control flow.
%res = fir.if %cond -> !hlfir.expr<1xf32> {
fir.result %arg : !hlfir.expr<1xf32>
} else {
fir.result %arg : !hlfir.expr<1xf32>
}
fir.result %res : !hlfir.expr<1xf32>
}
// Value exits the loop and is used by apply.
%apply = hlfir.apply %loop, %c1 : (!hlfir.expr<1xf32>, index) -> f32
hlfir.destroy %el : !hlfir.expr<1xf32>
return %apply : f32
}
// CHECK-LABEL: func.func @test_complex_nesting_valid
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK-NOT: hlfir.destroy
// CHECK: arith.constant 1.000000e+02 : f32
// Recursive .Default walker can find an apply site deep within nested regions
// (if inside a loop).
func.func @test_deep_nested_discovery(%arg0: !fir.shape<1>, %n: index, %cond: i1) -> f32 {
%c1 = arith.constant 1 : index
%f0 = arith.constant 0.0 : f32
%el = hlfir.elemental %arg0 : (!fir.shape<1>) -> !hlfir.expr<1xf32> {
^bb0(%i: index):
%c100 = arith.constant 100.0 : f32
hlfir.yield_element %c100 : f32
}
%res = fir.do_loop %i = %c1 to %n step %c1 iter_args(%arg = %el) -> (!hlfir.expr<1xf32>) {
%inner = fir.if %cond -> !hlfir.expr<1xf32> {
// The .Default walker on the fir.if finds this.
%apply = hlfir.apply %arg, %c1 : (!hlfir.expr<1xf32>, index) -> f32
"test.sink"(%apply) : (f32) -> ()
fir.result %arg : !hlfir.expr<1xf32>
} else {
fir.result %arg : !hlfir.expr<1xf32>
}
fir.result %inner : !hlfir.expr<1xf32>
}
hlfir.destroy %el : !hlfir.expr<1xf32>
return %f0 : f32
}
// CHECK-LABEL: func.func @test_deep_nested_discovery
// CHECK-NOT: hlfir.elemental
// CHECK-NOT: hlfir.apply
// CHECK-NOT: hlfir.destroy
// CHECK: arith.constant 1.000000e+02 : f32
// Check an elemental is not inlined into a loop if a variable used by the
// elemental is modified within the loop body before the apply site.
// This ensures dataflow safety in the execution path.
func.func @test_loop_body_conflict(%arg0: !fir.ref<f32>, %arg1: !fir.ref<!fir.array<10xf32>>) {
%c10 = arith.constant 10 : index
%shape = fir.shape %c10 : (index) -> !fir.shape<1>
// Producer: Elemental depends on the value in %arg0.
%elemental = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%i: index):
%val = fir.load %arg0 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
// Consumer Loop: Modifies %arg0 before calling hlfir.apply.
%c1 = arith.constant 1 : index
fir.do_loop %i = %c1 to %c10 step %c1 {
// This store should trigger conflict check.
%new_val = arith.constant 1.0 : f32
fir.store %new_val to %arg0 : !fir.ref<f32>
// Apply site: Should not be inlined because of the store above.
%val_to_store = hlfir.apply %elemental, %i : (!hlfir.expr<10xf32>, index) -> f32
%addr = hlfir.designate %arg1 (%i) : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
hlfir.assign %val_to_store to %addr : f32, !fir.ref<f32>
}
hlfir.destroy %elemental : !hlfir.expr<10xf32>
return
}
// CHECK-LABEL: func.func @test_loop_body_conflict
// CHECK: hlfir.elemental
// CHECK: fir.do_loop
// CHECK: fir.store
// CHECK: hlfir.apply
// CHECK: hlfir.destroy
// Check sibling block conflict: apply is in then block, but the store is in
// the else block where arg0 is modified. Inlining is not safe.
func.func @test_sibling_block_conflict(%arg0: !fir.ref<f32>, %arg1: !fir.ref<!fir.array<10xf32>>, %cond: i1) {
%c10 = arith.constant 10 : index
%shape = fir.shape %c10 : (index) -> !fir.shape<1>
// Producer depends on %arg0.
%elemental = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%i: index):
%val = fir.load %arg0 : !fir.ref<f32>
hlfir.yield_element %val : f32
}
%c1 = arith.constant 1 : index
fir.do_loop %i = %c1 to %c10 step %c1 {
fir.if %cond {
// Apply site.
%val_to_store = hlfir.apply %elemental, %i : (!hlfir.expr<10xf32>, index) -> f32
%addr = hlfir.designate %arg1 (%i) : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
hlfir.assign %val_to_store to %addr : f32, !fir.ref<f32>
} else {
// Conflict: Sibling block modifies the dependency.
%new_val = arith.constant 2.0 : f32
fir.store %new_val to %arg0 : !fir.ref<f32>
}
}
hlfir.destroy %elemental : !hlfir.expr<10xf32>
return
}
// CHECK-LABEL: func.func @test_sibling_block_conflict
// CHECK: hlfir.elemental
// CHECK: hlfir.apply
// Declare an external function with side effects.
func.func private @opaque_call() -> f32
// Check if inlining is blocked for fir.call inside the elemental block.
func.func @test_opaque_call_conflict(%arg1: !fir.ref<!fir.array<10xf32>>) {
%c10 = arith.constant 10 : index
%shape = fir.shape %c10 : (index) -> !fir.shape<1>
%elemental = hlfir.elemental %shape : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
^bb0(%i: index):
%val = fir.call @opaque_call() : () -> f32
hlfir.yield_element %val : f32
}
%c1 = arith.constant 1 : index
fir.do_loop %i = %c1 to %c10 step %c1 {
%val_to_store = hlfir.apply %elemental, %i : (!hlfir.expr<10xf32>, index) -> f32
%addr = hlfir.designate %arg1 (%i) : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
hlfir.assign %val_to_store to %addr : f32, !fir.ref<f32>
}
hlfir.destroy %elemental : !hlfir.expr<10xf32>
return
}
// CHECK-LABEL: func.func @test_opaque_call_conflict
// CHECK: hlfir.elemental
// CHECK: hlfir.apply