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