FLANG
fold-implementation.h
1//===-- lib/Evaluate/fold-implementation.h --------------------------------===//
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#ifndef FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
10#define FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
11
12#include "character.h"
13#include "host.h"
14#include "int-power.h"
15#include "flang/Common/indirection.h"
16#include "flang/Common/template.h"
17#include "flang/Common/unwrap.h"
18#include "flang/Evaluate/characteristics.h"
19#include "flang/Evaluate/common.h"
20#include "flang/Evaluate/constant.h"
21#include "flang/Evaluate/expression.h"
22#include "flang/Evaluate/fold.h"
23#include "flang/Evaluate/intrinsics-library.h"
24#include "flang/Evaluate/intrinsics.h"
25#include "flang/Evaluate/shape.h"
26#include "flang/Evaluate/tools.h"
27#include "flang/Evaluate/traverse.h"
28#include "flang/Evaluate/type.h"
29#include "flang/Parser/message.h"
30#include "flang/Semantics/scope.h"
31#include "flang/Semantics/symbol.h"
32#include "flang/Semantics/tools.h"
33#include <algorithm>
34#include <cmath>
35#include <cstdio>
36#include <optional>
37#include <type_traits>
38#include <variant>
39
40// Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE
41// to leak out of <math.h>.
42#undef HUGE
43
44namespace Fortran::evaluate {
45
46// Don't use Kahan extended precision summation any more when folding
47// transformational intrinsic functions other than SUM, since it is
48// not used in the runtime implementations of those functions and we
49// want results to match.
50static constexpr bool useKahanSummation{false};
51
52// Utilities
53template <typename T> class Folder {
54public:
55 explicit Folder(FoldingContext &c, bool forOptionalArgument = false)
56 : context_{c}, forOptionalArgument_{forOptionalArgument} {}
57 std::optional<Constant<T>> GetNamedConstant(const Symbol &);
58 std::optional<Constant<T>> ApplySubscripts(const Constant<T> &array,
59 const std::vector<Constant<SubscriptInteger>> &subscripts);
60 std::optional<Constant<T>> ApplyComponent(Constant<SomeDerived> &&,
61 const Symbol &component,
62 const std::vector<Constant<SubscriptInteger>> * = nullptr);
63 std::optional<Constant<T>> GetConstantComponent(
64 Component &, const std::vector<Constant<SubscriptInteger>> * = nullptr);
65 std::optional<Constant<T>> Folding(ArrayRef &);
66 std::optional<Constant<T>> Folding(DataRef &);
67 Expr<T> Folding(Designator<T> &&);
68 Constant<T> *Folding(std::optional<ActualArgument> &);
69
70 Expr<T> CSHIFT(FunctionRef<T> &&);
71 Expr<T> EOSHIFT(FunctionRef<T> &&);
72 Expr<T> MERGE(FunctionRef<T> &&);
73 Expr<T> PACK(FunctionRef<T> &&);
74 Expr<T> RESHAPE(FunctionRef<T> &&);
75 Expr<T> SPREAD(FunctionRef<T> &&);
76 Expr<T> TRANSPOSE(FunctionRef<T> &&);
77 Expr<T> UNPACK(FunctionRef<T> &&);
78
79 Expr<T> TRANSFER(FunctionRef<T> &&);
80
81private:
82 FoldingContext &context_;
83 bool forOptionalArgument_{false};
84};
85
86std::optional<Constant<SubscriptInteger>> GetConstantSubscript(
87 FoldingContext &, Subscript &, const NamedEntity &, int dim);
88
89// Helper to use host runtime on scalars for folding.
90template <typename TR, typename... TA>
91std::optional<std::function<Scalar<TR>(FoldingContext &, Scalar<TA>...)>>
92GetHostRuntimeWrapper(const std::string &name) {
93 std::vector<DynamicType> argTypes{TA{}.GetType()...};
94 if (auto hostWrapper{GetHostRuntimeWrapper(name, TR{}.GetType(), argTypes)}) {
95 return [hostWrapper](
96 FoldingContext &context, Scalar<TA>... args) -> Scalar<TR> {
97 std::vector<Expr<SomeType>> genericArgs{
98 AsGenericExpr(Constant<TA>{args})...};
99 return GetScalarConstantValue<TR>(
100 (*hostWrapper)(context, std::move(genericArgs)))
101 .value();
102 };
103 }
104 return std::nullopt;
105}
106
107// FoldOperation() rewrites expression tree nodes.
108// If there is any possibility that the rewritten node will
109// not have the same representation type, the result of
110// FoldOperation() will be packaged in an Expr<> of the same
111// specific type.
112
113// no-op base case
114template <typename A>
115common::IfNoLvalue<Expr<ResultType<A>>, A> FoldOperation(
116 FoldingContext &, A &&x) {
117 static_assert(!std::is_same_v<A, Expr<ResultType<A>>>,
118 "call Fold() instead for Expr<>");
119 return Expr<ResultType<A>>{std::move(x)};
120}
121
122Component FoldOperation(FoldingContext &, Component &&);
123NamedEntity FoldOperation(FoldingContext &, NamedEntity &&);
124Triplet FoldOperation(FoldingContext &, Triplet &&);
125Subscript FoldOperation(FoldingContext &, Subscript &&);
126ArrayRef FoldOperation(FoldingContext &, ArrayRef &&);
127CoarrayRef FoldOperation(FoldingContext &, CoarrayRef &&);
128DataRef FoldOperation(FoldingContext &, DataRef &&);
129Substring FoldOperation(FoldingContext &, Substring &&);
130ComplexPart FoldOperation(FoldingContext &, ComplexPart &&);
131template <typename T>
132Expr<T> FoldOperation(FoldingContext &, FunctionRef<T> &&);
133template <typename T>
134Expr<T> FoldOperation(FoldingContext &context, Designator<T> &&designator) {
135 return Folder<T>{context}.Folding(std::move(designator));
136}
141Expr<ImpliedDoIndex::Result> FoldOperation(
142 FoldingContext &context, ImpliedDoIndex &&);
143template <typename T>
144Expr<T> FoldOperation(FoldingContext &, ArrayConstructor<T> &&);
146template <typename T>
147Expr<T> FoldOperation(FoldingContext &, ConditionalExpr<T> &&);
148
149template <typename T>
150std::optional<Constant<T>> Folder<T>::GetNamedConstant(const Symbol &symbol0) {
151 const Symbol &symbol{ResolveAssociations(symbol0)};
152 if (IsNamedConstant(symbol)) {
153 if (const auto *object{
154 symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
155 if (const auto *constant{UnwrapConstantValue<T>(object->init())}) {
156 return *constant;
157 }
158 }
159 }
160 return std::nullopt;
161}
162
163template <typename T>
164std::optional<Constant<T>> Folder<T>::Folding(ArrayRef &aRef) {
165 std::vector<Constant<SubscriptInteger>> subscripts;
166 int dim{0};
167 for (Subscript &ss : aRef.subscript()) {
168 if (auto constant{GetConstantSubscript(context_, ss, aRef.base(), dim++)}) {
169 subscripts.emplace_back(std::move(*constant));
170 } else {
171 return std::nullopt;
172 }
173 }
174 if (Component * component{aRef.base().UnwrapComponent()}) {
175 return GetConstantComponent(*component, &subscripts);
176 } else if (std::optional<Constant<T>> array{
177 GetNamedConstant(aRef.base().GetLastSymbol())}) {
178 return ApplySubscripts(*array, subscripts);
179 } else {
180 return std::nullopt;
181 }
182}
183
184template <typename T>
185std::optional<Constant<T>> Folder<T>::Folding(DataRef &ref) {
186 return common::visit(
187 common::visitors{
188 [this](SymbolRef &sym) { return GetNamedConstant(*sym); },
189 [this](Component &comp) {
190 comp = FoldOperation(context_, std::move(comp));
191 return GetConstantComponent(comp);
192 },
193 [this](ArrayRef &aRef) {
194 aRef = FoldOperation(context_, std::move(aRef));
195 return Folding(aRef);
196 },
197 [](CoarrayRef &) { return std::optional<Constant<T>>{}; },
198 },
199 ref.u);
200}
201
202// TODO: This would be more natural as a member function of Constant<T>.
203template <typename T>
204std::optional<Constant<T>> Folder<T>::ApplySubscripts(const Constant<T> &array,
205 const std::vector<Constant<SubscriptInteger>> &subscripts) {
206 const auto &shape{array.shape()};
207 const auto &lbounds{array.lbounds()};
208 int rank{GetRank(shape)};
209 CHECK(rank == static_cast<int>(subscripts.size()));
210 std::size_t elements{1};
211 ConstantSubscripts resultShape;
212 ConstantSubscripts ssLB;
213 for (const auto &ss : subscripts) {
214 if (ss.Rank() == 1) {
215 resultShape.push_back(static_cast<ConstantSubscript>(ss.size()));
216 elements *= ss.size();
217 ssLB.push_back(ss.lbounds().front());
218 } else if (ss.Rank() > 1) {
219 return std::nullopt; // error recovery
220 }
221 }
222 ConstantSubscripts ssAt(rank, 0), at(rank, 0), tmp(1, 0);
223 std::vector<Scalar<T>> values;
224 while (elements-- > 0) {
225 bool increment{true};
226 int k{0};
227 for (int j{0}; j < rank; ++j) {
228 if (subscripts[j].Rank() == 0) {
229 at[j] = subscripts[j].GetScalarValue().value().ToInt64();
230 } else {
231 CHECK(k < GetRank(resultShape));
232 tmp[0] = ssLB.at(k) + ssAt.at(k);
233 at[j] = subscripts[j].At(tmp).ToInt64();
234 if (increment) {
235 if (++ssAt[k] == resultShape[k]) {
236 ssAt[k] = 0;
237 } else {
238 increment = false;
239 }
240 }
241 ++k;
242 }
243 if (at[j] < lbounds[j] || at[j] >= lbounds[j] + shape[j]) {
244 context_.messages().Say(
245 "Subscript value (%jd) is out of range on dimension %d in reference to a constant array value"_err_en_US,
246 at[j], j + 1);
247 return std::nullopt;
248 }
249 }
250 values.emplace_back(array.At(at));
251 CHECK(!increment || elements == 0);
252 CHECK(k == GetRank(resultShape));
253 }
254 if constexpr (T::category == TypeCategory::Character) {
255 return Constant<T>{array.LEN(), std::move(values), std::move(resultShape)};
256 } else if constexpr (std::is_same_v<T, SomeDerived>) {
257 return Constant<T>{array.result().derivedTypeSpec(), std::move(values),
258 std::move(resultShape)};
259 } else {
260 return Constant<T>{std::move(values), std::move(resultShape)};
261 }
262}
263
264template <typename T>
265std::optional<Constant<T>> Folder<T>::ApplyComponent(
266 Constant<SomeDerived> &&structures, const Symbol &component,
267 const std::vector<Constant<SubscriptInteger>> *subscripts) {
268 if (auto scalar{structures.GetScalarValue()}) {
269 if (std::optional<Expr<SomeType>> expr{scalar->Find(component)}) {
270 if (const Constant<T> *value{UnwrapConstantValue<T>(*expr)}) {
271 if (subscripts) {
272 return ApplySubscripts(*value, *subscripts);
273 } else {
274 return *value;
275 }
276 }
277 }
278 } else {
279 // A(:)%scalar_component & A(:)%array_component(subscripts)
280 std::unique_ptr<ArrayConstructor<T>> array;
281 if (structures.empty()) {
282 return std::nullopt;
283 }
284 ConstantSubscripts at{structures.lbounds()};
285 do {
286 StructureConstructor scalar{structures.At(at)};
287 if (std::optional<Expr<SomeType>> expr{scalar.Find(component)}) {
288 if (const Constant<T> *value{UnwrapConstantValue<T>(expr.value())}) {
289 if (!array.get()) {
290 // This technique ensures that character length or derived type
291 // information is propagated to the array constructor.
292 auto *typedExpr{UnwrapExpr<Expr<T>>(expr.value())};
293 CHECK(typedExpr);
294 array = std::make_unique<ArrayConstructor<T>>(*typedExpr);
295 if constexpr (T::category == TypeCategory::Character) {
296 array->set_LEN(Expr<SubscriptInteger>{value->LEN()});
297 }
298 }
299 if (subscripts) {
300 if (auto element{ApplySubscripts(*value, *subscripts)}) {
301 CHECK(element->Rank() == 0);
302 array->Push(Expr<T>{std::move(*element)});
303 } else {
304 return std::nullopt;
305 }
306 } else {
307 CHECK(value->Rank() == 0);
308 array->Push(Expr<T>{*value});
309 }
310 } else {
311 return std::nullopt;
312 }
313 }
314 } while (structures.IncrementSubscripts(at));
315 // Fold the ArrayConstructor<> into a Constant<>.
316 CHECK(array);
317 Expr<T> result{Fold(context_, Expr<T>{std::move(*array)})};
318 if (auto *constant{UnwrapConstantValue<T>(result)}) {
319 return constant->Reshape(common::Clone(structures.shape()));
320 }
321 }
322 return std::nullopt;
323}
324
325template <typename T>
326std::optional<Constant<T>> Folder<T>::GetConstantComponent(Component &component,
327 const std::vector<Constant<SubscriptInteger>> *subscripts) {
328 if (std::optional<Constant<SomeDerived>> structures{common::visit(
329 common::visitors{
330 [&](const Symbol &symbol) {
331 return Folder<SomeDerived>{context_}.GetNamedConstant(symbol);
332 },
333 [&](ArrayRef &aRef) {
334 return Folder<SomeDerived>{context_}.Folding(aRef);
335 },
336 [&](Component &base) {
337 return Folder<SomeDerived>{context_}.GetConstantComponent(base);
338 },
339 [&](CoarrayRef &) {
340 return std::optional<Constant<SomeDerived>>{};
341 },
342 },
343 component.base().u)}) {
344 return ApplyComponent(
345 std::move(*structures), component.GetLastSymbol(), subscripts);
346 } else {
347 return std::nullopt;
348 }
349}
350
351template <typename T> Expr<T> Folder<T>::Folding(Designator<T> &&designator) {
352 if constexpr (T::category == TypeCategory::Character) {
353 if (auto *substring{common::Unwrap<Substring>(designator.u)}) {
354 if (std::optional<Expr<SomeCharacter>> folded{
355 substring->Fold(context_)}) {
356 if (const auto *specific{std::get_if<Expr<T>>(&folded->u)}) {
357 return std::move(*specific);
358 }
359 }
360 // We used to fold zero-length substrings into zero-length
361 // constants here, but that led to problems in variable
362 // definition contexts.
363 }
364 } else if constexpr (T::category == TypeCategory::Real) {
365 if (auto *zPart{std::get_if<ComplexPart>(&designator.u)}) {
366 *zPart = FoldOperation(context_, std::move(*zPart));
368 if (auto zConst{Folder<ComplexT>{context_}.Folding(zPart->complex())}) {
369 return Fold(context_,
371 zPart->part() == ComplexPart::Part::IM,
372 Expr<ComplexT>{std::move(*zConst)}}});
373 } else {
374 return Expr<T>{Designator<T>{std::move(*zPart)}};
375 }
376 }
377 }
378 return common::visit(
379 common::visitors{
380 [&](SymbolRef &&symbol) {
381 if (auto constant{GetNamedConstant(*symbol)}) {
382 return Expr<T>{std::move(*constant)};
383 }
384 return Expr<T>{std::move(designator)};
385 },
386 [&](ArrayRef &&aRef) {
387 aRef = FoldOperation(context_, std::move(aRef));
388 if (auto c{Folding(aRef)}) {
389 return Expr<T>{std::move(*c)};
390 } else {
391 return Expr<T>{Designator<T>{std::move(aRef)}};
392 }
393 },
394 [&](Component &&component) {
395 component = FoldOperation(context_, std::move(component));
396 if (auto c{GetConstantComponent(component)}) {
397 return Expr<T>{std::move(*c)};
398 } else {
399 return Expr<T>{Designator<T>{std::move(component)}};
400 }
401 },
402 [&](auto &&x) {
403 return Expr<T>{
404 Designator<T>{FoldOperation(context_, std::move(x))}};
405 },
406 },
407 std::move(designator.u));
408}
409
410// Apply type conversion and re-folding if necessary.
411// This is where BOZ arguments are converted.
412template <typename T>
413Constant<T> *Folder<T>::Folding(std::optional<ActualArgument> &arg) {
414 if (auto *expr{UnwrapExpr<Expr<SomeType>>(arg)}) {
415 *expr = Fold(context_, std::move(*expr));
416 if constexpr (T::category != TypeCategory::Derived) {
417 if (!UnwrapExpr<Expr<T>>(*expr)) {
418 if (const Symbol *
419 var{forOptionalArgument_
420 ? UnwrapWholeSymbolOrComponentDataRef(*expr)
421 : nullptr};
422 var && (IsOptional(*var) || IsAllocatableOrObjectPointer(var))) {
423 // can't safely convert item that may not be present
424 } else if (auto converted{
425 ConvertToType(T::GetType(), std::move(*expr))}) {
426 *expr = Fold(context_, std::move(*converted));
427 }
428 }
429 }
430 return UnwrapConstantValue<T>(*expr);
431 }
432 return nullptr;
433}
434
435template <typename... A, std::size_t... I>
436std::optional<std::tuple<const Constant<A> *...>> GetConstantArgumentsHelper(
437 FoldingContext &context, ActualArguments &arguments,
438 bool hasOptionalArgument, std::index_sequence<I...>) {
439 static_assert(sizeof...(A) > 0);
440 std::tuple<const Constant<A> *...> args{
441 Folder<A>{context, hasOptionalArgument}.Folding(arguments.at(I))...};
442 if ((... && (std::get<I>(args)))) {
443 return args;
444 } else {
445 return std::nullopt;
446 }
447}
448
449template <typename... A>
450std::optional<std::tuple<const Constant<A> *...>> GetConstantArguments(
451 FoldingContext &context, ActualArguments &args, bool hasOptionalArgument) {
452 return GetConstantArgumentsHelper<A...>(
453 context, args, hasOptionalArgument, std::index_sequence_for<A...>{});
454}
455
456template <typename... A, std::size_t... I>
457std::optional<std::tuple<Scalar<A>...>> GetScalarConstantArgumentsHelper(
458 FoldingContext &context, ActualArguments &args, bool hasOptionalArgument,
459 std::index_sequence<I...>) {
460 if (auto constArgs{
461 GetConstantArguments<A...>(context, args, hasOptionalArgument)}) {
462 return std::tuple<Scalar<A>...>{
463 std::get<I>(*constArgs)->GetScalarValue().value()...};
464 } else {
465 return std::nullopt;
466 }
467}
468
469template <typename... A>
470std::optional<std::tuple<Scalar<A>...>> GetScalarConstantArguments(
471 FoldingContext &context, ActualArguments &args, bool hasOptionalArgument) {
472 return GetScalarConstantArgumentsHelper<A...>(
473 context, args, hasOptionalArgument, std::index_sequence_for<A...>{});
474}
475
476// helpers to fold intrinsic function references
477// Define callable types used in a common utility that
478// takes care of array and cast/conversion aspects for elemental intrinsics
479
480template <typename TR, typename... TArgs>
481using ScalarFunc = std::function<Scalar<TR>(const Scalar<TArgs> &...)>;
482template <typename TR, typename... TArgs>
483using ScalarFuncWithContext =
484 std::function<Scalar<TR>(FoldingContext &, const Scalar<TArgs> &...)>;
485
486template <template <typename, typename...> typename WrapperType, typename TR,
487 typename... TA, std::size_t... I>
488Expr<TR> FoldElementalIntrinsicHelper(FoldingContext &context,
489 FunctionRef<TR> &&funcRef, WrapperType<TR, TA...> func,
490 bool hasOptionalArgument, std::index_sequence<I...>) {
491 if (std::optional<std::tuple<const Constant<TA> *...>> args{
492 GetConstantArguments<TA...>(
493 context, funcRef.arguments(), hasOptionalArgument)}) {
494 // Compute the shape of the result based on shapes of arguments
495 ConstantSubscripts shape;
496 int rank{0};
497 const ConstantSubscripts *shapes[]{&std::get<I>(*args)->shape()...};
498 const int ranks[]{std::get<I>(*args)->Rank()...};
499 for (unsigned int i{0}; i < sizeof...(TA); ++i) {
500 if (ranks[i] > 0) {
501 if (rank == 0) {
502 rank = ranks[i];
503 shape = *shapes[i];
504 } else {
505 if (shape != *shapes[i]) {
506 // TODO: Rank compatibility was already checked but it seems to be
507 // the first place where the actual shapes are checked to be the
508 // same. Shouldn't this be checked elsewhere so that this is also
509 // checked for non constexpr call to elemental intrinsics function?
510 context.messages().Say(
511 "Arguments in elemental intrinsic function are not conformable"_err_en_US);
512 return Expr<TR>{std::move(funcRef)};
513 }
514 }
515 }
516 }
517 CHECK(rank == GetRank(shape));
518 // Compute all the scalar values of the results
519 std::vector<Scalar<TR>> results;
520 std::optional<uint64_t> n{TotalElementCount(shape)};
521 if (!n) {
522 context.messages().Say(
523 "Too many elements in elemental intrinsic function result"_err_en_US);
524 return Expr<TR>{std::move(funcRef)};
525 }
526 if (*n > 0) {
527 ConstantBounds bounds{shape};
528 ConstantSubscripts resultIndex(rank, 1);
529 ConstantSubscripts argIndex[]{std::get<I>(*args)->lbounds()...};
530 do {
531 if constexpr (std::is_same_v<WrapperType<TR, TA...>,
532 ScalarFuncWithContext<TR, TA...>>) {
533 results.emplace_back(
534 func(context, std::get<I>(*args)->At(argIndex[I])...));
535 } else if constexpr (std::is_same_v<WrapperType<TR, TA...>,
536 ScalarFunc<TR, TA...>>) {
537 results.emplace_back(func(std::get<I>(*args)->At(argIndex[I])...));
538 }
539 (std::get<I>(*args)->IncrementSubscripts(argIndex[I]), ...);
540 } while (bounds.IncrementSubscripts(resultIndex));
541 }
542 // Build and return constant result
543 if constexpr (TR::category == TypeCategory::Character) {
544 auto len{static_cast<ConstantSubscript>(
545 results.empty() ? 0 : results[0].length())};
546 return Expr<TR>{Constant<TR>{len, std::move(results), std::move(shape)}};
547 } else if constexpr (TR::category == TypeCategory::Derived) {
548 if (!results.empty()) {
549 return Expr<TR>{rank == 0
550 ? Constant<TR>{results.front()}
551 : Constant<TR>{results.front().derivedTypeSpec(),
552 std::move(results), std::move(shape)}};
553 }
554 } else {
555 return Expr<TR>{Constant<TR>{std::move(results), std::move(shape)}};
556 }
557 }
558 return Expr<TR>{std::move(funcRef)};
559}
560
561template <typename TR, typename... TA>
562Expr<TR> FoldElementalIntrinsic(FoldingContext &context,
563 FunctionRef<TR> &&funcRef, ScalarFunc<TR, TA...> func,
564 bool hasOptionalArgument = false) {
565 return FoldElementalIntrinsicHelper<ScalarFunc, TR, TA...>(context,
566 std::move(funcRef), func, hasOptionalArgument,
567 std::index_sequence_for<TA...>{});
568}
569template <typename TR, typename... TA>
570Expr<TR> FoldElementalIntrinsic(FoldingContext &context,
571 FunctionRef<TR> &&funcRef, ScalarFuncWithContext<TR, TA...> func,
572 bool hasOptionalArgument = false) {
573 return FoldElementalIntrinsicHelper<ScalarFuncWithContext, TR, TA...>(context,
574 std::move(funcRef), func, hasOptionalArgument,
575 std::index_sequence_for<TA...>{});
576}
577
578std::optional<std::int64_t> GetInt64ArgOr(
579 const std::optional<ActualArgument> &, std::int64_t defaultValue);
580
581template <typename A, typename B>
582std::optional<std::vector<A>> GetIntegerVector(const B &x) {
583 static_assert(std::is_integral_v<A>);
584 if (const auto *someInteger{UnwrapExpr<Expr<SomeInteger>>(x)}) {
585 return common::visit(
586 [](const auto &typedExpr) -> std::optional<std::vector<A>> {
587 using T = ResultType<decltype(typedExpr)>;
588 if (const auto *constant{UnwrapConstantValue<T>(typedExpr)}) {
589 if (constant->Rank() == 1) {
590 std::vector<A> result;
591 for (const auto &value : constant->values()) {
592 result.push_back(static_cast<A>(value.ToInt64()));
593 }
594 return result;
595 }
596 }
597 return std::nullopt;
598 },
599 someInteger->u);
600 }
601 return std::nullopt;
602}
603
604// Transform an intrinsic function reference that contains user errors
605// into an intrinsic with the same characteristic but the "invalid" name.
606// This to prevent generating warnings over and over if the expression
607// gets re-folded.
608template <typename T> Expr<T> MakeInvalidIntrinsic(FunctionRef<T> &&funcRef) {
609 SpecificIntrinsic invalid{std::get<SpecificIntrinsic>(funcRef.proc().u)};
610 invalid.name = IntrinsicProcTable::InvalidName;
611 return Expr<T>{FunctionRef<T>{ProcedureDesignator{std::move(invalid)},
612 ActualArguments{std::move(funcRef.arguments())}}};
613}
614
615template <typename T> Expr<T> Folder<T>::CSHIFT(FunctionRef<T> &&funcRef) {
616 auto args{funcRef.arguments()};
617 CHECK(args.size() == 3);
618 const auto *array{UnwrapConstantValue<T>(args[0])};
619 const auto *shiftExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])};
620 auto dim{GetInt64ArgOr(args[2], 1)};
621 if (!array || !shiftExpr || !dim) {
622 return Expr<T>{std::move(funcRef)};
623 }
624 auto convertedShift{Fold(context_,
625 ConvertToType<SubscriptInteger>(Expr<SomeInteger>{*shiftExpr}))};
626 const auto *shift{UnwrapConstantValue<SubscriptInteger>(convertedShift)};
627 if (!shift) {
628 return Expr<T>{std::move(funcRef)};
629 }
630 // Arguments are constant
631 if (*dim < 1 || *dim > array->Rank()) {
632 context_.messages().Say("Invalid 'dim=' argument (%jd) in CSHIFT"_err_en_US,
633 static_cast<std::intmax_t>(*dim));
634 } else if (shift->Rank() > 0 && shift->Rank() != array->Rank() - 1) {
635 // message already emitted from intrinsic look-up
636 } else {
637 int rank{array->Rank()};
638 int zbDim{static_cast<int>(*dim) - 1};
639 bool ok{true};
640 if (shift->Rank() > 0) {
641 int k{0};
642 for (int j{0}; j < rank; ++j) {
643 if (j != zbDim) {
644 if (array->shape()[j] != shift->shape()[k]) {
645 context_.messages().Say(
646 "Invalid 'shift=' argument in CSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US,
647 k + 1, static_cast<std::intmax_t>(shift->shape()[k]),
648 static_cast<std::intmax_t>(array->shape()[j]));
649 ok = false;
650 }
651 ++k;
652 }
653 }
654 }
655 if (ok) {
656 std::vector<Scalar<T>> resultElements;
657 ConstantSubscripts arrayLB{array->lbounds()};
658 ConstantSubscripts arrayAt{arrayLB};
659 ConstantSubscript &dimIndex{arrayAt[zbDim]};
660 ConstantSubscript dimLB{dimIndex}; // initial value
661 ConstantSubscript dimExtent{array->shape()[zbDim]};
662 ConstantSubscripts shiftLB{shift->lbounds()};
663 for (auto n{GetSize(array->shape())}; n > 0; --n) {
664 ConstantSubscript origDimIndex{dimIndex};
665 ConstantSubscripts shiftAt;
666 if (shift->Rank() > 0) {
667 int k{0};
668 for (int j{0}; j < rank; ++j) {
669 if (j != zbDim) {
670 shiftAt.emplace_back(shiftLB[k++] + arrayAt[j] - arrayLB[j]);
671 }
672 }
673 }
674 ConstantSubscript shiftCount{shift->At(shiftAt).ToInt64()};
675 dimIndex = dimLB + ((dimIndex - dimLB + shiftCount) % dimExtent);
676 if (dimIndex < dimLB) {
677 dimIndex += dimExtent;
678 } else if (dimIndex >= dimLB + dimExtent) {
679 dimIndex -= dimExtent;
680 }
681 resultElements.push_back(array->At(arrayAt));
682 dimIndex = origDimIndex;
683 array->IncrementSubscripts(arrayAt);
684 }
685 return Expr<T>{PackageConstant<T>(
686 std::move(resultElements), *array, array->shape())};
687 }
688 }
689 // Invalid, prevent re-folding
690 return MakeInvalidIntrinsic(std::move(funcRef));
691}
692
693template <typename T> Expr<T> Folder<T>::EOSHIFT(FunctionRef<T> &&funcRef) {
694 auto args{funcRef.arguments()};
695 CHECK(args.size() == 4);
696 const auto *array{UnwrapConstantValue<T>(args[0])};
697 const auto *shiftExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])};
698 auto dim{GetInt64ArgOr(args[3], 1)};
699 if (!array || !shiftExpr || !dim) {
700 return Expr<T>{std::move(funcRef)};
701 }
702 // Apply type conversions to the shift= and boundary= arguments.
703 auto convertedShift{Fold(context_,
704 ConvertToType<SubscriptInteger>(Expr<SomeInteger>{*shiftExpr}))};
705 const auto *shift{UnwrapConstantValue<SubscriptInteger>(convertedShift)};
706 if (!shift) {
707 return Expr<T>{std::move(funcRef)};
708 }
709 const Constant<T> *boundary{nullptr};
710 std::optional<Expr<SomeType>> convertedBoundary;
711 if (const auto *boundaryExpr{UnwrapExpr<Expr<SomeType>>(args[2])}) {
712 convertedBoundary = Fold(context_,
713 ConvertToType(array->GetType(), Expr<SomeType>{*boundaryExpr}));
714 boundary = UnwrapExpr<Constant<T>>(convertedBoundary);
715 if (!boundary) {
716 return Expr<T>{std::move(funcRef)};
717 }
718 }
719 // Arguments are constant
720 if (*dim < 1 || *dim > array->Rank()) {
721 context_.messages().Say(
722 "Invalid 'dim=' argument (%jd) in EOSHIFT"_err_en_US,
723 static_cast<std::intmax_t>(*dim));
724 } else if (shift->Rank() > 0 && shift->Rank() != array->Rank() - 1) {
725 // message already emitted from intrinsic look-up
726 } else if (boundary && boundary->Rank() > 0 &&
727 boundary->Rank() != array->Rank() - 1) {
728 // ditto
729 } else {
730 int rank{array->Rank()};
731 int zbDim{static_cast<int>(*dim) - 1};
732 bool ok{true};
733 if (shift->Rank() > 0) {
734 int k{0};
735 for (int j{0}; j < rank; ++j) {
736 if (j != zbDim) {
737 if (array->shape()[j] != shift->shape()[k]) {
738 context_.messages().Say(
739 "Invalid 'shift=' argument in EOSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US,
740 k + 1, static_cast<std::intmax_t>(shift->shape()[k]),
741 static_cast<std::intmax_t>(array->shape()[j]));
742 ok = false;
743 }
744 ++k;
745 }
746 }
747 }
748 if (boundary && boundary->Rank() > 0) {
749 int k{0};
750 for (int j{0}; j < rank; ++j) {
751 if (j != zbDim) {
752 if (array->shape()[j] != boundary->shape()[k]) {
753 context_.messages().Say(
754 "Invalid 'boundary=' argument in EOSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US,
755 k + 1, static_cast<std::intmax_t>(boundary->shape()[k]),
756 static_cast<std::intmax_t>(array->shape()[j]));
757 ok = false;
758 }
759 ++k;
760 }
761 }
762 }
763 if (ok) {
764 std::vector<Scalar<T>> resultElements;
765 ConstantSubscripts arrayLB{array->lbounds()};
766 ConstantSubscripts arrayAt{arrayLB};
767 ConstantSubscript &dimIndex{arrayAt[zbDim]};
768 ConstantSubscript dimLB{dimIndex}; // initial value
769 ConstantSubscript dimExtent{array->shape()[zbDim]};
770 ConstantSubscripts shiftLB{shift->lbounds()};
771 ConstantSubscripts boundaryLB;
772 if (boundary) {
773 boundaryLB = boundary->lbounds();
774 }
775 for (auto n{GetSize(array->shape())}; n > 0; --n) {
776 ConstantSubscript origDimIndex{dimIndex};
777 ConstantSubscripts shiftAt;
778 if (shift->Rank() > 0) {
779 int k{0};
780 for (int j{0}; j < rank; ++j) {
781 if (j != zbDim) {
782 shiftAt.emplace_back(shiftLB[k++] + arrayAt[j] - arrayLB[j]);
783 }
784 }
785 }
786 ConstantSubscript shiftCount{shift->At(shiftAt).ToInt64()};
787 dimIndex += shiftCount;
788 if (dimIndex >= dimLB && dimIndex < dimLB + dimExtent) {
789 resultElements.push_back(array->At(arrayAt));
790 } else if (boundary) {
791 ConstantSubscripts boundaryAt;
792 if (boundary->Rank() > 0) {
793 for (int j{0}; j < rank; ++j) {
794 int k{0};
795 if (j != zbDim) {
796 boundaryAt.emplace_back(
797 boundaryLB[k++] + arrayAt[j] - arrayLB[j]);
798 }
799 }
800 }
801 resultElements.push_back(boundary->At(boundaryAt));
802 } else if constexpr (T::category == TypeCategory::Integer ||
803 T::category == TypeCategory::Unsigned ||
804 T::category == TypeCategory::Real ||
805 T::category == TypeCategory::Complex ||
806 T::category == TypeCategory::Logical) {
807 resultElements.emplace_back();
808 } else if constexpr (T::category == TypeCategory::Character) {
809 auto len{static_cast<std::size_t>(array->LEN())};
810 typename Scalar<T>::value_type space{' '};
811 resultElements.emplace_back(len, space);
812 } else {
813 DIE("no derived type boundary");
814 }
815 dimIndex = origDimIndex;
816 array->IncrementSubscripts(arrayAt);
817 }
818 return Expr<T>{PackageConstant<T>(
819 std::move(resultElements), *array, array->shape())};
820 }
821 }
822 // Invalid, prevent re-folding
823 return MakeInvalidIntrinsic(std::move(funcRef));
824}
825
826template <typename T> Expr<T> Folder<T>::MERGE(FunctionRef<T> &&funcRef) {
827 return FoldElementalIntrinsic<T, T, T, LogicalResult>(context_,
828 std::move(funcRef),
829 ScalarFunc<T, T, T, LogicalResult>(
830 [](const Scalar<T> &ifTrue, const Scalar<T> &ifFalse,
831 const Scalar<LogicalResult> &predicate) -> Scalar<T> {
832 return predicate.IsTrue() ? ifTrue : ifFalse;
833 }));
834}
835
836template <typename T> Expr<T> Folder<T>::PACK(FunctionRef<T> &&funcRef) {
837 auto args{funcRef.arguments()};
838 CHECK(args.size() == 3);
839 const auto *array{UnwrapConstantValue<T>(args[0])};
840 const auto *vector{UnwrapConstantValue<T>(args[2])};
841 auto convertedMask{Fold(context_,
842 ConvertToType<LogicalResult>(
843 Expr<SomeLogical>{DEREF(UnwrapExpr<Expr<SomeLogical>>(args[1]))}))};
844 const auto *mask{UnwrapConstantValue<LogicalResult>(convertedMask)};
845 if (!array || !mask || (args[2] && !vector)) {
846 return Expr<T>{std::move(funcRef)};
847 }
848 // Arguments are constant.
849 ConstantSubscript arrayElements{GetSize(array->shape())};
850 ConstantSubscript truths{0};
851 ConstantSubscripts maskAt{mask->lbounds()};
852 if (mask->Rank() == 0) {
853 if (mask->At(maskAt).IsTrue()) {
854 truths = arrayElements;
855 }
856 } else if (array->shape() != mask->shape()) {
857 // Error already emitted from intrinsic processing
858 return MakeInvalidIntrinsic(std::move(funcRef));
859 } else {
860 for (ConstantSubscript j{0}; j < arrayElements;
861 ++j, mask->IncrementSubscripts(maskAt)) {
862 if (mask->At(maskAt).IsTrue()) {
863 ++truths;
864 }
865 }
866 }
867 std::vector<Scalar<T>> resultElements;
868 ConstantSubscripts arrayAt{array->lbounds()};
869 ConstantSubscript resultSize{truths};
870 if (vector) {
871 resultSize = vector->shape().at(0);
872 if (resultSize < truths) {
873 context_.messages().Say(
874 "Invalid 'vector=' argument in PACK: the 'mask=' argument has %jd true elements, but the vector has only %jd elements"_err_en_US,
875 static_cast<std::intmax_t>(truths),
876 static_cast<std::intmax_t>(resultSize));
877 return MakeInvalidIntrinsic(std::move(funcRef));
878 }
879 }
880 for (ConstantSubscript j{0}; j < truths;) {
881 if (mask->At(maskAt).IsTrue()) {
882 resultElements.push_back(array->At(arrayAt));
883 ++j;
884 }
885 array->IncrementSubscripts(arrayAt);
886 mask->IncrementSubscripts(maskAt);
887 }
888 if (vector) {
889 ConstantSubscripts vectorAt{vector->lbounds()};
890 vectorAt.at(0) += truths;
891 for (ConstantSubscript j{truths}; j < resultSize; ++j) {
892 resultElements.push_back(vector->At(vectorAt));
893 ++vectorAt[0];
894 }
895 }
896 return Expr<T>{PackageConstant<T>(std::move(resultElements), *array,
897 ConstantSubscripts{static_cast<ConstantSubscript>(resultSize)})};
898}
899
900template <typename T> Expr<T> Folder<T>::RESHAPE(FunctionRef<T> &&funcRef) {
901 auto args{funcRef.arguments()};
902 CHECK(args.size() == 4);
903 const auto *source{UnwrapConstantValue<T>(args[0])};
904 const auto *pad{UnwrapConstantValue<T>(args[2])};
905 std::optional<std::vector<ConstantSubscript>> shape{
906 GetIntegerVector<ConstantSubscript>(args[1])};
907 std::optional<std::vector<int>> order{GetIntegerVector<int>(args[3])};
908 std::optional<uint64_t> optResultElement;
909 std::optional<std::vector<int>> dimOrder;
910 bool ok{true};
911 if (shape) {
912 if (shape->size() > common::maxRank) {
913 context_.messages().Say(
914 "Size of 'shape=' argument (%zd) must not be greater than %d"_err_en_US,
915 shape->size(), common::maxRank);
916 ok = false;
917 } else if (HasNegativeExtent(*shape)) {
918 context_.messages().Say(
919 "'shape=' argument (%s) must not have a negative extent"_err_en_US,
920 DEREF(args[1]->UnwrapExpr()).AsFortran());
921 ok = false;
922 } else {
923 optResultElement = TotalElementCount(*shape);
924 if (!optResultElement) {
925 context_.messages().Say(
926 "'shape=' argument (%s) specifies an array with too many elements"_err_en_US,
927 DEREF(args[1]->UnwrapExpr()).AsFortran());
928 ok = false;
929 }
930 }
931 if (order) {
932 dimOrder = ValidateDimensionOrder(GetRank(*shape), *order);
933 if (!dimOrder) {
934 context_.messages().Say(
935 "Invalid 'order=' argument (%s) in RESHAPE"_err_en_US,
936 DEREF(args[3]->UnwrapExpr()).AsFortran());
937 ok = false;
938 }
939 }
940 }
941 if (!ok) {
942 // convert into an invalid intrinsic procedure call below
943 } else if (!source || !shape || (args[2] && !pad) || (args[3] && !order)) {
944 return Expr<T>{std::move(funcRef)}; // Non-constant arguments
945 } else {
946 uint64_t resultElements{*optResultElement};
947 std::vector<int> *dimOrderPtr{dimOrder ? &dimOrder.value() : nullptr};
948 if (resultElements > source->size() && (!pad || pad->empty())) {
949 context_.messages().Say(
950 "Too few elements in 'source=' argument and 'pad=' "
951 "argument is not present or has null size"_err_en_US);
952 ok = false;
953 } else {
954 Constant<T> result{!source->empty() || !pad
955 ? source->Reshape(std::move(shape.value()))
956 : pad->Reshape(std::move(shape.value()))};
957 ConstantSubscripts subscripts{result.lbounds()};
958 auto copied{result.CopyFrom(*source,
959 std::min(static_cast<uint64_t>(source->size()), resultElements),
960 subscripts, dimOrderPtr)};
961 if (copied < resultElements) {
962 CHECK(pad);
963 copied += result.CopyFrom(
964 *pad, resultElements - copied, subscripts, dimOrderPtr);
965 }
966 CHECK(copied == resultElements);
967 return Expr<T>{std::move(result)};
968 }
969 }
970 // Invalid, prevent re-folding
971 return MakeInvalidIntrinsic(std::move(funcRef));
972}
973
974template <typename T> Expr<T> Folder<T>::SPREAD(FunctionRef<T> &&funcRef) {
975 auto args{funcRef.arguments()};
976 CHECK(args.size() == 3);
977 const Constant<T> *source{UnwrapConstantValue<T>(args[0])};
978 auto dim{ToInt64(args[1])};
979 auto ncopies{ToInt64(args[2])};
980 if (!source || !dim) {
981 return Expr<T>{std::move(funcRef)};
982 }
983 int sourceRank{source->Rank()};
984 if (sourceRank >= common::maxRank) {
985 context_.messages().Say(
986 "SOURCE= argument to SPREAD has rank %d but must have rank less than %d"_err_en_US,
987 sourceRank, common::maxRank);
988 } else if (*dim < 1 || *dim > sourceRank + 1) {
989 context_.messages().Say(
990 "DIM=%d argument to SPREAD must be between 1 and %d"_err_en_US, *dim,
991 sourceRank + 1);
992 } else if (!ncopies) {
993 return Expr<T>{std::move(funcRef)};
994 } else {
995 if (*ncopies < 0) {
996 ncopies = 0;
997 }
998 // TODO: Consider moving this implementation (after the user error
999 // checks), along with other transformational intrinsics, into
1000 // constant.h (or a new header) so that the transformationals
1001 // are available for all Constant<>s without needing to be packaged
1002 // as references to intrinsic functions for folding.
1003 ConstantSubscripts shape{source->shape()};
1004 shape.insert(shape.begin() + *dim - 1, *ncopies);
1005 Constant<T> spread{source->Reshape(std::move(shape))};
1006 std::optional<uint64_t> n{TotalElementCount(spread.shape())};
1007 if (!n) {
1008 context_.messages().Say("Too many elements in SPREAD result"_err_en_US);
1009 } else {
1010 std::vector<int> dimOrder;
1011 for (int j{0}; j < sourceRank; ++j) {
1012 dimOrder.push_back(j < *dim - 1 ? j : j + 1);
1013 }
1014 dimOrder.push_back(*dim - 1);
1015 ConstantSubscripts at{spread.lbounds()}; // all 1
1016 spread.CopyFrom(*source, *n, at, &dimOrder);
1017 return Expr<T>{std::move(spread)};
1018 }
1019 }
1020 // Invalid, prevent re-folding
1021 return MakeInvalidIntrinsic(std::move(funcRef));
1022}
1023
1024template <typename T> Expr<T> Folder<T>::TRANSPOSE(FunctionRef<T> &&funcRef) {
1025 auto args{funcRef.arguments()};
1026 CHECK(args.size() == 1);
1027 const auto *matrix{UnwrapConstantValue<T>(args[0])};
1028 if (!matrix) {
1029 return Expr<T>{std::move(funcRef)};
1030 }
1031 // Argument is constant. Traverse its elements in transposed order.
1032 std::vector<Scalar<T>> resultElements;
1033 ConstantSubscripts at(2);
1034 for (ConstantSubscript j{0}; j < matrix->shape()[0]; ++j) {
1035 at[0] = matrix->lbounds()[0] + j;
1036 for (ConstantSubscript k{0}; k < matrix->shape()[1]; ++k) {
1037 at[1] = matrix->lbounds()[1] + k;
1038 resultElements.push_back(matrix->At(at));
1039 }
1040 }
1041 at = matrix->shape();
1042 std::swap(at[0], at[1]);
1043 return Expr<T>{PackageConstant<T>(std::move(resultElements), *matrix, at)};
1044}
1045
1046template <typename T> Expr<T> Folder<T>::UNPACK(FunctionRef<T> &&funcRef) {
1047 auto args{funcRef.arguments()};
1048 CHECK(args.size() == 3);
1049 const auto *vector{UnwrapConstantValue<T>(args[0])};
1050 auto convertedMask{Fold(context_,
1051 ConvertToType<LogicalResult>(
1052 Expr<SomeLogical>{DEREF(UnwrapExpr<Expr<SomeLogical>>(args[1]))}))};
1053 const auto *mask{UnwrapConstantValue<LogicalResult>(convertedMask)};
1054 const auto *field{UnwrapConstantValue<T>(args[2])};
1055 if (!vector || !mask || !field) {
1056 return Expr<T>{std::move(funcRef)};
1057 }
1058 // Arguments are constant.
1059 if (field->Rank() > 0 && field->shape() != mask->shape()) {
1060 // Error already emitted from intrinsic processing
1061 return MakeInvalidIntrinsic(std::move(funcRef));
1062 }
1063 ConstantSubscript maskElements{GetSize(mask->shape())};
1064 ConstantSubscript truths{0};
1065 ConstantSubscripts maskAt{mask->lbounds()};
1066 for (ConstantSubscript j{0}; j < maskElements;
1067 ++j, mask->IncrementSubscripts(maskAt)) {
1068 if (mask->At(maskAt).IsTrue()) {
1069 ++truths;
1070 }
1071 }
1072 if (truths > GetSize(vector->shape())) {
1073 context_.messages().Say(
1074 "Invalid 'vector=' argument in UNPACK: the 'mask=' argument has %jd true elements, but the vector has only %jd elements"_err_en_US,
1075 static_cast<std::intmax_t>(truths),
1076 static_cast<std::intmax_t>(GetSize(vector->shape())));
1077 return MakeInvalidIntrinsic(std::move(funcRef));
1078 }
1079 std::vector<Scalar<T>> resultElements;
1080 ConstantSubscripts vectorAt{vector->lbounds()};
1081 ConstantSubscripts fieldAt{field->lbounds()};
1082 for (ConstantSubscript j{0}; j < maskElements; ++j) {
1083 if (mask->At(maskAt).IsTrue()) {
1084 resultElements.push_back(vector->At(vectorAt));
1085 vector->IncrementSubscripts(vectorAt);
1086 } else {
1087 resultElements.push_back(field->At(fieldAt));
1088 }
1089 mask->IncrementSubscripts(maskAt);
1090 field->IncrementSubscripts(fieldAt);
1091 }
1092 return Expr<T>{
1093 PackageConstant<T>(std::move(resultElements), *vector, mask->shape())};
1094}
1095
1096std::optional<Expr<SomeType>> FoldTransfer(
1097 FoldingContext &, const ActualArguments &);
1098
1099template <typename T> Expr<T> Folder<T>::TRANSFER(FunctionRef<T> &&funcRef) {
1100 if (auto folded{FoldTransfer(context_, funcRef.arguments())}) {
1101 return DEREF(UnwrapExpr<Expr<T>>(*folded));
1102 } else {
1103 return Expr<T>{std::move(funcRef)};
1104 }
1105}
1106
1107// TODO: Once the backend supports character extremums we could support
1108// min/max with non-optional arguments to trees of extremum operations.
1109template <typename T>
1110Expr<T> FoldMINorMAX(
1111 FoldingContext &context, FunctionRef<T> &&funcRef, Ordering order) {
1112 static_assert(T::category == TypeCategory::Integer ||
1113 T::category == TypeCategory::Unsigned ||
1114 T::category == TypeCategory::Real ||
1115 T::category == TypeCategory::Character);
1116
1117 // Lots of constraints:
1118 // - We want Extremum<T> generated by semantics to compare equal to
1119 // Extremum<T> written out to module files as max or min calls.
1120 // - Users can also write min/max calls that must also compare equal
1121 // to min/max calls that wind up being written to module files.
1122 // - Extremeum<T> is binary and can't currently handle processing
1123 // optional arguments that may show up in 3rd + argument.
1124 // - The code below only accepts more than 2 arguments if all the
1125 // arguments are constant (and hence known to be present).
1126 // - ConvertExprToHLFIR can't currently handle Extremum<Character>
1127 // - Semantics doesn't currently generate Extremum<Character>
1128 // The original code did the folding of arguments and the overall extremum
1129 // operation in a single pass. This was shorter code-wise, but took me
1130 // a while to tease out all the logic and was doing redundant work.
1131 // So I split it into two passes:
1132 // 1) fold the arguments and check if they are constant,
1133 // 2) Decide if we:
1134 // - can constant-fold the min/max operation, or
1135 // - need to generate an extremum anyway,
1136 // and do it if so.
1137 // Otherwise, return the original call.
1138 auto &args{funcRef.arguments()};
1139 std::size_t nargs{args.size()};
1140 bool allArgsConstant{true};
1141 bool extremumAnyway{nargs == 2 && T::category != TypeCategory::Character};
1142 // 1a)Fold the first two arguments.
1143 {
1144 Folder<T> folder{context, /*forOptionalArgument=*/false};
1145 if (!folder.Folding(args[0])) {
1146 allArgsConstant = false;
1147 }
1148 if (!folder.Folding(args[1])) {
1149 allArgsConstant = false;
1150 }
1151 }
1152 // 1b) Fold any optional arguments.
1153 if (nargs > 2) {
1154 Folder<T> folder{context, /*forOptionalArgument=*/true};
1155 for (std::size_t i{2}; i < nargs; ++i) {
1156 if (args[i]) {
1157 if (!folder.Folding(args[i])) {
1158 allArgsConstant = false;
1159 }
1160 }
1161 }
1162 }
1163 // 2) If we can fold the result or the call to min/max may compare equal to
1164 // an extremum generated by semantics go ahead and convert to an extremum,
1165 // and try to fold the result.
1166 if (allArgsConstant || extremumAnyway) {
1167 // Folding updates the argument expressions in place, no need to call
1168 // Fold() on each argument again.
1169 if (const auto *resultp{UnwrapExpr<Expr<T>>(args[0])}) {
1170 Expr<T> result{*resultp};
1171 for (std::size_t i{1}; i < nargs; ++i) {
1172 if (const auto *tExpr{UnwrapExpr<Expr<T>>(args[i])}) {
1173 result = FoldOperation(
1174 context, Extremum<T>{order, std::move(result), *tExpr});
1175 } else {
1176 // This should never happen, but here is a value to return.
1177 return Expr<T>{std::move(funcRef)};
1178 }
1179 }
1180 return result;
1181 }
1182 }
1183 // If we decided to not generate an extremum just return the original call,
1184 // with the arguments folded.
1185 return Expr<T>{std::move(funcRef)};
1186}
1187
1188// For AMAX0, AMIN0, AMAX1, AMIN1, DMAX1, DMIN1, MAX0, MIN0, MAX1, and MIN1
1189// a special care has to be taken to insert the conversion on the result
1190// of the MIN/MAX. This is made slightly more complex by the extension
1191// supported by f18 that arguments may have different kinds. This implies
1192// that the created MIN/MAX result type cannot be deduced from the standard but
1193// has to be deduced from the arguments.
1194// e.g. AMAX0(int8, int4) is rewritten to REAL(MAX(int8, INT(int4, 8)))).
1195template <typename T>
1196Expr<T> RewriteSpecificMINorMAX(
1197 FoldingContext &context, FunctionRef<T> &&funcRef) {
1198 ActualArguments &args{funcRef.arguments()};
1199 auto &intrinsic{DEREF(std::get_if<SpecificIntrinsic>(&funcRef.proc().u))};
1200 // Rewrite MAX1(args) to INT(MAX(args)) and fold. Same logic for MIN1.
1201 // Find result type for max/min based on the arguments.
1202 std::optional<DynamicType> resultType;
1203 ActualArgument *resultTypeArg{nullptr};
1204 for (auto j{args.size()}; j-- > 0;) {
1205 if (args[j]) {
1206 DynamicType type{args[j]->GetType().value()};
1207 // Handle mixed real/integer arguments: all the previous arguments were
1208 // integers and this one is real. The type of the MAX/MIN result will
1209 // be the one of the real argument.
1210 if (!resultType ||
1211 (type.category() == resultType->category() &&
1212 type.kind() > resultType->kind()) ||
1213 resultType->category() == TypeCategory::Integer) {
1214 resultType = type;
1215 resultTypeArg = &*args[j];
1216 }
1217 }
1218 }
1219 if (!resultType) { // error recovery
1220 return Expr<T>{std::move(funcRef)};
1221 }
1222 intrinsic.name =
1223 intrinsic.name.find("max") != std::string::npos ? "max"s : "min"s;
1224 intrinsic.characteristics.value().functionResult.value().SetType(*resultType);
1225 auto insertConversion{[&](const auto &x) -> Expr<T> {
1226 using TR = ResultType<decltype(x)>;
1227 FunctionRef<TR> maxRef{
1228 ProcedureDesignator{funcRef.proc()}, ActualArguments{args}};
1229 return Fold(context, ConvertToType<T>(AsCategoryExpr(std::move(maxRef))));
1230 }};
1231 if (auto *sx{UnwrapExpr<Expr<SomeReal>>(*resultTypeArg)}) {
1232 return common::visit(insertConversion, sx->u);
1233 } else if (auto *sx{UnwrapExpr<Expr<SomeInteger>>(*resultTypeArg)}) {
1234 return common::visit(insertConversion, sx->u);
1235 } else {
1236 return Expr<T>{std::move(funcRef)}; // error recovery
1237 }
1238}
1239
1240// FoldIntrinsicFunction()
1241template <int KIND>
1242Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction(
1244template <int KIND>
1245Expr<Type<TypeCategory::Unsigned, KIND>> FoldIntrinsicFunction(
1246 FoldingContext &context,
1248template <int KIND>
1249Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
1251template <int KIND>
1252Expr<Type<TypeCategory::Complex, KIND>> FoldIntrinsicFunction(
1254template <int KIND>
1255Expr<Type<TypeCategory::Logical, KIND>> FoldIntrinsicFunction(
1257
1258template <typename T>
1259Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
1260 ActualArguments &args{funcRef.arguments()};
1261 const auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)};
1262 if (!intrinsic || intrinsic->name != "kind") {
1263 // Don't fold the argument to KIND(); it might be a TypeParamInquiry
1264 // with a forced result type that doesn't match the parameter.
1265 for (std::optional<ActualArgument> &arg : args) {
1266 if (arg && arg->GetConditionalArg()) {
1267 FoldConditionalArg(context, arg);
1268 } else if (auto *expr{UnwrapExpr<Expr<SomeType>>(arg)}) {
1269 *expr = Fold(context, std::move(*expr));
1270 }
1271 }
1272 }
1273 if (intrinsic) {
1274 // Skip intrinsic folding if any argument is still a conditional arg
1275 // (i.e. its condition was not a compile-time constant). When the
1276 // condition is a compile-time constant, FoldConditionalArg already resolved
1277 // it to a plain Expr above, and intrinsic folding proceeds normally.
1278 //
1279 // TODO:
1280 // For elemental/pure intrinsics, distribute the call over each
1281 // consequent of the conditional arg and fold each branch independently:
1282 // abs((c1 ? a : c2 ? b : c))
1283 // → (c1 ? abs(a) : c2 ? abs(b) : abs(c))
1284 // Use ForEachConsequent to walk the chain, clone the call per
1285 // consequent, fold each clone, and reassemble into a new ConditionalArg.
1286 // When multiple arguments are conditional args, distribute one at a
1287 // time to avoid a combinatorial cross-product expansion.
1288 // This is NOT valid for non-elemental intrinsics like RESHAPE or
1289 // TRANSFER whose results depend on seeing all arguments together.
1290 //
1291 // TODO (conformance):
1292 // Type-inquiry intrinsics whose result depends only on the argument's
1293 // declared type/rank (e.g. KIND, BIT_SIZE, DIGITS, HUGE, TINY, EPSILON,
1294 // PRECISION, RANGE, RADIX, MAXEXPONENT, MINEXPONENT, STORAGE_SIZE, RANK)
1295 // are foldable even when the condition is not constant, because C1538/C1539
1296 // guarantee every consequent has the same type and rank. Because they are
1297 // not folded here, a reference such as
1298 // integer, parameter :: k = kind((flag ? a : b))
1299 // is wrongly rejected ("cannot be computed as a constant value") even
1300 // though it is a valid F2023 constant expression.
1301 // Fix:
1302 // For such a curated allow-list of type-only inquiries, before the bailout
1303 // below, a curated allow-list of type-only inquiries, before the bailout
1304 // below, replace the conditional-arg argument with its first non-.NIL.
1305 // consequent (a representative) and fold normally. This must NOT be
1306 // applied to shape/value inquiries (SIZE, SHAPE, LBOUND/UBOUND, LEN of
1307 // deferred length, ALLOCATED, ASSOCIATED, PRESENT, IS_CONTIGUOUS), whose
1308 // results can differ between consequents.
1309 for (const std::optional<ActualArgument> &arg : args) {
1310 if (arg && arg->isConditionalArg()) {
1311 return Expr<T>{std::move(funcRef)};
1312 }
1313 }
1314 const std::string name{intrinsic->name};
1315 if (name == "cshift") {
1316 return Folder<T>{context}.CSHIFT(std::move(funcRef));
1317 } else if (name == "eoshift") {
1318 return Folder<T>{context}.EOSHIFT(std::move(funcRef));
1319 } else if (name == "merge") {
1320 return Folder<T>{context}.MERGE(std::move(funcRef));
1321 } else if (name == "pack") {
1322 return Folder<T>{context}.PACK(std::move(funcRef));
1323 } else if (name == "reshape") {
1324 return Folder<T>{context}.RESHAPE(std::move(funcRef));
1325 } else if (name == "spread") {
1326 return Folder<T>{context}.SPREAD(std::move(funcRef));
1327 } else if (name == "transfer") {
1328 return Folder<T>{context}.TRANSFER(std::move(funcRef));
1329 } else if (name == "transpose") {
1330 return Folder<T>{context}.TRANSPOSE(std::move(funcRef));
1331 } else if (name == "unpack") {
1332 return Folder<T>{context}.UNPACK(std::move(funcRef));
1333 }
1334 // TODO: extends_type_of, same_type_as
1335 if constexpr (!std::is_same_v<T, SomeDerived>) {
1336 return FoldIntrinsicFunction(context, std::move(funcRef));
1337 }
1338 }
1339 return Expr<T>{std::move(funcRef)};
1340}
1341
1342// Array constructor folding
1343template <typename T> class ArrayConstructorFolder {
1344public:
1345 explicit ArrayConstructorFolder(FoldingContext &c) : context_{c} {}
1346
1347 Expr<T> FoldArray(ArrayConstructor<T> &&array) {
1348 if constexpr (T::category == TypeCategory::Character) {
1349 if (const auto *len{array.LEN()}) {
1350 charLength_ = ToInt64(Fold(context_, common::Clone(*len)));
1351 knownCharLength_ = charLength_.has_value();
1352 }
1353 }
1354 // Calls FoldArray(const ArrayConstructorValues<T> &) below
1355 if (FoldArray(array)) {
1356 auto n{static_cast<ConstantSubscript>(elements_.size())};
1357 if constexpr (std::is_same_v<T, SomeDerived>) {
1358 return Expr<T>{Constant<T>{array.GetType().GetDerivedTypeSpec(),
1359 std::move(elements_), ConstantSubscripts{n}}};
1360 } else if constexpr (T::category == TypeCategory::Character) {
1361 if (charLength_) {
1362 return Expr<T>{Constant<T>{
1363 *charLength_, std::move(elements_), ConstantSubscripts{n}}};
1364 }
1365 } else {
1366 return Expr<T>{Constant<T>{
1367 std::move(elements_), ConstantSubscripts{n}, resultInfo_}};
1368 }
1369 }
1370 return Expr<T>{std::move(array)};
1371 }
1372
1373private:
1374 bool FoldArray(const Expr<T> &expr) {
1375 Expr<T> folded{Fold(context_, common::Clone(expr))};
1376 if (const auto *c{UnwrapConstantValue<T>(folded)}) {
1377 // Copy elements in Fortran array element order
1378 if (!c->empty()) {
1379 ConstantSubscripts index{c->lbounds()};
1380 do {
1381 elements_.emplace_back(c->At(index));
1382 } while (c->IncrementSubscripts(index));
1383 }
1384 if constexpr (T::category == TypeCategory::Character) {
1385 if (!knownCharLength_) {
1386 charLength_ = std::max(c->LEN(), charLength_.value_or(-1));
1387 }
1388 } else if constexpr (T::category == TypeCategory::Real ||
1389 T::category == TypeCategory::Complex) {
1390 if (c->result().isFromInexactLiteralConversion()) {
1391 resultInfo_.set_isFromInexactLiteralConversion();
1392 }
1393 }
1394 return true;
1395 } else {
1396 return false;
1397 }
1398 }
1399 bool FoldArray(const common::CopyableIndirection<Expr<T>> &expr) {
1400 return FoldArray(expr.value());
1401 }
1402 bool FoldArray(const ImpliedDo<T> &iDo) {
1404 Fold(context_, Expr<SubscriptInteger>{iDo.lower()})};
1406 Fold(context_, Expr<SubscriptInteger>{iDo.upper()})};
1408 Fold(context_, Expr<SubscriptInteger>{iDo.stride()})};
1409 std::optional<ConstantSubscript> start{ToInt64(lower)}, end{ToInt64(upper)},
1410 step{ToInt64(stride)};
1411 if (start && end && step && *step != 0) {
1412 bool result{true};
1413 ConstantSubscript &j{context_.StartImpliedDo(iDo.name(), *start)};
1414 if (*step > 0) {
1415 for (; j <= *end; j += *step) {
1416 result &= FoldArray(iDo.values());
1417 }
1418 } else {
1419 for (; j >= *end; j += *step) {
1420 result &= FoldArray(iDo.values());
1421 }
1422 }
1423 context_.EndImpliedDo(iDo.name());
1424 return result;
1425 } else {
1426 return false;
1427 }
1428 }
1429 bool FoldArray(const ArrayConstructorValue<T> &x) {
1430 return common::visit([&](const auto &y) { return FoldArray(y); }, x.u);
1431 }
1432 bool FoldArray(const ArrayConstructorValues<T> &xs) {
1433 for (const auto &x : xs) {
1434 if (!FoldArray(x)) {
1435 return false;
1436 }
1437 }
1438 return true;
1439 }
1440
1441 FoldingContext &context_;
1442 std::vector<Scalar<T>> elements_;
1443 std::optional<ConstantSubscript> charLength_;
1444 bool knownCharLength_{false};
1445 typename Constant<T>::Result resultInfo_;
1446};
1447
1448template <typename T>
1449Expr<T> FoldOperation(FoldingContext &context, ArrayConstructor<T> &&array) {
1450 return ArrayConstructorFolder<T>{context}.FoldArray(std::move(array));
1451}
1452
1453// Array operation elemental application: When all operands to an operation
1454// are constant arrays, array constructors without any implied DO loops,
1455// &/or expanded scalars, pull the operation "into" the array result by
1456// applying it in an elementwise fashion. For example, [A,1]+[B,2]
1457// is rewritten into [A+B,1+2] and then partially folded to [A+B,3].
1458
1459// If possible, restructures an array expression into an array constructor
1460// that comprises a "flat" ArrayConstructorValues with no implied DO loops.
1461template <typename T>
1462bool ArrayConstructorIsFlat(const ArrayConstructorValues<T> &values) {
1463 for (const ArrayConstructorValue<T> &x : values) {
1464 if (!std::holds_alternative<Expr<T>>(x.u)) {
1465 return false;
1466 }
1467 }
1468 return true;
1469}
1470
1471template <typename T>
1472std::optional<Expr<T>> AsFlatArrayConstructor(const Expr<T> &expr) {
1473 if (const auto *c{UnwrapConstantValue<T>(expr)}) {
1474 ArrayConstructor<T> result{expr};
1475 if (!c->empty()) {
1476 ConstantSubscripts at{c->lbounds()};
1477 do {
1478 result.Push(Expr<T>{Constant<T>{c->At(at)}});
1479 } while (c->IncrementSubscripts(at));
1480 }
1481 return std::make_optional<Expr<T>>(std::move(result));
1482 } else if (const auto *a{UnwrapExpr<ArrayConstructor<T>>(expr)}) {
1483 if (ArrayConstructorIsFlat(*a)) {
1484 return std::make_optional<Expr<T>>(expr);
1485 }
1486 } else if (const auto *p{UnwrapExpr<Parentheses<T>>(expr)}) {
1487 return AsFlatArrayConstructor(Expr<T>{p->left()});
1488 }
1489 return std::nullopt;
1490}
1491
1492template <TypeCategory CAT>
1493std::enable_if_t<CAT != TypeCategory::Derived,
1494 std::optional<Expr<SomeKind<CAT>>>>
1495AsFlatArrayConstructor(const Expr<SomeKind<CAT>> &expr) {
1496 return common::visit(
1497 [&](const auto &kindExpr) -> std::optional<Expr<SomeKind<CAT>>> {
1498 if (auto flattened{AsFlatArrayConstructor(kindExpr)}) {
1499 return Expr<SomeKind<CAT>>{std::move(*flattened)};
1500 } else {
1501 return std::nullopt;
1502 }
1503 },
1504 expr.u);
1505}
1506
1507// FromArrayConstructor is a subroutine for MapOperation() below.
1508// Given a flat ArrayConstructor<T> and a shape, it wraps the array
1509// into an Expr<T>, folds it, and returns the resulting wrapped
1510// array constructor or constant array value.
1511template <typename T>
1512std::optional<Expr<T>> FromArrayConstructor(
1513 FoldingContext &context, ArrayConstructor<T> &&values, const Shape &shape) {
1514 if (auto constShape{AsConstantExtents(context, shape)};
1515 constShape && !HasNegativeExtent(*constShape)) {
1516 Expr<T> result{Fold(context, Expr<T>{std::move(values)})};
1517 if (auto *constant{UnwrapConstantValue<T>(result)}) {
1518 // Elements and shape are both constant.
1519 return Expr<T>{constant->Reshape(std::move(*constShape))};
1520 }
1521 if (constShape->size() == 1) {
1522 if (auto elements{GetShape(context, result)}) {
1523 if (auto constElements{AsConstantExtents(context, *elements)}) {
1524 if (constElements->size() == 1 &&
1525 constElements->at(0) == constShape->at(0)) {
1526 // Elements are not constant, but array constructor has
1527 // the right known shape and can be simply returned as is.
1528 return std::move(result);
1529 }
1530 }
1531 }
1532 }
1533 }
1534 return std::nullopt;
1535}
1536
1537// MapOperation is a utility for various specializations of ApplyElementwise()
1538// that follow. Given one or two flat ArrayConstructor<OPERAND> (wrapped in an
1539// Expr<OPERAND>) for some specific operand type(s), apply a given function f
1540// to each of their corresponding elements to produce a flat
1541// ArrayConstructor<RESULT> (wrapped in an Expr<RESULT>).
1542// Preserves shape.
1543
1544// Unary case
1545template <typename RESULT, typename OPERAND>
1546std::optional<Expr<RESULT>> MapOperation(FoldingContext &context,
1547 std::function<Expr<RESULT>(Expr<OPERAND> &&)> &&f, const Shape &shape,
1548 [[maybe_unused]] std::optional<Expr<SubscriptInteger>> &&length,
1549 Expr<OPERAND> &&values) {
1550 ArrayConstructor<RESULT> result{values};
1551 if constexpr (common::HasMember<OPERAND, AllIntrinsicCategoryTypes>) {
1552 common::visit(
1553 [&](auto &&kindExpr) {
1554 using kindType = ResultType<decltype(kindExpr)>;
1555 auto &aConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)};
1556 for (auto &acValue : aConst) {
1557 auto &scalar{std::get<Expr<kindType>>(acValue.u)};
1558 result.Push(Fold(context, f(Expr<OPERAND>{std::move(scalar)})));
1559 }
1560 },
1561 std::move(values.u));
1562 } else {
1563 auto &aConst{std::get<ArrayConstructor<OPERAND>>(values.u)};
1564 for (auto &acValue : aConst) {
1565 auto &scalar{std::get<Expr<OPERAND>>(acValue.u)};
1566 result.Push(Fold(context, f(std::move(scalar))));
1567 }
1568 }
1569 if constexpr (RESULT::category == TypeCategory::Character) {
1570 if (length) {
1571 result.set_LEN(std::move(*length));
1572 }
1573 }
1574 return FromArrayConstructor(context, std::move(result), shape);
1575}
1576
1577template <typename RESULT, typename A>
1578ArrayConstructor<RESULT> ArrayConstructorFromMold(
1579 const A &prototype, std::optional<Expr<SubscriptInteger>> &&length) {
1580 ArrayConstructor<RESULT> result{prototype};
1581 if constexpr (RESULT::category == TypeCategory::Character) {
1582 if (length) {
1583 result.set_LEN(std::move(*length));
1584 }
1585 }
1586 return result;
1587}
1588
1589template <typename LEFT, typename RIGHT>
1590bool ShapesMatch(FoldingContext &context,
1591 const ArrayConstructor<LEFT> &leftArrConst,
1592 const ArrayConstructor<RIGHT> &rightArrConst) {
1593 auto rightIter{rightArrConst.begin()};
1594 for (auto &leftValue : leftArrConst) {
1595 CHECK(rightIter != rightArrConst.end());
1596 auto &leftExpr{std::get<Expr<LEFT>>(leftValue.u)};
1597 auto &rightExpr{std::get<Expr<RIGHT>>(rightIter->u)};
1598 if (leftExpr.Rank() != rightExpr.Rank()) {
1599 return false;
1600 }
1601 std::optional<Shape> leftShape{GetShape(context, leftExpr)};
1602 std::optional<Shape> rightShape{GetShape(context, rightExpr)};
1603 if (!leftShape || !rightShape || *leftShape != *rightShape) {
1604 return false;
1605 }
1606 ++rightIter;
1607 }
1608 return true;
1609}
1610
1611// array * array case
1612template <typename RESULT, typename LEFT, typename RIGHT>
1613auto MapOperation(FoldingContext &context,
1614 std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f,
1615 const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length,
1616 Expr<LEFT> &&leftValues, Expr<RIGHT> &&rightValues)
1617 -> std::optional<Expr<RESULT>> {
1618 auto result{ArrayConstructorFromMold<RESULT>(leftValues, std::move(length))};
1619 auto &leftArrConst{std::get<ArrayConstructor<LEFT>>(leftValues.u)};
1620 if constexpr (common::HasMember<RIGHT, AllIntrinsicCategoryTypes>) {
1621 bool mapped{common::visit(
1622 [&](auto &&kindExpr) -> bool {
1623 using kindType = ResultType<decltype(kindExpr)>;
1624
1625 auto &rightArrConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)};
1626 if (!ShapesMatch(context, leftArrConst, rightArrConst)) {
1627 return false;
1628 }
1629 auto rightIter{rightArrConst.begin()};
1630 for (auto &leftValue : leftArrConst) {
1631 CHECK(rightIter != rightArrConst.end());
1632 auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)};
1633 auto &rightScalar{std::get<Expr<kindType>>(rightIter->u)};
1634 result.Push(Fold(context,
1635 f(std::move(leftScalar), Expr<RIGHT>{std::move(rightScalar)})));
1636 ++rightIter;
1637 }
1638 return true;
1639 },
1640 std::move(rightValues.u))};
1641 if (!mapped) {
1642 return std::nullopt;
1643 }
1644 } else {
1645 auto &rightArrConst{std::get<ArrayConstructor<RIGHT>>(rightValues.u)};
1646 if (!ShapesMatch(context, leftArrConst, rightArrConst)) {
1647 return std::nullopt;
1648 }
1649 auto rightIter{rightArrConst.begin()};
1650 for (auto &leftValue : leftArrConst) {
1651 CHECK(rightIter != rightArrConst.end());
1652 auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)};
1653 auto &rightScalar{std::get<Expr<RIGHT>>(rightIter->u)};
1654 result.Push(
1655 Fold(context, f(std::move(leftScalar), std::move(rightScalar))));
1656 ++rightIter;
1657 }
1658 }
1659 return FromArrayConstructor(context, std::move(result), shape);
1660}
1661
1662// array * scalar case
1663template <typename RESULT, typename LEFT, typename RIGHT>
1664auto MapOperation(FoldingContext &context,
1665 std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f,
1666 const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length,
1667 Expr<LEFT> &&leftValues, const Expr<RIGHT> &rightScalar)
1668 -> std::optional<Expr<RESULT>> {
1669 auto result{ArrayConstructorFromMold<RESULT>(leftValues, std::move(length))};
1670 auto &leftArrConst{std::get<ArrayConstructor<LEFT>>(leftValues.u)};
1671 for (auto &leftValue : leftArrConst) {
1672 auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)};
1673 result.Push(
1674 Fold(context, f(std::move(leftScalar), Expr<RIGHT>{rightScalar})));
1675 }
1676 return FromArrayConstructor(context, std::move(result), shape);
1677}
1678
1679// scalar * array case
1680template <typename RESULT, typename LEFT, typename RIGHT>
1681auto MapOperation(FoldingContext &context,
1682 std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f,
1683 const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length,
1684 const Expr<LEFT> &leftScalar, Expr<RIGHT> &&rightValues)
1685 -> std::optional<Expr<RESULT>> {
1686 auto result{ArrayConstructorFromMold<RESULT>(leftScalar, std::move(length))};
1687 if constexpr (common::HasMember<RIGHT, AllIntrinsicCategoryTypes>) {
1688 common::visit(
1689 [&](auto &&kindExpr) {
1690 using kindType = ResultType<decltype(kindExpr)>;
1691 auto &rightArrConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)};
1692 for (auto &rightValue : rightArrConst) {
1693 auto &rightScalar{std::get<Expr<kindType>>(rightValue.u)};
1694 result.Push(Fold(context,
1695 f(Expr<LEFT>{leftScalar},
1696 Expr<RIGHT>{std::move(rightScalar)})));
1697 }
1698 },
1699 std::move(rightValues.u));
1700 } else {
1701 auto &rightArrConst{std::get<ArrayConstructor<RIGHT>>(rightValues.u)};
1702 for (auto &rightValue : rightArrConst) {
1703 auto &rightScalar{std::get<Expr<RIGHT>>(rightValue.u)};
1704 result.Push(
1705 Fold(context, f(Expr<LEFT>{leftScalar}, std::move(rightScalar))));
1706 }
1707 }
1708 return FromArrayConstructor(context, std::move(result), shape);
1709}
1710
1711template <typename DERIVED, typename RESULT, typename... OPD>
1712std::optional<Expr<SubscriptInteger>> ComputeResultLength(
1714 if constexpr (RESULT::category == TypeCategory::Character) {
1715 return Expr<RESULT>{operation.derived()}.LEN();
1716 }
1717 return std::nullopt;
1718}
1719
1720// ApplyElementwise() recursively folds the operand expression(s) of an
1721// operation, then attempts to apply the operation to the (corresponding)
1722// scalar element(s) of those operands. Returns std::nullopt for scalars
1723// or unlinearizable operands.
1724template <typename DERIVED, typename RESULT, typename OPERAND>
1725auto ApplyElementwise(FoldingContext &context,
1727 std::function<Expr<RESULT>(Expr<OPERAND> &&)> &&f)
1728 -> std::optional<Expr<RESULT>> {
1729 auto &expr{operation.left()};
1730 expr = Fold(context, std::move(expr));
1731 if (expr.Rank() > 0) {
1732 if (std::optional<Shape> shape{GetShape(context, expr)}) {
1733 if (auto values{AsFlatArrayConstructor(expr)}) {
1734 return MapOperation(context, std::move(f), *shape,
1735 ComputeResultLength(operation), std::move(*values));
1736 }
1737 }
1738 }
1739 return std::nullopt;
1740}
1741
1742template <typename DERIVED, typename RESULT, typename OPERAND>
1743auto ApplyElementwise(
1745 -> std::optional<Expr<RESULT>> {
1746 return ApplyElementwise(context, operation,
1747 std::function<Expr<RESULT>(Expr<OPERAND> &&)>{
1748 [](Expr<OPERAND> &&operand) {
1749 return Expr<RESULT>{DERIVED{std::move(operand)}};
1750 }});
1751}
1752
1753template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
1754auto ApplyElementwise(FoldingContext &context,
1756 std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f)
1757 -> std::optional<Expr<RESULT>> {
1758 auto resultLength{ComputeResultLength(operation)};
1759 auto &leftExpr{operation.left()};
1760 auto &rightExpr{operation.right()};
1761 if (leftExpr.Rank() != rightExpr.Rank() && leftExpr.Rank() != 0 &&
1762 rightExpr.Rank() != 0) {
1763 return std::nullopt; // error recovery
1764 }
1765 leftExpr = Fold(context, std::move(leftExpr));
1766 rightExpr = Fold(context, std::move(rightExpr));
1767 if (leftExpr.Rank() > 0) {
1768 if (std::optional<Shape> leftShape{GetShape(context, leftExpr)}) {
1769 if (auto left{AsFlatArrayConstructor(leftExpr)}) {
1770 if (rightExpr.Rank() > 0) {
1771 if (std::optional<Shape> rightShape{GetShape(context, rightExpr)}) {
1772 if (auto right{AsFlatArrayConstructor(rightExpr)}) {
1773 if (CheckConformance(context.messages(), *leftShape, *rightShape,
1774 CheckConformanceFlags::EitherScalarExpandable)
1775 .value_or(false /*fail if not known now to conform*/)) {
1776 return MapOperation(context, std::move(f), *leftShape,
1777 std::move(resultLength), std::move(*left),
1778 std::move(*right));
1779 } else {
1780 return std::nullopt;
1781 }
1782 return MapOperation(context, std::move(f), *leftShape,
1783 std::move(resultLength), std::move(*left), std::move(*right));
1784 }
1785 }
1786 } else if (IsExpandableScalar(rightExpr, context, *leftShape)) {
1787 return MapOperation(context, std::move(f), *leftShape,
1788 std::move(resultLength), std::move(*left), rightExpr);
1789 }
1790 }
1791 }
1792 } else if (rightExpr.Rank() > 0) {
1793 if (std::optional<Shape> rightShape{GetShape(context, rightExpr)}) {
1794 if (IsExpandableScalar(leftExpr, context, *rightShape)) {
1795 if (auto right{AsFlatArrayConstructor(rightExpr)}) {
1796 return MapOperation(context, std::move(f), *rightShape,
1797 std::move(resultLength), leftExpr, std::move(*right));
1798 }
1799 }
1800 }
1801 }
1802 return std::nullopt;
1803}
1804
1805template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
1806auto ApplyElementwise(
1808 -> std::optional<Expr<RESULT>> {
1809 return ApplyElementwise(context, operation,
1810 std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)>{
1811 [](Expr<LEFT> &&left, Expr<RIGHT> &&right) {
1812 return Expr<RESULT>{DERIVED{std::move(left), std::move(right)}};
1813 }});
1814}
1815
1816// Unary operations
1817
1818template <typename TO, typename FROM>
1819common::IfNoLvalue<std::optional<TO>, FROM> ConvertString(FROM &&s) {
1820 if constexpr (std::is_same_v<TO, FROM>) {
1821 return std::make_optional<TO>(std::move(s));
1822 } else {
1823 // Fortran character conversion is well defined between distinct kinds
1824 // only when the actual characters are valid 7-bit ASCII.
1825 TO str;
1826 for (auto iter{s.cbegin()}; iter != s.cend(); ++iter) {
1827 if (static_cast<std::uint64_t>(*iter) > 127) {
1828 return std::nullopt;
1829 }
1830 str.push_back(static_cast<typename TO::value_type>(*iter));
1831 }
1832 return std::make_optional<TO>(std::move(str));
1833 }
1834}
1835
1836template <typename TO, TypeCategory FROMCAT>
1837Expr<TO> FoldOperation(
1838 FoldingContext &context, Convert<TO, FROMCAT> &&convert) {
1839 if (auto array{ApplyElementwise(context, convert)}) {
1840 return *array;
1841 }
1842 struct {
1843 FoldingContext &context;
1844 Convert<TO, FROMCAT> &convert;
1845 } msvcWorkaround{context, convert};
1846 return common::visit(
1847 [&msvcWorkaround](auto &kindExpr) -> Expr<TO> {
1848 using Operand = ResultType<decltype(kindExpr)>;
1849 // This variable is a workaround for msvc which emits an error when
1850 // using the FROMCAT template parameter below.
1851 TypeCategory constexpr FromCat{FROMCAT};
1852 static_assert(FromCat == Operand::category);
1853 auto &convert{msvcWorkaround.convert};
1854 if (auto value{GetScalarConstantValue<Operand>(kindExpr)}) {
1855 FoldingContext &ctx{msvcWorkaround.context};
1856 if constexpr (TO::category == TypeCategory::Integer) {
1857 if constexpr (FromCat == TypeCategory::Integer) {
1858 auto converted{Scalar<TO>::ConvertSigned(*value)};
1859 if (converted.overflow) {
1860 ctx.Warn(common::UsageWarning::FoldingException,
1861 "conversion of %s_%d to INTEGER(%d) overflowed; result is %s"_warn_en_US,
1862 value->SignedDecimal(), Operand::kind, TO::kind,
1863 converted.value.SignedDecimal());
1864 }
1865 return ScalarConstantToExpr(std::move(converted.value));
1866 } else if constexpr (FromCat == TypeCategory::Unsigned) {
1867 auto converted{Scalar<TO>::ConvertUnsigned(*value)};
1868 if ((converted.overflow || converted.value.IsNegative())) {
1869 ctx.Warn(common::UsageWarning::FoldingException,
1870 "conversion of %s_U%d to INTEGER(%d) overflowed; result is %s"_warn_en_US,
1871 value->UnsignedDecimal(), Operand::kind, TO::kind,
1872 converted.value.SignedDecimal());
1873 }
1874 return ScalarConstantToExpr(std::move(converted.value));
1875 } else if constexpr (FromCat == TypeCategory::Real) {
1876 auto converted{value->template ToInteger<Scalar<TO>>()};
1877 if (converted.flags.test(RealFlag::InvalidArgument)) {
1878 ctx.Warn(common::UsageWarning::FoldingException,
1879 "REAL(%d) to INTEGER(%d) conversion: invalid argument"_warn_en_US,
1880 Operand::kind, TO::kind);
1881 } else if (converted.flags.test(RealFlag::Overflow)) {
1882 ctx.Warn(common::UsageWarning::FoldingException,
1883 "REAL(%d) to INTEGER(%d) conversion overflowed"_warn_en_US,
1884 Operand::kind, TO::kind);
1885 }
1886 return ScalarConstantToExpr(std::move(converted.value));
1887 }
1888 } else if constexpr (TO::category == TypeCategory::Unsigned) {
1889 if constexpr (FromCat == TypeCategory::Integer ||
1890 FromCat == TypeCategory::Unsigned) {
1891 return Expr<TO>{
1892 Constant<TO>{Scalar<TO>::ConvertUnsigned(*value).value}};
1893 } else if constexpr (FromCat == TypeCategory::Real) {
1894 return Expr<TO>{
1895 Constant<TO>{value->template ToInteger<Scalar<TO>>().value}};
1896 }
1897 } else if constexpr (TO::category == TypeCategory::Real) {
1898 if constexpr (FromCat == TypeCategory::Integer ||
1899 FromCat == TypeCategory::Unsigned) {
1900 auto converted{Scalar<TO>::FromInteger(
1901 *value, FromCat == TypeCategory::Unsigned)};
1902 if (!converted.flags.empty()) {
1903 char buffer[64];
1904 std::snprintf(buffer, sizeof buffer,
1905 "INTEGER(%d) to REAL(%d) conversion", Operand::kind,
1906 TO::kind);
1907 ctx.RealFlagWarnings(converted.flags, buffer);
1908 }
1909 return ScalarConstantToExpr(std::move(converted.value));
1910 } else if constexpr (FromCat == TypeCategory::Real) {
1911 auto converted{Scalar<TO>::Convert(*value)};
1912 char buffer[64];
1913 if (!converted.flags.empty()) {
1914 std::snprintf(buffer, sizeof buffer,
1915 "REAL(%d) to REAL(%d) conversion", Operand::kind, TO::kind);
1916 ctx.RealFlagWarnings(converted.flags, buffer);
1917 }
1918 if (ctx.targetCharacteristics().areSubnormalsFlushedToZero()) {
1919 converted.value = converted.value.FlushSubnormalToZero();
1920 }
1921 return ScalarConstantToExpr(std::move(converted.value));
1922 }
1923 } else if constexpr (TO::category == TypeCategory::Complex) {
1924 if constexpr (FromCat == TypeCategory::Complex) {
1925 return FoldOperation(ctx,
1927 AsExpr(Convert<typename TO::Part>{AsCategoryExpr(
1928 Constant<typename Operand::Part>{value->REAL()})}),
1929 AsExpr(Convert<typename TO::Part>{AsCategoryExpr(
1930 Constant<typename Operand::Part>{value->AIMAG()})})});
1931 }
1932 } else if constexpr (TO::category == TypeCategory::Character &&
1933 FromCat == TypeCategory::Character) {
1934 if (auto converted{ConvertString<Scalar<TO>>(std::move(*value))}) {
1935 return ScalarConstantToExpr(std::move(*converted));
1936 }
1937 } else if constexpr (TO::category == TypeCategory::Logical &&
1938 FromCat == TypeCategory::Logical) {
1939 return Expr<TO>{value->IsTrue()};
1940 }
1941 } else if constexpr (TO::category == FromCat &&
1942 FromCat != TypeCategory::Character) {
1943 // Conversion of non-constant in same type category
1944 if constexpr (std::is_same_v<Operand, TO>) {
1945 return std::move(kindExpr); // remove needless conversion
1946 } else if constexpr (TO::category == TypeCategory::Logical ||
1947 TO::category == TypeCategory::Integer) {
1948 if (auto *innerConv{
1949 std::get_if<Convert<Operand, TO::category>>(&kindExpr.u)}) {
1950 // Conversion of conversion of same category & kind
1951 if (auto *x{std::get_if<Expr<TO>>(&innerConv->left().u)}) {
1952 if constexpr (TO::category == TypeCategory::Logical ||
1953 TO::kind <= Operand::kind) {
1954 return std::move(*x); // no-op Logical or Integer
1955 // widening/narrowing conversion pair
1956 } else if constexpr (std::is_same_v<TO,
1957 DescriptorInquiry::Result>) {
1958 if (std::holds_alternative<DescriptorInquiry>(x->u) ||
1959 std::holds_alternative<TypeParamInquiry>(x->u)) {
1960 // int(int(size(...),kind=k),kind=8) -> size(...)
1961 return std::move(*x);
1962 }
1963 }
1964 }
1965 }
1966 }
1967 }
1968 return Expr<TO>{std::move(convert)};
1969 },
1970 convert.left().u);
1971}
1972
1973template <typename T>
1974Expr<T> FoldOperation(FoldingContext &context, Parentheses<T> &&x) {
1975 auto &operand{x.left()};
1976 operand = Fold(context, std::move(operand));
1977 if (auto value{GetScalarConstantValue<T>(operand)}) {
1978 // Preserve parentheses, even around constants.
1979 return Expr<T>{Parentheses<T>{Expr<T>{Constant<T>{*value}}}};
1980 } else if (std::holds_alternative<Parentheses<T>>(operand.u)) {
1981 // ((x)) -> (x)
1982 return std::move(operand);
1983 } else {
1984 return Expr<T>{Parentheses<T>{std::move(operand)}};
1985 }
1986}
1987
1988template <typename T>
1989Expr<T> FoldOperation(FoldingContext &context, Negate<T> &&x) {
1990 if (auto array{ApplyElementwise(context, x)}) {
1991 return *array;
1992 }
1993 auto &operand{x.left()};
1994 if (auto *nn{std::get_if<Negate<T>>(&x.left().u)}) {
1995 // -(-x) -> (x)
1996 if (IsVariable(nn->left())) {
1997 return FoldOperation(context, Parentheses<T>{std::move(nn->left())});
1998 } else {
1999 return std::move(nn->left());
2000 }
2001 } else if (auto value{GetScalarConstantValue<T>(operand)}) {
2002 if constexpr (T::category == TypeCategory::Integer) {
2003 auto negated{value->Negate()};
2004 if (negated.overflow) {
2005 context.Warn(common::UsageWarning::FoldingException,
2006 "INTEGER(%d) negation overflowed"_warn_en_US, T::kind);
2007 }
2008 return Expr<T>{Constant<T>{std::move(negated.value)}};
2009 } else if constexpr (T::category == TypeCategory::Unsigned) {
2010 return Expr<T>{Constant<T>{std::move(value->Negate().value)}};
2011 } else {
2012 // REAL & COMPLEX negation: no exceptions possible
2013 return Expr<T>{Constant<T>{value->Negate()}};
2014 }
2015 }
2016 return Expr<T>{std::move(x)};
2017}
2018
2019// Binary (dyadic) operations
2020
2021template <typename LEFT, typename RIGHT>
2022std::optional<std::pair<Scalar<LEFT>, Scalar<RIGHT>>> OperandsAreConstants(
2023 const Expr<LEFT> &x, const Expr<RIGHT> &y) {
2024 if (auto xvalue{GetScalarConstantValue<LEFT>(x)}) {
2025 if (auto yvalue{GetScalarConstantValue<RIGHT>(y)}) {
2026 return {std::make_pair(*xvalue, *yvalue)};
2027 }
2028 }
2029 return std::nullopt;
2030}
2031
2032template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
2033std::optional<std::pair<Scalar<LEFT>, Scalar<RIGHT>>> OperandsAreConstants(
2034 const Operation<DERIVED, RESULT, LEFT, RIGHT> &operation) {
2035 return OperandsAreConstants(operation.left(), operation.right());
2036}
2037
2038template <typename T>
2039Expr<T> FoldOperation(FoldingContext &context, Add<T> &&x) {
2040 if (auto array{ApplyElementwise(context, x)}) {
2041 return *array;
2042 }
2043 if (auto folded{OperandsAreConstants(x)}) {
2044 if constexpr (T::category == TypeCategory::Integer) {
2045 auto sum{folded->first.AddSigned(folded->second)};
2046 if (sum.overflow) {
2047 context.Warn(common::UsageWarning::FoldingException,
2048 "INTEGER(%d) addition overflowed"_warn_en_US, T::kind);
2049 }
2050 return Expr<T>{Constant<T>{sum.value}};
2051 } else if constexpr (T::category == TypeCategory::Unsigned) {
2052 return Expr<T>{
2053 Constant<T>{folded->first.AddUnsigned(folded->second).value}};
2054 } else {
2055 auto sum{folded->first.Add(
2056 folded->second, context.targetCharacteristics().roundingMode())};
2057 context.RealFlagWarnings(sum.flags, "addition");
2058 if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
2059 sum.value = sum.value.FlushSubnormalToZero();
2060 }
2061 return Expr<T>{Constant<T>{sum.value}};
2062 }
2063 } else if constexpr (T::category == TypeCategory::Integer ||
2064 T::category == TypeCategory::Unsigned) {
2065 if (auto c{GetScalarConstantValue<T>(x.right())}) {
2066 if (c->IsZero() && x.left().Rank() == 0) {
2067 if (IsVariable(x.left())) {
2068 return FoldOperation(context, Parentheses<T>{std::move(x.left())});
2069 } else {
2070 return std::move(x.left());
2071 }
2072 }
2073 } else if (auto c{GetScalarConstantValue<T>(x.left())}) {
2074 if (c->IsZero() && x.right().Rank() == 0) {
2075 if (IsVariable(x.right())) {
2076 return FoldOperation(context, Parentheses<T>{std::move(x.right())});
2077 } else {
2078 return std::move(x.right());
2079 }
2080 }
2081 }
2082 }
2083 return Expr<T>{std::move(x)};
2084}
2085
2086template <typename T>
2087Expr<T> FoldOperation(FoldingContext &context, Subtract<T> &&x) {
2088 if (auto array{ApplyElementwise(context, x)}) {
2089 return *array;
2090 }
2091 if (auto folded{OperandsAreConstants(x)}) {
2092 if constexpr (T::category == TypeCategory::Integer) {
2093 auto difference{folded->first.SubtractSigned(folded->second)};
2094 if (difference.overflow) {
2095 context.Warn(common::UsageWarning::FoldingException,
2096 "INTEGER(%d) subtraction overflowed"_warn_en_US, T::kind);
2097 }
2098 return Expr<T>{Constant<T>{difference.value}};
2099 } else if constexpr (T::category == TypeCategory::Unsigned) {
2100 return Expr<T>{
2101 Constant<T>{folded->first.SubtractSigned(folded->second).value}};
2102 } else {
2103 auto difference{folded->first.Subtract(
2104 folded->second, context.targetCharacteristics().roundingMode())};
2105 context.RealFlagWarnings(difference.flags, "subtraction");
2106 if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
2107 difference.value = difference.value.FlushSubnormalToZero();
2108 }
2109 return Expr<T>{Constant<T>{difference.value}};
2110 }
2111 } else if constexpr (T::category == TypeCategory::Integer ||
2112 T::category == TypeCategory::Unsigned) {
2113 if (auto c{GetScalarConstantValue<T>(x.right())}) {
2114 if (c->IsZero() && x.left().Rank() == 0) {
2115 if (IsVariable(x.left())) {
2116 return FoldOperation(context, Parentheses<T>{std::move(x.left())});
2117 } else {
2118 return std::move(x.left());
2119 }
2120 }
2121 }
2122 }
2123 return Expr<T>{std::move(x)};
2124}
2125
2126template <typename T>
2127Expr<T> FoldOperation(FoldingContext &context, Multiply<T> &&x) {
2128 if (auto array{ApplyElementwise(context, x)}) {
2129 return *array;
2130 }
2131 if (auto folded{OperandsAreConstants(x)}) {
2132 if constexpr (T::category == TypeCategory::Integer) {
2133 auto product{folded->first.MultiplySigned(folded->second)};
2134 if (product.SignedMultiplicationOverflowed()) {
2135 context.Warn(common::UsageWarning::FoldingException,
2136 "INTEGER(%d) multiplication overflowed"_warn_en_US, T::kind);
2137 }
2138 return Expr<T>{Constant<T>{product.lower}};
2139 } else if constexpr (T::category == TypeCategory::Unsigned) {
2140 return Expr<T>{
2141 Constant<T>{folded->first.MultiplyUnsigned(folded->second).lower}};
2142 } else {
2143 auto product{folded->first.Multiply(
2144 folded->second, context.targetCharacteristics().roundingMode())};
2145 context.RealFlagWarnings(product.flags, "multiplication");
2146 if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
2147 product.value = product.value.FlushSubnormalToZero();
2148 }
2149 return Expr<T>{Constant<T>{product.value}};
2150 }
2151 } else if constexpr (T::category == TypeCategory::Integer) {
2152 if (auto c{GetScalarConstantValue<T>(x.right())}) {
2153 x.right() = std::move(x.left());
2154 x.left() = Expr<T>{std::move(*c)};
2155 }
2156 if (auto c{GetScalarConstantValue<T>(x.left())}) {
2157 if (c->IsZero() && x.right().Rank() == 0) {
2158 return std::move(x.left());
2159 } else if (c->CompareSigned(Scalar<T>{1}) == Ordering::Equal) {
2160 if (IsVariable(x.right())) {
2161 return FoldOperation(context, Parentheses<T>{std::move(x.right())});
2162 } else {
2163 return std::move(x.right());
2164 }
2165 } else if (c->CompareSigned(Scalar<T>{-1}) == Ordering::Equal) {
2166 return FoldOperation(context, Negate<T>{std::move(x.right())});
2167 }
2168 }
2169 }
2170 return Expr<T>{std::move(x)};
2171}
2172
2173template <typename T>
2174Expr<T> FoldOperation(FoldingContext &context, Divide<T> &&x) {
2175 if (auto array{ApplyElementwise(context, x)}) {
2176 return *array;
2177 }
2178 if (auto folded{OperandsAreConstants(x)}) {
2179 if constexpr (T::category == TypeCategory::Integer) {
2180 auto quotAndRem{folded->first.DivideSigned(folded->second)};
2181 if (quotAndRem.divisionByZero) {
2182 context.Warn(common::UsageWarning::FoldingException,
2183 "INTEGER(%d) division by zero"_warn_en_US, T::kind);
2184 return Expr<T>{std::move(x)};
2185 }
2186 if (quotAndRem.overflow) {
2187 context.Warn(common::UsageWarning::FoldingException,
2188 "INTEGER(%d) division overflowed"_warn_en_US, T::kind);
2189 }
2190 return Expr<T>{Constant<T>{quotAndRem.quotient}};
2191 } else if constexpr (T::category == TypeCategory::Unsigned) {
2192 auto quotAndRem{folded->first.DivideUnsigned(folded->second)};
2193 if (quotAndRem.divisionByZero) {
2194 context.Warn(common::UsageWarning::FoldingException,
2195 "UNSIGNED(%d) division by zero"_warn_en_US, T::kind);
2196 return Expr<T>{std::move(x)};
2197 }
2198 return Expr<T>{Constant<T>{quotAndRem.quotient}};
2199 } else {
2200 auto quotient{folded->first.Divide(
2201 folded->second, context.targetCharacteristics().roundingMode())};
2202 // Don't warn about -1./0., 0./0., or 1./0. from a module file
2203 // they are interpreted as canonical Fortran representations of -Inf,
2204 // NaN, and Inf respectively.
2205 bool isCanonicalNaNOrInf{false};
2206 if constexpr (T::category == TypeCategory::Real) {
2207 if (folded->second.IsZero() && context.moduleFileName().has_value()) {
2208 using IntType = typename T::Scalar::Word;
2209 auto intNumerator{folded->first.template ToInteger<IntType>()};
2210 isCanonicalNaNOrInf = intNumerator.flags == RealFlags{} &&
2211 intNumerator.value >= IntType{-1} &&
2212 intNumerator.value <= IntType{1};
2213 }
2214 }
2215 if (!isCanonicalNaNOrInf) {
2216 context.RealFlagWarnings(quotient.flags, "division");
2217 }
2218 if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
2219 quotient.value = quotient.value.FlushSubnormalToZero();
2220 }
2221 return Expr<T>{Constant<T>{quotient.value}};
2222 }
2223 }
2224 return Expr<T>{std::move(x)};
2225}
2226
2227template <typename T>
2228Expr<T> FoldOperation(FoldingContext &context, Power<T> &&x) {
2229 if (auto array{ApplyElementwise(context, x)}) {
2230 return *array;
2231 }
2232 if (auto folded{OperandsAreConstants(x)}) {
2233 if constexpr (T::category == TypeCategory::Integer) {
2234 auto power{folded->first.Power(folded->second)};
2235 if (power.divisionByZero) {
2236 context.Warn(common::UsageWarning::FoldingException,
2237 "INTEGER(%d) zero to negative power"_warn_en_US, T::kind);
2238 } else if (power.overflow) {
2239 context.Warn(common::UsageWarning::FoldingException,
2240 "INTEGER(%d) power overflowed"_warn_en_US, T::kind);
2241 } else if (power.zeroToZero) {
2242 context.Warn(common::UsageWarning::FoldingException,
2243 "INTEGER(%d) 0**0 is not defined"_warn_en_US, T::kind);
2244 }
2245 return Expr<T>{Constant<T>{power.power}};
2246 } else {
2247 if (folded->first.IsZero()) {
2248 if (folded->second.IsZero()) {
2249 context.Warn(common::UsageWarning::FoldingException,
2250 "REAL/COMPLEX 0**0 is not defined"_warn_en_US);
2251 } else {
2252 return Expr<T>(Constant<T>{folded->first}); // 0. ** nonzero -> 0.
2253 }
2254 } else if (auto callable{GetHostRuntimeWrapper<T, T, T>("pow")}) {
2255 return Expr<T>{
2256 Constant<T>{(*callable)(context, folded->first, folded->second)}};
2257 } else {
2258 context.Warn(common::UsageWarning::FoldingFailure,
2259 "Power for %s cannot be folded on host"_warn_en_US,
2260 T{}.AsFortran());
2261 }
2262 }
2263 }
2264 return Expr<T>{std::move(x)};
2265}
2266
2267template <typename T>
2268Expr<T> FoldOperation(FoldingContext &context, RealToIntPower<T> &&x) {
2269 if (auto array{ApplyElementwise(context, x)}) {
2270 return *array;
2271 }
2272 return common::visit(
2273 [&](auto &y) -> Expr<T> {
2274 if (auto folded{OperandsAreConstants(x.left(), y)}) {
2275 auto power{evaluate::IntPower(folded->first, folded->second)};
2276 context.RealFlagWarnings(power.flags, "power with INTEGER exponent");
2277 if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
2278 power.value = power.value.FlushSubnormalToZero();
2279 }
2280 return Expr<T>{Constant<T>{power.value}};
2281 } else {
2282 return Expr<T>{std::move(x)};
2283 }
2284 },
2285 x.right().u);
2286}
2287
2288template <typename T>
2289Expr<T> FoldOperation(FoldingContext &context, ConditionalExpr<T> &&x) {
2290 x.condition() = Fold(context, std::move(x.condition()));
2291 // If the condition is a scalar logical constant, select the branch.
2292 if (auto cst{GetScalarConstantValue<LogicalResult>(x.condition())}) {
2293 return cst->IsTrue() ? Fold(context, std::move(x.thenValue()))
2294 : Fold(context, std::move(x.elseValue()));
2295 }
2296 return Expr<T>{std::move(x)};
2297}
2298
2299template <typename T>
2300Expr<T> FoldOperation(FoldingContext &context, Extremum<T> &&x) {
2301 if (auto array{ApplyElementwise(context, x,
2302 std::function<Expr<T>(Expr<T> &&, Expr<T> &&)>{[=](Expr<T> &&l,
2303 Expr<T> &&r) {
2304 return Expr<T>{Extremum<T>{x.ordering, std::move(l), std::move(r)}};
2305 }})}) {
2306 return *array;
2307 }
2308 if (auto folded{OperandsAreConstants(x)}) {
2309 if constexpr (T::category == TypeCategory::Integer) {
2310 if (folded->first.CompareSigned(folded->second) == x.ordering) {
2311 return Expr<T>{Constant<T>{folded->first}};
2312 }
2313 } else if constexpr (T::category == TypeCategory::Unsigned) {
2314 if (folded->first.CompareUnsigned(folded->second) == x.ordering) {
2315 return Expr<T>{Constant<T>{folded->first}};
2316 }
2317 } else if constexpr (T::category == TypeCategory::Real) {
2318 if (folded->first.IsNotANumber() ||
2319 (folded->first.Compare(folded->second) == Relation::Less) ==
2320 (x.ordering == Ordering::Less)) {
2321 return Expr<T>{Constant<T>{folded->first}};
2322 }
2323 } else {
2324 static_assert(T::category == TypeCategory::Character);
2325 // Result of MIN and MAX on character has the length of
2326 // the longest argument.
2327 auto maxLen{std::max(folded->first.length(), folded->second.length())};
2328 bool isFirst{x.ordering == Compare(folded->first, folded->second)};
2329 auto res{isFirst ? std::move(folded->first) : std::move(folded->second)};
2330 res = res.length() == maxLen
2331 ? std::move(res)
2332 : CharacterUtils<T::kind>::Resize(res, maxLen);
2333 return Expr<T>{Constant<T>{std::move(res)}};
2334 }
2335 return Expr<T>{Constant<T>{folded->second}};
2336 }
2337 return Expr<T>{std::move(x)};
2338}
2339
2340template <int KIND>
2342 FoldingContext &context, Expr<SomeType> &&expr) {
2343 using Result = Type<TypeCategory::Real, KIND>;
2344 std::optional<Expr<Result>> result;
2345 common::visit(
2346 [&](auto &&x) {
2347 using From = std::decay_t<decltype(x)>;
2348 if constexpr (std::is_same_v<From, BOZLiteralConstant>) {
2349 // Move the bits without any integer->real conversion
2350 From original{x};
2351 result = ConvertToType<Result>(std::move(x));
2352 const auto *constant{UnwrapExpr<Constant<Result>>(*result)};
2353 CHECK(constant);
2354 Scalar<Result> real{constant->GetScalarValue().value()};
2355 From converted{From::ConvertUnsigned(real.RawBits()).value};
2356 if (original != converted) { // C1601
2357 context.Warn(common::UsageWarning::FoldingValueChecks,
2358 "Nonzero bits truncated from BOZ literal constant in REAL intrinsic"_warn_en_US);
2359 }
2360 } else if constexpr (IsNumericCategoryExpr<From>()) {
2361 result = Fold(context, ConvertToType<Result>(std::move(x)));
2362 } else {
2363 common::die("ToReal: bad argument expression");
2364 }
2365 },
2366 std::move(expr.u));
2367 return result.value();
2368}
2369
2370// REAL(z) and AIMAG(z)
2371template <int KIND>
2373 FoldingContext &context, ComplexComponent<KIND> &&x) {
2374 using Operand = Type<TypeCategory::Complex, KIND>;
2375 using Result = Type<TypeCategory::Real, KIND>;
2376 if (auto array{ApplyElementwise(context, x,
2377 std::function<Expr<Result>(Expr<Operand> &&)>{
2378 [=](Expr<Operand> &&operand) {
2380 x.isImaginaryPart, std::move(operand)}};
2381 }})}) {
2382 return *array;
2383 }
2384 auto &operand{x.left()};
2385 if (auto value{GetScalarConstantValue<Operand>(operand)}) {
2386 if (x.isImaginaryPart) {
2387 return Expr<Result>{Constant<Result>{value->AIMAG()}};
2388 } else {
2389 return Expr<Result>{Constant<Result>{value->REAL()}};
2390 }
2391 }
2392 return Expr<Result>{std::move(x)};
2393}
2394
2395template <typename T>
2396Expr<T> ExpressionBase<T>::Rewrite(FoldingContext &context, Expr<T> &&expr) {
2397 return common::visit(
2398 [&](auto &&x) -> Expr<T> {
2399 if constexpr (IsSpecificIntrinsicType<T>) {
2400 return FoldOperation(context, std::move(x));
2401 } else if constexpr (std::is_same_v<T, SomeDerived>) {
2402 return FoldOperation(context, std::move(x));
2403 } else if constexpr (common::HasMember<decltype(x),
2404 TypelessExpression>) {
2405 return std::move(expr);
2406 } else {
2407 return Expr<T>{Fold(context, std::move(x))};
2408 }
2409 },
2410 std::move(expr.u));
2411}
2412
2413FOR_EACH_TYPE_AND_KIND(extern template class ExpressionBase, )
2414} // namespace Fortran::evaluate
2415#endif // FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
Definition fold-implementation.h:1343
Definition expression.h:506
Definition variable.h:205
Definition variable.h:243
Definition variable.h:357
Definition variable.h:73
Definition expression.h:394
Definition constant.h:60
Definition constant.h:147
Definition variable.h:381
Definition type.h:73
Definition common.h:215
Definition expression.h:65
Definition fold-implementation.h:53
Definition common.h:217
Definition call.h:394
Definition expression.h:444
Definition variable.h:101
Definition expression.h:113
Definition expression.h:784
Definition variable.h:304
Definition variable.h:160
Definition variable.h:136
Definition type.h:56
Definition symbol.h:907
Definition call.h:34
Definition ParserActions.h:24
Definition expression.h:295
Definition expression.h:472
Definition expression.h:256
Definition expression.h:356
Definition expression.h:210
Definition variable.h:288
Definition expression.h:316
Definition expression.h:339
Definition expression.h:436
Definition expression.h:309
Definition expression.h:246
Definition expression.h:228
Definition expression.h:323
Definition expression.h:331
Definition type.h:399
Definition variable.h:191
Definition expression.h:302