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