1 //===-- lib/Evaluate/fold-logical.cpp -------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "fold-implementation.h"
10 #include "fold-matmul.h"
11 #include "fold-reduction.h"
12 #include "flang/Evaluate/check-expression.h"
13 #include "flang/Runtime/magic-numbers.h"
15 namespace Fortran::evaluate
{
18 static std::optional
<Expr
<SomeType
>> ZeroExtend(const Constant
<T
> &c
) {
19 std::vector
<Scalar
<LargestInt
>> exts
;
20 for (const auto &v
: c
.values()) {
21 exts
.push_back(Scalar
<LargestInt
>::ConvertUnsigned(v
).value
);
24 Constant
<LargestInt
>(std::move(exts
), ConstantSubscripts(c
.shape())));
27 // for ALL, ANY & PARITY
29 static Expr
<T
> FoldAllAnyParity(FoldingContext
&context
, FunctionRef
<T
> &&ref
,
30 Scalar
<T
> (Scalar
<T
>::*operation
)(const Scalar
<T
> &) const,
32 static_assert(T::category
== TypeCategory::Logical
);
33 std::optional
<int> dim
;
34 if (std::optional
<ArrayAndMask
<T
>> arrayAndMask
{
35 ProcessReductionArgs
<T
>(context
, ref
.arguments(), dim
,
36 /*ARRAY(MASK)=*/0, /*DIM=*/1)}) {
37 OperationAccumulator accumulator
{arrayAndMask
->array
, operation
};
38 return Expr
<T
>{DoReduction
<T
>(
39 arrayAndMask
->array
, arrayAndMask
->mask
, dim
, identity
, accumulator
)};
41 return Expr
<T
>{std::move(ref
)};
44 // OUT_OF_RANGE(x,mold[,round]) references are entirely rewritten here into
45 // expressions, which are then folded into constants when 'x' and 'round'
46 // are constant. It is guaranteed that 'x' is evaluated at most once.
48 template <int X_RKIND
, int MOLD_IKIND
>
49 Expr
<SomeReal
> RealToIntBoundHelper(bool round
, bool negate
) {
50 using RType
= Type
<TypeCategory::Real
, X_RKIND
>;
51 using RealType
= Scalar
<RType
>;
52 using IntType
= Scalar
<Type
<TypeCategory::Integer
, MOLD_IKIND
>>;
53 RealType result
{}; // 0.
54 common::RoundingMode roundingMode
{round
55 ? common::RoundingMode::TiesAwayFromZero
56 : common::RoundingMode::ToZero
};
57 // Add decreasing powers of two to the result to find the largest magnitude
58 // value that can be converted to the integer type without overflow.
59 RealType at
{RealType::FromInteger(IntType
{negate
? -1 : 1}).value
};
61 while (!at
.template ToInteger
<IntType
>(roundingMode
)
62 .flags
.test(RealFlag::Overflow
)) {
63 auto tmp
{at
.SCALE(IntType
{1})};
64 if (tmp
.flags
.test(RealFlag::Overflow
)) {
72 at
= at
.SCALE(IntType
{-1}).value
;
76 auto tmp
{at
.Add(result
)};
77 if (tmp
.flags
.test(RealFlag::Inexact
)) {
79 } else if (!tmp
.value
.template ToInteger
<IntType
>(roundingMode
)
80 .flags
.test(RealFlag::Overflow
)) {
84 return AsCategoryExpr(Constant
<RType
>{std::move(result
)});
87 static Expr
<SomeReal
> RealToIntBound(
88 int xRKind
, int moldIKind
, bool round
, bool negate
) {
91 switch (moldIKind) { \
93 return RealToIntBoundHelper<RK, 1>(round, negate); \
96 return RealToIntBoundHelper<RK, 2>(round, negate); \
99 return RealToIntBoundHelper<RK, 4>(round, negate); \
102 return RealToIntBoundHelper<RK, 8>(round, negate); \
105 return RealToIntBoundHelper<RK, 16>(round, negate); \
128 DIE("RealToIntBound: no case");
132 class RealToIntLimitHelper
{
134 using Result
= std::optional
<Expr
<SomeReal
>>;
135 using Types
= RealTypes
;
136 RealToIntLimitHelper(
137 FoldingContext
&context
, Expr
<SomeReal
> &&hi
, Expr
<SomeReal
> &lo
)
138 : context_
{context
}, hi_
{std::move(hi
)}, lo_
{lo
} {}
139 template <typename T
> Result
Test() {
140 if (UnwrapExpr
<Expr
<T
>>(hi_
)) {
141 bool promote
{T::kind
< 16};
143 if (auto hiV
{GetScalarConstantValue
<T
>(hi_
)}) {
144 auto loV
{GetScalarConstantValue
<T
>(lo_
)};
145 CHECK(loV
.has_value());
146 auto diff
{hiV
->Subtract(*loV
, Rounding
{common::RoundingMode::ToZero
})};
148 (diff
.flags
.test(RealFlag::Overflow
) ||
149 diff
.flags
.test(RealFlag::Inexact
));
150 constResult
= AsCategoryExpr(Constant
<T
>{std::move(diff
.value
)});
153 constexpr int nextKind
{T::kind
< 4 ? 4 : T::kind
== 4 ? 8 : 16};
154 using T2
= Type
<TypeCategory::Real
, nextKind
>;
155 hi_
= Expr
<SomeReal
>{Fold(context_
, ConvertToType
<T2
>(std::move(hi_
)))};
156 lo_
= Expr
<SomeReal
>{Fold(context_
, ConvertToType
<T2
>(std::move(lo_
)))};
158 // Use promoted constants on next iteration of SearchTypes
165 return AsCategoryExpr(std::move(hi_
) - Expr
<SomeReal
>{lo_
});
173 FoldingContext
&context_
;
178 static std::optional
<Expr
<SomeReal
>> RealToIntLimit(
179 FoldingContext
&context
, Expr
<SomeReal
> &&hi
, Expr
<SomeReal
> &lo
) {
180 return common::SearchTypes(RealToIntLimitHelper
{context
, std::move(hi
), lo
});
183 // RealToRealBounds() returns a pair (HUGE(x),REAL(HUGE(mold),KIND(x)))
184 // when REAL(HUGE(x),KIND(mold)) overflows, and std::nullopt otherwise.
185 template <int X_RKIND
, int MOLD_RKIND
>
186 std::optional
<std::pair
<Expr
<SomeReal
>, Expr
<SomeReal
>>>
187 RealToRealBoundsHelper() {
188 using RType
= Type
<TypeCategory::Real
, X_RKIND
>;
189 using RealType
= Scalar
<RType
>;
190 using MoldRealType
= Scalar
<Type
<TypeCategory::Real
, MOLD_RKIND
>>;
191 if (!MoldRealType::Convert(RealType::HUGE()).flags
.test(RealFlag::Overflow
)) {
194 return std::make_pair(AsCategoryExpr(Constant
<RType
>{
195 RealType::Convert(MoldRealType::HUGE()).value
}),
196 AsCategoryExpr(Constant
<RType
>{RealType::HUGE()}));
200 static std::optional
<std::pair
<Expr
<SomeReal
>, Expr
<SomeReal
>>>
201 RealToRealBounds(int xRKind
, int moldRKind
) {
204 switch (moldRKind) { \
206 return RealToRealBoundsHelper<RK, 2>(); \
209 return RealToRealBoundsHelper<RK, 3>(); \
212 return RealToRealBoundsHelper<RK, 4>(); \
215 return RealToRealBoundsHelper<RK, 8>(); \
218 return RealToRealBoundsHelper<RK, 10>(); \
221 return RealToRealBoundsHelper<RK, 16>(); \
244 DIE("RealToRealBounds: no case");
248 template <int X_IKIND
, int MOLD_RKIND
>
249 std::optional
<Expr
<SomeInteger
>> IntToRealBoundHelper(bool negate
) {
250 using IType
= Type
<TypeCategory::Integer
, X_IKIND
>;
251 using IntType
= Scalar
<IType
>;
252 using RealType
= Scalar
<Type
<TypeCategory::Real
, MOLD_RKIND
>>;
253 IntType result
{}; // 0
255 std::optional
<IntType
> next
;
256 for (int bit
{0}; bit
< IntType::bits
; ++bit
) {
257 IntType power
{IntType
{}.IBSET(bit
)};
258 if (power
.IsNegative()) {
263 power
= power
.Negate().value
;
265 auto tmp
{power
.AddSigned(result
)};
267 RealType::FromInteger(tmp
.value
).flags
.test(RealFlag::Overflow
)) {
273 CHECK(result
.CompareSigned(*next
) != Ordering::Equal
);
279 if (result
.CompareSigned(IntType::HUGE()) == Ordering::Equal
) {
282 return AsCategoryExpr(Constant
<IType
>{std::move(result
)});
286 static std::optional
<Expr
<SomeInteger
>> IntToRealBound(
287 int xIKind
, int moldRKind
, bool negate
) {
290 switch (moldRKind) { \
292 return IntToRealBoundHelper<IK, 2>(negate); \
295 return IntToRealBoundHelper<IK, 3>(negate); \
298 return IntToRealBoundHelper<IK, 4>(negate); \
301 return IntToRealBoundHelper<IK, 8>(negate); \
304 return IntToRealBoundHelper<IK, 10>(negate); \
307 return IntToRealBoundHelper<IK, 16>(negate); \
327 DIE("IntToRealBound: no case");
331 template <int X_IKIND
, int MOLD_IKIND
>
332 std::optional
<Expr
<SomeInteger
>> IntToIntBoundHelper() {
333 if constexpr (X_IKIND
<= MOLD_IKIND
) {
336 using XIType
= Type
<TypeCategory::Integer
, X_IKIND
>;
337 using IntegerType
= Scalar
<XIType
>;
338 using MoldIType
= Type
<TypeCategory::Integer
, MOLD_IKIND
>;
339 using MoldIntegerType
= Scalar
<MoldIType
>;
340 return AsCategoryExpr(Constant
<XIType
>{
341 IntegerType::ConvertSigned(MoldIntegerType::HUGE()).value
});
345 static std::optional
<Expr
<SomeInteger
>> IntToIntBound(
346 int xIKind
, int moldIKind
) {
349 switch (moldIKind) { \
351 return IntToIntBoundHelper<IK, 1>(); \
354 return IntToIntBoundHelper<IK, 2>(); \
357 return IntToIntBoundHelper<IK, 4>(); \
360 return IntToIntBoundHelper<IK, 8>(); \
363 return IntToIntBoundHelper<IK, 16>(); \
383 DIE("IntToIntBound: no case");
387 // ApplyIntrinsic() constructs the typed expression representation
388 // for a specific intrinsic function reference.
389 // TODO: maybe move into tools.h?
390 class IntrinsicCallHelper
{
392 explicit IntrinsicCallHelper(SpecificCall
&&call
) : call_
{call
} {
393 CHECK(proc_
.IsFunction());
394 typeAndShape_
= proc_
.functionResult
->GetTypeAndShape();
395 CHECK(typeAndShape_
!= nullptr);
397 using Result
= std::optional
<Expr
<SomeType
>>;
398 using Types
= LengthlessIntrinsicTypes
;
399 template <typename T
> Result
Test() {
400 if (T::category
== typeAndShape_
->type().category() &&
401 T::kind
== typeAndShape_
->type().kind()) {
402 return AsGenericExpr(FunctionRef
<T
>{
403 ProcedureDesignator
{std::move(call_
.specificIntrinsic
)},
404 std::move(call_
.arguments
)});
412 const characteristics::Procedure
&proc_
{
413 call_
.specificIntrinsic
.characteristics
.value()};
414 const characteristics::TypeAndShape
*typeAndShape_
{nullptr};
417 static Expr
<SomeType
> ApplyIntrinsic(
418 FoldingContext
&context
, const std::string
&func
, ActualArguments
&&args
) {
420 context
.intrinsics().Probe(CallCharacteristics
{func
}, args
, context
)};
421 CHECK(found
.has_value());
422 auto result
{common::SearchTypes(IntrinsicCallHelper
{std::move(*found
)})};
423 CHECK(result
.has_value());
427 static Expr
<LogicalResult
> CompareUnsigned(FoldingContext
&context
,
428 const char *intrin
, Expr
<SomeType
> &&x
, Expr
<SomeType
> &&y
) {
429 Expr
<SomeType
> result
{ApplyIntrinsic(context
, intrin
,
431 ActualArgument
{std::move(x
)}, ActualArgument
{std::move(y
)}})};
432 return DEREF(UnwrapExpr
<Expr
<LogicalResult
>>(result
));
435 // Determines the right kind of INTEGER to hold the bits of a REAL type.
436 static Expr
<SomeType
> IntTransferMold(
437 const TargetCharacteristics
&target
, DynamicType realType
, bool asVector
) {
438 CHECK(realType
.category() == TypeCategory::Real
);
439 int rKind
{realType
.kind()};
440 int iKind
{std::max
<int>(target
.GetAlignment(TypeCategory::Real
, rKind
),
441 target
.GetByteSize(TypeCategory::Real
, rKind
))};
442 CHECK(target
.CanSupportType(TypeCategory::Integer
, iKind
));
443 DynamicType iType
{TypeCategory::Integer
, iKind
};
444 ConstantSubscripts shape
;
446 shape
= ConstantSubscripts
{1};
448 Constant
<SubscriptInteger
> value
{
449 std::vector
<Scalar
<SubscriptInteger
>>{0}, std::move(shape
)};
450 auto expr
{ConvertToType(iType
, AsGenericExpr(std::move(value
)))};
451 CHECK(expr
.has_value());
452 return std::move(*expr
);
455 static Expr
<SomeType
> GetRealBits(FoldingContext
&context
, Expr
<SomeReal
> &&x
) {
456 auto xType
{x
.GetType()};
457 CHECK(xType
.has_value());
458 bool asVector
{x
.Rank() > 0};
459 return ApplyIntrinsic(context
, "transfer",
460 ActualArguments
{ActualArgument
{AsGenericExpr(std::move(x
))},
461 ActualArgument
{IntTransferMold(
462 context
.targetCharacteristics(), *xType
, asVector
)}});
466 static Expr
<Type
<TypeCategory::Logical
, KIND
>> RewriteOutOfRange(
467 FoldingContext
&context
,
468 FunctionRef
<Type
<TypeCategory::Logical
, KIND
>> &&funcRef
) {
469 using ResultType
= Type
<TypeCategory::Logical
, KIND
>;
470 ActualArguments
&args
{funcRef
.arguments()};
471 // Fold x= and round= unconditionally
472 if (auto *x
{UnwrapExpr
<Expr
<SomeType
>>(args
[0])}) {
473 *args
[0] = Fold(context
, std::move(*x
));
475 if (args
.size() >= 3) {
476 if (auto *round
{UnwrapExpr
<Expr
<SomeType
>>(args
[2])}) {
477 *args
[2] = Fold(context
, std::move(*round
));
480 if (auto *x
{UnwrapExpr
<Expr
<SomeType
>>(args
[0])}) {
481 x
= UnwrapExpr
<Expr
<SomeType
>>(args
[0]);
483 if (const auto *mold
{UnwrapExpr
<Expr
<SomeType
>>(args
[1])}) {
484 DynamicType xType
{x
->GetType().value()};
485 std::optional
<Expr
<LogicalResult
>> result
;
486 bool alwaysFalse
{false};
487 if (auto *iXExpr
{UnwrapExpr
<Expr
<SomeInteger
>>(*x
)}) {
488 int iXKind
{iXExpr
->GetType().value().kind()};
489 if (auto *iMoldExpr
{UnwrapExpr
<Expr
<SomeInteger
>>(*mold
)}) {
490 // INTEGER -> INTEGER
491 int iMoldKind
{iMoldExpr
->GetType().value().kind()};
492 if (auto hi
{IntToIntBound(iXKind
, iMoldKind
)}) {
493 // 'hi' is INT(HUGE(mold), KIND(x))
494 // OUT_OF_RANGE(x,mold) = (x + (hi + 1)) .UGT. (2*hi + 1)
495 auto one
{DEREF(UnwrapExpr
<Expr
<SomeInteger
>>(ConvertToType(
496 xType
, AsGenericExpr(Constant
<SubscriptInteger
>{1}))))};
497 auto lhs
{std::move(*iXExpr
) +
498 (Expr
<SomeInteger
>{*hi
} + Expr
<SomeInteger
>{one
})};
499 auto two
{DEREF(UnwrapExpr
<Expr
<SomeInteger
>>(ConvertToType(
500 xType
, AsGenericExpr(Constant
<SubscriptInteger
>{2}))))};
501 auto rhs
{std::move(two
) * std::move(*hi
) + std::move(one
)};
502 result
= CompareUnsigned(context
, "bgt",
503 Expr
<SomeType
>{std::move(lhs
)}, Expr
<SomeType
>{std::move(rhs
)});
507 } else if (auto *rMoldExpr
{UnwrapExpr
<Expr
<SomeReal
>>(*mold
)}) {
509 int rMoldKind
{rMoldExpr
->GetType().value().kind()};
510 if (auto hi
{IntToRealBound(iXKind
, rMoldKind
, /*negate=*/false)}) {
511 // OUT_OF_RANGE(x,mold) = (x - lo) .UGT. (hi - lo)
512 auto lo
{IntToRealBound(iXKind
, rMoldKind
, /*negate=*/true)};
513 CHECK(lo
.has_value());
514 auto lhs
{std::move(*iXExpr
) - Expr
<SomeInteger
>{*lo
}};
515 auto rhs
{std::move(*hi
) - std::move(*lo
)};
516 result
= CompareUnsigned(context
, "bgt",
517 Expr
<SomeType
>{std::move(lhs
)}, Expr
<SomeType
>{std::move(rhs
)});
522 } else if (auto *rXExpr
{UnwrapExpr
<Expr
<SomeReal
>>(*x
)}) {
523 int rXKind
{rXExpr
->GetType().value().kind()};
524 if (auto *iMoldExpr
{UnwrapExpr
<Expr
<SomeInteger
>>(*mold
)}) {
526 int iMoldKind
{iMoldExpr
->GetType().value().kind()};
527 auto hi
{RealToIntBound(rXKind
, iMoldKind
, false, false)};
528 auto lo
{RealToIntBound(rXKind
, iMoldKind
, false, true)};
529 if (args
.size() >= 3) {
530 // Bounds depend on round= value
531 if (auto *round
{UnwrapExpr
<Expr
<SomeType
>>(args
[2])}) {
532 if (const Symbol
* whole
{UnwrapWholeSymbolDataRef(*round
)};
533 whole
&& semantics::IsOptional(whole
->GetUltimate()) &&
534 context
.languageFeatures().ShouldWarn(
535 common::UsageWarning::OptionalMustBePresent
)) {
536 if (auto source
{args
[2]->sourceLocation()}) {
537 context
.messages().Say(
538 common::UsageWarning::OptionalMustBePresent
, *source
,
539 "ROUND= argument to OUT_OF_RANGE() is an optional dummy argument that must be present at execution"_warn_en_US
);
542 auto rlo
{RealToIntBound(rXKind
, iMoldKind
, true, true)};
543 auto rhi
{RealToIntBound(rXKind
, iMoldKind
, true, false)};
544 auto mlo
{Fold(context
,
545 ApplyIntrinsic(context
, "merge",
547 ActualArgument
{Expr
<SomeType
>{std::move(rlo
)}},
548 ActualArgument
{Expr
<SomeType
>{std::move(lo
)}},
549 ActualArgument
{Expr
<SomeType
>{*round
}}}))};
550 auto mhi
{Fold(context
,
551 ApplyIntrinsic(context
, "merge",
553 ActualArgument
{Expr
<SomeType
>{std::move(rhi
)}},
554 ActualArgument
{Expr
<SomeType
>{std::move(hi
)}},
555 ActualArgument
{std::move(*round
)}}))};
556 lo
= std::move(DEREF(UnwrapExpr
<Expr
<SomeReal
>>(mlo
)));
557 hi
= std::move(DEREF(UnwrapExpr
<Expr
<SomeReal
>>(mhi
)));
560 // OUT_OF_RANGE(x,mold[,round]) =
561 // TRANSFER(x - lo, int) .UGT. TRANSFER(hi - lo, int)
562 hi
= Fold(context
, std::move(hi
));
563 lo
= Fold(context
, std::move(lo
));
564 if (auto rhs
{RealToIntLimit(context
, std::move(hi
), lo
)}) {
565 Expr
<SomeReal
> lhs
{std::move(*rXExpr
) - std::move(lo
)};
566 result
= CompareUnsigned(context
, "bgt",
567 GetRealBits(context
, std::move(lhs
)),
568 GetRealBits(context
, std::move(*rhs
)));
570 } else if (auto *rMoldExpr
{UnwrapExpr
<Expr
<SomeReal
>>(*mold
)}) {
572 // Only finite arguments with ABS(x) > HUGE(mold) are .TRUE.
573 // OUT_OF_RANGE(x,mold) =
574 // TRANSFER(ABS(x) - HUGE(mold), int) - 1 .ULT.
575 // TRANSFER(HUGE(mold), int)
576 // Note that OUT_OF_RANGE(+/-Inf or NaN,mold) =
577 // TRANSFER(+Inf or Nan, int) - 1 .ULT. TRANSFER(HUGE(mold), int)
578 int rMoldKind
{rMoldExpr
->GetType().value().kind()};
579 if (auto bounds
{RealToRealBounds(rXKind
, rMoldKind
)}) {
580 auto &[moldHuge
, xHuge
]{*bounds
};
581 Expr
<SomeType
> abs
{ApplyIntrinsic(context
, "abs",
583 ActualArgument
{Expr
<SomeType
>{std::move(*rXExpr
)}}})};
584 auto &absR
{DEREF(UnwrapExpr
<Expr
<SomeReal
>>(abs
))};
585 Expr
<SomeType
> diffBits
{
586 GetRealBits(context
, std::move(absR
) - std::move(moldHuge
))};
587 auto &diffBitsI
{DEREF(UnwrapExpr
<Expr
<SomeInteger
>>(diffBits
))};
588 Expr
<SomeType
> decr
{std::move(diffBitsI
) -
589 Expr
<SomeInteger
>{Expr
<SubscriptInteger
>{1}}};
590 result
= CompareUnsigned(context
, "blt", std::move(decr
),
591 GetRealBits(context
, std::move(xHuge
)));
598 // xType can never overflow moldType, so
599 // OUT_OF_RANGE(x) = (x /= 0) .AND. .FALSE.
600 // which has the same shape as x.
601 Expr
<LogicalResult
> scalarFalse
{
602 Constant
<LogicalResult
>{Scalar
<LogicalResult
>{false}}};
604 if (auto nez
{Relate(context
.messages(), RelationalOperator::NE
,
606 AsGenericExpr(Constant
<SubscriptInteger
>{0}))}) {
607 result
= Expr
<LogicalResult
>{LogicalOperation
<LogicalResult::kind
>{
608 LogicalOperator::And
, std::move(*nez
), std::move(scalarFalse
)}};
611 result
= std::move(scalarFalse
);
615 auto restorer
{context
.messages().DiscardMessages()};
617 context
, AsExpr(ConvertToType
<ResultType
>(std::move(*result
))));
621 return AsExpr(std::move(funcRef
));
624 static std::optional
<common::RoundingMode
> GetRoundingMode(
625 const std::optional
<ActualArgument
> &arg
) {
627 if (const auto *cst
{UnwrapExpr
<Constant
<SomeDerived
>>(*arg
)}) {
628 if (auto constr
{cst
->GetScalarValue()}) {
629 if (StructureConstructorValues
& values
{constr
->values()};
630 values
.size() == 1) {
631 const Expr
<SomeType
> &value
{values
.begin()->second
.value()};
632 if (auto code
{ToInt64(value
)}) {
633 return static_cast<common::RoundingMode
>(*code
);
643 Expr
<Type
<TypeCategory::Logical
, KIND
>> FoldIntrinsicFunction(
644 FoldingContext
&context
,
645 FunctionRef
<Type
<TypeCategory::Logical
, KIND
>> &&funcRef
) {
646 using T
= Type
<TypeCategory::Logical
, KIND
>;
647 ActualArguments
&args
{funcRef
.arguments()};
648 auto *intrinsic
{std::get_if
<SpecificIntrinsic
>(&funcRef
.proc().u
)};
650 std::string name
{intrinsic
->name
};
651 using SameInt
= Type
<TypeCategory::Integer
, KIND
>;
653 return FoldAllAnyParity(
654 context
, std::move(funcRef
), &Scalar
<T
>::AND
, Scalar
<T
>{true});
655 } else if (name
== "any") {
656 return FoldAllAnyParity(
657 context
, std::move(funcRef
), &Scalar
<T
>::OR
, Scalar
<T
>{false});
658 } else if (name
== "associated") {
659 bool gotConstant
{true};
660 const Expr
<SomeType
> *firstArgExpr
{args
[0]->UnwrapExpr()};
661 if (!firstArgExpr
|| !IsNullPointer(*firstArgExpr
)) {
663 } else if (args
[1]) { // There's a second argument
664 const Expr
<SomeType
> *secondArgExpr
{args
[1]->UnwrapExpr()};
665 if (!secondArgExpr
|| !IsNullPointer(*secondArgExpr
)) {
669 return gotConstant
? Expr
<T
>{false} : Expr
<T
>{std::move(funcRef
)};
670 } else if (name
== "bge" || name
== "bgt" || name
== "ble" || name
== "blt") {
671 static_assert(std::is_same_v
<Scalar
<LargestInt
>, BOZLiteralConstant
>);
673 // The arguments to these intrinsics can be of different types. In that
674 // case, the shorter of the two would need to be zero-extended to match
675 // the size of the other. If at least one of the operands is not a constant,
676 // the zero-extending will be done during lowering. Otherwise, the folding
677 // must be done here.
678 std::optional
<Expr
<SomeType
>> constArgs
[2];
679 for (int i
{0}; i
<= 1; i
++) {
680 if (BOZLiteralConstant
* x
{UnwrapExpr
<BOZLiteralConstant
>(args
[i
])}) {
681 constArgs
[i
] = AsGenericExpr(Constant
<LargestInt
>{std::move(*x
)});
682 } else if (auto *x
{UnwrapExpr
<Expr
<SomeInteger
>>(args
[i
])}) {
684 [&](const auto &ix
) {
685 using IntT
= typename
std::decay_t
<decltype(ix
)>::Result
;
686 if (auto *c
{UnwrapConstantValue
<IntT
>(ix
)}) {
687 constArgs
[i
] = ZeroExtend(*c
);
694 if (constArgs
[0] && constArgs
[1]) {
695 auto fptr
{&Scalar
<LargestInt
>::BGE
};
696 if (name
== "bge") { // done in fptr declaration
697 } else if (name
== "bgt") {
698 fptr
= &Scalar
<LargestInt
>::BGT
;
699 } else if (name
== "ble") {
700 fptr
= &Scalar
<LargestInt
>::BLE
;
701 } else if (name
== "blt") {
702 fptr
= &Scalar
<LargestInt
>::BLT
;
704 common::die("missing case to fold intrinsic function %s", name
.c_str());
707 for (int i
{0}; i
<= 1; i
++) {
708 *args
[i
] = std::move(constArgs
[i
].value());
711 return FoldElementalIntrinsic
<T
, LargestInt
, LargestInt
>(context
,
713 ScalarFunc
<T
, LargestInt
, LargestInt
>(
715 const Scalar
<LargestInt
> &i
, const Scalar
<LargestInt
> &j
) {
716 return Scalar
<T
>{std::invoke(fptr
, i
, j
)};
719 return Expr
<T
>{std::move(funcRef
)};
721 } else if (name
== "btest") {
722 if (const auto *ix
{UnwrapExpr
<Expr
<SomeInteger
>>(args
[0])}) {
723 return common::visit(
725 using IT
= ResultType
<decltype(x
)>;
726 return FoldElementalIntrinsic
<T
, IT
, SameInt
>(context
,
728 ScalarFunc
<T
, IT
, SameInt
>(
729 [&](const Scalar
<IT
> &x
, const Scalar
<SameInt
> &pos
) {
730 auto posVal
{pos
.ToInt64()};
731 if (posVal
< 0 || posVal
>= x
.bits
) {
732 context
.messages().Say(
733 "POS=%jd out of range for BTEST"_err_en_US
,
734 static_cast<std::intmax_t>(posVal
));
736 return Scalar
<T
>{x
.BTEST(posVal
)};
741 } else if (name
== "dot_product") {
742 return FoldDotProduct
<T
>(context
, std::move(funcRef
));
743 } else if (name
== "extends_type_of") {
744 // Type extension testing with EXTENDS_TYPE_OF() ignores any type
745 // parameters. Returns a constant truth value when the result is known now.
746 if (args
[0] && args
[1]) {
747 auto t0
{args
[0]->GetType()};
748 auto t1
{args
[1]->GetType()};
750 if (auto result
{t0
->ExtendsTypeOf(*t1
)}) {
751 return Expr
<T
>{*result
};
755 } else if (name
== "isnan" || name
== "__builtin_ieee_is_nan") {
756 // Only replace the type of the function if we can do the fold
757 if (args
[0] && args
[0]->UnwrapExpr() &&
758 IsActuallyConstant(*args
[0]->UnwrapExpr())) {
759 auto restorer
{context
.messages().DiscardMessages()};
760 using DefaultReal
= Type
<TypeCategory::Real
, 4>;
761 return FoldElementalIntrinsic
<T
, DefaultReal
>(context
, std::move(funcRef
),
762 ScalarFunc
<T
, DefaultReal
>([](const Scalar
<DefaultReal
> &x
) {
763 return Scalar
<T
>{x
.IsNotANumber()};
766 } else if (name
== "__builtin_ieee_is_negative") {
767 auto restorer
{context
.messages().DiscardMessages()};
768 using DefaultReal
= Type
<TypeCategory::Real
, 4>;
769 if (args
[0] && args
[0]->UnwrapExpr() &&
770 IsActuallyConstant(*args
[0]->UnwrapExpr())) {
771 return FoldElementalIntrinsic
<T
, DefaultReal
>(context
, std::move(funcRef
),
772 ScalarFunc
<T
, DefaultReal
>([](const Scalar
<DefaultReal
> &x
) {
773 return Scalar
<T
>{x
.IsNegative()};
776 } else if (name
== "__builtin_ieee_is_normal") {
777 auto restorer
{context
.messages().DiscardMessages()};
778 using DefaultReal
= Type
<TypeCategory::Real
, 4>;
779 if (args
[0] && args
[0]->UnwrapExpr() &&
780 IsActuallyConstant(*args
[0]->UnwrapExpr())) {
781 return FoldElementalIntrinsic
<T
, DefaultReal
>(context
, std::move(funcRef
),
782 ScalarFunc
<T
, DefaultReal
>([](const Scalar
<DefaultReal
> &x
) {
783 return Scalar
<T
>{x
.IsNormal()};
786 } else if (name
== "is_contiguous") {
788 if (auto *expr
{args
[0]->UnwrapExpr()}) {
789 if (auto contiguous
{IsContiguous(*expr
, context
)}) {
790 return Expr
<T
>{*contiguous
};
792 } else if (auto *assumedType
{args
[0]->GetAssumedTypeDummy()}) {
793 if (auto contiguous
{IsContiguous(*assumedType
, context
)}) {
794 return Expr
<T
>{*contiguous
};
798 } else if (name
== "is_iostat_end") {
799 if (args
[0] && args
[0]->UnwrapExpr() &&
800 IsActuallyConstant(*args
[0]->UnwrapExpr())) {
801 using Int64
= Type
<TypeCategory::Integer
, 8>;
802 return FoldElementalIntrinsic
<T
, Int64
>(context
, std::move(funcRef
),
803 ScalarFunc
<T
, Int64
>([](const Scalar
<Int64
> &x
) {
804 return Scalar
<T
>{x
.ToInt64() == FORTRAN_RUNTIME_IOSTAT_END
};
807 } else if (name
== "is_iostat_eor") {
808 if (args
[0] && args
[0]->UnwrapExpr() &&
809 IsActuallyConstant(*args
[0]->UnwrapExpr())) {
810 using Int64
= Type
<TypeCategory::Integer
, 8>;
811 return FoldElementalIntrinsic
<T
, Int64
>(context
, std::move(funcRef
),
812 ScalarFunc
<T
, Int64
>([](const Scalar
<Int64
> &x
) {
813 return Scalar
<T
>{x
.ToInt64() == FORTRAN_RUNTIME_IOSTAT_EOR
};
816 } else if (name
== "lge" || name
== "lgt" || name
== "lle" || name
== "llt") {
817 // Rewrite LGE/LGT/LLE/LLT into ASCII character relations
818 auto *cx0
{UnwrapExpr
<Expr
<SomeCharacter
>>(args
[0])};
819 auto *cx1
{UnwrapExpr
<Expr
<SomeCharacter
>>(args
[1])};
823 PackageRelation(name
== "lge" ? RelationalOperator::GE
824 : name
== "lgt" ? RelationalOperator::GT
825 : name
== "lle" ? RelationalOperator::LE
826 : RelationalOperator::LT
,
827 ConvertToType
<Ascii
>(std::move(*cx0
)),
828 ConvertToType
<Ascii
>(std::move(*cx1
)))));
830 } else if (name
== "logical") {
831 if (auto *expr
{UnwrapExpr
<Expr
<SomeLogical
>>(args
[0])}) {
832 return Fold(context
, ConvertToType
<T
>(std::move(*expr
)));
834 } else if (name
== "matmul") {
835 return FoldMatmul(context
, std::move(funcRef
));
836 } else if (name
== "out_of_range") {
837 return RewriteOutOfRange
<KIND
>(context
, std::move(funcRef
));
838 } else if (name
== "parity") {
839 return FoldAllAnyParity(
840 context
, std::move(funcRef
), &Scalar
<T
>::NEQV
, Scalar
<T
>{false});
841 } else if (name
== "same_type_as") {
842 // Type equality testing with SAME_TYPE_AS() ignores any type parameters.
843 // Returns a constant truth value when the result is known now.
844 if (args
[0] && args
[1]) {
845 auto t0
{args
[0]->GetType()};
846 auto t1
{args
[1]->GetType()};
848 if (auto result
{t0
->SameTypeAs(*t1
)}) {
849 return Expr
<T
>{*result
};
853 } else if (name
== "__builtin_ieee_support_datatype") {
854 return Expr
<T
>{true};
855 } else if (name
== "__builtin_ieee_support_denormal") {
856 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
857 IeeeFeature::Denormal
)};
858 } else if (name
== "__builtin_ieee_support_divide") {
859 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
860 IeeeFeature::Divide
)};
861 } else if (name
== "__builtin_ieee_support_flag") {
862 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
863 IeeeFeature::Flags
)};
864 } else if (name
== "__builtin_ieee_support_halting") {
865 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
866 IeeeFeature::Halting
)};
867 } else if (name
== "__builtin_ieee_support_inf") {
869 context
.targetCharacteristics().ieeeFeatures().test(IeeeFeature::Inf
)};
870 } else if (name
== "__builtin_ieee_support_io") {
872 context
.targetCharacteristics().ieeeFeatures().test(IeeeFeature::Io
)};
873 } else if (name
== "__builtin_ieee_support_nan") {
875 context
.targetCharacteristics().ieeeFeatures().test(IeeeFeature::NaN
)};
876 } else if (name
== "__builtin_ieee_support_rounding") {
877 if (context
.targetCharacteristics().ieeeFeatures().test(
878 IeeeFeature::Rounding
)) {
879 if (auto mode
{GetRoundingMode(args
[0])}) {
880 return Expr
<T
>{mode
!= common::RoundingMode::TiesAwayFromZero
};
883 } else if (name
== "__builtin_ieee_support_sqrt") {
885 context
.targetCharacteristics().ieeeFeatures().test(IeeeFeature::Sqrt
)};
886 } else if (name
== "__builtin_ieee_support_standard") {
887 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
888 IeeeFeature::Standard
)};
889 } else if (name
== "__builtin_ieee_support_subnormal") {
890 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
891 IeeeFeature::Subnormal
)};
892 } else if (name
== "__builtin_ieee_support_underflow_control") {
893 return Expr
<T
>{context
.targetCharacteristics().ieeeFeatures().test(
894 IeeeFeature::UnderflowControl
)};
896 return Expr
<T
>{std::move(funcRef
)};
899 template <typename T
>
900 Expr
<LogicalResult
> FoldOperation(
901 FoldingContext
&context
, Relational
<T
> &&relation
) {
902 if (auto array
{ApplyElementwise(context
, relation
,
903 std::function
<Expr
<LogicalResult
>(Expr
<T
> &&, Expr
<T
> &&)>{
904 [=](Expr
<T
> &&x
, Expr
<T
> &&y
) {
905 return Expr
<LogicalResult
>{Relational
<SomeType
>{
906 Relational
<T
>{relation
.opr
, std::move(x
), std::move(y
)}}};
910 if (auto folded
{OperandsAreConstants(relation
)}) {
912 if constexpr (T::category
== TypeCategory::Integer
) {
914 Satisfies(relation
.opr
, folded
->first
.CompareSigned(folded
->second
));
915 } else if constexpr (T::category
== TypeCategory::Real
) {
916 result
= Satisfies(relation
.opr
, folded
->first
.Compare(folded
->second
));
917 } else if constexpr (T::category
== TypeCategory::Complex
) {
918 result
= (relation
.opr
== RelationalOperator::EQ
) ==
919 folded
->first
.Equals(folded
->second
);
920 } else if constexpr (T::category
== TypeCategory::Character
) {
921 result
= Satisfies(relation
.opr
, Compare(folded
->first
, folded
->second
));
923 static_assert(T::category
!= TypeCategory::Logical
);
925 return Expr
<LogicalResult
>{Constant
<LogicalResult
>{result
}};
927 return Expr
<LogicalResult
>{Relational
<SomeType
>{std::move(relation
)}};
930 Expr
<LogicalResult
> FoldOperation(
931 FoldingContext
&context
, Relational
<SomeType
> &&relation
) {
932 return common::visit(
934 return Expr
<LogicalResult
>{FoldOperation(context
, std::move(x
))};
936 std::move(relation
.u
));
940 Expr
<Type
<TypeCategory::Logical
, KIND
>> FoldOperation(
941 FoldingContext
&context
, Not
<KIND
> &&x
) {
942 if (auto array
{ApplyElementwise(context
, x
)}) {
945 using Ty
= Type
<TypeCategory::Logical
, KIND
>;
946 auto &operand
{x
.left()};
947 if (auto value
{GetScalarConstantValue
<Ty
>(operand
)}) {
948 return Expr
<Ty
>{Constant
<Ty
>{!value
->IsTrue()}};
954 Expr
<Type
<TypeCategory::Logical
, KIND
>> FoldOperation(
955 FoldingContext
&context
, LogicalOperation
<KIND
> &&operation
) {
956 using LOGICAL
= Type
<TypeCategory::Logical
, KIND
>;
957 if (auto array
{ApplyElementwise(context
, operation
,
958 std::function
<Expr
<LOGICAL
>(Expr
<LOGICAL
> &&, Expr
<LOGICAL
> &&)>{
959 [=](Expr
<LOGICAL
> &&x
, Expr
<LOGICAL
> &&y
) {
960 return Expr
<LOGICAL
>{LogicalOperation
<KIND
>{
961 operation
.logicalOperator
, std::move(x
), std::move(y
)}};
965 if (auto folded
{OperandsAreConstants(operation
)}) {
966 bool xt
{folded
->first
.IsTrue()}, yt
{folded
->second
.IsTrue()}, result
{};
967 switch (operation
.logicalOperator
) {
968 case LogicalOperator::And
:
971 case LogicalOperator::Or
:
974 case LogicalOperator::Eqv
:
977 case LogicalOperator::Neqv
:
980 case LogicalOperator::Not
:
981 DIE("not a binary operator");
983 return Expr
<LOGICAL
>{Constant
<LOGICAL
>{result
}};
985 return Expr
<LOGICAL
>{std::move(operation
)};
988 #ifdef _MSC_VER // disable bogus warning about missing definitions
989 #pragma warning(disable : 4661)
991 FOR_EACH_LOGICAL_KIND(template class ExpressionBase
, )
992 template class ExpressionBase
<SomeLogical
>;
993 } // namespace Fortran::evaluate