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