1 //===-- lib/Evaluate/fold-logical.cpp -------------------------------------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "fold-implementation.h"
10 #include "fold-reduction.h"
11 #include "flang/Evaluate/check-expression.h"
12
13 namespace Fortran::evaluate {
14
15 template <typename T>
ZeroExtend(const Constant<T> & c)16 static std::optional<Expr<SomeType>> ZeroExtend(const Constant<T> &c) {
17 std::vector<Scalar<LargestInt>> exts;
18 for (const auto &v : c.values()) {
19 exts.push_back(Scalar<LargestInt>::ConvertUnsigned(v).value);
20 }
21 return AsGenericExpr(
22 Constant<LargestInt>(std::move(exts), ConstantSubscripts(c.shape())));
23 }
24
25 // for ALL & ANY
26 template <typename T>
FoldAllAny(FoldingContext & context,FunctionRef<T> && ref,Scalar<T> (Scalar<T>::* operation)(const Scalar<T> &)const,Scalar<T> identity)27 static Expr<T> FoldAllAny(FoldingContext &context, FunctionRef<T> &&ref,
28 Scalar<T> (Scalar<T>::*operation)(const Scalar<T> &) const,
29 Scalar<T> identity) {
30 static_assert(T::category == TypeCategory::Logical);
31 using Element = Scalar<T>;
32 std::optional<int> dim;
33 if (std::optional<Constant<T>> array{
34 ProcessReductionArgs<T>(context, ref.arguments(), dim, identity,
35 /*ARRAY(MASK)=*/0, /*DIM=*/1)}) {
36 auto accumulator{[&](Element &element, const ConstantSubscripts &at) {
37 element = (element.*operation)(array->At(at));
38 }};
39 return Expr<T>{DoReduction<T>(*array, dim, identity, accumulator)};
40 }
41 return Expr<T>{std::move(ref)};
42 }
43
44 template <int KIND>
FoldIntrinsicFunction(FoldingContext & context,FunctionRef<Type<TypeCategory::Logical,KIND>> && funcRef)45 Expr<Type<TypeCategory::Logical, KIND>> FoldIntrinsicFunction(
46 FoldingContext &context,
47 FunctionRef<Type<TypeCategory::Logical, KIND>> &&funcRef) {
48 using T = Type<TypeCategory::Logical, KIND>;
49 ActualArguments &args{funcRef.arguments()};
50 auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)};
51 CHECK(intrinsic);
52 std::string name{intrinsic->name};
53 using SameInt = Type<TypeCategory::Integer, KIND>;
54 if (name == "all") {
55 return FoldAllAny(
56 context, std::move(funcRef), &Scalar<T>::AND, Scalar<T>{true});
57 } else if (name == "any") {
58 return FoldAllAny(
59 context, std::move(funcRef), &Scalar<T>::OR, Scalar<T>{false});
60 } else if (name == "associated") {
61 bool gotConstant{true};
62 const Expr<SomeType> *firstArgExpr{args[0]->UnwrapExpr()};
63 if (!firstArgExpr || !IsNullPointer(*firstArgExpr)) {
64 gotConstant = false;
65 } else if (args[1]) { // There's a second argument
66 const Expr<SomeType> *secondArgExpr{args[1]->UnwrapExpr()};
67 if (!secondArgExpr || !IsNullPointer(*secondArgExpr)) {
68 gotConstant = false;
69 }
70 }
71 return gotConstant ? Expr<T>{false} : Expr<T>{std::move(funcRef)};
72 } else if (name == "bge" || name == "bgt" || name == "ble" || name == "blt") {
73 static_assert(std::is_same_v<Scalar<LargestInt>, BOZLiteralConstant>);
74
75 // The arguments to these intrinsics can be of different types. In that
76 // case, the shorter of the two would need to be zero-extended to match
77 // the size of the other. If at least one of the operands is not a constant,
78 // the zero-extending will be done during lowering. Otherwise, the folding
79 // must be done here.
80 std::optional<Expr<SomeType>> constArgs[2];
81 for (int i{0}; i <= 1; i++) {
82 if (BOZLiteralConstant * x{UnwrapExpr<BOZLiteralConstant>(args[i])}) {
83 constArgs[i] = AsGenericExpr(Constant<LargestInt>{std::move(*x)});
84 } else if (auto *x{UnwrapExpr<Expr<SomeInteger>>(args[i])}) {
85 common::visit(
86 [&](const auto &ix) {
87 using IntT = typename std::decay_t<decltype(ix)>::Result;
88 if (auto *c{UnwrapConstantValue<IntT>(ix)}) {
89 constArgs[i] = ZeroExtend(*c);
90 }
91 },
92 x->u);
93 }
94 }
95
96 if (constArgs[0] && constArgs[1]) {
97 auto fptr{&Scalar<LargestInt>::BGE};
98 if (name == "bge") { // done in fptr declaration
99 } else if (name == "bgt") {
100 fptr = &Scalar<LargestInt>::BGT;
101 } else if (name == "ble") {
102 fptr = &Scalar<LargestInt>::BLE;
103 } else if (name == "blt") {
104 fptr = &Scalar<LargestInt>::BLT;
105 } else {
106 common::die("missing case to fold intrinsic function %s", name.c_str());
107 }
108
109 for (int i{0}; i <= 1; i++) {
110 *args[i] = std::move(constArgs[i].value());
111 }
112
113 return FoldElementalIntrinsic<T, LargestInt, LargestInt>(context,
114 std::move(funcRef),
115 ScalarFunc<T, LargestInt, LargestInt>(
116 [&fptr](
117 const Scalar<LargestInt> &i, const Scalar<LargestInt> &j) {
118 return Scalar<T>{std::invoke(fptr, i, j)};
119 }));
120 } else {
121 return Expr<T>{std::move(funcRef)};
122 }
123 } else if (name == "btest") {
124 if (const auto *ix{UnwrapExpr<Expr<SomeInteger>>(args[0])}) {
125 return common::visit(
126 [&](const auto &x) {
127 using IT = ResultType<decltype(x)>;
128 return FoldElementalIntrinsic<T, IT, SameInt>(context,
129 std::move(funcRef),
130 ScalarFunc<T, IT, SameInt>(
131 [&](const Scalar<IT> &x, const Scalar<SameInt> &pos) {
132 auto posVal{pos.ToInt64()};
133 if (posVal < 0 || posVal >= x.bits) {
134 context.messages().Say(
135 "POS=%jd out of range for BTEST"_err_en_US,
136 static_cast<std::intmax_t>(posVal));
137 }
138 return Scalar<T>{x.BTEST(posVal)};
139 }));
140 },
141 ix->u);
142 }
143 } else if (name == "extends_type_of") {
144 // Type extension testing with EXTENDS_TYPE_OF() ignores any type
145 // parameters. Returns a constant truth value when the result is known now.
146 if (args[0] && args[1]) {
147 auto t0{args[0]->GetType()};
148 auto t1{args[1]->GetType()};
149 if (t0 && t1) {
150 if (auto result{t0->ExtendsTypeOf(*t1)}) {
151 return Expr<T>{*result};
152 }
153 }
154 }
155 } else if (name == "isnan" || name == "__builtin_ieee_is_nan") {
156 // A warning about an invalid argument is discarded from converting
157 // the argument of isnan() / IEEE_IS_NAN().
158 auto restorer{context.messages().DiscardMessages()};
159 using DefaultReal = Type<TypeCategory::Real, 4>;
160 return FoldElementalIntrinsic<T, DefaultReal>(context, std::move(funcRef),
161 ScalarFunc<T, DefaultReal>([](const Scalar<DefaultReal> &x) {
162 return Scalar<T>{x.IsNotANumber()};
163 }));
164 } else if (name == "__builtin_ieee_is_negative") {
165 auto restorer{context.messages().DiscardMessages()};
166 using DefaultReal = Type<TypeCategory::Real, 4>;
167 return FoldElementalIntrinsic<T, DefaultReal>(context, std::move(funcRef),
168 ScalarFunc<T, DefaultReal>([](const Scalar<DefaultReal> &x) {
169 return Scalar<T>{x.IsNegative()};
170 }));
171 } else if (name == "__builtin_ieee_is_normal") {
172 auto restorer{context.messages().DiscardMessages()};
173 using DefaultReal = Type<TypeCategory::Real, 4>;
174 return FoldElementalIntrinsic<T, DefaultReal>(context, std::move(funcRef),
175 ScalarFunc<T, DefaultReal>([](const Scalar<DefaultReal> &x) {
176 return Scalar<T>{x.IsNormal()};
177 }));
178 } else if (name == "is_contiguous") {
179 if (args.at(0)) {
180 if (auto *expr{args[0]->UnwrapExpr()}) {
181 if (IsSimplyContiguous(*expr, context)) {
182 return Expr<T>{true};
183 }
184 }
185 }
186 } else if (name == "lge" || name == "lgt" || name == "lle" || name == "llt") {
187 // Rewrite LGE/LGT/LLE/LLT into ASCII character relations
188 auto *cx0{UnwrapExpr<Expr<SomeCharacter>>(args[0])};
189 auto *cx1{UnwrapExpr<Expr<SomeCharacter>>(args[1])};
190 if (cx0 && cx1) {
191 return Fold(context,
192 ConvertToType<T>(
193 PackageRelation(name == "lge" ? RelationalOperator::GE
194 : name == "lgt" ? RelationalOperator::GT
195 : name == "lle" ? RelationalOperator::LE
196 : RelationalOperator::LT,
197 ConvertToType<Ascii>(std::move(*cx0)),
198 ConvertToType<Ascii>(std::move(*cx1)))));
199 }
200 } else if (name == "logical") {
201 if (auto *expr{UnwrapExpr<Expr<SomeLogical>>(args[0])}) {
202 return Fold(context, ConvertToType<T>(std::move(*expr)));
203 }
204 } else if (name == "merge") {
205 return FoldMerge<T>(context, std::move(funcRef));
206 } else if (name == "same_type_as") {
207 // Type equality testing with SAME_TYPE_AS() ignores any type parameters.
208 // Returns a constant truth value when the result is known now.
209 if (args[0] && args[1]) {
210 auto t0{args[0]->GetType()};
211 auto t1{args[1]->GetType()};
212 if (t0 && t1) {
213 if (auto result{t0->SameTypeAs(*t1)}) {
214 return Expr<T>{*result};
215 }
216 }
217 }
218 } else if (name == "__builtin_ieee_support_datatype" ||
219 name == "__builtin_ieee_support_denormal" ||
220 name == "__builtin_ieee_support_divide" ||
221 name == "__builtin_ieee_support_divide" ||
222 name == "__builtin_ieee_support_inf" ||
223 name == "__builtin_ieee_support_io" ||
224 name == "__builtin_ieee_support_nan" ||
225 name == "__builtin_ieee_support_sqrt" ||
226 name == "__builtin_ieee_support_standard" ||
227 name == "__builtin_ieee_support_subnormal" ||
228 name == "__builtin_ieee_support_underflow_control") {
229 return Expr<T>{true};
230 }
231 // TODO: dot_product, is_iostat_end,
232 // is_iostat_eor, logical, matmul, out_of_range,
233 // parity
234 return Expr<T>{std::move(funcRef)};
235 }
236
237 template <typename T>
FoldOperation(FoldingContext & context,Relational<T> && relation)238 Expr<LogicalResult> FoldOperation(
239 FoldingContext &context, Relational<T> &&relation) {
240 if (auto array{ApplyElementwise(context, relation,
241 std::function<Expr<LogicalResult>(Expr<T> &&, Expr<T> &&)>{
242 [=](Expr<T> &&x, Expr<T> &&y) {
243 return Expr<LogicalResult>{Relational<SomeType>{
244 Relational<T>{relation.opr, std::move(x), std::move(y)}}};
245 }})}) {
246 return *array;
247 }
248 if (auto folded{OperandsAreConstants(relation)}) {
249 bool result{};
250 if constexpr (T::category == TypeCategory::Integer) {
251 result =
252 Satisfies(relation.opr, folded->first.CompareSigned(folded->second));
253 } else if constexpr (T::category == TypeCategory::Real) {
254 result = Satisfies(relation.opr, folded->first.Compare(folded->second));
255 } else if constexpr (T::category == TypeCategory::Complex) {
256 result = (relation.opr == RelationalOperator::EQ) ==
257 folded->first.Equals(folded->second);
258 } else if constexpr (T::category == TypeCategory::Character) {
259 result = Satisfies(relation.opr, Compare(folded->first, folded->second));
260 } else {
261 static_assert(T::category != TypeCategory::Logical);
262 }
263 return Expr<LogicalResult>{Constant<LogicalResult>{result}};
264 }
265 return Expr<LogicalResult>{Relational<SomeType>{std::move(relation)}};
266 }
267
FoldOperation(FoldingContext & context,Relational<SomeType> && relation)268 Expr<LogicalResult> FoldOperation(
269 FoldingContext &context, Relational<SomeType> &&relation) {
270 return common::visit(
271 [&](auto &&x) {
272 return Expr<LogicalResult>{FoldOperation(context, std::move(x))};
273 },
274 std::move(relation.u));
275 }
276
277 template <int KIND>
FoldOperation(FoldingContext & context,Not<KIND> && x)278 Expr<Type<TypeCategory::Logical, KIND>> FoldOperation(
279 FoldingContext &context, Not<KIND> &&x) {
280 if (auto array{ApplyElementwise(context, x)}) {
281 return *array;
282 }
283 using Ty = Type<TypeCategory::Logical, KIND>;
284 auto &operand{x.left()};
285 if (auto value{GetScalarConstantValue<Ty>(operand)}) {
286 return Expr<Ty>{Constant<Ty>{!value->IsTrue()}};
287 }
288 return Expr<Ty>{x};
289 }
290
291 template <int KIND>
FoldOperation(FoldingContext & context,LogicalOperation<KIND> && operation)292 Expr<Type<TypeCategory::Logical, KIND>> FoldOperation(
293 FoldingContext &context, LogicalOperation<KIND> &&operation) {
294 using LOGICAL = Type<TypeCategory::Logical, KIND>;
295 if (auto array{ApplyElementwise(context, operation,
296 std::function<Expr<LOGICAL>(Expr<LOGICAL> &&, Expr<LOGICAL> &&)>{
297 [=](Expr<LOGICAL> &&x, Expr<LOGICAL> &&y) {
298 return Expr<LOGICAL>{LogicalOperation<KIND>{
299 operation.logicalOperator, std::move(x), std::move(y)}};
300 }})}) {
301 return *array;
302 }
303 if (auto folded{OperandsAreConstants(operation)}) {
304 bool xt{folded->first.IsTrue()}, yt{folded->second.IsTrue()}, result{};
305 switch (operation.logicalOperator) {
306 case LogicalOperator::And:
307 result = xt && yt;
308 break;
309 case LogicalOperator::Or:
310 result = xt || yt;
311 break;
312 case LogicalOperator::Eqv:
313 result = xt == yt;
314 break;
315 case LogicalOperator::Neqv:
316 result = xt != yt;
317 break;
318 case LogicalOperator::Not:
319 DIE("not a binary operator");
320 }
321 return Expr<LOGICAL>{Constant<LOGICAL>{result}};
322 }
323 return Expr<LOGICAL>{std::move(operation)};
324 }
325
326 #ifdef _MSC_VER // disable bogus warning about missing definitions
327 #pragma warning(disable : 4661)
328 #endif
329 FOR_EACH_LOGICAL_KIND(template class ExpressionBase, )
330 template class ExpressionBase<SomeLogical>;
331 } // namespace Fortran::evaluate
332