[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:03:19 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-fir-hlfir

Author: khaki3

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/201524.diff


1 Files Affected:

- (modified) flang/lib/Lower/ConvertExprToHLFIR.cpp (+81) 


``````````diff
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()));

``````````

</details>


https://github.com/llvm/llvm-project/pull/201524


More information about the flang-commits mailing list