[flang-commits] [flang] [flang] Short-circuit scalar .AND./.OR. guarded by PRESENT() (PR #201524)
via flang-commits
flang-commits at lists.llvm.org
Thu Jun 4 01:02:30 PDT 2026
https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/201524
Example:
```fortran
subroutine s(field, mask)
real, intent(in) :: field(:,:,:)
real, intent(in), optional :: mask(:,:,:)
if (present(mask) .and. (size(mask,3) /= size(field,3))) then
error stop 'inconsistent dimensions'
end if
end subroutine
real :: f(4,5,6)
call s(f) ! mask is absent
```
Fortran logical operators do not short-circuit, so `size(mask,3)` is evaluated even when `present(mask)` is false. Lowering emits the descriptor read (`fir.box_dims`) and combines it with the `present` result via `arith.andi`, so an absent `mask` dereferences a null descriptor and crashes at run time (without optimization, where the read is not eliminated). This idiom is non-conforming (F2023 §15.5.2.13 p3(4)) but widely used and accepted by other compilers.
Fix: lower a scalar `.AND.`/`.OR.` whose left operand references `PRESENT` with short-circuit semantics. The right operand is generated inside an `fir.if` guarded by the left operand, so it is evaluated only when its value is needed (the left is true for `.AND.`, false for `.OR.`). The standard permits not evaluating an operand whose value is not required (F2018 §10.1.7). The descriptor read in the right operand is therefore never emitted on a path where the argument may be absent, and no `arith.andi` is produced.
Because `.AND.` is left-associative, chained guards such as
```fortran
present(a) .and. present(b) .and. (size(a,3) /= size(b,3))
```
evaluate the descriptor reads only after all presence tests pass.
>From eb7ce84e4bc2e9188406102a3ad6a2b1a28976bc Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Thu, 4 Jun 2026 00:55:30 -0700
Subject: [PATCH] [flang] Short-circuit scalar .AND./.OR. guarded by PRESENT()
The idiom
IF (PRESENT(x) .AND. SIZE(x, d) == n) ...
is common in Fortran code that passes an absent OPTIONAL argument and
relies on the inquiry on the right not being reached when x is absent.
Fortran logical operators are not required to short-circuit, so lowering
evaluated SIZE(x, d) unconditionally and dereferenced the (null)
descriptor of the absent argument, crashing at runtime when the guarding
PRESENT test was meant to prevent it.
Lower a scalar .AND./.OR. whose left operand references PRESENT with
short-circuit semantics: the right operand is generated inside an fir.if
so it is only evaluated when the left operand makes its value relevant
(true for .AND., false for .OR.). The standard permits not evaluating an
operand whose value is not needed (F2018 10.1.7). Because .AND. is
left-associative, chained guards such as
PRESENT(a) .AND. PRESENT(b) .AND. SIZE(a, d) == SIZE(b, d)
evaluate the descriptor reads only after all presence tests pass.
---
flang/lib/Lower/ConvertExprToHLFIR.cpp | 81 ++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index ad680269dea5c..5b40d6ebcc6c5 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -13,6 +13,7 @@
#include "flang/Lower/ConvertExprToHLFIR.h"
#include "flang/Evaluate/shape.h"
#include "flang/Evaluate/tools.h"
+#include "flang/Evaluate/traverse.h"
#include "flang/Lower/AbstractConverter.h"
#include "flang/Lower/Allocatable.h"
#include "flang/Lower/CallInterface.h"
@@ -51,6 +52,26 @@ static bool isParenthesized(const Fortran::evaluate::Expr<T> &expr) {
}
}
+/// Return true if \p expr references the intrinsic function PRESENT. Used to
+/// recognize the `PRESENT(x) .AND. <expr using x>` (and `.OR.`) guard idiom so
+/// the guarded operand can be lowered with short-circuit semantics, avoiding an
+/// unconditional descriptor read on an argument that is only known to be
+/// present once the presence test is true.
+template <typename A>
+static bool exprReferencesPresent(const A &expr) {
+ struct PresentFinder
+ : public Fortran::evaluate::AnyTraverse<PresentFinder> {
+ PresentFinder()
+ : Fortran::evaluate::AnyTraverse<PresentFinder>(*this) {}
+ using Fortran::evaluate::AnyTraverse<PresentFinder>::operator();
+ bool operator()(const Fortran::evaluate::SpecificIntrinsic &intrinsic)
+ const {
+ return intrinsic.name == "present";
+ }
+ } finder;
+ return finder(expr);
+}
+
/// Lower Designators to HLFIR.
class HlfirDesignatorBuilder {
private:
@@ -1697,12 +1718,72 @@ class HlfirBuilder {
return hlfir::EntityWithAttributes{elemental};
}
+ /// Lower a scalar `.AND.`/`.OR.` with short-circuit semantics: the right
+ /// operand is evaluated inside an `fir.if` so that, for `.AND.`, it only runs
+ /// when the left operand is true (and for `.OR.`, only when it is false).
+ /// This is used for the `PRESENT(x) .AND. <expr using x>` guard idiom, so the
+ /// descriptor read in the right operand is not emitted on a path where the
+ /// optional argument may be absent. Fortran does not mandate short-circuit
+ /// evaluation, but the standard permits not evaluating an operand whose value
+ /// is not needed (F2018 10.1.7).
+ template <typename D, typename R, typename LO, typename RO>
+ hlfir::EntityWithAttributes
+ genShortCircuitLogicalOp(const Fortran::evaluate::Operation<D, R, LO, RO> &op,
+ Fortran::evaluate::LogicalOperator logicalOp) {
+ fir::FirOpBuilder &builder = getBuilder();
+ mlir::Location loc = getLoc();
+ mlir::Type resultType = Fortran::lower::getFIRType(
+ builder.getContext(), R::category, R::kind, /*params=*/{});
+
+ hlfir::Entity left = hlfir::loadTrivialScalar(loc, builder, gen(op.left()));
+ mlir::Value cond = builder.createConvert(loc, builder.getI1Type(), left);
+
+ auto genRhs = [&]() {
+ hlfir::Entity right =
+ hlfir::loadTrivialScalar(loc, builder, gen(op.right()));
+ fir::ResultOp::create(builder, loc,
+ builder.createConvert(loc, resultType, right));
+ };
+ auto genConst = [&](bool value) {
+ fir::ResultOp::create(
+ builder, loc,
+ builder.createConvert(loc, resultType, builder.createBool(loc, value)));
+ };
+
+ // .AND.: left true -> result is rhs, left false -> result is .false.
+ // .OR. : left true -> result is .true., left false -> result is rhs.
+ const bool isAnd = logicalOp == Fortran::evaluate::LogicalOperator::And;
+ mlir::Value result =
+ isAnd ? builder.genIfOp(loc, {resultType}, cond,
+ /*withElseRegion=*/true)
+ .genThen([&]() { genRhs(); })
+ .genElse([&]() { genConst(false); })
+ .getResults()[0]
+ : builder.genIfOp(loc, {resultType}, cond,
+ /*withElseRegion=*/true)
+ .genThen([&]() { genConst(true); })
+ .genElse([&]() { genRhs(); })
+ .getResults()[0];
+ return hlfir::EntityWithAttributes{result};
+ }
+
template <typename D, typename R, typename LO, typename RO>
hlfir::EntityWithAttributes
gen(const Fortran::evaluate::Operation<D, R, LO, RO> &op) {
auto &builder = getBuilder();
mlir::Location loc = getLoc();
const int rank = op.Rank();
+ if constexpr (R::category == Fortran::common::TypeCategory::Logical &&
+ std::is_same_v<
+ D, Fortran::evaluate::LogicalOperation<R::kind>>) {
+ const Fortran::evaluate::LogicalOperator logicalOp =
+ op.derived().logicalOperator;
+ if (rank == 0 &&
+ (logicalOp == Fortran::evaluate::LogicalOperator::And ||
+ logicalOp == Fortran::evaluate::LogicalOperator::Or) &&
+ exprReferencesPresent(op.left()))
+ return genShortCircuitLogicalOp(op, logicalOp);
+ }
BinaryOp<D> binaryOp;
auto left = hlfir::loadTrivialScalar(loc, builder, gen(op.left()));
auto right = hlfir::loadTrivialScalar(loc, builder, gen(op.right()));
More information about the flang-commits
mailing list