[flang-commits] [flang] [flang] [test] add tests for FIRToSCF (PR #176026)

Susan Tan ス-ザン タン via flang-commits flang-commits at lists.llvm.org
Wed Jan 14 13:21:47 PST 2026


https://github.com/SusanTan updated https://github.com/llvm/llvm-project/pull/176026

>From f4921f7b5c9daef5e8afea19852f08a87e3c0e96 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Wed, 14 Jan 2026 12:17:08 -0800
Subject: [PATCH 1/3] add tests

---
 flang/test/Fir/FirToSCF/any.fir               |  41 +++++
 flang/test/Fir/FirToSCF/do-extra.fir          | 149 ++++++++++++++++++
 flang/test/Fir/FirToSCF/if-extra.fir          | 122 ++++++++++++++
 .../test/Fir/FirToSCF/iterate-while-extra.fir | 117 ++++++++++++++
 .../test/Fir/FirToSCF/nested-unregistered.fir |  22 +++
 flang/test/Fir/FirToSCF/normalize.fir         |  69 ++++++++
 flang/test/Fir/FirToSCF/sum.fir               |  48 ++++++
 7 files changed, 568 insertions(+)
 create mode 100644 flang/test/Fir/FirToSCF/any.fir
 create mode 100644 flang/test/Fir/FirToSCF/do-extra.fir
 create mode 100644 flang/test/Fir/FirToSCF/if-extra.fir
 create mode 100644 flang/test/Fir/FirToSCF/iterate-while-extra.fir
 create mode 100644 flang/test/Fir/FirToSCF/nested-unregistered.fir
 create mode 100644 flang/test/Fir/FirToSCF/normalize.fir
 create mode 100644 flang/test/Fir/FirToSCF/sum.fir

diff --git a/flang/test/Fir/FirToSCF/any.fir b/flang/test/Fir/FirToSCF/any.fir
new file mode 100644
index 0000000000000..8616984e6d234
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/any.fir
@@ -0,0 +1,41 @@
+// RUN: fir-opt %s --fir-to-scf --allow-unregistered-dialect | FileCheck %s
+
+// Test that compiler generated fir.do_loop with an result from an iter_arg
+// is converted.
+//
+// derived from:
+// subroutine test_any(a,b,res)
+//   integer :: a(3), b(3)
+//   logical :: res
+//   res = any(a/=b)
+// end
+
+// CHECK-LABEL: func.func @test_any_
+// CHECK:       [[SCF_RESULT:%[0-9]+]] = scf.for
+// CHECK:       scf.yield
+// CHECK-NEXT:  }
+// CHECK-NEXT:  [[CONVERT:%[0-9]+]] = fir.convert [[SCF_RESULT]]
+
+func.func @test_any_(%arg0: !fir.ref<!fir.array<3xi32>> {fir.bindc_name = "a"}, %arg1: !fir.ref<!fir.array<3xi32>> {fir.bindc_name = "b"}, %arg2: !fir.ref<!fir.logical<4>> {fir.bindc_name = "res"}) attributes {fir.internal_name = "_QPtest_any"} {
+  %c1 = arith.constant 1 : index
+  %false = arith.constant false
+  %c3 = arith.constant 3 : index
+  %0 = fir.undefined !fir.dscope
+  %1 = fircg.ext_declare %arg0(%c3) dummy_scope %0 {uniq_name = "_QFtest_anyEa"} : (!fir.ref<!fir.array<3xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<3xi32>>
+  %2 = fircg.ext_declare %arg1(%c3) dummy_scope %0 {uniq_name = "_QFtest_anyEb"} : (!fir.ref<!fir.array<3xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<3xi32>>
+  %3 = fircg.ext_declare %arg2 dummy_scope %0 {uniq_name = "_QFtest_anyEres"} : (!fir.ref<!fir.logical<4>>, !fir.dscope) -> !fir.ref<!fir.logical<4>>
+  %4 = fir.do_loop %arg3 = %c1 to %c3 step %c1 iter_args(%arg4 = %false) -> (i1) {
+    %6 = fircg.ext_array_coor %1(%c3)<%arg3> : (!fir.ref<!fir.array<3xi32>>, index, index) -> !fir.ref<i32>
+    %7 = fircg.ext_array_coor %2(%c3)<%arg3> : (!fir.ref<!fir.array<3xi32>>, index, index) -> !fir.ref<i32>
+    %8 = fir.load %6 : !fir.ref<i32>
+    %9 = fir.load %7 : !fir.ref<i32>
+    %10 = arith.cmpi ne, %8, %9 : i32
+    %11 = arith.ori %arg4, %10 : i1
+    fir.result %11 : i1
+  }
+  %5 = fir.convert %4 : (i1) -> !fir.logical<4>
+  fir.store %5 to %3 : !fir.ref<!fir.logical<4>>
+  return
+}
+
+
diff --git a/flang/test/Fir/FirToSCF/do-extra.fir b/flang/test/Fir/FirToSCF/do-extra.fir
new file mode 100644
index 0000000000000..46203c1509d11
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/do-extra.fir
@@ -0,0 +1,149 @@
+// Test conversions of fir.do_loop, including cases nested in other regions.
+//
+// RUN: fir-opt %s --fir-to-scf --allow-unregistered-dialect | FileCheck %s
+
+// Test that compiler generated fir.allocas are converted, slightly edited from:
+// subroutine loop(a)
+//   integer :: a(4)
+//   a = 1
+// end
+
+// CHECK-LABEL: func.func @implied_do
+// CHECK:       [[SUB:%[0-9]+]] = arith.subi %c4{{.*}}, %c1{{.*}} : index
+// CHECK-NEXT:  [[ADD:%[0-9]+]] = arith.addi [[SUB]], %c1{{.*}} : index
+// CHECK-NEXT:  [[DIV:%[0-9]+]] = arith.divsi [[ADD]], %c1{{.*}} : index
+// CHECK:       scf.for %arg1 = %c0{{.*}} to [[DIV]] step %c1{{.*}} {
+
+func.func @implied_do(%arg0: !fir.ref<!fir.array<4xi32>> {fir.bindc_name = "a"}) {
+  %c1 = arith.constant 1 : index
+  %c1_i32 = arith.constant 1 : i32
+  %c4 = arith.constant 4 : index
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.shape %c4 : (index) -> !fir.shape<1>
+  %2 = fir.declare %arg0(%1) dummy_scope %0 {uniq_name = "_QFloopEa"} : (!fir.ref<!fir.array<4xi32>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<4xi32>>
+  fir.do_loop %arg1 = %c1 to %c4 step %c1 unordered {
+    %3 = fir.array_coor %2(%1) %arg1 : (!fir.ref<!fir.array<4xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+    fir.store %c1_i32 to %3 : !fir.ref<i32>
+  }
+  return
+}
+
+// Test fir.do_loop that is not directly inside a func.func but nested inside
+// another region.
+//
+// subroutine implied_do_in_acc_serial(a)
+//   integer :: a(4)
+//   !$acc serial
+//   a = 1
+//   !$acc end serial
+// end
+
+// CHECK-LABEL: @implied_do_in_acc_serial
+// CHECK-NOT:   fir.do_loop
+
+func.func @implied_do_in_acc_serial(%arg0: !fir.ref<!fir.array<4xi32>> {fir.bindc_name = "a"}) {
+  %c1 = arith.constant 1 : index
+  %c1_i32 = arith.constant 1 : i32
+  %c4 = arith.constant 4 : index
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.shape %c4 : (index) -> !fir.shape<1>
+  %2 = fir.declare %arg0(%1) dummy_scope %0 {uniq_name = "_QFloopEa"} : (!fir.ref<!fir.array<4xi32>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<4xi32>>
+  acc.serial {
+    fir.do_loop %arg1 = %c1 to %c4 step %c1 unordered {
+      %3 = fir.array_coor %2(%1) %arg1 : (!fir.ref<!fir.array<4xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+      fir.store %c1_i32 to %3 : !fir.ref<i32>
+    }
+    acc.yield
+  }
+  return
+}
+
+// Test single result fir.do_loop conversion, edited down from:
+// subroutine printImplied(a)
+//   integer :: a(4)
+//   write(*,*) (a(i),i=1,4)
+// end
+
+// CHECK-LABEL: func.func @_QPprintimplied
+// CHECK:       [[SUB:%[0-9]+]] = arith.subi %c4, %c1 : index
+// CHECK:       [[ADD:%[0-9]+]] = arith.addi [[SUB]], %c1 : index
+// CHECK:       [[DIV:%[0-9]+]] = arith.divsi [[ADD]], %c1 : index
+// CHECK:       [[FOR:%[0-9]+]] = scf.for %{{.*}} = %{{.*}} to [[DIV]] step %{{.*}} iter_args(%{{.*}} = %c1) -> (index) {
+// CHECK:       [[IV:%[0-9]+]] = arith.addi %c1, {{.*}} : index
+// CHECK:       fir.convert [[IV]] : (index) -> i32
+// CHECK:       scf.yield
+
+func.func @_QPprintimplied(%arg0: !fir.ref<!fir.array<4xi32>> {fir.bindc_name = "a"}) {
+  %c1 = arith.constant 1 : index
+  %c4 = arith.constant 4 : index
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.shape %c4 : (index) -> !fir.shape<1>
+  %2 = fir.declare %arg0(%1) dummy_scope %0 {uniq_name = "_QFprintimpliedEa"} : (!fir.ref<!fir.array<4xi32>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<4xi32>>
+  %3 = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFprintimpliedEi"}
+  %4 = fir.declare %3 {uniq_name = "_QFprintimpliedEi"} : (!fir.ref<i32>) -> !fir.ref<i32>
+  %5 = fir.do_loop %arg1 = %c1 to %c4 step %c1 -> index {
+    %13 = fir.convert %arg1 : (index) -> i32
+    fir.store %13 to %4 : !fir.ref<i32>
+    %14 = fir.load %4 : !fir.ref<i32>
+    %15 = fir.convert %14 : (i32) -> i64
+    %16 = fir.array_coor %2(%1) %15 : (!fir.ref<!fir.array<4xi32>>, !fir.shape<1>, i64) -> !fir.ref<i32>
+    %17 = fir.load %16 : !fir.ref<i32>
+    %19 = arith.addi %arg1, %c1 : index
+    fir.result %19 : index
+  }
+  %6 = fir.convert %5 : (index) -> i32
+  fir.store %6 to %4 : !fir.ref<i32>
+  return
+}
+
+// Test of a simple maxval do loop.
+//
+// CHECK-LABEL: func.func @mv_
+// CHECK:       [[FOR:%[0-9]+]]:2 = scf.for
+// CHECK:       fir.store [[FOR]]#0
+
+func.func @mv_(%arg0: !fir.ref<!fir.array<3xi32>> {fir.bindc_name = "a", llvm.nocapture}, %arg1: !fir.ref<i32> {fir.bindc_name = "r", llvm.nocapture}) attributes {fir.internal_name = "_QPmv"} {
+  %false = arith.constant false
+  %c1 = arith.constant 1 : index
+  %true = arith.constant true
+  %c-2147483648_i32 = arith.constant -2147483648 : i32
+  %c3 = arith.constant 3 : index
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.shape %c3 : (index) -> !fir.shape<1>
+  %2 = fir.declare %arg0(%1) dummy_scope %0 {uniq_name = "_QFmvEa"} : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<3xi32>>
+  %3 = fir.declare %arg1 dummy_scope %0 {uniq_name = "_QFmvEr"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  %4:2 = fir.do_loop %arg2 = %c1 to %c3 step %c1 unordered iter_args(%arg3 = %c-2147483648_i32, %arg4 = %true) -> (i32, i1) {
+    %5 = fir.array_coor %2(%1) %arg2 : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+    %6 = fir.load %5 : !fir.ref<i32>
+    %7 = arith.cmpi sgt, %6, %arg3 : i32
+    %8 = arith.ori %7, %arg4 : i1
+    %9 = arith.select %8, %6, %arg3 : i32
+    fir.result %9, %false : i32, i1
+  }
+  fir.store %4#0 to %3 : !fir.ref<i32>
+  return
+}
+
+// CHECK-LABEL:   func.func @do_with_iv_result_noinc(
+// CHECK-SAME:        %[[ARG0:.*]]: index, %[[ARG1:.*]]: index, %[[ARG2:.*]]: index, %[[ARG3:.*]]: !fir.ref<index>) {
+// CHECK:           %[[VAL_0:.*]] = arith.subi %[[ARG1]], %[[ARG0]] : index
+// CHECK:           %[[VAL_1:.*]] = arith.addi %[[VAL_0]], %[[ARG2]] : index
+// CHECK:           %[[VAL_2:.*]] = arith.divsi %[[VAL_1]], %[[ARG2]] : index
+// CHECK:           %[[C0:.*]] = arith.constant 0 : index
+// CHECK:           %[[C1:.*]] = arith.constant 1 : index
+// CHECK:           scf.for %{{.*}} = %[[C0]] to %[[VAL_2]] step %[[C1]] iter_args(%{{.*}} = %[[ARG0]]) -> (index) {
+// CHECK:             %[[MUL:.*]] = arith.muli %{{.*}}, %[[ARG2]] : index
+// CHECK:             %[[ADD0:.*]] = arith.addi %[[ARG0]], %[[MUL]] : index
+// CHECK:             %[[ADD1:.*]] = arith.addi %[[ADD0]], %[[ARG2]] : index
+// CHECK:             scf.yield %[[ADD1]] : index
+// CHECK:           }
+
+func.func @do_with_iv_result_noinc(%arg0: index, %arg1: index, %arg2: index, %arg3: !fir.ref<index>) {
+  %0 = fir.do_loop %arg4 = %arg0 to %arg1 step %arg2 -> index {
+    fir.result %arg4 : index
+  }
+  fir.store %0 to %arg3 : !fir.ref<index>
+  return
+}
+
+
diff --git a/flang/test/Fir/FirToSCF/if-extra.fir b/flang/test/Fir/FirToSCF/if-extra.fir
new file mode 100644
index 0000000000000..ef9a2e53076db
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/if-extra.fir
@@ -0,0 +1,122 @@
+// Test conversions of fir.if (extra coverage vs upstream `if.fir`).
+//
+// RUN: fir-opt %s --fir-to-scf --allow-unregistered-dialect | FileCheck %s
+
+// Two sided if conversion
+//  subroutine double(x)
+//    integer :: x
+//    if (x .lt. 0) then
+//      x = 0
+//    else
+//      x = 1
+//    endif
+//  end
+
+// CHECK-LABEL: func.func @double
+// CHECK:       [[COND:%[0-9]+]] = arith.cmpi
+// CHECK-NEXT:  scf.if [[COND]] {
+// CHECK-NEXT:  fir.store
+// CHECK-NEXT:  } else {
+// CHECK-NEXT:  fir.store
+// CHECK-NEXT:  }
+
+func.func @double(%arg0: !fir.ref<i32> {fir.bindc_name = "x"}) {
+  %c1_i32 = arith.constant 1 : i32
+  %c0_i32 = arith.constant 0 : i32
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.declare %arg0 dummy_scope %0 {uniq_name = "double"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  %2 = fir.load %1 : !fir.ref<i32>
+  %3 = arith.cmpi slt, %2, %c0_i32 : i32
+  fir.if %3 {
+    fir.store %c0_i32 to %1 : !fir.ref<i32>
+  } else {
+    fir.store %c1_i32 to %1 : !fir.ref<i32>
+  }
+  return
+}
+
+// One sided if conversion
+// subroutine single(x)
+//   integer :: x
+//   if (x .gt. 0) x = 0
+// end
+
+// CHECK-LABEL: func.func @single
+// CHECK:       [[COND:%[0-9]+]] = arith.cmpi
+// CHECK-NEXT:  scf.if [[COND]] {
+// CHECK-NEXT:  fir.store
+// CHECK-NEXT:  } else {
+// CHECK-NEXT:  }
+
+func.func @single(%arg0: !fir.ref<i32> {fir.bindc_name = "x"}) {
+  %c0_i32 = arith.constant 0 : i32
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.declare %arg0 dummy_scope %0 {uniq_name = "single"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  %2 = fir.load %1 : !fir.ref<i32>
+  %3 = arith.cmpi sgt, %2, %c0_i32 : i32
+  fir.if %3 {
+    fir.store %c0_i32 to %1 : !fir.ref<i32>
+  } else {
+  }
+  return
+}
+
+// Result if conversion (written by hand)
+
+// CHECK-LABEL: func.func @if_result
+// CHECK:       [[COND:%[0-9]+]] = arith.cmpi
+// CHECK-NEXT:  [[RES:%[0-9]+]] = scf.if [[COND]] -> (i32) {
+// CHECK-NEXT:    [[CON5:%.+]] = arith.constant 5 : i32
+// CHECK-NEXT:    scf.yield [[CON5]]
+// CHECK-NEXT:  } else {
+// CHECK-NEXT:    [[CON3:%.+]] = arith.constant 3 : i32
+// CHECK-NEXT:    scf.yield [[CON3]]
+// CHECK-NEXT:  }
+// CHECK-NEXT:  return [[RES]] : i32
+
+func.func @if_result(%arg0: !fir.ref<i32> {fir.bindc_name = "c"}) -> i32 {
+  %c0 = arith.constant 0 : i32
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.declare %arg0 dummy_scope %0 {uniq_name = "c"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  %2 = fir.load %1 : !fir.ref<i32>
+  %3 = arith.cmpi sgt, %2, %c0 : i32
+  %4 = fir.if %3 -> i32 {
+    %c5 = arith.constant 5 : i32
+    fir.result %c5 : i32
+  } else {
+    %c3 = arith.constant 3 : i32
+    fir.result %c3 : i32
+  }
+  return %4 : i32
+}
+
+// CHECK-LABEL: func.func @if_result_multi
+// CHECK:       [[COND:%[0-9]+]] = arith.cmpi
+// CHECK-NEXT:  [[IF:%[0-9]+]]:2 = scf.if [[COND]] -> (i32, i32) {
+// CHECK-NEXT:    [[CON5:%.+]] = arith.constant 5 : i32
+// CHECK-NEXT:    scf.yield [[CON5]]
+// CHECK-NEXT:  } else {
+// CHECK-NEXT:    [[CON3:%.+]] = arith.constant 3 : i32
+// CHECK-NEXT:    scf.yield [[CON3]]
+// CHECK-NEXT:  }
+// CHECK-NEXT:  [[RES:%[0-9]+]] = arith.addi [[IF]]#0, [[IF]]#1
+// CHECK-NEXT:  return [[RES]] : i32
+
+func.func @if_result_multi(%arg0: !fir.ref<i32> {fir.bindc_name = "c"}) -> i32 {
+  %c0 = arith.constant 0 : i32
+  %0 = fir.dummy_scope : !fir.dscope
+  %1 = fir.declare %arg0 dummy_scope %0 {uniq_name = "c"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  %2 = fir.load %1 : !fir.ref<i32>
+  %3 = arith.cmpi sgt, %2, %c0 : i32
+  %4:2 = fir.if %3 -> (i32, i32) {
+    %c5 = arith.constant 5 : i32
+    fir.result %c5, %c5 : i32, i32
+  } else {
+    %c3 = arith.constant 3 : i32
+    fir.result %c3, %c3 : i32, i32
+  }
+  %5 = arith.addi %4#0, %4#1 : i32
+  return %5 : i32
+}
+
+
diff --git a/flang/test/Fir/FirToSCF/iterate-while-extra.fir b/flang/test/Fir/FirToSCF/iterate-while-extra.fir
new file mode 100644
index 0000000000000..86fae1e76fd23
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/iterate-while-extra.fir
@@ -0,0 +1,117 @@
+// RUN: fir-opt %s --fir-to-scf --allow-unregistered-dialect | FileCheck %s
+
+// derived from:
+// subroutine ido3
+//   integer :: j
+//   write(*,*,err=404) (j,j=1,10)
+// 404 continue
+// end subroutine
+
+// CHECK-LABEL: func.func @iterate_while
+// CHECK:       scf.while (%[[IV:.*]] = %c1, %[[OK:.*]] = %true) : (index, i1) -> (index, i1) {
+// CHECK:         arith.cmpi
+// CHECK:         arith.cmpi
+// CHECK:         arith.andi
+// CHECK:         scf.condition
+// CHECK:       } do {
+// CHECK:       ^bb0
+// CHECK:         scf.if %[[OK]]
+// CHECK:         scf.yield
+
+func.func @iterate_while() {
+  %c10 = arith.constant 10 : index
+  %c1 = arith.constant 1 : index
+  %true = arith.constant true
+  %false = arith.constant false
+  %c4_i32 = arith.constant 4 : i32
+  %c6_i32 = arith.constant 6 : i32
+  %0 = fir.alloca i32 {bindc_name = "j", uniq_name = "_QFido3Ej"}
+  %1 = fircg.ext_declare %0 {uniq_name = "_QFido3Ej"} : (!fir.ref<i32>) -> !fir.ref<i32>
+  %2 = fir.address_of(@_QQclX16525d77bf698d01a3a03f9bc4435ebe) : !fir.ref<!fir.char<1,45>>
+  %3 = fir.convert %2 : (!fir.ref<!fir.char<1,45>>) -> !fir.ref<i8>
+  %4 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %3, %c4_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+  %5 = fir.call @_FortranAioEnableHandlers(%4, %false, %true, %false, %false, %false) fastmath<contract> : (!fir.ref<i8>, i1, i1, i1, i1, i1) -> none
+  %6:2 = fir.iterate_while (%arg0 = %c1 to %c10 step %c1) and (%arg1 = %true) -> (index, i1) {
+    %10 = arith.index_cast %arg0 : index to i32
+    fir.store %10 to %1 : !fir.ref<i32>
+    %11 = fir.if %arg1 -> (i1) {
+      %14 = fir.load %1 : !fir.ref<i32>
+      %15 = fir.call @_FortranAioOutputInteger32(%4, %14) fastmath<contract> : (!fir.ref<i8>, i32) -> i1
+      fir.result %15 : i1
+    } else {
+      fir.result %false : i1
+    }
+    %12 = arith.addi %arg0, %c1 overflow<nsw> : index
+    %13 = arith.select %11, %12, %arg0 : index
+    fir.result %13, %11 : index, i1
+  }
+  %7 = arith.index_cast %6#0 : index to i32
+  fir.store %7 to %1 : !fir.ref<i32>
+  %8 = fir.call @_FortranAioEndIoStatement(%4) fastmath<contract> : (!fir.ref<i8>) -> i32
+  %9 = arith.index_cast %8 : i32 to index
+  fir.select %9 : index [0, ^bb1, unit, ^bb1]
+^bb1:  // 2 preds: ^bb0, ^bb0
+  return
+}
+
+// Check if iv is added to scf.while result, and if the normalized loop will
+// return adjusted_iv+offset.
+//
+// derived from:
+// subroutine sub1(string)
+//  implicit none
+//  character*(*) string
+//  print *, len_trim(string)
+// end subroutine
+
+// CHECK-LABEL: func.func @normalize
+// CHECK:       [[WHILE:%[0-9]+]]:3 = scf.while (%[[IVIN:.*]] = %7, %[[OKIN:.*]] = %true, %[[IV2IN:.*]] = %7) : (index, i1, index) -> (index, i1, index) {
+// CHECK:         [[C0:%.*]] = arith.constant 0 : index
+// CHECK:         [[CMP0:%[0-9]+]] = arith.cmpi slt, [[C0]], %c-1 : index
+// CHECK:         [[CMP1:%[0-9]+]] = arith.cmpi sle, %[[IVIN]], %c0 : index
+// CHECK:         [[CMP2:%[0-9]+]] = arith.cmpi slt, %c-1, [[C0]] : index
+// CHECK:         [[CMP3:%[0-9]+]] = arith.cmpi sge, %[[IVIN]], %c0 : index
+// CHECK:         [[AND0:%[0-9]+]] = arith.andi [[CMP0]], [[CMP1]] : i1
+// CHECK:         [[AND1:%[0-9]+]] = arith.andi [[CMP2]], [[CMP3]] : i1
+// CHECK:         [[OR:%[0-9]+]] = arith.ori [[AND0]], [[AND1]] : i1
+// CHECK:         [[AND2:%[0-9]+]] = arith.andi %[[OKIN]], [[OR]] : i1
+// CHECK:         scf.condition([[AND2]]) %[[IVIN]], %[[OKIN]], %[[IV2IN]] : index, i1, index
+// CHECK:       } do {
+// CHECK:       ^bb0(%[[IV:.*]]: index, %[[OK:.*]]: i1, %[[IV2:.*]]: index):
+// CHECK:         [[NEXT:%[0-9]+]] = arith.addi %[[IV]], %c-1 : index
+// CHECK:         [[ISSPACE:%[0-9]+]] = arith.cmpi eq, %{{.*}}, %c32_i8 : i8
+// CHECK:         scf.yield [[NEXT]], [[ISSPACE]], %[[IV]] : index, i1, index
+
+func.func @normalize(%arg0: !fir.ref<!fir.char<1,?>> {fir.bindc_name = "string"}, %arg1: i64) {
+  %0 = fir.emboxchar %arg0, %arg1 : (!fir.ref<!fir.char<1,?>>, i64) -> !fir.boxchar<1>
+  %c32_i8 = arith.constant 32 : i8
+  %true = arith.constant true
+  %c0 = arith.constant 0 : index
+  %c-1 = arith.constant -1 : index
+  %c1 = arith.constant 1 : index
+  %c5_i32 = arith.constant 5 : i32
+  %c6_i32 = arith.constant 6 : i32
+  %1 = fir.undefined !fir.dscope
+  %2:2 = fir.unboxchar %0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+  %3 = fircg.ext_declare %2#0 typeparams %2#1 dummy_scope %1 {uniq_name = "_QFsub1Estring"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> !fir.ref<!fir.char<1,?>>
+  %4 = fir.address_of(@_QQclX9c7b903babe9837742def6df4989c6e7) : !fir.ref<!fir.char<1,40>>
+  %5 = fir.convert %4 : (!fir.ref<!fir.char<1,40>>) -> !fir.ref<i8>
+  %6 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %5, %c5_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+  %7 = arith.subi %2#1, %c1 : index
+  %8:2 = fir.iterate_while (%arg2 = %7 to %c0 step %c-1) and (%arg3 = %true) iter_args(%arg4 = %7) -> (index) {
+    %14 = fir.convert %3 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<!fir.array<?x!fir.char<1>>>
+    %15 = fir.coordinate_of %14, %arg2 : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+    %16 = fir.convert %15 : (!fir.ref<!fir.char<1>>) -> !fir.ref<i8>
+    %17 = fir.load %16 : !fir.ref<i8>
+    %18 = arith.cmpi eq, %17, %c32_i8 : i8
+    fir.result %18, %arg2 : i1, index
+  }
+  %9 = arith.addi %8#1, %c1 : index
+  %10 = arith.select %8#0, %c0, %9 : index
+  %11 = arith.index_cast %10 : index to i32
+  %12 = fir.call @_FortranAioOutputInteger32(%6, %11) fastmath<contract> : (!fir.ref<i8>, i32) -> i1
+  %13 = fir.call @_FortranAioEndIoStatement(%6) fastmath<contract> : (!fir.ref<i8>) -> i32
+  return
+}
+
+
diff --git a/flang/test/Fir/FirToSCF/nested-unregistered.fir b/flang/test/Fir/FirToSCF/nested-unregistered.fir
new file mode 100644
index 0000000000000..11de94fecf6bb
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/nested-unregistered.fir
@@ -0,0 +1,22 @@
+// Test conversions of nested ops, including a fir.do_loop nested inside an
+// OpenACC kernels region.
+//
+// RUN: fir-opt %s --fir-to-scf | FileCheck %s
+
+// CHECK-LABEL: func.func @nested_region
+// CHECK-NOT:   fir.do_loop
+
+func.func @nested_region() {
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  fir.do_loop %arg0 = %c1 to %c2 step %c1 {
+    acc.kernels {
+      fir.do_loop %arg1 = %c1 to %c2 step %c1 {
+      }
+      acc.terminator
+    }
+  }
+  return
+}
+
+
diff --git a/flang/test/Fir/FirToSCF/normalize.fir b/flang/test/Fir/FirToSCF/normalize.fir
new file mode 100644
index 0000000000000..6e43f8664586a
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/normalize.fir
@@ -0,0 +1,69 @@
+// Test normalization behavior of fir.do_loop lowering to scf.
+//
+// RUN: fir-opt %s --fir-to-scf --allow-unregistered-dialect | FileCheck %s
+
+// Test that loops with unknown step are normalized.
+//
+// e.g.
+// subroutine loop(a,lb,ub,step)
+//   integer :: a(8), lb, ub, step
+//   do i = lb, ub, step
+//     a(i) = i
+//   enddo
+// end
+
+// CHECK-LABEL: func.func @unknown_step_
+// CHECK:       [[LB:%[0-9]+]] = arith.index_cast %arg0
+// CHECK:       [[UB:%[0-9]+]] = arith.index_cast %arg1
+// CHECK:       [[STEP:%[0-9]+]] = arith.index_cast %arg2
+// CHECK:       [[SUB:%[0-9]+]] = arith.subi [[UB]], [[LB]] : index
+// CHECK-NEXT:  [[ADD:%[0-9]+]] = arith.addi [[SUB]], [[STEP]] : index
+// CHECK-NEXT:  [[DIV:%[0-9]+]] = arith.divsi [[ADD]], [[STEP]] : index
+// CHECK:       scf.for %arg4 = %c0{{.*}} to [[DIV]] step %c1{{.*}}
+// CHECK-NEXT:    [[IVMUL:%[0-9]+]] = arith.muli %arg4, [[STEP]] : index
+// CHECK-NEXT:    [[IVADD:%[0-9]+]] = arith.addi [[LB]], [[IVMUL]] : index
+// CHECK-NEXT:    memref.store [[IVADD]], %arg3[]
+
+func.func @unknown_step_(%arg0: i64, %arg1: i64, %arg2: i64, %arg3: memref<index>) {
+  %0 = arith.index_cast %arg0 : i64 to index
+  %1 = arith.index_cast %arg1 : i64 to index
+  %2 = arith.index_cast %arg2 : i64 to index
+  fir.do_loop %arg4 = %0 to %1 step %2 {
+    memref.store %arg4, %arg3[] : memref<index>
+  }
+  return
+}
+
+// Test that loops with known negative step are normalized.
+//
+// e.g.
+// subroutine loop(a)
+//   integer :: a(8)
+//   do i = 8, 1, -1
+//     a(i) = i
+//   enddo
+// end
+
+// CHECK-LABEL: func.func @negative_step_
+// CHECK:       [[LB:%.+]] = arith.constant 8
+// CHECK:       [[UB:%.+]] = arith.constant 1
+// CHECK:       [[STEP:%.+]] = arith.constant -1
+// CHECK:       [[SUB:%[0-9]+]] = arith.subi [[UB]], [[LB]] : index
+// CHECK-NEXT:  [[ADD:%[0-9]+]] = arith.addi [[SUB]], [[STEP]] : index
+// CHECK-NEXT:  [[DIV:%[0-9]+]] = arith.divsi [[ADD]], [[STEP]] : index
+// CHECK:       scf.for %arg1 = %c0{{.*}} to [[DIV]] step %c1{{.*}}
+// CHECK-NEXT:    [[IVMUL:%[0-9]+]] = arith.muli %arg1, [[STEP]] : index
+// CHECK-NEXT:    [[IVADD:%[0-9]+]] = arith.addi [[LB]], [[IVMUL]] : index
+// CHECK-NEXT:    memref.store [[IVADD]], %arg0[]
+
+func.func @negative_step_(%arg0: memref<index>) {
+  %c8 = arith.constant 8 : index
+  %c1 = arith.constant 1 : index
+  %c-1 = arith.constant -1 : index
+  fir.do_loop %arg1 = %c8 to %c1 step %c-1 {
+    memref.store %arg1, %arg0[] : memref<index>
+  }
+  return
+}
+
+
diff --git a/flang/test/Fir/FirToSCF/sum.fir b/flang/test/Fir/FirToSCF/sum.fir
new file mode 100644
index 0000000000000..a14f47aa9c419
--- /dev/null
+++ b/flang/test/Fir/FirToSCF/sum.fir
@@ -0,0 +1,48 @@
+// RUN: fir-opt %s --fir-to-scf --allow-unregistered-dialect | FileCheck %s
+
+// Test that compiler generated fir.do_loop with a result from an iter_arg
+// via a nested fir.if is converted.
+//
+// derived from:
+// subroutine reduce(a,r)
+//   integer :: a(3,3), r(3)
+//   r = sum(a,dim=1,mask =.true.)
+// end
+
+// CHECK-LABEL: func.func @reduce_
+// CHECK:       [[FOR_RESULT:%[0-9]+]] = scf.for
+// CHECK:       [[IF_RESULT:%[0-9]+]] = scf.if
+// CHECK:       scf.yield
+// CHECK-NEXT:  } else {
+// CHECK-NEXT:  scf.yield
+// CHECK-NEXT:  }
+// CHECK-NEXT:  scf.yield [[IF_RESULT]]
+// CHECK:       fir.store [[FOR_RESULT]]
+
+func.func @reduce_(%arg0: !fir.ref<!fir.array<3x3xi32>> {fir.bindc_name = "a"}, %arg1: !fir.ref<!fir.array<3xi32>> {fir.bindc_name = "r"}) attributes {fir.internal_name = "_QPreduce", noinline} {
+  %c0_i32 = arith.constant 0 : i32
+  %c1 = arith.constant 1 : index
+  %true = arith.constant true
+  %c3 = arith.constant 3 : index
+  %0 = fir.undefined !fir.dscope
+  %1 = fircg.ext_declare %arg0(%c3, %c3) dummy_scope %0 {uniq_name = "_QFreduceEa"} : (!fir.ref<!fir.array<3x3xi32>>, index, index, !fir.dscope) -> !fir.ref<!fir.array<3x3xi32>>
+  %2 = fircg.ext_declare %arg1(%c3) dummy_scope %0 {uniq_name = "_QFreduceEr"} : (!fir.ref<!fir.array<3xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<3xi32>>
+  fir.do_loop %arg2 = %c1 to %c3 step %c1 unordered {
+    %3 = fir.do_loop %arg3 = %c1 to %c3 step %c1 unordered iter_args(%arg4 = %c0_i32) -> (i32) {
+      %5 = fir.if %true -> (i32) {
+        %6 = fircg.ext_array_coor %1(%c3, %c3)<%arg3, %arg2> : (!fir.ref<!fir.array<3x3xi32>>, index, index, index, index) -> !fir.ref<i32>
+        %7 = fir.load %6 : !fir.ref<i32>
+        %8 = arith.addi %arg4, %7 : i32
+        fir.result %8 : i32
+      } else {
+        fir.result %arg4 : i32
+      }
+      fir.result %5 : i32
+    }
+    %4 = fircg.ext_array_coor %2(%c3)<%arg2> : (!fir.ref<!fir.array<3xi32>>, index, index) -> !fir.ref<i32>
+    fir.store %3 to %4 : !fir.ref<i32>
+  }
+  return
+}
+
+

>From d8a9c320353e72bbd7b6e924d3bed944f3979b8f Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Wed, 14 Jan 2026 13:18:22 -0800
Subject: [PATCH 2/3] add to utility

---
 flang/lib/Lower/OpenACC.cpp | 84 ++++++++++++++++++++++++-------------
 1 file changed, 56 insertions(+), 28 deletions(-)

diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index cfbcf06c4b39e..4e34b1da6c3a4 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -946,6 +946,28 @@ emitCtorDtorPair(mlir::OpBuilder &modBuilder, fir::FirOpBuilder &builder,
                                 /*implicit=*/false, asFortran);
 }
 
+/// Return true iff this OpenACC clause is valid for lowering a global/COMMON
+/// symbol via module-level global ctor/dtor. Other clauses must be handled
+/// through structured declare lowering (e.g. `present`, `deviceptr`, `copy`).
+///
+/// OpenACC 3.0:
+/// - 3000: In a Fortran module declaration section, only create, copyin,
+///         device_resident clauses are allowed.
+/// - 3001: link is also allowed.
+static bool isValidClauseForGlobalDeclare(mlir::acc::DataClause clause) {
+  switch (clause) {
+  case mlir::acc::DataClause::acc_create:
+  case mlir::acc::DataClause::acc_create_zero:
+  case mlir::acc::DataClause::acc_copyin:
+  case mlir::acc::DataClause::acc_copyin_readonly:
+  case mlir::acc::DataClause::acc_declare_device_resident:
+  case mlir::acc::DataClause::acc_declare_link:
+    return true;
+  default:
+    return false;
+  }
+}
+
 template <typename EntryOp, typename ExitOp>
 static void genDeclareDataOperandOperations(
     const Fortran::parser::AccObjectList &objectList,
@@ -964,34 +986,40 @@ static void genDeclareDataOperandOperations(
     // Handle COMMON/global symbols via module-level ctor/dtor path.
     if (symbol.detailsIf<Fortran::semantics::CommonBlockDetails>() ||
         Fortran::semantics::FindCommonBlockContaining(symbol)) {
-      emitCommonGlobal(
-          converter, builder, accObject, dataClause,
-          [&](mlir::OpBuilder &modBuilder, [[maybe_unused]] mlir::Location loc,
-              [[maybe_unused]] fir::GlobalOp globalOp,
-              [[maybe_unused]] mlir::acc::DataClause clause,
-              std::stringstream &asFortranStr, const std::string &ctorName) {
-            if constexpr (std::is_same_v<EntryOp, mlir::acc::DeclareLinkOp>) {
-              createDeclareGlobalOp<
-                  mlir::acc::GlobalConstructorOp, mlir::acc::DeclareLinkOp,
-                  mlir::acc::DeclareEnterOp, mlir::acc::DeclareLinkOp>(
-                  modBuilder, builder, loc, globalOp, clause, ctorName,
-                  /*implicit=*/false, asFortranStr);
-            } else if constexpr (std::is_same_v<EntryOp, mlir::acc::CreateOp> ||
-                                 std::is_same_v<EntryOp, mlir::acc::CopyinOp> ||
-                                 std::is_same_v<
-                                     EntryOp,
-                                     mlir::acc::DeclareDeviceResidentOp> ||
-                                 std::is_same_v<ExitOp, mlir::acc::CopyoutOp>) {
-              emitCtorDtorPair<EntryOp, ExitOp>(modBuilder, builder, loc,
-                                                globalOp, clause, asFortranStr,
-                                                ctorName);
-            } else {
-              // No module-level ctor/dtor for this clause (e.g., deviceptr,
-              // present). Handled via structured declare region only.
-              return;
-            }
-          });
-      continue;
+      // Only certain clauses are valid for module-level global ctor/dtor.
+      // Other clauses (e.g. present/deviceptr/copy) are handled with structured
+      // declare lowering below.
+      if (isValidClauseForGlobalDeclare(dataClause)) {
+        emitCommonGlobal(
+            converter, builder, accObject, dataClause,
+            [&](mlir::OpBuilder &modBuilder,
+                [[maybe_unused]] mlir::Location loc,
+                [[maybe_unused]] fir::GlobalOp globalOp,
+                [[maybe_unused]] mlir::acc::DataClause clause,
+                std::stringstream &asFortranStr, const std::string &ctorName) {
+              if constexpr (std::is_same_v<EntryOp, mlir::acc::DeclareLinkOp>) {
+                createDeclareGlobalOp<
+                    mlir::acc::GlobalConstructorOp, mlir::acc::DeclareLinkOp,
+                    mlir::acc::DeclareEnterOp, mlir::acc::DeclareLinkOp>(
+                    modBuilder, builder, loc, globalOp, clause, ctorName,
+                    /*implicit=*/false, asFortranStr);
+              } else if constexpr (
+                  std::is_same_v<EntryOp, mlir::acc::CreateOp> ||
+                  std::is_same_v<EntryOp, mlir::acc::CopyinOp> ||
+                  std::is_same_v<EntryOp, mlir::acc::DeclareDeviceResidentOp>) {
+                // Module-level ctor/dtor path for valid global declare clauses.
+                emitCtorDtorPair<EntryOp, ExitOp>(modBuilder, builder, loc,
+                                                  globalOp, clause,
+                                                  asFortranStr, ctorName);
+              } else {
+                // For other EntryOp kinds (e.g. deviceptr/present/copy), the
+                // structured declare lowering below is responsible for
+                // generating the correct IR.
+                return;
+              }
+            });
+        continue;
+      }
     }
     Fortran::semantics::MaybeExpr designator = Fortran::common::visit(
         [&](auto &&s) { return ea.Analyze(s); }, accObject.u);

>From 9aa64933dca07988bc5326b64454bc909b1d9832 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Wed, 14 Jan 2026 13:21:36 -0800
Subject: [PATCH 3/3] tweak

---
 flang/lib/Lower/OpenACC.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 4e34b1da6c3a4..8f96ac2cf143c 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -992,10 +992,8 @@ static void genDeclareDataOperandOperations(
       if (isValidClauseForGlobalDeclare(dataClause)) {
         emitCommonGlobal(
             converter, builder, accObject, dataClause,
-            [&](mlir::OpBuilder &modBuilder,
-                [[maybe_unused]] mlir::Location loc,
-                [[maybe_unused]] fir::GlobalOp globalOp,
-                [[maybe_unused]] mlir::acc::DataClause clause,
+            [&](mlir::OpBuilder &modBuilder, mlir::Location loc,
+                fir::GlobalOp globalOp, mlir::acc::DataClause clause,
                 std::stringstream &asFortranStr, const std::string &ctorName) {
               if constexpr (std::is_same_v<EntryOp, mlir::acc::DeclareLinkOp>) {
                 createDeclareGlobalOp<



More information about the flang-commits mailing list