[flang-commits] [flang] a0f0d63 - [flang] Added more tests for Flang LICM. (#191083)

via flang-commits flang-commits at lists.llvm.org
Thu Apr 9 12:42:31 PDT 2026


Author: Slava Zakharin
Date: 2026-04-09T12:42:26-07:00
New Revision: a0f0d6342e0cd75b7f41e0e6aae0944393b68a62

URL: https://github.com/llvm/llvm-project/commit/a0f0d6342e0cd75b7f41e0e6aae0944393b68a62
DIFF: https://github.com/llvm/llvm-project/commit/a0f0d6342e0cd75b7f41e0e6aae0944393b68a62.diff

LOG: [flang] Added more tests for Flang LICM. (#191083)

Added: 
    flang/test/Transforms/licm-nested-hoist-aggressive.fir
    flang/test/Transforms/licm-nested-hoist-disabled.fir

Modified: 
    

Removed: 
    


################################################################################
diff  --git a/flang/test/Transforms/licm-nested-hoist-aggressive.fir b/flang/test/Transforms/licm-nested-hoist-aggressive.fir
new file mode 100644
index 0000000000000..440d30e4b5eac
--- /dev/null
+++ b/flang/test/Transforms/licm-nested-hoist-aggressive.fir
@@ -0,0 +1,215 @@
+// RUN: fir-opt -flang-licm='hoist-from-nested-regions=aggressive' --split-input-file %s | FileCheck %s
+
+// Tests for the "aggressive" nested hoisting mode, which hoists all safe
+// invariant operations from nested regions (not just fir.convert).
+
+// Test that pure ops (arith, fir.shape, fir.convert) inside scf.if are all
+// hoisted out of the loop in aggressive mode.
+// CHECK-LABEL:   func.func @test_aggressive_pure_ops(
+// CHECK-SAME:      %[[ARG0:.*]]: !fir.ref<!fir.array<10xf32>>)
+// CHECK-DAG:     %[[C5:.*]] = arith.constant 5 : index
+// CHECK-DAG:     %[[C10:.*]] = arith.constant 10 : index
+// CHECK:         %[[CVT:.*]] = fir.convert %[[ARG0]]
+// CHECK:         %[[SHP:.*]] = fir.shape %[[C10]]
+// CHECK:         %[[IDX:.*]] = arith.addi %{{.*}}, %[[C5]]
+// CHECK:         scf.for
+// CHECK:           scf.if
+// CHECK-NOT:         fir.convert
+// CHECK-NOT:         fir.shape
+// CHECK-NOT:         arith.addi
+// CHECK:             memref.store %{{.*}}, %[[CVT]][%[[IDX]]]
+func.func @test_aggressive_pure_ops(%arg0: !fir.ref<!fir.array<10xf32>>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c3 = arith.constant 3 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %cst = arith.constant 1.000000e+00 : f32
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cond = arith.cmpi slt, %i, %c5 : index
+    scf.if %cond {
+      %cvt = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      %shp = fir.shape %c10 : (index) -> !fir.shape<1>
+      %idx = arith.addi %c3, %c5 : index
+      memref.store %cst, %cvt[%idx] : memref<10xf32>
+    }
+  }
+  return
+}
+
+// -----
+// Test that an invariant fir.load and its dependent fir.convert chain inside
+// scf.if are hoisted in aggressive mode, when the loaded memory is not
+// modified inside the loop.
+// CHECK-LABEL:   func.func @test_aggressive_invariant_load(
+// CHECK-SAME:      %[[ARG0:.*]]: !fir.ref<!fir.array<10xf32>>,
+// CHECK-SAME:      %[[ARG1:.*]]: !fir.ref<i32> {fir.bindc_name = "n"})
+// CHECK:         %[[DECL:.*]] = fir.declare %[[ARG1]]
+// CHECK:         %[[CVT:.*]] = fir.convert %[[ARG0]]
+// CHECK:         %[[LOAD:.*]] = fir.load %[[DECL]]
+// CHECK:         %[[IDX:.*]] = fir.convert %[[LOAD]] : (i32) -> index
+// CHECK:         scf.for
+// CHECK:           scf.if
+// CHECK-NOT:         fir.convert
+// CHECK-NOT:         fir.load
+// CHECK:             memref.store %{{.*}}, %[[CVT]][%[[IDX]]]
+func.func @test_aggressive_invariant_load(%arg0: !fir.ref<!fir.array<10xf32>>, %arg1: !fir.ref<i32> {fir.bindc_name = "n"}) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %cst = arith.constant 1.000000e+00 : f32
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.declare %arg1 dummy_scope %0 arg 2 {uniq_name = "_QFtestEn"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cond = arith.cmpi slt, %i, %c5 : index
+    scf.if %cond {
+      %cvt = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      %val = fir.load %1 : !fir.ref<i32>
+      %idx = fir.convert %val : (i32) -> index
+      memref.store %cst, %cvt[%idx] : memref<10xf32>
+    }
+  }
+  return
+}
+
+// -----
+// Test that a fir.load inside scf.if is NOT hoisted even in aggressive mode
+// when the loaded memory is modified inside the loop.
+// CHECK-LABEL:   func.func @test_aggressive_modified_load_not_hoisted(
+// CHECK-SAME:      %[[ARG0:.*]]: !fir.ref<!fir.array<10xf32>>,
+// CHECK-SAME:      %[[ARG1:.*]]: !fir.ref<i32>)
+// CHECK:         %[[CVT:.*]] = fir.convert %[[ARG0]]
+// CHECK:         scf.for
+// CHECK:           scf.if
+// CHECK:             fir.load %[[ARG1]]
+// CHECK:             fir.convert
+// CHECK:             memref.store
+// CHECK:           fir.store {{.*}} to %[[ARG1]]
+func.func @test_aggressive_modified_load_not_hoisted(%arg0: !fir.ref<!fir.array<10xf32>>, %arg1: !fir.ref<i32>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %c1_i32 = arith.constant 1 : i32
+  %cst = arith.constant 1.000000e+00 : f32
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cond = arith.cmpi slt, %i, %c5 : index
+    scf.if %cond {
+      %cvt = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      %val = fir.load %arg1 : !fir.ref<i32>
+      %idx = fir.convert %val : (i32) -> index
+      memref.store %cst, %cvt[%idx] : memref<10xf32>
+    }
+    fir.store %c1_i32 to %arg1 : !fir.ref<i32>
+  }
+  return
+}
+
+// -----
+// Test that aggressive mode hoists pure ops from deeply nested scf.if regions
+// and through user-propagation chains.
+// CHECK-LABEL:   func.func @test_aggressive_deeply_nested(
+// CHECK-SAME:      %[[ARG0:.*]]: !fir.ref<!fir.array<10xf32>>)
+// CHECK-DAG:     %[[C3:.*]] = arith.constant 3 : index
+// CHECK-DAG:     %[[C5:.*]] = arith.constant 5 : index
+// CHECK:         %[[CVT:.*]] = fir.convert %[[ARG0]]
+// CHECK:         %[[IDX:.*]] = arith.addi %[[C3]], %[[C5]]
+// CHECK:         %[[SHP:.*]] = fir.shape %{{.*}}
+// CHECK:         scf.for
+// CHECK:           scf.if
+// CHECK:             scf.if
+// CHECK-NOT:           fir.convert
+// CHECK-NOT:           arith.addi
+// CHECK-NOT:           fir.shape
+// CHECK:               memref.store
+func.func @test_aggressive_deeply_nested(%arg0: !fir.ref<!fir.array<10xf32>>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c3 = arith.constant 3 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %cst = arith.constant 1.000000e+00 : f32
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cond1 = arith.cmpi slt, %i, %c5 : index
+    scf.if %cond1 {
+      %cond2 = arith.cmpi slt, %i, %c3 : index
+      scf.if %cond2 {
+        %cvt = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+        %idx = arith.addi %c3, %c5 : index
+        %shp = fir.shape %c10 : (index) -> !fir.shape<1>
+        memref.store %cst, %cvt[%idx] : memref<10xf32>
+      }
+    }
+  }
+  return
+}
+
+// -----
+// Test that a fir.load of an OPTIONAL variable inside scf.if is NOT hoisted
+// even in aggressive mode, because it is conditionally executed and
+// isNonOptionalScalar fails for optional variables. The trip count shortcut
+// (loop runs >= 1 iteration) cannot be used for conditionally-executed loads
+// since the condition might never be true.
+// CHECK-LABEL:   func.func @test_aggressive_conditional_optional_load(
+// CHECK-SAME:      %[[ARG0:.*]]: !fir.ref<!fir.array<10xf32>>,
+// CHECK-SAME:      %[[ARG1:.*]]: !fir.ref<i32>)
+// CHECK:         %[[DECL:.*]] = fir.declare %[[ARG1]]
+// CHECK:         %[[CVT:.*]] = fir.convert %[[ARG0]]
+// CHECK:         scf.for
+// CHECK:           scf.if
+// The fir.load must stay inside scf.if (conditionally executed + optional):
+// CHECK:             fir.load %[[DECL]]
+// CHECK:             fir.convert
+// CHECK:             memref.store
+func.func @test_aggressive_conditional_optional_load(%arg0: !fir.ref<!fir.array<10xf32>>, %arg1: !fir.ref<i32>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %cst = arith.constant 1.000000e+00 : f32
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.declare %arg1 dummy_scope %0 arg 2 {fortran_attrs = #fir.var_attrs<optional>, uniq_name = "_QFtestEn"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cond = arith.cmpi slt, %i, %c5 : index
+    scf.if %cond {
+      %cvt = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      %val = fir.load %1 : !fir.ref<i32>
+      %idx = fir.convert %val : (i32) -> index
+      memref.store %cst, %cvt[%idx] : memref<10xf32>
+    }
+  }
+  return
+}
+
+// -----
+// Test that in cheap mode (default), only fir.convert is hoisted from nested
+// regions, while other pure ops (arith.addi) remain inside. In aggressive mode,
+// both would be hoisted.
+// This test uses the default (cheap) mode as a cross-check.
+// RUN: fir-opt -flang-licm %s --split-input-file 2>&1
+// CHECK-LABEL:   func.func @test_cheap_vs_aggressive(
+// CHECK-SAME:      %[[ARG0:.*]]: !fir.ref<!fir.array<10xf32>>)
+// CHECK:         %[[CVT:.*]] = fir.convert %[[ARG0]]
+// CHECK:         scf.for
+// CHECK:           scf.if
+// In aggressive mode, arith.addi is also hoisted:
+// CHECK-NOT:         arith.addi
+// CHECK:             memref.store %{{.*}}, %[[CVT]]
+func.func @test_cheap_vs_aggressive(%arg0: !fir.ref<!fir.array<10xf32>>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c3 = arith.constant 3 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %cst = arith.constant 1.000000e+00 : f32
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cond = arith.cmpi slt, %i, %c5 : index
+    scf.if %cond {
+      %cvt = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      %idx = arith.addi %c3, %c5 : index
+      memref.store %cst, %cvt[%idx] : memref<10xf32>
+    }
+  }
+  return
+}

diff  --git a/flang/test/Transforms/licm-nested-hoist-disabled.fir b/flang/test/Transforms/licm-nested-hoist-disabled.fir
new file mode 100644
index 0000000000000..428d5e7b49ddd
--- /dev/null
+++ b/flang/test/Transforms/licm-nested-hoist-disabled.fir
@@ -0,0 +1,33 @@
+// RUN: fir-opt -flang-licm='hoist-from-nested-regions=none' %s | FileCheck %s
+
+// Verify that with hoist-from-nested-regions=none, fir.convert inside
+// scf.if is NOT hoisted out of the loop.
+// CHECK-LABEL: func.func @test_nested_no_hoist(
+// CHECK:         scf.for
+// CHECK:           scf.if
+// CHECK:             fir.convert
+// CHECK:             memref.store
+// CHECK:           scf.if
+// CHECK:             fir.convert
+// CHECK:             memref.store
+func.func @test_nested_no_hoist(%arg0: !fir.ref<!fir.array<10xf32>>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c5 = arith.constant 5 : index
+  %c10 = arith.constant 10 : index
+  %cst1 = arith.constant 1.000000e+00 : f32
+  %cst2 = arith.constant 2.000000e+00 : f32
+  scf.for %i = %c0 to %c10 step %c1 {
+    %cmp1 = arith.cmpi slt, %i, %c5 : index
+    scf.if %cmp1 {
+      %mem = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      memref.store %cst1, %mem[%i] : memref<10xf32>
+    }
+    %cmp2 = arith.cmpi sge, %i, %c5 : index
+    scf.if %cmp2 {
+      %mem = fir.convert %arg0 : (!fir.ref<!fir.array<10xf32>>) -> memref<10xf32>
+      memref.store %cst2, %mem[%i] : memref<10xf32>
+    }
+  }
+  return
+}


        


More information about the flang-commits mailing list