[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