[flang-commits] [flang] [flang] Fix segfault in CSHIFT/EOSHIFT with dynamically optional DIM (PR #184431)

Sairudra More via flang-commits flang-commits at lists.llvm.org
Tue Mar 3 12:00:30 PST 2026


https://github.com/Saieiei created https://github.com/llvm/llvm-project/pull/184431

When `DIM` is passed as an optional dummy argument and is absent at runtime, the HLFIR lowering for the `CSHIFT` and `EOSHIFT` intrinsics was still treating it as if it were always present. The lowering path would unconditionally load the `DIM` reference, which becomes a null pointer in the absent case, leading to a null dereference and a runtime segmentation fault.

The underlying issue was in the intrinsic argument lowering setup: the `dim` argument for `cshift`/`eoshift` was not marked with `handleDynamicOptional`. Because of this, `genIntrinsicRef()` never populated the `isPresent` state for the prepared `DIM` argument. Downstream, `handleDynamicOptional()` therefore evaluated to false in `HlfirCShiftLowering::lowerImpl()` and `HlfirEOShiftLowering::lowerImpl()`, and the implementation fell through to an unconditional scalar load (`fir.load`) even when `DIM` could be absent.

The fix is applied in two steps. First, the `dim` entries for `cshift` and `eoshift` in `IntrinsicCall.cpp` are updated to use `handleDynamicOptional`, enabling `genIntrinsicRef()` to track presence and allowing `getOperandVector()` to emit a guarded `fir.if` load that produces a fallback value when `DIM` is absent. Second, the HLFIR lowering implementations are updated to recognize this dynamic-optional form and replace the “0 when absent” placeholder with the standard-mandated default of `1`, matching the Fortran requirements for an absent `DIM` in §16.9.68 (`CSHIFT`) and §16.9.77 (`EOSHIFT`).


>From bf54be90c70ba4a2d6e276d49a6ef0d922ce31cc Mon Sep 17 00:00:00 2001
From: Sairudra More <moresair at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 3 Mar 2026 12:03:38 -0600
Subject: [PATCH] [flang] Fix segfault in CSHIFT/EOSHIFT with dynamically
 optional DIM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When DIM is an optional dummy argument that is absent at runtime,
the HLFIR lowering of CSHIFT/EOSHIFT was unconditionally loading the
DIM reference without checking for presence, causing a null pointer
dereference (segfault).

Root cause: The argLowering rules for 'cshift'/'eoshift' did not mark
the 'dim' argument with handleDynamicOptional. As a result, in
genIntrinsicRef(), 'isPresent' was never populated for the DIM
PreparedActualArgument, causing handleDynamicOptional() to return false
in HlfirCShiftLowering/HlfirEOShiftLowering::lowerImpl(), which then
fell through to an unconditional loadTrivialScalar (fir.load) even
when DIM may be absent at runtime.

Fix:
1. Add handleDynamicOptional to the 'dim' argLowering rule in the
   'cshift' and 'eoshift' entries in IntrinsicCall.cpp. This causes
   genIntrinsicRef() to populate isPresent for the DIM argument,
   which causes getOperandVector() to emit a guarded fir.if load
   that returns 0 when DIM is absent.

2. Update HlfirCShiftLowering::lowerImpl and
   HlfirEOShiftLowering::lowerImpl to detect the dynamic optional
   case (loweredActuals[N]->handleDynamicOptional()) and replace the
   0-when-absent value with 1, per Fortran standard §16.9.48 and
   §16.9.77 which specify that absent DIM defaults to 1.

Fixes: CPE-15069 (optarg_cshift.f90 segfault)
---
 flang/lib/Lower/HlfirIntrinsics.cpp           | 26 +++++++++-
 flang/lib/Optimizer/Builder/IntrinsicCall.cpp |  6 ++-
 .../test/Lower/HLFIR/cshift-optional-dim.f90  | 52 +++++++++++++++++++
 3 files changed, 80 insertions(+), 4 deletions(-)
 create mode 100644 flang/test/Lower/HLFIR/cshift-optional-dim.f90

diff --git a/flang/lib/Lower/HlfirIntrinsics.cpp b/flang/lib/Lower/HlfirIntrinsics.cpp
index f63628d7824ff..978e602c0fe05 100644
--- a/flang/lib/Lower/HlfirIntrinsics.cpp
+++ b/flang/lib/Lower/HlfirIntrinsics.cpp
@@ -489,6 +489,17 @@ mlir::Value HlfirCShiftLowering::lowerImpl(
   if (!dim) {
     // If DIM is not present, drop the last element which is a null Value.
     operands.truncate(2);
+  } else if (loweredActuals[2] && loweredActuals[2]->handleDynamicOptional()) {
+    // DIM is a dynamically optional dummy argument. getOperandVector already
+    // loaded it under an isPresent guard, returning 0 when absent. Per the
+    // Fortran standard (§16.9.48), absent DIM defaults to 1.
+    mlir::Type dimType = dim.getType();
+    mlir::Value zero = builder.createIntegerConstant(loc, dimType, 0);
+    mlir::Value one = builder.createIntegerConstant(loc, dimType, 1);
+    mlir::Value dimIsAbsent = mlir::arith::CmpIOp::create(
+        builder, loc, mlir::arith::CmpIPredicate::eq, dim, zero);
+    dim = mlir::arith::SelectOp::create(builder, loc, dimIsAbsent, one, dim);
+    operands[2] = dim;
   } else {
     // If DIM is present, then dereference it if it is a ref.
     dim = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{dim});
@@ -509,9 +520,20 @@ mlir::Value HlfirEOShiftLowering::lowerImpl(
   mlir::Value shift = operands[1];
   mlir::Value boundary = operands[2];
   mlir::Value dim = operands[3];
-  // If DIM is present, then dereference it if it is a ref.
-  if (dim)
+  if (loweredActuals[3] && loweredActuals[3]->handleDynamicOptional()) {
+    // DIM is a dynamically optional dummy argument. getOperandVector already
+    // loaded it under an isPresent guard, returning 0 when absent. Per the
+    // Fortran standard (§16.9.77), absent DIM defaults to 1.
+    mlir::Type dimType = dim.getType();
+    mlir::Value zero = builder.createIntegerConstant(loc, dimType, 0);
+    mlir::Value one = builder.createIntegerConstant(loc, dimType, 1);
+    mlir::Value dimIsAbsent = mlir::arith::CmpIOp::create(
+        builder, loc, mlir::arith::CmpIPredicate::eq, dim, zero);
+    dim = mlir::arith::SelectOp::create(builder, loc, dimIsAbsent, one, dim);
+  } else if (dim) {
+    // If DIM is statically present, dereference it if it is a ref.
     dim = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{dim});
+  }
 
   mlir::Type resultType = computeResultType(array, stmtResultType);
 
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index 090236fe3e5c6..e04649b600fd9 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -254,7 +254,9 @@ static constexpr IntrinsicHandler handlers[]{
      /*isElemental=*/false},
     {"cshift",
      &I::genCshift,
-     {{{"array", asAddr}, {"shift", asAddr}, {"dim", asValue}}},
+     {{{"array", asAddr},
+       {"shift", asAddr},
+       {"dim", asValue, handleDynamicOptional}}},
      /*isElemental=*/false},
     {"date_and_time",
      &I::genDateAndTime,
@@ -281,7 +283,7 @@ static constexpr IntrinsicHandler handlers[]{
      {{{"array", asBox},
        {"shift", asAddr},
        {"boundary", asBox, handleDynamicOptional},
-       {"dim", asValue}}},
+       {"dim", asValue, handleDynamicOptional}}},
      /*isElemental=*/false},
     {"erfc_scaled", &I::genErfcScaled},
     {"etime",
diff --git a/flang/test/Lower/HLFIR/cshift-optional-dim.f90 b/flang/test/Lower/HLFIR/cshift-optional-dim.f90
new file mode 100644
index 0000000000000..557dacd01a45b
--- /dev/null
+++ b/flang/test/Lower/HLFIR/cshift-optional-dim.f90
@@ -0,0 +1,52 @@
+! Test lowering of CSHIFT/EOSHIFT with a dynamically optional DIM argument.
+! When DIM is an optional dummy argument that is absent at runtime, it must
+! default to 1 (Fortran standard §16.9.48, §16.9.77). Prior to the fix, the
+! absent case caused a segmentation fault because the optional reference was
+! unconditionally dereferenced without checking for presence.
+! RUN: bbc -emit-hlfir -o - -I nowhere %s 2>&1 | FileCheck %s
+
+! CSHIFT 2D with optional DIM - DIM may be absent at runtime (defaults to 1).
+subroutine cshift_optional_dim(a, sh, dim)
+  integer :: a(:,:), sh(:)
+  integer, optional :: dim
+  a = CSHIFT(a, sh, dim)
+end subroutine
+! CHECK-LABEL: func.func @_QPcshift_optional_dim(
+! CHECK-SAME:    {{.*}}: !fir.ref<i32> {fir.bindc_name = "dim", fir.optional})
+! CHECK:         %[[DECL_DIM:.*]]:2 = hlfir.declare {{.*}} {fortran_attrs = #fir.var_attrs<optional>
+! CHECK:         %[[IS_PRESENT:.*]] = fir.is_present %[[DECL_DIM]]#0 : (!fir.ref<i32>) -> i1
+! CHECK:         %[[DIM_OR_ZERO:.*]] = fir.if %[[IS_PRESENT]] -> (i32) {
+! CHECK:           %[[LOADED:.*]] = fir.load %[[DECL_DIM]]#0 : !fir.ref<i32>
+! CHECK:           fir.result %[[LOADED]] : i32
+! CHECK:         } else {
+! CHECK:           arith.constant 0 : i32
+! CHECK:           fir.result
+! CHECK:         }
+! CHECK:         %[[ZERO:.*]] = arith.constant 0 : i32
+! CHECK:         %[[ONE:.*]] = arith.constant 1 : i32
+! CHECK:         %[[IS_ABSENT:.*]] = arith.cmpi eq, %[[DIM_OR_ZERO]], %[[ZERO]] : i32
+! CHECK:         %[[ACTUAL_DIM:.*]] = arith.select %[[IS_ABSENT]], %[[ONE]], %[[DIM_OR_ZERO]] : i32
+! CHECK:         hlfir.cshift {{.*}} dim %[[ACTUAL_DIM]] :
+
+! EOSHIFT 2D with optional DIM - DIM may be absent at runtime (defaults to 1).
+subroutine eoshift_optional_dim(a, sh, dim)
+  integer :: a(:,:), sh(:)
+  integer, optional :: dim
+  a = EOSHIFT(a, sh, dim=dim)
+end subroutine
+! CHECK-LABEL: func.func @_QPeoshift_optional_dim(
+! CHECK-SAME:    {{.*}}: !fir.ref<i32> {fir.bindc_name = "dim", fir.optional})
+! CHECK:         %[[DECL_DIM2:.*]]:2 = hlfir.declare {{.*}} {fortran_attrs = #fir.var_attrs<optional>
+! CHECK:         %[[IS_PRESENT2:.*]] = fir.is_present %[[DECL_DIM2]]#0 : (!fir.ref<i32>) -> i1
+! CHECK:         %[[DIM_OR_ZERO2:.*]] = fir.if %[[IS_PRESENT2]] -> (i32) {
+! CHECK:           %[[LOADED2:.*]] = fir.load %[[DECL_DIM2]]#0 : !fir.ref<i32>
+! CHECK:           fir.result %[[LOADED2]] : i32
+! CHECK:         } else {
+! CHECK:           arith.constant 0 : i32
+! CHECK:           fir.result
+! CHECK:         }
+! CHECK:         %[[ZERO2:.*]] = arith.constant 0 : i32
+! CHECK:         %[[ONE2:.*]] = arith.constant 1 : i32
+! CHECK:         %[[IS_ABSENT2:.*]] = arith.cmpi eq, %[[DIM_OR_ZERO2]], %[[ZERO2]] : i32
+! CHECK:         %[[ACTUAL_DIM2:.*]] = arith.select %[[IS_ABSENT2]], %[[ONE2]], %[[DIM_OR_ZERO2]] : i32
+! CHECK:         hlfir.eoshift {{.*}} dim %[[ACTUAL_DIM2]] :



More information about the flang-commits mailing list