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