[flang-commits] [flang] [flang][Evaluate] Fold type-only inquiries of conditional arguments (PR #227466)
Vineet Kumar via flang-commits
flang-commits at lists.llvm.org
Tue Sep 29 13:56:08 PDT 2026
https://github.com/vntkmr created https://github.com/llvm/llvm-project/pull/227466
Fold a reference to an intrinsic inquiry function whose result depends only on the declared type, kind type parameters, or rank of an argument when that argument is a conditional argument with a non-constant condition:
integer, parameter :: k = kind((flag ? a : b))
Every consequent-arg of a conditional argument has the same declared type and kind type parameters (F2023 C1538) and the same rank unless all of them are assumed-rank (C1539), so such an inquiry has the same value whichever consequent-arg is chosen at run time. FoldOperation(FunctionRef) folds the reference by substituting the first consequent-arg for the conditional argument in a copy of the reference and keeps the result when it is a constant.
The qualifying arguments are the dummy arguments that the intrinsic table marks with ArgFlag::onlyConstantInquiry, which IntrinsicInterface::Match records as
DummyDataObject::Attr::OnlyIntrinsicInquiry: X in KIND, BIT_SIZE, DIGITS, EPSILON, HUGE, MAXEXPONENT, MINEXPONENT, PRECISION, RADIX, RANGE, RANK, TINY, A in NEW_LINE, and X in the IEEE_SUPPORT_* inquiry functions. IsConstantExpr() already uses the same attribute to accept these references as constant expressions; this change makes folding produce the value.
Inquiries whose result can differ between consequent-args stay unfolded: SIZE, SHAPE, LBOUND, UBOUND, LEN, STORAGE_SIZE, ALLOCATED, ASSOCIATED, IS_CONTIGUOUS, and RANK when every consequent-arg is assumed-rank. A conditional argument with a .NIL. consequent also stays unfolded. A .NIL. consequent can leave an OPTIONAL dummy argument absent at run time, and an inquiry can define a separate result for an absent argument, so no present consequent-arg can stand in for the whole argument. A TODO records the two-variant fold that could handle that case once llvm/llvm-project#227390 lets .NIL. reach the folder.
KIND of a conditional argument with a constant condition, such as kind((.true. ? a : b)), folds as well. FoldOperation skips argument folding for KIND, so the condition was not resolved before.
No portability warning is emitted, which matches the existing handling of kind(a + b) and of a conditional expression in a constant expression.
Tests: Evaluate/fold-conditional-arg.f90 gains a case for each covered inquiry, plus constant-condition, multi-branch, expression-consequent, and keyword-argument forms. Semantics/conditional-arg-inquiry.f90 checks that the inquiries listed above still do not fold.
These changes were generated with the assistance of AI tooling and have been reviewed, tested, and validated by the author.
>From 9e17a9e56764af15ad08608e7d11d36bf89ba4d6 Mon Sep 17 00:00:00 2001
From: Vineet Kumar <vineetk at hpe.com>
Date: Tue, 29 Sep 2026 14:54:22 -0500
Subject: [PATCH] [flang][Evaluate] Fold type-only inquiries of conditional
arguments
Fold a reference to an intrinsic inquiry function whose result depends
only on the declared type, kind type parameters, or rank of an argument
when that argument is a conditional argument with a non-constant
condition:
integer, parameter :: k = kind((flag ? a : b))
Every consequent-arg of a conditional argument has the same declared
type and kind type parameters (F2023 C1538) and the same rank unless
all of them are assumed-rank (C1539), so such an inquiry has the same
value whichever consequent-arg is chosen at run time.
FoldOperation(FunctionRef) folds the reference by substituting the
first consequent-arg for the conditional argument in a copy of the
reference and keeps the result when it is a constant.
The qualifying arguments are the dummy arguments that the intrinsic
table marks with ArgFlag::onlyConstantInquiry, which
IntrinsicInterface::Match records as
DummyDataObject::Attr::OnlyIntrinsicInquiry: X in KIND, BIT_SIZE,
DIGITS, EPSILON, HUGE, MAXEXPONENT, MINEXPONENT, PRECISION, RADIX,
RANGE, RANK, TINY, A in NEW_LINE, and X in the IEEE_SUPPORT_*
inquiry functions. IsConstantExpr() already uses the same attribute
to accept these references as constant expressions; this change makes
folding produce the value.
Inquiries whose result can differ between consequent-args stay
unfolded: SIZE, SHAPE, LBOUND, UBOUND, LEN, STORAGE_SIZE, ALLOCATED,
ASSOCIATED, IS_CONTIGUOUS, and RANK when every consequent-arg is
assumed-rank. A conditional argument with a .NIL. consequent also
stays unfolded. A .NIL. consequent can leave an OPTIONAL dummy
argument absent at run time, and an inquiry can define a separate
result for an absent argument, so no present consequent-arg can stand
in for the whole argument. A TODO records the two-variant fold that
could handle that case once llvm/llvm-project#227390 lets .NIL. reach
the folder.
KIND of a conditional argument with a constant condition, such as
kind((.true. ? a : b)), folds as well. FoldOperation skips argument
folding for KIND, so the condition was not resolved before.
No portability warning is emitted, which matches the existing handling
of kind(a + b) and of a conditional expression in a constant
expression.
Tests: Evaluate/fold-conditional-arg.f90 gains a case for each covered
inquiry, plus constant-condition, multi-branch, expression-consequent,
and keyword-argument forms. Semantics/conditional-arg-inquiry.f90
checks that the inquiries listed above still do not fold.
These changes were generated with the assistance of AI tooling and have
been reviewed, tested, and validated by the author.
---
flang/lib/Evaluate/fold-implementation.h | 88 +++++++++++++------
flang/test/Evaluate/fold-conditional-arg.f90 | 71 +++++++++++++++
.../Semantics/conditional-arg-inquiry.f90 | 68 ++++++++++++++
3 files changed, 199 insertions(+), 28 deletions(-)
create mode 100644 flang/test/Semantics/conditional-arg-inquiry.f90
diff --git a/flang/lib/Evaluate/fold-implementation.h b/flang/lib/Evaluate/fold-implementation.h
index f89fb1601176e..6ebcefab18a09 100644
--- a/flang/lib/Evaluate/fold-implementation.h
+++ b/flang/lib/Evaluate/fold-implementation.h
@@ -1366,6 +1366,25 @@ static inline Expr<SomeDerived> FoldEnumerationNextOrPrevious(
return Expr<SomeDerived>{std::move(funcRef)};
}
+// Returns true when the j-th dummy argument of an intrinsic contributes only
+// its declared type, kind type parameters, and rank to the result, such as X
+// in PRECISION(X). The intrinsic table marks these dummies with
+// ArgFlag::onlyConstantInquiry, and IsConstantExpr() uses the same attribute.
+// STORAGE_SIZE and LEN aren't marked, because their results also depend on
+// the dynamic type or on length type parameters.
+static inline bool IsTypeOnlyInquiryArg(
+ const SpecificIntrinsic &intrinsic, std::size_t j) {
+ const auto &dummies{intrinsic.characteristics.value().dummyArguments};
+ if (j < dummies.size()) {
+ if (const auto *object{
+ std::get_if<characteristics::DummyDataObject>(&dummies[j].u)}) {
+ return object->attrs.test(
+ characteristics::DummyDataObject::Attr::OnlyIntrinsicInquiry);
+ }
+ }
+ return false;
+}
+
template <typename T>
Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
ActualArguments &args{funcRef.arguments()};
@@ -1382,10 +1401,14 @@ Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
}
}
if (intrinsic) {
- // Skip intrinsic folding if any argument is still a conditional arg
- // (i.e. its condition was not a compile-time constant). When the
- // condition is a compile-time constant, FoldConditionalArg already resolved
- // it to a plain Expr above, and intrinsic folding proceeds normally.
+ // A conditional arg that survives the argument folding above has a
+ // condition that isn't a compile-time constant, or belongs to KIND, whose
+ // arguments aren't folded. Either way, skip intrinsic folding unless every
+ // such argument corresponds to a type-only inquiry dummy. F2023 C1538
+ // (declared type and kind type parameters) and C1539 (rank) make every
+ // consequent-arg agree on what such an inquiry examines, so the first
+ // consequent-arg stands in for the whole argument in a copy of the
+ // reference.
//
// TODO:
// For elemental/pure intrinsics, distribute the call over each
@@ -1396,32 +1419,41 @@ Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
// consequent, fold each clone, and reassemble into a new ConditionalArg.
// When multiple arguments are conditional args, distribute one at a
// time to avoid a combinatorial cross-product expansion.
- // This is NOT valid for non-elemental intrinsics like RESHAPE or
- // TRANSFER whose results depend on seeing all arguments together.
- //
- // TODO (conformance):
- // Type-inquiry intrinsics whose result depends only on the argument's
- // declared type/rank (e.g. KIND, BIT_SIZE, DIGITS, HUGE, TINY, EPSILON,
- // PRECISION, RANGE, RADIX, MAXEXPONENT, MINEXPONENT, STORAGE_SIZE, RANK)
- // are foldable even when the condition is not constant, because C1538/C1539
- // guarantee every consequent has the same type and rank. Because they are
- // not folded here, a reference such as
- // integer, parameter :: k = kind((flag ? a : b))
- // is wrongly rejected ("cannot be computed as a constant value") even
- // though it is a valid F2023 constant expression.
- // Fix:
- // For such a curated allow-list of type-only inquiries, before the bailout
- // below, a curated allow-list of type-only inquiries, before the bailout
- // below, replace the conditional-arg argument with its first non-.NIL.
- // consequent (a representative) and fold normally. This must NOT be
- // applied to shape/value inquiries (SIZE, SHAPE, LBOUND/UBOUND, LEN of
- // deferred length, ALLOCATED, ASSOCIATED, PRESENT, IS_CONTIGUOUS), whose
- // results can differ between consequents.
- for (const std::optional<ActualArgument> &arg : args) {
- if (arg && arg->isConditionalArg()) {
- return Expr<T>{std::move(funcRef)};
+ // This is not valid for non-elemental intrinsics such as RESHAPE or
+ // TRANSFER, whose results depend on seeing all arguments together.
+ std::optional<FunctionRef<T>> viaRepresentative;
+ for (std::size_t j{0}; j < args.size(); ++j) {
+ if (args[j] && args[j]->isConditionalArg()) {
+ const ActualArgument::ConditionalArg &condArg{
+ DEREF(args[j]->GetConditionalArg())};
+ // A .NIL. consequent can leave an OPTIONAL dummy absent at run time,
+ // and an inquiry such as IEEE_SUPPORT_UNDERFLOW_CONTROL gives a
+ // different answer for an absent argument than for a present one.
+ // TODO: Fold such a reference twice, once with the representative
+ // and once with the argument absent, and keep the result when both
+ // agree. Blocked on llvm/llvm-project#227390: semantics rejects .NIL.
+ // for every OPTIONAL dummy of an intrinsic, so this can't be tested.
+ if (condArg.HasNilConsequent() ||
+ !IsTypeOnlyInquiryArg(*intrinsic, j)) {
+ return Expr<T>{std::move(funcRef)};
+ }
+ if (!viaRepresentative) {
+ viaRepresentative.emplace(funcRef);
+ }
+ // Assigning the Expr keeps the argument's keyword and dummy intent.
+ *viaRepresentative->arguments()[j] =
+ common::Clone(DEREF(condArg.FirstNonNilConsequent()));
}
}
+ if (viaRepresentative) {
+ Expr<T> folded{FoldOperation(context, std::move(*viaRepresentative))};
+ // Anything but a constant still refers to the representative alone, so
+ // keep the conditional reference. RANK of consequent-args that are all
+ // assumed-rank, which C1539 allows, folds to a DescriptorInquiry.
+ return std::holds_alternative<Constant<T>>(folded.u)
+ ? std::move(folded)
+ : Expr<T>{std::move(funcRef)};
+ }
const std::string name{intrinsic->name};
if (name == "cshift") {
return Folder<T>{context}.CSHIFT(std::move(funcRef));
diff --git a/flang/test/Evaluate/fold-conditional-arg.f90 b/flang/test/Evaluate/fold-conditional-arg.f90
index 27c79e500110f..9ce8d77f29d13 100644
--- a/flang/test/Evaluate/fold-conditional-arg.f90
+++ b/flang/test/Evaluate/fold-conditional-arg.f90
@@ -3,6 +3,7 @@
! When a condition in a conditional-arg is a compile-time constant,
! the conditional-arg should be folded to the selected consequent,
! enabling further constant folding of the enclosing expression.
+! Type-only inquiries fold regardless of the condition.
module m_funcs
implicit none
@@ -24,6 +25,8 @@ subroutine sub_optional(x)
module m
use m_funcs
+ use ieee_arithmetic, only: ieee_support_underflow_control, &
+ ieee_support_flag, ieee_invalid
implicit none
! Basic: .TRUE. selects the first consequent.
@@ -108,6 +111,74 @@ module m
logical, parameter :: test_double_paren_multi = &
abs(((.false. ? -10 : .true. ? -20 : -30))) == 20
+ ! Type-only inquiries fold even when the condition isn't constant.
+ ! F2023 C1538 (declared type and kind type parameters) and C1539 (rank)
+ ! require every consequent-arg to agree on what such an inquiry examines,
+ ! so the first consequent-arg stands in for the whole conditional argument.
+ logical :: flag, flag2
+ integer(4) :: i4a, i4b, i4c
+ integer(8) :: i8a, i8b
+ real(4) :: r4a, r4b
+ real(8) :: r8a, r8b
+ complex(8) :: z8a, z8b
+ character(4) :: c4a
+ character(8) :: c8a
+ real(4) :: a2a(2, 3), a2b(4, 5), a2c(1, 1)
+ logical, parameter :: test_kind_int = kind((flag ? i8a : i8b)) == 8
+ logical, parameter :: test_kind_real = kind((flag ? r8a : r8b)) == 8
+ logical, parameter :: test_kind_complex = kind((flag ? z8a : z8b)) == 8
+ ! Character lengths differ; kind agrees.
+ logical, parameter :: test_kind_char = kind((flag ? c4a : c8a)) == 1
+ logical, parameter :: test_kind_multi = &
+ kind((flag ? i4a : flag2 ? i4b : i4c)) == 4
+ ! An expression consequent-arg is a valid representative.
+ logical, parameter :: test_kind_expr = kind((flag ? i8a + 1_8 : i8b)) == 8
+ ! FoldOperation doesn't fold the argument of KIND, so a constant condition
+ ! reaches the inquiry unresolved.
+ logical, parameter :: test_kind_const_cond = kind((.true. ? i8a : i8b)) == 8
+ logical, parameter :: test_kind_const_cond_tail = &
+ kind((.false. ? i8a : i8b)) == 8
+ logical, parameter :: test_bit_size = bit_size((flag ? i8a : i8b)) == 64
+ logical, parameter :: test_digits_int = digits((flag ? i4a : i4b)) == 31
+ logical, parameter :: test_digits_real = digits((flag ? r8a : r8b)) == 53
+ logical, parameter :: test_huge_int = huge((flag ? i4a : i4b)) == huge(0_4)
+ logical, parameter :: test_huge_real = &
+ huge((flag ? r4a : r4b)) == huge(0.0_4)
+ logical, parameter :: test_tiny = tiny((flag ? r8a : r8b)) == tiny(0.0_8)
+ logical, parameter :: test_epsilon = &
+ epsilon((flag ? r4a : r4b)) == epsilon(0.0_4)
+ logical, parameter :: test_precision_real = &
+ precision((flag ? r8a : r8b)) == 15
+ logical, parameter :: test_precision_complex = &
+ precision((flag ? z8a : z8b)) == 15
+ logical, parameter :: test_range_int = range((flag ? i8a : i8b)) == 18
+ logical, parameter :: test_range_real = range((flag ? r4a : r4b)) == 37
+ logical, parameter :: test_radix = radix((flag ? r4a : r4b)) == 2
+ logical, parameter :: test_maxexponent = &
+ maxexponent((flag ? r4a : r4b)) == 128
+ logical, parameter :: test_minexponent = &
+ minexponent((flag ? r4a : r4b)) == -125
+ logical, parameter :: test_rank_scalar = rank((flag ? i4a : i4b)) == 0
+ ! Shapes differ; rank agrees.
+ logical, parameter :: test_rank_array = &
+ rank((flag ? a2a : flag2 ? a2b : a2c)) == 2
+ ! Character lengths differ; kind agrees.
+ logical, parameter :: test_new_line = &
+ new_line((flag ? c4a : c8a)) == achar(10)
+ ! The intrinsic table flags X= of the IEEE inquiry functions as a type-only
+ ! inquiry too.
+ logical, parameter :: test_ieee_support = &
+ ieee_support_underflow_control((flag ? r4a : r4b)) .eqv. &
+ ieee_support_underflow_control(r4a)
+ ! The check is per dummy position: X= is the second dummy here, and the
+ ! keyword form reaches the same position after argument rearrangement.
+ logical, parameter :: test_ieee_flag_pos2 = &
+ ieee_support_flag(ieee_invalid, (flag ? r4a : r4b)) .eqv. &
+ ieee_support_flag(ieee_invalid, r4a)
+ logical, parameter :: test_ieee_flag_keyword = &
+ ieee_support_flag(x=(flag ? r4a : r4b), flag=ieee_invalid) .eqv. &
+ ieee_support_flag(ieee_invalid, r4a)
+
contains
! Non-intrinsic (user-defined) function calls with conditional args.
diff --git a/flang/test/Semantics/conditional-arg-inquiry.f90 b/flang/test/Semantics/conditional-arg-inquiry.f90
new file mode 100644
index 0000000000000..9f585447cb37c
--- /dev/null
+++ b/flang/test/Semantics/conditional-arg-inquiry.f90
@@ -0,0 +1,68 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+! Inquiries of a conditional argument (F2023 R1526-R1528) whose result can
+! differ between consequent-args must not fold through a representative
+! consequent-arg when the condition isn't constant. Compare with the type-only
+! inquiries in Evaluate/fold-conditional-arg.f90, which do fold.
+module m
+ use ieee_arithmetic, only: ieee_support_flag, ieee_invalid, ieee_overflow, &
+ ieee_support_rounding, ieee_round_type
+ implicit none
+ type :: base
+ integer :: i
+ end type
+ logical :: flag
+ integer :: a1(3), a2(5), a3(2:4)
+ integer :: m2(2, 3)
+ integer, allocatable :: alloc1(:), alloc2(:)
+ integer, pointer :: ptr1(:), ptr2(:)
+ integer, target :: tgt(10)
+ real :: x, y
+ type(ieee_round_type) :: mode
+ !ERROR: Must be a constant value
+ integer, parameter :: p_size = size((flag ? a1 : a2))
+ !ERROR: Must be a constant value
+ integer, parameter :: p_size_dim = size((flag ? m2 : m2), dim=1)
+ !ERROR: Must be a constant value
+ integer, parameter :: p_shape(1) = shape((flag ? a1 : a2))
+ !ERROR: Must be a constant value
+ integer, parameter :: p_lbound = lbound((flag ? a1 : a3), 1)
+ !ERROR: Must be a constant value
+ integer, parameter :: p_ubound = ubound((flag ? a1 : a3), 1)
+ !ERROR: Must be a constant value
+ logical, parameter :: p_allocated = allocated((flag ? alloc1 : alloc2))
+ !ERROR: Must be a constant value
+ logical, parameter :: p_associated = associated((flag ? ptr1 : ptr2))
+ !ERROR: Must be a constant value
+ logical, parameter :: p_contiguous = is_contiguous((flag ? tgt(1:10:2) : tgt))
+ ! FLAG= of IEEE_SUPPORT_FLAG isn't a type-only inquiry dummy, so the
+ ! conditional argument must not fold through a representative there even
+ ! though X= would qualify.
+ !ERROR: Must be a constant value
+ logical, parameter :: p_flag_pos = ieee_support_flag((flag ? ieee_invalid : ieee_overflow), x)
+ ! One non-qualifying conditional argument stops the fold for the whole
+ ! reference, even when the other one qualifies.
+ !ERROR: Must be a constant value
+ logical, parameter :: p_flag_both = ieee_support_flag((flag ? ieee_invalid : ieee_overflow), (flag ? x : y))
+ ! X= qualifies, but the copy folded with the representative isn't a constant
+ ! when ROUND_VALUE= is a variable, so the original reference is kept.
+ !ERROR: Must be a constant value
+ logical, parameter :: p_round = ieee_support_rounding(mode, (flag ? x : y))
+contains
+ ! LEN and STORAGE_SIZE depend on length type parameters and dynamic type,
+ ! which C1538 doesn't require to agree between consequent-args.
+ subroutine s(ca, cb, pa, pb)
+ character(*) :: ca, cb
+ class(base) :: pa, pb
+ !ERROR: Must be a constant value
+ integer, parameter :: p_len = len((flag ? ca : cb))
+ !ERROR: Must be a constant value
+ integer, parameter :: p_storage_size = storage_size((flag ? pa : pb))
+ end subroutine
+ ! C1539 permits consequent-args that are all assumed-rank, and their ranks
+ ! can differ at run time.
+ subroutine r(ar1, ar2)
+ integer :: ar1(..), ar2(..)
+ !ERROR: Value of named constant 'p_rank' (rank(( flag ? ar1 : ar2 ))) cannot be computed as a constant value
+ integer, parameter :: p_rank = rank((flag ? ar1 : ar2))
+ end subroutine
+end module
More information about the flang-commits
mailing list