[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