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