[flang-commits] [flang] 234b846 - [Flang] Compute elemental character MIN/MAX result length in HLFIR (#189464)

via flang-commits flang-commits at lists.llvm.org
Wed Apr 1 08:12:57 PDT 2026


Author: Sairudra More
Date: 2026-04-01T20:42:51+05:30
New Revision: 234b8469e92aa3bfcc08e5b7abd6fcbb11a258a6

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

LOG: [Flang] Compute elemental character MIN/MAX result length in HLFIR (#189464)

This patch fixes lowering of elemental character MIN/MAX in HLFIR.

Previously, these cases could hit a lowering-time TODO
`ElementalIntrinsicCallBuilder::computeDynamicCharacterResultLength` and
abort. This change computes the character result length as the maximum
length of the present actual arguments, allowing valid elemental
character MIN/MAX calls to lower successfully.

Added regression coverage for elemental character MIN/MAX, including
differing-length arguments.

Co-authored-by: Sairudra More <moresair at pe31.hpc.amslabs.hpecorp.net>

Added: 
    flang/test/Lower/HLFIR/elemental-character-min-max.f90

Modified: 
    flang/lib/Lower/ConvertCall.cpp

Removed: 
    


################################################################################
diff  --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index ae9d1733d053d..ea65a49355330 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -2758,10 +2758,53 @@ class ElementalIntrinsicCallBuilder
           intrinsic->name == "merge")
         return loweredActuals[0].value().genCharLength(
             callContext.loc, callContext.getBuilder());
-    // Character MIN/MAX is the min/max of the arguments length that are
-    // present.
-    TODO(callContext.loc,
-         "compute elemental character min/max function result length in HLFIR");
+    // Character MIN/MAX result length is the length of the longest
+    // argument that is present.
+    assert(intrinsic &&
+           (intrinsic->name == "min" || intrinsic->name == "max") &&
+           "unexpected elemental intrinsic with character result");
+    fir::FirOpBuilder &builder = callContext.getBuilder();
+    mlir::Location loc = callContext.loc;
+    mlir::Type idxTy = builder.getIndexType();
+    mlir::Value resultLength;
+    for (auto &preparedActual : loweredActuals) {
+      if (!preparedActual)
+        continue;
+      mlir::Value argLen;
+      if (preparedActual->handleDynamicOptional()) {
+        // genCharLength must not be called on an absent optional: the
+        // descriptor may be unreadable (e.g. assumed-length character).
+        // Guard it with a fir.if so the read only happens when present.
+        mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+        mlir::Value isPresent = preparedActual->getIsPresent();
+        auto &capture = *preparedActual;
+        argLen =
+            builder
+                .genIfOp(loc, {idxTy}, isPresent,
+                         /*withElseRegion=*/true)
+                .genThen([&]() {
+                  mlir::Value len = capture.genCharLength(loc, builder);
+                  len = builder.createConvert(loc, idxTy, len);
+                  fir::ResultOp::create(builder, loc, len);
+                })
+                .genElse([&]() { fir::ResultOp::create(builder, loc, zero); })
+                .getResults()[0];
+      } else {
+        argLen = preparedActual->genCharLength(loc, builder);
+        argLen = builder.createConvert(loc, idxTy, argLen);
+      }
+      if (!resultLength) {
+        resultLength = argLen;
+      } else {
+        mlir::Value cmp = mlir::arith::CmpIOp::create(
+            builder, loc, mlir::arith::CmpIPredicate::sgt, argLen,
+            resultLength);
+        resultLength = mlir::arith::SelectOp::create(builder, loc, cmp, argLen,
+                                                     resultLength);
+      }
+    }
+    assert(resultLength && "MIN/MAX must have at least two arguments");
+    return resultLength;
   }
 
   mlir::Value getPolymorphicResultMold(

diff  --git a/flang/test/Lower/HLFIR/elemental-character-min-max.f90 b/flang/test/Lower/HLFIR/elemental-character-min-max.f90
new file mode 100644
index 0000000000000..fc149f0759188
--- /dev/null
+++ b/flang/test/Lower/HLFIR/elemental-character-min-max.f90
@@ -0,0 +1,129 @@
+! Test lowering of elemental character MIN/MAX to HLFIR
+! RUN: bbc -emit-hlfir -o - %s | FileCheck %s
+
+! Test elemental character MIN with two array arguments of the same length.
+subroutine test_elemental_char_min(a, b, res)
+  character(5) :: a(10), b(10), res(10)
+  res = min(a, b)
+end subroutine
+! CHECK-LABEL:   func.func @_QPtest_elemental_char_min(
+! CHECK:           %[[C5_A:.*]] = arith.constant 5 : index
+! CHECK:           %[[A:.*]]:2 = hlfir.declare {{.*}}Ea"
+! CHECK:           %[[C5_B:.*]] = arith.constant 5 : index
+! CHECK:           %[[B:.*]]:2 = hlfir.declare {{.*}}Eb"
+! CHECK:           %[[RES:.*]]:2 = hlfir.declare {{.*}}Eres"
+! CHECK:           %[[CMP:.*]] = arith.cmpi sgt, %[[C5_B]], %[[C5_A]] : index
+! CHECK:           %[[RESULT_LEN:.*]] = arith.select %[[CMP]], %[[C5_B]], %[[C5_A]] : index
+! CHECK:           %[[ELEMENTAL:.*]] = hlfir.elemental %{{.*}} typeparams %[[RESULT_LEN]] unordered : (!fir.shape<1>, index) -> !hlfir.expr<10x!fir.char<1,?>> {
+! CHECK:           ^bb0(%[[IDX:.*]]: index):
+! CHECK:             %{{.*}} = hlfir.char_extremum min, %{{.*}}, %{{.*}} :
+! CHECK:             hlfir.yield_element
+! CHECK:           }
+
+! Test elemental character MAX with two array arguments.
+subroutine test_elemental_char_max(a, b, res)
+  character(5) :: a(10), b(10), res(10)
+  res = max(a, b)
+end subroutine
+! CHECK-LABEL:   func.func @_QPtest_elemental_char_max(
+! CHECK:           %[[ELEMENTAL:.*]] = hlfir.elemental %{{.*}} typeparams %{{.*}} unordered : (!fir.shape<1>, index) -> !hlfir.expr<10x!fir.char<1,?>> {
+! CHECK:           ^bb0(%[[IDX:.*]]: index):
+! CHECK:             %{{.*}} = hlfir.char_extremum max, %{{.*}}, %{{.*}} :
+! CHECK:             hlfir.yield_element
+! CHECK:           }
+
+! Test elemental character MIN with 
diff erent argument lengths.
+! The result length must be the maximum of the argument lengths.
+subroutine test_elemental_char_min_
diff _len(a, b, res)
+  character(5) :: a(10)
+  character(7) :: b(10), res(10)
+  res = min(a, b)
+end subroutine
+! CHECK-LABEL:   func.func @_QPtest_elemental_char_min_
diff _len(
+! CHECK:           %[[C5:.*]] = arith.constant 5 : index
+! CHECK:           %[[A:.*]]:2 = hlfir.declare {{.*}}Ea"
+! CHECK:           %[[C7:.*]] = arith.constant 7 : index
+! CHECK:           %[[B:.*]]:2 = hlfir.declare {{.*}}Eb"
+! CHECK:           %[[CMP:.*]] = arith.cmpi sgt, %[[C7]], %[[C5]] : index
+! CHECK:           %[[RESULT_LEN:.*]] = arith.select %[[CMP]], %[[C7]], %[[C5]] : index
+! CHECK:           %[[ELEMENTAL:.*]] = hlfir.elemental %{{.*}} typeparams %[[RESULT_LEN]] unordered : (!fir.shape<1>, index) -> !hlfir.expr<10x!fir.char<1,?>> {
+
+! Test elemental character MIN with three array arguments.
+! The result length must be the maximum across all argument lengths.
+subroutine test_elemental_char_min_three(a, b, c, res)
+  character(5) :: a(10), b(10), c(10), res(10)
+  res = min(a, b, c)
+end subroutine
+! CHECK-LABEL:   func.func @_QPtest_elemental_char_min_three(
+! CHECK:           %[[C5_A:.*]] = arith.constant 5 : index
+! CHECK:           %[[A:.*]]:2 = hlfir.declare {{.*}}Ea"
+! CHECK:           %[[C5_B:.*]] = arith.constant 5 : index
+! CHECK:           %[[B:.*]]:2 = hlfir.declare {{.*}}Eb"
+! CHECK:           %[[C5_C:.*]] = arith.constant 5 : index
+! CHECK:           %[[C:.*]]:2 = hlfir.declare {{.*}}Ec"
+! CHECK:           %[[CMP1:.*]] = arith.cmpi sgt, %[[C5_B]], %[[C5_A]] : index
+! CHECK:           %[[MAX1:.*]] = arith.select %[[CMP1]], %[[C5_B]], %[[C5_A]] : index
+! CHECK:           %[[CMP2:.*]] = arith.cmpi sgt, %[[C5_C]], %[[MAX1]] : index
+! CHECK:           %[[RESULT_LEN:.*]] = arith.select %[[CMP2]], %[[C5_C]], %[[MAX1]] : index
+! CHECK:           %[[ELEMENTAL:.*]] = hlfir.elemental %{{.*}} typeparams %[[RESULT_LEN]] unordered : (!fir.shape<1>, index) -> !hlfir.expr<10x!fir.char<1,?>> {
+! CHECK:           ^bb0(%[[IDX:.*]]: index):
+! CHECK:             %{{.*}} = hlfir.char_extremum min, %{{.*}}, %{{.*}}, %{{.*}} :
+! CHECK:             hlfir.yield_element
+! CHECK:           }
+
+! Test elemental character MIN with an optional dummy argument of fixed length.
+! The result length must guard the optional argument's length with fir.if so
+! genCharLength is not called on an absent optional (unsafe for assumed length).
+subroutine test_elemental_char_min_optional(a, b, c, res)
+  character(5) :: a(10), b(10), res(10)
+  character(5), optional :: c(10)
+  res = min(a, b, c)
+end subroutine
+! CHECK-LABEL:   func.func @_QPtest_elemental_char_min_optional(
+! CHECK:           %[[C5_A:.*]] = arith.constant 5 : index
+! CHECK:           %[[A:.*]]:2 = hlfir.declare {{.*}}Ea"
+! CHECK:           %[[C5_B:.*]] = arith.constant 5 : index
+! CHECK:           %[[B:.*]]:2 = hlfir.declare {{.*}}Eb"
+! CHECK:           %[[C5_C:.*]] = arith.constant 5 : index
+! CHECK:           %[[C:.*]]:2 = hlfir.declare {{.*}}Ec"
+! CHECK:           %[[IS_PRESENT:.*]] = fir.is_present %[[C]]#0
+! CHECK:           %[[CMP1:.*]] = arith.cmpi sgt, %[[C5_B]], %[[C5_A]] : index
+! CHECK:           %[[MAX1:.*]] = arith.select %[[CMP1]], %[[C5_B]], %[[C5_A]] : index
+! CHECK:           %[[C0:.*]] = arith.constant 0 : index
+! CHECK:           %[[OPT_LEN:.*]] = fir.if %[[IS_PRESENT]] -> (index) {
+! CHECK:             fir.result %[[C5_C]] : index
+! CHECK:           } else {
+! CHECK:             fir.result %[[C0]] : index
+! CHECK:           }
+! CHECK:           %[[CMP2:.*]] = arith.cmpi sgt, %[[OPT_LEN]], %[[MAX1]] : index
+! CHECK:           %[[RESULT_LEN:.*]] = arith.select %[[CMP2]], %[[OPT_LEN]], %[[MAX1]] : index
+! CHECK:           %[[ELEMENTAL:.*]] = hlfir.elemental %{{.*}} typeparams %[[RESULT_LEN]]
+
+! Test elemental character MIN with an assumed-length optional dummy argument.
+! genCharLength reads from the descriptor, so it must be guarded with fir.if
+! to avoid reading an invalid descriptor when the argument is absent.
+subroutine test_elemental_char_min_assumed_optional(a, b, c, res)
+  character(*), intent(in) :: a(:), b(:)
+  character(*), intent(in), optional :: c(:)
+  character(*), intent(out) :: res(:)
+  res = min(a, b, c)
+end subroutine
+! CHECK-LABEL:   func.func @_QPtest_elemental_char_min_assumed_optional(
+! CHECK:           %[[A:.*]]:2 = hlfir.declare {{.*}}Ea"
+! CHECK:           %[[B:.*]]:2 = hlfir.declare {{.*}}Eb"
+! CHECK:           %[[C:.*]]:2 = hlfir.declare {{.*}}Ec"
+! CHECK:           %[[IS_PRESENT:.*]] = fir.is_present %[[C]]#0
+! CHECK:           %[[LEN_A:.*]] = fir.box_elesize %[[A]]#1
+! CHECK:           %[[LEN_B:.*]] = fir.box_elesize %[[B]]#1
+! CHECK:           %[[CMP1:.*]] = arith.cmpi sgt, %[[LEN_B]], %[[LEN_A]] : index
+! CHECK:           %[[MAX1:.*]] = arith.select %[[CMP1]], %[[LEN_B]], %[[LEN_A]] : index
+! CHECK:           %[[C0:.*]] = arith.constant 0 : index
+! CHECK:           %[[OPT_LEN:.*]] = fir.if %[[IS_PRESENT]] -> (index) {
+! CHECK:             %[[LEN_C:.*]] = fir.box_elesize %[[C]]#1
+! CHECK:             fir.result %[[LEN_C]] : index
+! CHECK:           } else {
+! CHECK:             fir.result %[[C0]] : index
+! CHECK:           }
+! CHECK:           %[[CMP2:.*]] = arith.cmpi sgt, %[[OPT_LEN]], %[[MAX1]] : index
+! CHECK:           %[[RESULT_LEN:.*]] = arith.select %[[CMP2]], %[[OPT_LEN]], %[[MAX1]] : index
+! CHECK:           %[[ELEMENTAL:.*]] = hlfir.elemental %{{.*}} typeparams %[[RESULT_LEN]] unordered


        


More information about the flang-commits mailing list