[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