| //===-- lib/Evaluate/fold-implementation.h --------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_ |
| #define FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_ |
| |
| #include "character.h" |
| #include "host.h" |
| #include "int-power.h" |
| #include "flang/Common/indirection.h" |
| #include "flang/Common/template.h" |
| #include "flang/Common/unwrap.h" |
| #include "flang/Evaluate/characteristics.h" |
| #include "flang/Evaluate/common.h" |
| #include "flang/Evaluate/constant.h" |
| #include "flang/Evaluate/expression.h" |
| #include "flang/Evaluate/fold.h" |
| #include "flang/Evaluate/formatting.h" |
| #include "flang/Evaluate/intrinsics-library.h" |
| #include "flang/Evaluate/intrinsics.h" |
| #include "flang/Evaluate/shape.h" |
| #include "flang/Evaluate/tools.h" |
| #include "flang/Evaluate/traverse.h" |
| #include "flang/Evaluate/type.h" |
| #include "flang/Parser/message.h" |
| #include "flang/Semantics/scope.h" |
| #include "flang/Semantics/symbol.h" |
| #include "flang/Semantics/tools.h" |
| #include <algorithm> |
| #include <cmath> |
| #include <complex> |
| #include <cstdio> |
| #include <optional> |
| #include <type_traits> |
| #include <variant> |
| |
| // Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE |
| // to leak out of <math.h>. |
| #undef HUGE |
| |
| namespace Fortran::evaluate { |
| |
| // Don't use Kahan extended precision summation any more when folding |
| // transformational intrinsic functions other than SUM, since it is |
| // not used in the runtime implementations of those functions and we |
| // want results to match. |
| static constexpr bool useKahanSummation{false}; |
| |
| // Utilities |
| template <typename T> class Folder { |
| public: |
| explicit Folder(FoldingContext &c, bool forOptionalArgument = false) |
| : context_{c}, forOptionalArgument_{forOptionalArgument} {} |
| std::optional<Constant<T>> GetNamedConstant(const Symbol &); |
| std::optional<Constant<T>> ApplySubscripts(const Constant<T> &array, |
| const std::vector<Constant<SubscriptInteger>> &subscripts); |
| std::optional<Constant<T>> ApplyComponent(Constant<SomeDerived> &&, |
| const Symbol &component, |
| const std::vector<Constant<SubscriptInteger>> * = nullptr); |
| std::optional<Constant<T>> GetConstantComponent( |
| Component &, const std::vector<Constant<SubscriptInteger>> * = nullptr); |
| std::optional<Constant<T>> Folding(ArrayRef &); |
| std::optional<Constant<T>> Folding(DataRef &); |
| Expr<T> Folding(Designator<T> &&); |
| Constant<T> *Folding(std::optional<ActualArgument> &); |
| |
| Expr<T> CSHIFT(FunctionRef<T> &&); |
| Expr<T> EOSHIFT(FunctionRef<T> &&); |
| Expr<T> MERGE(FunctionRef<T> &&); |
| Expr<T> PACK(FunctionRef<T> &&); |
| Expr<T> RESHAPE(FunctionRef<T> &&); |
| Expr<T> SPREAD(FunctionRef<T> &&); |
| Expr<T> TRANSPOSE(FunctionRef<T> &&); |
| Expr<T> UNPACK(FunctionRef<T> &&); |
| |
| Expr<T> TRANSFER(FunctionRef<T> &&); |
| |
| private: |
| FoldingContext &context_; |
| bool forOptionalArgument_{false}; |
| }; |
| |
| std::optional<Constant<SubscriptInteger>> GetConstantSubscript( |
| FoldingContext &, Subscript &, const NamedEntity &, int dim); |
| |
| // Helper to use host runtime on scalars for folding. |
| template <typename TR, typename... TA> |
| std::optional<std::function<Scalar<TR>(FoldingContext &, Scalar<TA>...)>> |
| GetHostRuntimeWrapper(const std::string &name) { |
| std::vector<DynamicType> argTypes{TA{}.GetType()...}; |
| if (auto hostWrapper{GetHostRuntimeWrapper(name, TR{}.GetType(), argTypes)}) { |
| return [hostWrapper]( |
| FoldingContext &context, Scalar<TA>... args) -> Scalar<TR> { |
| std::vector<Expr<SomeType>> genericArgs{ |
| AsGenericExpr(Constant<TA>{args})...}; |
| return GetScalarConstantValue<TR>( |
| (*hostWrapper)(context, std::move(genericArgs))) |
| .value(); |
| }; |
| } |
| return std::nullopt; |
| } |
| |
| // FoldOperation() rewrites expression tree nodes. |
| // If there is any possibility that the rewritten node will |
| // not have the same representation type, the result of |
| // FoldOperation() will be packaged in an Expr<> of the same |
| // specific type. |
| |
| // no-op base case |
| template <typename A> |
| common::IfNoLvalue<Expr<ResultType<A>>, A> FoldOperation( |
| FoldingContext &, A &&x) { |
| static_assert(!std::is_same_v<A, Expr<ResultType<A>>>, |
| "call Fold() instead for Expr<>"); |
| return Expr<ResultType<A>>{std::move(x)}; |
| } |
| |
| Component FoldOperation(FoldingContext &, Component &&); |
| NamedEntity FoldOperation(FoldingContext &, NamedEntity &&); |
| Triplet FoldOperation(FoldingContext &, Triplet &&); |
| Subscript FoldOperation(FoldingContext &, Subscript &&); |
| ArrayRef FoldOperation(FoldingContext &, ArrayRef &&); |
| CoarrayRef FoldOperation(FoldingContext &, CoarrayRef &&); |
| DataRef FoldOperation(FoldingContext &, DataRef &&); |
| Substring FoldOperation(FoldingContext &, Substring &&); |
| ComplexPart FoldOperation(FoldingContext &, ComplexPart &&); |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &, FunctionRef<T> &&); |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Designator<T> &&designator) { |
| return Folder<T>{context}.Folding(std::move(designator)); |
| } |
| Expr<TypeParamInquiry::Result> FoldOperation( |
| FoldingContext &, TypeParamInquiry &&); |
| Expr<ImpliedDoIndex::Result> FoldOperation( |
| FoldingContext &context, ImpliedDoIndex &&); |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &, ArrayConstructor<T> &&); |
| Expr<SomeDerived> FoldOperation(FoldingContext &, StructureConstructor &&); |
| |
| template <typename T> |
| std::optional<Constant<T>> Folder<T>::GetNamedConstant(const Symbol &symbol0) { |
| const Symbol &symbol{ResolveAssociations(symbol0)}; |
| if (IsNamedConstant(symbol)) { |
| if (const auto *object{ |
| symbol.detailsIf<semantics::ObjectEntityDetails>()}) { |
| if (const auto *constant{UnwrapConstantValue<T>(object->init())}) { |
| return *constant; |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename T> |
| std::optional<Constant<T>> Folder<T>::Folding(ArrayRef &aRef) { |
| std::vector<Constant<SubscriptInteger>> subscripts; |
| int dim{0}; |
| for (Subscript &ss : aRef.subscript()) { |
| if (auto constant{GetConstantSubscript(context_, ss, aRef.base(), dim++)}) { |
| subscripts.emplace_back(std::move(*constant)); |
| } else { |
| return std::nullopt; |
| } |
| } |
| if (Component * component{aRef.base().UnwrapComponent()}) { |
| return GetConstantComponent(*component, &subscripts); |
| } else if (std::optional<Constant<T>> array{ |
| GetNamedConstant(aRef.base().GetLastSymbol())}) { |
| return ApplySubscripts(*array, subscripts); |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| template <typename T> |
| std::optional<Constant<T>> Folder<T>::Folding(DataRef &ref) { |
| return common::visit( |
| common::visitors{ |
| [this](SymbolRef &sym) { return GetNamedConstant(*sym); }, |
| [this](Component &comp) { |
| comp = FoldOperation(context_, std::move(comp)); |
| return GetConstantComponent(comp); |
| }, |
| [this](ArrayRef &aRef) { |
| aRef = FoldOperation(context_, std::move(aRef)); |
| return Folding(aRef); |
| }, |
| [](CoarrayRef &) { return std::optional<Constant<T>>{}; }, |
| }, |
| ref.u); |
| } |
| |
| // TODO: This would be more natural as a member function of Constant<T>. |
| template <typename T> |
| std::optional<Constant<T>> Folder<T>::ApplySubscripts(const Constant<T> &array, |
| const std::vector<Constant<SubscriptInteger>> &subscripts) { |
| const auto &shape{array.shape()}; |
| const auto &lbounds{array.lbounds()}; |
| int rank{GetRank(shape)}; |
| CHECK(rank == static_cast<int>(subscripts.size())); |
| std::size_t elements{1}; |
| ConstantSubscripts resultShape; |
| ConstantSubscripts ssLB; |
| for (const auto &ss : subscripts) { |
| if (ss.Rank() == 1) { |
| resultShape.push_back(static_cast<ConstantSubscript>(ss.size())); |
| elements *= ss.size(); |
| ssLB.push_back(ss.lbounds().front()); |
| } else if (ss.Rank() > 1) { |
| return std::nullopt; // error recovery |
| } |
| } |
| ConstantSubscripts ssAt(rank, 0), at(rank, 0), tmp(1, 0); |
| std::vector<Scalar<T>> values; |
| while (elements-- > 0) { |
| bool increment{true}; |
| int k{0}; |
| for (int j{0}; j < rank; ++j) { |
| if (subscripts[j].Rank() == 0) { |
| at[j] = subscripts[j].GetScalarValue().value().ToInt64(); |
| } else { |
| CHECK(k < GetRank(resultShape)); |
| tmp[0] = ssLB.at(k) + ssAt.at(k); |
| at[j] = subscripts[j].At(tmp).ToInt64(); |
| if (increment) { |
| if (++ssAt[k] == resultShape[k]) { |
| ssAt[k] = 0; |
| } else { |
| increment = false; |
| } |
| } |
| ++k; |
| } |
| if (at[j] < lbounds[j] || at[j] >= lbounds[j] + shape[j]) { |
| context_.messages().Say( |
| "Subscript value (%jd) is out of range on dimension %d in reference to a constant array value"_err_en_US, |
| at[j], j + 1); |
| return std::nullopt; |
| } |
| } |
| values.emplace_back(array.At(at)); |
| CHECK(!increment || elements == 0); |
| CHECK(k == GetRank(resultShape)); |
| } |
| if constexpr (T::category == TypeCategory::Character) { |
| return Constant<T>{array.LEN(), std::move(values), std::move(resultShape)}; |
| } else if constexpr (std::is_same_v<T, SomeDerived>) { |
| return Constant<T>{array.result().derivedTypeSpec(), std::move(values), |
| std::move(resultShape)}; |
| } else { |
| return Constant<T>{std::move(values), std::move(resultShape)}; |
| } |
| } |
| |
| template <typename T> |
| std::optional<Constant<T>> Folder<T>::ApplyComponent( |
| Constant<SomeDerived> &&structures, const Symbol &component, |
| const std::vector<Constant<SubscriptInteger>> *subscripts) { |
| if (auto scalar{structures.GetScalarValue()}) { |
| if (std::optional<Expr<SomeType>> expr{scalar->Find(component)}) { |
| if (const Constant<T> *value{UnwrapConstantValue<T>(*expr)}) { |
| if (subscripts) { |
| return ApplySubscripts(*value, *subscripts); |
| } else { |
| return *value; |
| } |
| } |
| } |
| } else { |
| // A(:)%scalar_component & A(:)%array_component(subscripts) |
| std::unique_ptr<ArrayConstructor<T>> array; |
| if (structures.empty()) { |
| return std::nullopt; |
| } |
| ConstantSubscripts at{structures.lbounds()}; |
| do { |
| StructureConstructor scalar{structures.At(at)}; |
| if (std::optional<Expr<SomeType>> expr{scalar.Find(component)}) { |
| if (const Constant<T> *value{UnwrapConstantValue<T>(expr.value())}) { |
| if (!array.get()) { |
| // This technique ensures that character length or derived type |
| // information is propagated to the array constructor. |
| auto *typedExpr{UnwrapExpr<Expr<T>>(expr.value())}; |
| CHECK(typedExpr); |
| array = std::make_unique<ArrayConstructor<T>>(*typedExpr); |
| } |
| if (subscripts) { |
| if (auto element{ApplySubscripts(*value, *subscripts)}) { |
| CHECK(element->Rank() == 0); |
| array->Push(Expr<T>{std::move(*element)}); |
| } else { |
| return std::nullopt; |
| } |
| } else { |
| CHECK(value->Rank() == 0); |
| array->Push(Expr<T>{*value}); |
| } |
| } else { |
| return std::nullopt; |
| } |
| } |
| } while (structures.IncrementSubscripts(at)); |
| // Fold the ArrayConstructor<> into a Constant<>. |
| CHECK(array); |
| Expr<T> result{Fold(context_, Expr<T>{std::move(*array)})}; |
| if (auto *constant{UnwrapConstantValue<T>(result)}) { |
| return constant->Reshape(common::Clone(structures.shape())); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename T> |
| std::optional<Constant<T>> Folder<T>::GetConstantComponent(Component &component, |
| const std::vector<Constant<SubscriptInteger>> *subscripts) { |
| if (std::optional<Constant<SomeDerived>> structures{common::visit( |
| common::visitors{ |
| [&](const Symbol &symbol) { |
| return Folder<SomeDerived>{context_}.GetNamedConstant(symbol); |
| }, |
| [&](ArrayRef &aRef) { |
| return Folder<SomeDerived>{context_}.Folding(aRef); |
| }, |
| [&](Component &base) { |
| return Folder<SomeDerived>{context_}.GetConstantComponent(base); |
| }, |
| [&](CoarrayRef &) { |
| return std::optional<Constant<SomeDerived>>{}; |
| }, |
| }, |
| component.base().u)}) { |
| return ApplyComponent( |
| std::move(*structures), component.GetLastSymbol(), subscripts); |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| template <typename T> Expr<T> Folder<T>::Folding(Designator<T> &&designator) { |
| if constexpr (T::category == TypeCategory::Character) { |
| if (auto *substring{common::Unwrap<Substring>(designator.u)}) { |
| if (std::optional<Expr<SomeCharacter>> folded{ |
| substring->Fold(context_)}) { |
| if (const auto *specific{std::get_if<Expr<T>>(&folded->u)}) { |
| return std::move(*specific); |
| } |
| } |
| // We used to fold zero-length substrings into zero-length |
| // constants here, but that led to problems in variable |
| // definition contexts. |
| } |
| } else if constexpr (T::category == TypeCategory::Real) { |
| if (auto *zPart{std::get_if<ComplexPart>(&designator.u)}) { |
| *zPart = FoldOperation(context_, std::move(*zPart)); |
| using ComplexT = Type<TypeCategory::Complex, T::kind>; |
| if (auto zConst{Folder<ComplexT>{context_}.Folding(zPart->complex())}) { |
| return Fold(context_, |
| Expr<T>{ComplexComponent<T::kind>{ |
| zPart->part() == ComplexPart::Part::IM, |
| Expr<ComplexT>{std::move(*zConst)}}}); |
| } else { |
| return Expr<T>{Designator<T>{std::move(*zPart)}}; |
| } |
| } |
| } |
| return common::visit( |
| common::visitors{ |
| [&](SymbolRef &&symbol) { |
| if (auto constant{GetNamedConstant(*symbol)}) { |
| return Expr<T>{std::move(*constant)}; |
| } |
| return Expr<T>{std::move(designator)}; |
| }, |
| [&](ArrayRef &&aRef) { |
| aRef = FoldOperation(context_, std::move(aRef)); |
| if (auto c{Folding(aRef)}) { |
| return Expr<T>{std::move(*c)}; |
| } else { |
| return Expr<T>{Designator<T>{std::move(aRef)}}; |
| } |
| }, |
| [&](Component &&component) { |
| component = FoldOperation(context_, std::move(component)); |
| if (auto c{GetConstantComponent(component)}) { |
| return Expr<T>{std::move(*c)}; |
| } else { |
| return Expr<T>{Designator<T>{std::move(component)}}; |
| } |
| }, |
| [&](auto &&x) { |
| return Expr<T>{ |
| Designator<T>{FoldOperation(context_, std::move(x))}}; |
| }, |
| }, |
| std::move(designator.u)); |
| } |
| |
| // Apply type conversion and re-folding if necessary. |
| // This is where BOZ arguments are converted. |
| template <typename T> |
| Constant<T> *Folder<T>::Folding(std::optional<ActualArgument> &arg) { |
| if (auto *expr{UnwrapExpr<Expr<SomeType>>(arg)}) { |
| if constexpr (T::category != TypeCategory::Derived) { |
| if (!UnwrapExpr<Expr<T>>(*expr)) { |
| if (const Symbol * |
| var{forOptionalArgument_ |
| ? UnwrapWholeSymbolOrComponentDataRef(*expr) |
| : nullptr}; |
| var && (IsOptional(*var) || IsAllocatableOrObjectPointer(var))) { |
| // can't safely convert item that may not be present |
| } else if (auto converted{ |
| ConvertToType(T::GetType(), std::move(*expr))}) { |
| *expr = Fold(context_, std::move(*converted)); |
| } |
| } |
| } |
| return UnwrapConstantValue<T>(*expr); |
| } |
| return nullptr; |
| } |
| |
| template <typename... A, std::size_t... I> |
| std::optional<std::tuple<const Constant<A> *...>> GetConstantArgumentsHelper( |
| FoldingContext &context, ActualArguments &arguments, |
| bool hasOptionalArgument, std::index_sequence<I...>) { |
| static_assert(sizeof...(A) > 0); |
| std::tuple<const Constant<A> *...> args{ |
| Folder<A>{context, hasOptionalArgument}.Folding(arguments.at(I))...}; |
| if ((... && (std::get<I>(args)))) { |
| return args; |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| template <typename... A> |
| std::optional<std::tuple<const Constant<A> *...>> GetConstantArguments( |
| FoldingContext &context, ActualArguments &args, bool hasOptionalArgument) { |
| return GetConstantArgumentsHelper<A...>( |
| context, args, hasOptionalArgument, std::index_sequence_for<A...>{}); |
| } |
| |
| template <typename... A, std::size_t... I> |
| std::optional<std::tuple<Scalar<A>...>> GetScalarConstantArgumentsHelper( |
| FoldingContext &context, ActualArguments &args, bool hasOptionalArgument, |
| std::index_sequence<I...>) { |
| if (auto constArgs{ |
| GetConstantArguments<A...>(context, args, hasOptionalArgument)}) { |
| return std::tuple<Scalar<A>...>{ |
| std::get<I>(*constArgs)->GetScalarValue().value()...}; |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| template <typename... A> |
| std::optional<std::tuple<Scalar<A>...>> GetScalarConstantArguments( |
| FoldingContext &context, ActualArguments &args, bool hasOptionalArgument) { |
| return GetScalarConstantArgumentsHelper<A...>( |
| context, args, hasOptionalArgument, std::index_sequence_for<A...>{}); |
| } |
| |
| // helpers to fold intrinsic function references |
| // Define callable types used in a common utility that |
| // takes care of array and cast/conversion aspects for elemental intrinsics |
| |
| template <typename TR, typename... TArgs> |
| using ScalarFunc = std::function<Scalar<TR>(const Scalar<TArgs> &...)>; |
| template <typename TR, typename... TArgs> |
| using ScalarFuncWithContext = |
| std::function<Scalar<TR>(FoldingContext &, const Scalar<TArgs> &...)>; |
| |
| template <template <typename, typename...> typename WrapperType, typename TR, |
| typename... TA, std::size_t... I> |
| Expr<TR> FoldElementalIntrinsicHelper(FoldingContext &context, |
| FunctionRef<TR> &&funcRef, WrapperType<TR, TA...> func, |
| bool hasOptionalArgument, std::index_sequence<I...>) { |
| if (std::optional<std::tuple<const Constant<TA> *...>> args{ |
| GetConstantArguments<TA...>( |
| context, funcRef.arguments(), hasOptionalArgument)}) { |
| // Compute the shape of the result based on shapes of arguments |
| ConstantSubscripts shape; |
| int rank{0}; |
| const ConstantSubscripts *shapes[]{&std::get<I>(*args)->shape()...}; |
| const int ranks[]{std::get<I>(*args)->Rank()...}; |
| for (unsigned int i{0}; i < sizeof...(TA); ++i) { |
| if (ranks[i] > 0) { |
| if (rank == 0) { |
| rank = ranks[i]; |
| shape = *shapes[i]; |
| } else { |
| if (shape != *shapes[i]) { |
| // TODO: Rank compatibility was already checked but it seems to be |
| // the first place where the actual shapes are checked to be the |
| // same. Shouldn't this be checked elsewhere so that this is also |
| // checked for non constexpr call to elemental intrinsics function? |
| context.messages().Say( |
| "Arguments in elemental intrinsic function are not conformable"_err_en_US); |
| return Expr<TR>{std::move(funcRef)}; |
| } |
| } |
| } |
| } |
| CHECK(rank == GetRank(shape)); |
| // Compute all the scalar values of the results |
| std::vector<Scalar<TR>> results; |
| std::optional<uint64_t> n{TotalElementCount(shape)}; |
| if (!n) { |
| context.messages().Say( |
| "Too many elements in elemental intrinsic function result"_err_en_US); |
| return Expr<TR>{std::move(funcRef)}; |
| } |
| if (*n > 0) { |
| ConstantBounds bounds{shape}; |
| ConstantSubscripts resultIndex(rank, 1); |
| ConstantSubscripts argIndex[]{std::get<I>(*args)->lbounds()...}; |
| do { |
| if constexpr (std::is_same_v<WrapperType<TR, TA...>, |
| ScalarFuncWithContext<TR, TA...>>) { |
| results.emplace_back( |
| func(context, std::get<I>(*args)->At(argIndex[I])...)); |
| } else if constexpr (std::is_same_v<WrapperType<TR, TA...>, |
| ScalarFunc<TR, TA...>>) { |
| results.emplace_back(func(std::get<I>(*args)->At(argIndex[I])...)); |
| } |
| (std::get<I>(*args)->IncrementSubscripts(argIndex[I]), ...); |
| } while (bounds.IncrementSubscripts(resultIndex)); |
| } |
| // Build and return constant result |
| if constexpr (TR::category == TypeCategory::Character) { |
| auto len{static_cast<ConstantSubscript>( |
| results.empty() ? 0 : results[0].length())}; |
| return Expr<TR>{Constant<TR>{len, std::move(results), std::move(shape)}}; |
| } else if constexpr (TR::category == TypeCategory::Derived) { |
| if (!results.empty()) { |
| return Expr<TR>{rank == 0 |
| ? Constant<TR>{results.front()} |
| : Constant<TR>{results.front().derivedTypeSpec(), |
| std::move(results), std::move(shape)}}; |
| } |
| } else { |
| return Expr<TR>{Constant<TR>{std::move(results), std::move(shape)}}; |
| } |
| } |
| return Expr<TR>{std::move(funcRef)}; |
| } |
| |
| template <typename TR, typename... TA> |
| Expr<TR> FoldElementalIntrinsic(FoldingContext &context, |
| FunctionRef<TR> &&funcRef, ScalarFunc<TR, TA...> func, |
| bool hasOptionalArgument = false) { |
| return FoldElementalIntrinsicHelper<ScalarFunc, TR, TA...>(context, |
| std::move(funcRef), func, hasOptionalArgument, |
| std::index_sequence_for<TA...>{}); |
| } |
| template <typename TR, typename... TA> |
| Expr<TR> FoldElementalIntrinsic(FoldingContext &context, |
| FunctionRef<TR> &&funcRef, ScalarFuncWithContext<TR, TA...> func, |
| bool hasOptionalArgument = false) { |
| return FoldElementalIntrinsicHelper<ScalarFuncWithContext, TR, TA...>(context, |
| std::move(funcRef), func, hasOptionalArgument, |
| std::index_sequence_for<TA...>{}); |
| } |
| |
| std::optional<std::int64_t> GetInt64ArgOr( |
| const std::optional<ActualArgument> &, std::int64_t defaultValue); |
| |
| template <typename A, typename B> |
| std::optional<std::vector<A>> GetIntegerVector(const B &x) { |
| static_assert(std::is_integral_v<A>); |
| if (const auto *someInteger{UnwrapExpr<Expr<SomeInteger>>(x)}) { |
| return common::visit( |
| [](const auto &typedExpr) -> std::optional<std::vector<A>> { |
| using T = ResultType<decltype(typedExpr)>; |
| if (const auto *constant{UnwrapConstantValue<T>(typedExpr)}) { |
| if (constant->Rank() == 1) { |
| std::vector<A> result; |
| for (const auto &value : constant->values()) { |
| result.push_back(static_cast<A>(value.ToInt64())); |
| } |
| return result; |
| } |
| } |
| return std::nullopt; |
| }, |
| someInteger->u); |
| } |
| return std::nullopt; |
| } |
| |
| // Transform an intrinsic function reference that contains user errors |
| // into an intrinsic with the same characteristic but the "invalid" name. |
| // This to prevent generating warnings over and over if the expression |
| // gets re-folded. |
| template <typename T> Expr<T> MakeInvalidIntrinsic(FunctionRef<T> &&funcRef) { |
| SpecificIntrinsic invalid{std::get<SpecificIntrinsic>(funcRef.proc().u)}; |
| invalid.name = IntrinsicProcTable::InvalidName; |
| return Expr<T>{FunctionRef<T>{ProcedureDesignator{std::move(invalid)}, |
| ActualArguments{std::move(funcRef.arguments())}}}; |
| } |
| |
| template <typename T> Expr<T> Folder<T>::CSHIFT(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 3); |
| const auto *array{UnwrapConstantValue<T>(args[0])}; |
| const auto *shiftExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])}; |
| auto dim{GetInt64ArgOr(args[2], 1)}; |
| if (!array || !shiftExpr || !dim) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| auto convertedShift{Fold(context_, |
| ConvertToType<SubscriptInteger>(Expr<SomeInteger>{*shiftExpr}))}; |
| const auto *shift{UnwrapConstantValue<SubscriptInteger>(convertedShift)}; |
| if (!shift) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| // Arguments are constant |
| if (*dim < 1 || *dim > array->Rank()) { |
| context_.messages().Say("Invalid 'dim=' argument (%jd) in CSHIFT"_err_en_US, |
| static_cast<std::intmax_t>(*dim)); |
| } else if (shift->Rank() > 0 && shift->Rank() != array->Rank() - 1) { |
| // message already emitted from intrinsic look-up |
| } else { |
| int rank{array->Rank()}; |
| int zbDim{static_cast<int>(*dim) - 1}; |
| bool ok{true}; |
| if (shift->Rank() > 0) { |
| int k{0}; |
| for (int j{0}; j < rank; ++j) { |
| if (j != zbDim) { |
| if (array->shape()[j] != shift->shape()[k]) { |
| context_.messages().Say( |
| "Invalid 'shift=' argument in CSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US, |
| k + 1, static_cast<std::intmax_t>(shift->shape()[k]), |
| static_cast<std::intmax_t>(array->shape()[j])); |
| ok = false; |
| } |
| ++k; |
| } |
| } |
| } |
| if (ok) { |
| std::vector<Scalar<T>> resultElements; |
| ConstantSubscripts arrayLB{array->lbounds()}; |
| ConstantSubscripts arrayAt{arrayLB}; |
| ConstantSubscript &dimIndex{arrayAt[zbDim]}; |
| ConstantSubscript dimLB{dimIndex}; // initial value |
| ConstantSubscript dimExtent{array->shape()[zbDim]}; |
| ConstantSubscripts shiftLB{shift->lbounds()}; |
| for (auto n{GetSize(array->shape())}; n > 0; --n) { |
| ConstantSubscript origDimIndex{dimIndex}; |
| ConstantSubscripts shiftAt; |
| if (shift->Rank() > 0) { |
| int k{0}; |
| for (int j{0}; j < rank; ++j) { |
| if (j != zbDim) { |
| shiftAt.emplace_back(shiftLB[k++] + arrayAt[j] - arrayLB[j]); |
| } |
| } |
| } |
| ConstantSubscript shiftCount{shift->At(shiftAt).ToInt64()}; |
| dimIndex = dimLB + ((dimIndex - dimLB + shiftCount) % dimExtent); |
| if (dimIndex < dimLB) { |
| dimIndex += dimExtent; |
| } else if (dimIndex >= dimLB + dimExtent) { |
| dimIndex -= dimExtent; |
| } |
| resultElements.push_back(array->At(arrayAt)); |
| dimIndex = origDimIndex; |
| array->IncrementSubscripts(arrayAt); |
| } |
| return Expr<T>{PackageConstant<T>( |
| std::move(resultElements), *array, array->shape())}; |
| } |
| } |
| // Invalid, prevent re-folding |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| |
| template <typename T> Expr<T> Folder<T>::EOSHIFT(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 4); |
| const auto *array{UnwrapConstantValue<T>(args[0])}; |
| const auto *shiftExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])}; |
| auto dim{GetInt64ArgOr(args[3], 1)}; |
| if (!array || !shiftExpr || !dim) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| // Apply type conversions to the shift= and boundary= arguments. |
| auto convertedShift{Fold(context_, |
| ConvertToType<SubscriptInteger>(Expr<SomeInteger>{*shiftExpr}))}; |
| const auto *shift{UnwrapConstantValue<SubscriptInteger>(convertedShift)}; |
| if (!shift) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| const Constant<T> *boundary{nullptr}; |
| std::optional<Expr<SomeType>> convertedBoundary; |
| if (const auto *boundaryExpr{UnwrapExpr<Expr<SomeType>>(args[2])}) { |
| convertedBoundary = Fold(context_, |
| ConvertToType(array->GetType(), Expr<SomeType>{*boundaryExpr})); |
| boundary = UnwrapExpr<Constant<T>>(convertedBoundary); |
| if (!boundary) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| } |
| // Arguments are constant |
| if (*dim < 1 || *dim > array->Rank()) { |
| context_.messages().Say( |
| "Invalid 'dim=' argument (%jd) in EOSHIFT"_err_en_US, |
| static_cast<std::intmax_t>(*dim)); |
| } else if (shift->Rank() > 0 && shift->Rank() != array->Rank() - 1) { |
| // message already emitted from intrinsic look-up |
| } else if (boundary && boundary->Rank() > 0 && |
| boundary->Rank() != array->Rank() - 1) { |
| // ditto |
| } else { |
| int rank{array->Rank()}; |
| int zbDim{static_cast<int>(*dim) - 1}; |
| bool ok{true}; |
| if (shift->Rank() > 0) { |
| int k{0}; |
| for (int j{0}; j < rank; ++j) { |
| if (j != zbDim) { |
| if (array->shape()[j] != shift->shape()[k]) { |
| context_.messages().Say( |
| "Invalid 'shift=' argument in EOSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US, |
| k + 1, static_cast<std::intmax_t>(shift->shape()[k]), |
| static_cast<std::intmax_t>(array->shape()[j])); |
| ok = false; |
| } |
| ++k; |
| } |
| } |
| } |
| if (boundary && boundary->Rank() > 0) { |
| int k{0}; |
| for (int j{0}; j < rank; ++j) { |
| if (j != zbDim) { |
| if (array->shape()[j] != boundary->shape()[k]) { |
| context_.messages().Say( |
| "Invalid 'boundary=' argument in EOSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US, |
| k + 1, static_cast<std::intmax_t>(boundary->shape()[k]), |
| static_cast<std::intmax_t>(array->shape()[j])); |
| ok = false; |
| } |
| ++k; |
| } |
| } |
| } |
| if (ok) { |
| std::vector<Scalar<T>> resultElements; |
| ConstantSubscripts arrayLB{array->lbounds()}; |
| ConstantSubscripts arrayAt{arrayLB}; |
| ConstantSubscript &dimIndex{arrayAt[zbDim]}; |
| ConstantSubscript dimLB{dimIndex}; // initial value |
| ConstantSubscript dimExtent{array->shape()[zbDim]}; |
| ConstantSubscripts shiftLB{shift->lbounds()}; |
| ConstantSubscripts boundaryLB; |
| if (boundary) { |
| boundaryLB = boundary->lbounds(); |
| } |
| for (auto n{GetSize(array->shape())}; n > 0; --n) { |
| ConstantSubscript origDimIndex{dimIndex}; |
| ConstantSubscripts shiftAt; |
| if (shift->Rank() > 0) { |
| int k{0}; |
| for (int j{0}; j < rank; ++j) { |
| if (j != zbDim) { |
| shiftAt.emplace_back(shiftLB[k++] + arrayAt[j] - arrayLB[j]); |
| } |
| } |
| } |
| ConstantSubscript shiftCount{shift->At(shiftAt).ToInt64()}; |
| dimIndex += shiftCount; |
| if (dimIndex >= dimLB && dimIndex < dimLB + dimExtent) { |
| resultElements.push_back(array->At(arrayAt)); |
| } else if (boundary) { |
| ConstantSubscripts boundaryAt; |
| if (boundary->Rank() > 0) { |
| for (int j{0}; j < rank; ++j) { |
| int k{0}; |
| if (j != zbDim) { |
| boundaryAt.emplace_back( |
| boundaryLB[k++] + arrayAt[j] - arrayLB[j]); |
| } |
| } |
| } |
| resultElements.push_back(boundary->At(boundaryAt)); |
| } else if constexpr (T::category == TypeCategory::Integer || |
| T::category == TypeCategory::Real || |
| T::category == TypeCategory::Complex || |
| T::category == TypeCategory::Logical) { |
| resultElements.emplace_back(); |
| } else if constexpr (T::category == TypeCategory::Character) { |
| auto len{static_cast<std::size_t>(array->LEN())}; |
| typename Scalar<T>::value_type space{' '}; |
| resultElements.emplace_back(len, space); |
| } else { |
| DIE("no derived type boundary"); |
| } |
| dimIndex = origDimIndex; |
| array->IncrementSubscripts(arrayAt); |
| } |
| return Expr<T>{PackageConstant<T>( |
| std::move(resultElements), *array, array->shape())}; |
| } |
| } |
| // Invalid, prevent re-folding |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| |
| template <typename T> Expr<T> Folder<T>::MERGE(FunctionRef<T> &&funcRef) { |
| return FoldElementalIntrinsic<T, T, T, LogicalResult>(context_, |
| std::move(funcRef), |
| ScalarFunc<T, T, T, LogicalResult>( |
| [](const Scalar<T> &ifTrue, const Scalar<T> &ifFalse, |
| const Scalar<LogicalResult> &predicate) -> Scalar<T> { |
| return predicate.IsTrue() ? ifTrue : ifFalse; |
| })); |
| } |
| |
| template <typename T> Expr<T> Folder<T>::PACK(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 3); |
| const auto *array{UnwrapConstantValue<T>(args[0])}; |
| const auto *vector{UnwrapConstantValue<T>(args[2])}; |
| auto convertedMask{Fold(context_, |
| ConvertToType<LogicalResult>( |
| Expr<SomeLogical>{DEREF(UnwrapExpr<Expr<SomeLogical>>(args[1]))}))}; |
| const auto *mask{UnwrapConstantValue<LogicalResult>(convertedMask)}; |
| if (!array || !mask || (args[2] && !vector)) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| // Arguments are constant. |
| ConstantSubscript arrayElements{GetSize(array->shape())}; |
| ConstantSubscript truths{0}; |
| ConstantSubscripts maskAt{mask->lbounds()}; |
| if (mask->Rank() == 0) { |
| if (mask->At(maskAt).IsTrue()) { |
| truths = arrayElements; |
| } |
| } else if (array->shape() != mask->shape()) { |
| // Error already emitted from intrinsic processing |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } else { |
| for (ConstantSubscript j{0}; j < arrayElements; |
| ++j, mask->IncrementSubscripts(maskAt)) { |
| if (mask->At(maskAt).IsTrue()) { |
| ++truths; |
| } |
| } |
| } |
| std::vector<Scalar<T>> resultElements; |
| ConstantSubscripts arrayAt{array->lbounds()}; |
| ConstantSubscript resultSize{truths}; |
| if (vector) { |
| resultSize = vector->shape().at(0); |
| if (resultSize < truths) { |
| context_.messages().Say( |
| "Invalid 'vector=' argument in PACK: the 'mask=' argument has %jd true elements, but the vector has only %jd elements"_err_en_US, |
| static_cast<std::intmax_t>(truths), |
| static_cast<std::intmax_t>(resultSize)); |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| } |
| for (ConstantSubscript j{0}; j < truths;) { |
| if (mask->At(maskAt).IsTrue()) { |
| resultElements.push_back(array->At(arrayAt)); |
| ++j; |
| } |
| array->IncrementSubscripts(arrayAt); |
| mask->IncrementSubscripts(maskAt); |
| } |
| if (vector) { |
| ConstantSubscripts vectorAt{vector->lbounds()}; |
| vectorAt.at(0) += truths; |
| for (ConstantSubscript j{truths}; j < resultSize; ++j) { |
| resultElements.push_back(vector->At(vectorAt)); |
| ++vectorAt[0]; |
| } |
| } |
| return Expr<T>{PackageConstant<T>(std::move(resultElements), *array, |
| ConstantSubscripts{static_cast<ConstantSubscript>(resultSize)})}; |
| } |
| |
| template <typename T> Expr<T> Folder<T>::RESHAPE(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 4); |
| const auto *source{UnwrapConstantValue<T>(args[0])}; |
| const auto *pad{UnwrapConstantValue<T>(args[2])}; |
| std::optional<std::vector<ConstantSubscript>> shape{ |
| GetIntegerVector<ConstantSubscript>(args[1])}; |
| std::optional<std::vector<int>> order{GetIntegerVector<int>(args[3])}; |
| if (!source || !shape || (args[2] && !pad) || (args[3] && !order)) { |
| return Expr<T>{std::move(funcRef)}; // Non-constant arguments |
| } else if (shape.value().size() > common::maxRank) { |
| context_.messages().Say( |
| "Size of 'shape=' argument must not be greater than %d"_err_en_US, |
| common::maxRank); |
| } else if (HasNegativeExtent(shape.value())) { |
| context_.messages().Say( |
| "'shape=' argument must not have a negative extent"_err_en_US); |
| } else { |
| std::optional<uint64_t> optResultElement{TotalElementCount(shape.value())}; |
| if (!optResultElement) { |
| context_.messages().Say( |
| "'shape=' argument has too many elements"_err_en_US); |
| } else { |
| int rank{GetRank(shape.value())}; |
| uint64_t resultElements{*optResultElement}; |
| std::optional<std::vector<int>> dimOrder; |
| if (order) { |
| dimOrder = ValidateDimensionOrder(rank, *order); |
| } |
| std::vector<int> *dimOrderPtr{dimOrder ? &dimOrder.value() : nullptr}; |
| if (order && !dimOrder) { |
| context_.messages().Say( |
| "Invalid 'order=' argument in RESHAPE"_err_en_US); |
| } else if (resultElements > source->size() && (!pad || pad->empty())) { |
| context_.messages().Say( |
| "Too few elements in 'source=' argument and 'pad=' " |
| "argument is not present or has null size"_err_en_US); |
| } else { |
| Constant<T> result{!source->empty() || !pad |
| ? source->Reshape(std::move(shape.value())) |
| : pad->Reshape(std::move(shape.value()))}; |
| ConstantSubscripts subscripts{result.lbounds()}; |
| auto copied{result.CopyFrom(*source, |
| std::min(static_cast<uint64_t>(source->size()), resultElements), |
| subscripts, dimOrderPtr)}; |
| if (copied < resultElements) { |
| CHECK(pad); |
| copied += result.CopyFrom( |
| *pad, resultElements - copied, subscripts, dimOrderPtr); |
| } |
| CHECK(copied == resultElements); |
| return Expr<T>{std::move(result)}; |
| } |
| } |
| } |
| // Invalid, prevent re-folding |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| |
| template <typename T> Expr<T> Folder<T>::SPREAD(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 3); |
| const Constant<T> *source{UnwrapConstantValue<T>(args[0])}; |
| auto dim{ToInt64(args[1])}; |
| auto ncopies{ToInt64(args[2])}; |
| if (!source || !dim) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| int sourceRank{source->Rank()}; |
| if (sourceRank >= common::maxRank) { |
| context_.messages().Say( |
| "SOURCE= argument to SPREAD has rank %d but must have rank less than %d"_err_en_US, |
| sourceRank, common::maxRank); |
| } else if (*dim < 1 || *dim > sourceRank + 1) { |
| context_.messages().Say( |
| "DIM=%d argument to SPREAD must be between 1 and %d"_err_en_US, *dim, |
| sourceRank + 1); |
| } else if (!ncopies) { |
| return Expr<T>{std::move(funcRef)}; |
| } else { |
| if (*ncopies < 0) { |
| ncopies = 0; |
| } |
| // TODO: Consider moving this implementation (after the user error |
| // checks), along with other transformational intrinsics, into |
| // constant.h (or a new header) so that the transformationals |
| // are available for all Constant<>s without needing to be packaged |
| // as references to intrinsic functions for folding. |
| ConstantSubscripts shape{source->shape()}; |
| shape.insert(shape.begin() + *dim - 1, *ncopies); |
| Constant<T> spread{source->Reshape(std::move(shape))}; |
| std::optional<uint64_t> n{TotalElementCount(spread.shape())}; |
| if (!n) { |
| context_.messages().Say("Too many elements in SPREAD result"_err_en_US); |
| } else { |
| std::vector<int> dimOrder; |
| for (int j{0}; j < sourceRank; ++j) { |
| dimOrder.push_back(j < *dim - 1 ? j : j + 1); |
| } |
| dimOrder.push_back(*dim - 1); |
| ConstantSubscripts at{spread.lbounds()}; // all 1 |
| spread.CopyFrom(*source, *n, at, &dimOrder); |
| return Expr<T>{std::move(spread)}; |
| } |
| } |
| // Invalid, prevent re-folding |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| |
| template <typename T> Expr<T> Folder<T>::TRANSPOSE(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 1); |
| const auto *matrix{UnwrapConstantValue<T>(args[0])}; |
| if (!matrix) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| // Argument is constant. Traverse its elements in transposed order. |
| std::vector<Scalar<T>> resultElements; |
| ConstantSubscripts at(2); |
| for (ConstantSubscript j{0}; j < matrix->shape()[0]; ++j) { |
| at[0] = matrix->lbounds()[0] + j; |
| for (ConstantSubscript k{0}; k < matrix->shape()[1]; ++k) { |
| at[1] = matrix->lbounds()[1] + k; |
| resultElements.push_back(matrix->At(at)); |
| } |
| } |
| at = matrix->shape(); |
| std::swap(at[0], at[1]); |
| return Expr<T>{PackageConstant<T>(std::move(resultElements), *matrix, at)}; |
| } |
| |
| template <typename T> Expr<T> Folder<T>::UNPACK(FunctionRef<T> &&funcRef) { |
| auto args{funcRef.arguments()}; |
| CHECK(args.size() == 3); |
| const auto *vector{UnwrapConstantValue<T>(args[0])}; |
| auto convertedMask{Fold(context_, |
| ConvertToType<LogicalResult>( |
| Expr<SomeLogical>{DEREF(UnwrapExpr<Expr<SomeLogical>>(args[1]))}))}; |
| const auto *mask{UnwrapConstantValue<LogicalResult>(convertedMask)}; |
| const auto *field{UnwrapConstantValue<T>(args[2])}; |
| if (!vector || !mask || !field) { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| // Arguments are constant. |
| if (field->Rank() > 0 && field->shape() != mask->shape()) { |
| // Error already emitted from intrinsic processing |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| ConstantSubscript maskElements{GetSize(mask->shape())}; |
| ConstantSubscript truths{0}; |
| ConstantSubscripts maskAt{mask->lbounds()}; |
| for (ConstantSubscript j{0}; j < maskElements; |
| ++j, mask->IncrementSubscripts(maskAt)) { |
| if (mask->At(maskAt).IsTrue()) { |
| ++truths; |
| } |
| } |
| if (truths > GetSize(vector->shape())) { |
| context_.messages().Say( |
| "Invalid 'vector=' argument in UNPACK: the 'mask=' argument has %jd true elements, but the vector has only %jd elements"_err_en_US, |
| static_cast<std::intmax_t>(truths), |
| static_cast<std::intmax_t>(GetSize(vector->shape()))); |
| return MakeInvalidIntrinsic(std::move(funcRef)); |
| } |
| std::vector<Scalar<T>> resultElements; |
| ConstantSubscripts vectorAt{vector->lbounds()}; |
| ConstantSubscripts fieldAt{field->lbounds()}; |
| for (ConstantSubscript j{0}; j < maskElements; ++j) { |
| if (mask->At(maskAt).IsTrue()) { |
| resultElements.push_back(vector->At(vectorAt)); |
| vector->IncrementSubscripts(vectorAt); |
| } else { |
| resultElements.push_back(field->At(fieldAt)); |
| } |
| mask->IncrementSubscripts(maskAt); |
| field->IncrementSubscripts(fieldAt); |
| } |
| return Expr<T>{ |
| PackageConstant<T>(std::move(resultElements), *vector, mask->shape())}; |
| } |
| |
| std::optional<Expr<SomeType>> FoldTransfer( |
| FoldingContext &, const ActualArguments &); |
| |
| template <typename T> Expr<T> Folder<T>::TRANSFER(FunctionRef<T> &&funcRef) { |
| if (auto folded{FoldTransfer(context_, funcRef.arguments())}) { |
| return DEREF(UnwrapExpr<Expr<T>>(*folded)); |
| } else { |
| return Expr<T>{std::move(funcRef)}; |
| } |
| } |
| |
| template <typename T> |
| Expr<T> FoldMINorMAX( |
| FoldingContext &context, FunctionRef<T> &&funcRef, Ordering order) { |
| static_assert(T::category == TypeCategory::Integer || |
| T::category == TypeCategory::Real || |
| T::category == TypeCategory::Character); |
| auto &args{funcRef.arguments()}; |
| bool ok{true}; |
| std::optional<Expr<T>> result; |
| Folder<T> folder{context}; |
| for (std::optional<ActualArgument> &arg : args) { |
| // Call Folding on all arguments to make operand promotion explicit. |
| if (!folder.Folding(arg)) { |
| // TODO: Lowering can't handle having every FunctionRef for max and min |
| // being converted into Extremum<T>. That needs fixing. Until that |
| // is corrected, however, it is important that max and min references |
| // in module files be converted into Extremum<T> even when not constant; |
| // the Extremum<SubscriptInteger> operations created to normalize the |
| // values of array bounds are formatted as max operations in the |
| // declarations in modules, and need to be read back in as such in |
| // order for expression comparison to not produce false inequalities |
| // when checking function results for procedure interface compatibility. |
| if (!context.moduleFileName()) { |
| ok = false; |
| } |
| } |
| Expr<SomeType> *argExpr{arg ? arg->UnwrapExpr() : nullptr}; |
| if (argExpr) { |
| *argExpr = Fold(context, std::move(*argExpr)); |
| } |
| if (Expr<T> * tExpr{UnwrapExpr<Expr<T>>(argExpr)}) { |
| if (result) { |
| result = FoldOperation( |
| context, Extremum<T>{order, std::move(*result), Expr<T>{*tExpr}}); |
| } else { |
| result = Expr<T>{*tExpr}; |
| } |
| } else { |
| ok = false; |
| } |
| } |
| return ok && result ? std::move(*result) : Expr<T>{std::move(funcRef)}; |
| } |
| |
| // For AMAX0, AMIN0, AMAX1, AMIN1, DMAX1, DMIN1, MAX0, MIN0, MAX1, and MIN1 |
| // a special care has to be taken to insert the conversion on the result |
| // of the MIN/MAX. This is made slightly more complex by the extension |
| // supported by f18 that arguments may have different kinds. This implies |
| // that the created MIN/MAX result type cannot be deduced from the standard but |
| // has to be deduced from the arguments. |
| // e.g. AMAX0(int8, int4) is rewritten to REAL(MAX(int8, INT(int4, 8)))). |
| template <typename T> |
| Expr<T> RewriteSpecificMINorMAX( |
| FoldingContext &context, FunctionRef<T> &&funcRef) { |
| ActualArguments &args{funcRef.arguments()}; |
| auto &intrinsic{DEREF(std::get_if<SpecificIntrinsic>(&funcRef.proc().u))}; |
| // Rewrite MAX1(args) to INT(MAX(args)) and fold. Same logic for MIN1. |
| // Find result type for max/min based on the arguments. |
| std::optional<DynamicType> resultType; |
| ActualArgument *resultTypeArg{nullptr}; |
| for (auto j{args.size()}; j-- > 0;) { |
| if (args[j]) { |
| DynamicType type{args[j]->GetType().value()}; |
| // Handle mixed real/integer arguments: all the previous arguments were |
| // integers and this one is real. The type of the MAX/MIN result will |
| // be the one of the real argument. |
| if (!resultType || |
| (type.category() == resultType->category() && |
| type.kind() > resultType->kind()) || |
| resultType->category() == TypeCategory::Integer) { |
| resultType = type; |
| resultTypeArg = &*args[j]; |
| } |
| } |
| } |
| if (!resultType) { // error recovery |
| return Expr<T>{std::move(funcRef)}; |
| } |
| intrinsic.name = |
| intrinsic.name.find("max") != std::string::npos ? "max"s : "min"s; |
| intrinsic.characteristics.value().functionResult.value().SetType(*resultType); |
| auto insertConversion{[&](const auto &x) -> Expr<T> { |
| using TR = ResultType<decltype(x)>; |
| FunctionRef<TR> maxRef{ |
| ProcedureDesignator{funcRef.proc()}, ActualArguments{args}}; |
| return Fold(context, ConvertToType<T>(AsCategoryExpr(std::move(maxRef)))); |
| }}; |
| if (auto *sx{UnwrapExpr<Expr<SomeReal>>(*resultTypeArg)}) { |
| return common::visit(insertConversion, sx->u); |
| } else if (auto *sx{UnwrapExpr<Expr<SomeInteger>>(*resultTypeArg)}) { |
| return common::visit(insertConversion, sx->u); |
| } else { |
| return Expr<T>{std::move(funcRef)}; // error recovery |
| } |
| } |
| |
| // FoldIntrinsicFunction() |
| template <int KIND> |
| Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction( |
| FoldingContext &context, FunctionRef<Type<TypeCategory::Integer, KIND>> &&); |
| template <int KIND> |
| Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction( |
| FoldingContext &context, FunctionRef<Type<TypeCategory::Real, KIND>> &&); |
| template <int KIND> |
| Expr<Type<TypeCategory::Complex, KIND>> FoldIntrinsicFunction( |
| FoldingContext &context, FunctionRef<Type<TypeCategory::Complex, KIND>> &&); |
| template <int KIND> |
| Expr<Type<TypeCategory::Logical, KIND>> FoldIntrinsicFunction( |
| FoldingContext &context, FunctionRef<Type<TypeCategory::Logical, KIND>> &&); |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) { |
| ActualArguments &args{funcRef.arguments()}; |
| const auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)}; |
| if (!intrinsic || intrinsic->name != "kind") { |
| // Don't fold the argument to KIND(); it might be a TypeParamInquiry |
| // with a forced result type that doesn't match the parameter. |
| for (std::optional<ActualArgument> &arg : args) { |
| if (auto *expr{UnwrapExpr<Expr<SomeType>>(arg)}) { |
| *expr = Fold(context, std::move(*expr)); |
| } |
| } |
| } |
| if (intrinsic) { |
| const std::string name{intrinsic->name}; |
| if (name == "cshift") { |
| return Folder<T>{context}.CSHIFT(std::move(funcRef)); |
| } else if (name == "eoshift") { |
| return Folder<T>{context}.EOSHIFT(std::move(funcRef)); |
| } else if (name == "merge") { |
| return Folder<T>{context}.MERGE(std::move(funcRef)); |
| } else if (name == "pack") { |
| return Folder<T>{context}.PACK(std::move(funcRef)); |
| } else if (name == "reshape") { |
| return Folder<T>{context}.RESHAPE(std::move(funcRef)); |
| } else if (name == "spread") { |
| return Folder<T>{context}.SPREAD(std::move(funcRef)); |
| } else if (name == "transfer") { |
| return Folder<T>{context}.TRANSFER(std::move(funcRef)); |
| } else if (name == "transpose") { |
| return Folder<T>{context}.TRANSPOSE(std::move(funcRef)); |
| } else if (name == "unpack") { |
| return Folder<T>{context}.UNPACK(std::move(funcRef)); |
| } |
| // TODO: extends_type_of, same_type_as |
| if constexpr (!std::is_same_v<T, SomeDerived>) { |
| return FoldIntrinsicFunction(context, std::move(funcRef)); |
| } |
| } |
| return Expr<T>{std::move(funcRef)}; |
| } |
| |
| Expr<ImpliedDoIndex::Result> FoldOperation(FoldingContext &, ImpliedDoIndex &&); |
| |
| // Array constructor folding |
| template <typename T> class ArrayConstructorFolder { |
| public: |
| explicit ArrayConstructorFolder(FoldingContext &c) : context_{c} {} |
| |
| Expr<T> FoldArray(ArrayConstructor<T> &&array) { |
| // Calls FoldArray(const ArrayConstructorValues<T> &) below |
| if (FoldArray(array)) { |
| auto n{static_cast<ConstantSubscript>(elements_.size())}; |
| if constexpr (std::is_same_v<T, SomeDerived>) { |
| return Expr<T>{Constant<T>{array.GetType().GetDerivedTypeSpec(), |
| std::move(elements_), ConstantSubscripts{n}}}; |
| } else if constexpr (T::category == TypeCategory::Character) { |
| if (const auto *len{array.LEN()}) { |
| auto length{Fold(context_, common::Clone(*len))}; |
| if (std::optional<ConstantSubscript> lengthValue{ToInt64(length)}) { |
| return Expr<T>{Constant<T>{ |
| *lengthValue, std::move(elements_), ConstantSubscripts{n}}}; |
| } |
| } |
| } else { |
| return Expr<T>{ |
| Constant<T>{std::move(elements_), ConstantSubscripts{n}}}; |
| } |
| } |
| return Expr<T>{std::move(array)}; |
| } |
| |
| private: |
| bool FoldArray(const Expr<T> &expr) { |
| Expr<T> folded{Fold(context_, common::Clone(expr))}; |
| if (const auto *c{UnwrapConstantValue<T>(folded)}) { |
| // Copy elements in Fortran array element order |
| if (!c->empty()) { |
| ConstantSubscripts index{c->lbounds()}; |
| do { |
| elements_.emplace_back(c->At(index)); |
| } while (c->IncrementSubscripts(index)); |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| bool FoldArray(const common::CopyableIndirection<Expr<T>> &expr) { |
| return FoldArray(expr.value()); |
| } |
| bool FoldArray(const ImpliedDo<T> &iDo) { |
| Expr<SubscriptInteger> lower{ |
| Fold(context_, Expr<SubscriptInteger>{iDo.lower()})}; |
| Expr<SubscriptInteger> upper{ |
| Fold(context_, Expr<SubscriptInteger>{iDo.upper()})}; |
| Expr<SubscriptInteger> stride{ |
| Fold(context_, Expr<SubscriptInteger>{iDo.stride()})}; |
| std::optional<ConstantSubscript> start{ToInt64(lower)}, end{ToInt64(upper)}, |
| step{ToInt64(stride)}; |
| if (start && end && step && *step != 0) { |
| bool result{true}; |
| ConstantSubscript &j{context_.StartImpliedDo(iDo.name(), *start)}; |
| if (*step > 0) { |
| for (; j <= *end; j += *step) { |
| result &= FoldArray(iDo.values()); |
| } |
| } else { |
| for (; j >= *end; j += *step) { |
| result &= FoldArray(iDo.values()); |
| } |
| } |
| context_.EndImpliedDo(iDo.name()); |
| return result; |
| } else { |
| return false; |
| } |
| } |
| bool FoldArray(const ArrayConstructorValue<T> &x) { |
| return common::visit([&](const auto &y) { return FoldArray(y); }, x.u); |
| } |
| bool FoldArray(const ArrayConstructorValues<T> &xs) { |
| for (const auto &x : xs) { |
| if (!FoldArray(x)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| FoldingContext &context_; |
| std::vector<Scalar<T>> elements_; |
| }; |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, ArrayConstructor<T> &&array) { |
| return ArrayConstructorFolder<T>{context}.FoldArray(std::move(array)); |
| } |
| |
| // Array operation elemental application: When all operands to an operation |
| // are constant arrays, array constructors without any implied DO loops, |
| // &/or expanded scalars, pull the operation "into" the array result by |
| // applying it in an elementwise fashion. For example, [A,1]+[B,2] |
| // is rewritten into [A+B,1+2] and then partially folded to [A+B,3]. |
| |
| // If possible, restructures an array expression into an array constructor |
| // that comprises a "flat" ArrayConstructorValues with no implied DO loops. |
| template <typename T> |
| bool ArrayConstructorIsFlat(const ArrayConstructorValues<T> &values) { |
| for (const ArrayConstructorValue<T> &x : values) { |
| if (!std::holds_alternative<Expr<T>>(x.u)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <typename T> |
| std::optional<Expr<T>> AsFlatArrayConstructor(const Expr<T> &expr) { |
| if (const auto *c{UnwrapConstantValue<T>(expr)}) { |
| ArrayConstructor<T> result{expr}; |
| if (!c->empty()) { |
| ConstantSubscripts at{c->lbounds()}; |
| do { |
| result.Push(Expr<T>{Constant<T>{c->At(at)}}); |
| } while (c->IncrementSubscripts(at)); |
| } |
| return std::make_optional<Expr<T>>(std::move(result)); |
| } else if (const auto *a{UnwrapExpr<ArrayConstructor<T>>(expr)}) { |
| if (ArrayConstructorIsFlat(*a)) { |
| return std::make_optional<Expr<T>>(expr); |
| } |
| } else if (const auto *p{UnwrapExpr<Parentheses<T>>(expr)}) { |
| return AsFlatArrayConstructor(Expr<T>{p->left()}); |
| } |
| return std::nullopt; |
| } |
| |
| template <TypeCategory CAT> |
| std::enable_if_t<CAT != TypeCategory::Derived, |
| std::optional<Expr<SomeKind<CAT>>>> |
| AsFlatArrayConstructor(const Expr<SomeKind<CAT>> &expr) { |
| return common::visit( |
| [&](const auto &kindExpr) -> std::optional<Expr<SomeKind<CAT>>> { |
| if (auto flattened{AsFlatArrayConstructor(kindExpr)}) { |
| return Expr<SomeKind<CAT>>{std::move(*flattened)}; |
| } else { |
| return std::nullopt; |
| } |
| }, |
| expr.u); |
| } |
| |
| // FromArrayConstructor is a subroutine for MapOperation() below. |
| // Given a flat ArrayConstructor<T> and a shape, it wraps the array |
| // into an Expr<T>, folds it, and returns the resulting wrapped |
| // array constructor or constant array value. |
| template <typename T> |
| std::optional<Expr<T>> FromArrayConstructor( |
| FoldingContext &context, ArrayConstructor<T> &&values, const Shape &shape) { |
| if (auto constShape{AsConstantExtents(context, shape)}) { |
| Expr<T> result{Fold(context, Expr<T>{std::move(values)})}; |
| if (auto *constant{UnwrapConstantValue<T>(result)}) { |
| // Elements and shape are both constant. |
| return Expr<T>{constant->Reshape(std::move(*constShape))}; |
| } |
| if (constShape->size() == 1) { |
| if (auto elements{GetShape(context, result)}) { |
| if (auto constElements{AsConstantExtents(context, *elements)}) { |
| if (constElements->size() == 1 && |
| constElements->at(0) == constShape->at(0)) { |
| // Elements are not constant, but array constructor has |
| // the right known shape and can be simply returned as is. |
| return std::move(result); |
| } |
| } |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| // MapOperation is a utility for various specializations of ApplyElementwise() |
| // that follow. Given one or two flat ArrayConstructor<OPERAND> (wrapped in an |
| // Expr<OPERAND>) for some specific operand type(s), apply a given function f |
| // to each of their corresponding elements to produce a flat |
| // ArrayConstructor<RESULT> (wrapped in an Expr<RESULT>). |
| // Preserves shape. |
| |
| // Unary case |
| template <typename RESULT, typename OPERAND> |
| std::optional<Expr<RESULT>> MapOperation(FoldingContext &context, |
| std::function<Expr<RESULT>(Expr<OPERAND> &&)> &&f, const Shape &shape, |
| [[maybe_unused]] std::optional<Expr<SubscriptInteger>> &&length, |
| Expr<OPERAND> &&values) { |
| ArrayConstructor<RESULT> result{values}; |
| if constexpr (common::HasMember<OPERAND, AllIntrinsicCategoryTypes>) { |
| common::visit( |
| [&](auto &&kindExpr) { |
| using kindType = ResultType<decltype(kindExpr)>; |
| auto &aConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)}; |
| for (auto &acValue : aConst) { |
| auto &scalar{std::get<Expr<kindType>>(acValue.u)}; |
| result.Push(Fold(context, f(Expr<OPERAND>{std::move(scalar)}))); |
| } |
| }, |
| std::move(values.u)); |
| } else { |
| auto &aConst{std::get<ArrayConstructor<OPERAND>>(values.u)}; |
| for (auto &acValue : aConst) { |
| auto &scalar{std::get<Expr<OPERAND>>(acValue.u)}; |
| result.Push(Fold(context, f(std::move(scalar)))); |
| } |
| } |
| if constexpr (RESULT::category == TypeCategory::Character) { |
| if (length) { |
| result.set_LEN(std::move(*length)); |
| } |
| } |
| return FromArrayConstructor(context, std::move(result), shape); |
| } |
| |
| template <typename RESULT, typename A> |
| ArrayConstructor<RESULT> ArrayConstructorFromMold( |
| const A &prototype, std::optional<Expr<SubscriptInteger>> &&length) { |
| ArrayConstructor<RESULT> result{prototype}; |
| if constexpr (RESULT::category == TypeCategory::Character) { |
| if (length) { |
| result.set_LEN(std::move(*length)); |
| } |
| } |
| return result; |
| } |
| |
| template <typename LEFT, typename RIGHT> |
| bool ShapesMatch(FoldingContext &context, |
| const ArrayConstructor<LEFT> &leftArrConst, |
| const ArrayConstructor<RIGHT> &rightArrConst) { |
| auto rightIter{rightArrConst.begin()}; |
| for (auto &leftValue : leftArrConst) { |
| CHECK(rightIter != rightArrConst.end()); |
| auto &leftExpr{std::get<Expr<LEFT>>(leftValue.u)}; |
| auto &rightExpr{std::get<Expr<RIGHT>>(rightIter->u)}; |
| if (leftExpr.Rank() != rightExpr.Rank()) { |
| return false; |
| } |
| std::optional<Shape> leftShape{GetShape(context, leftExpr)}; |
| std::optional<Shape> rightShape{GetShape(context, rightExpr)}; |
| if (!leftShape || !rightShape || *leftShape != *rightShape) { |
| return false; |
| } |
| ++rightIter; |
| } |
| return true; |
| } |
| |
| // array * array case |
| template <typename RESULT, typename LEFT, typename RIGHT> |
| auto MapOperation(FoldingContext &context, |
| std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f, |
| const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length, |
| Expr<LEFT> &&leftValues, Expr<RIGHT> &&rightValues) |
| -> std::optional<Expr<RESULT>> { |
| auto result{ArrayConstructorFromMold<RESULT>(leftValues, std::move(length))}; |
| auto &leftArrConst{std::get<ArrayConstructor<LEFT>>(leftValues.u)}; |
| if constexpr (common::HasMember<RIGHT, AllIntrinsicCategoryTypes>) { |
| bool mapped{common::visit( |
| [&](auto &&kindExpr) -> bool { |
| using kindType = ResultType<decltype(kindExpr)>; |
| |
| auto &rightArrConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)}; |
| if (!ShapesMatch(context, leftArrConst, rightArrConst)) { |
| return false; |
| } |
| auto rightIter{rightArrConst.begin()}; |
| for (auto &leftValue : leftArrConst) { |
| CHECK(rightIter != rightArrConst.end()); |
| auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)}; |
| auto &rightScalar{std::get<Expr<kindType>>(rightIter->u)}; |
| result.Push(Fold(context, |
| f(std::move(leftScalar), Expr<RIGHT>{std::move(rightScalar)}))); |
| ++rightIter; |
| } |
| return true; |
| }, |
| std::move(rightValues.u))}; |
| if (!mapped) { |
| return std::nullopt; |
| } |
| } else { |
| auto &rightArrConst{std::get<ArrayConstructor<RIGHT>>(rightValues.u)}; |
| if (!ShapesMatch(context, leftArrConst, rightArrConst)) { |
| return std::nullopt; |
| } |
| auto rightIter{rightArrConst.begin()}; |
| for (auto &leftValue : leftArrConst) { |
| CHECK(rightIter != rightArrConst.end()); |
| auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)}; |
| auto &rightScalar{std::get<Expr<RIGHT>>(rightIter->u)}; |
| result.Push( |
| Fold(context, f(std::move(leftScalar), std::move(rightScalar)))); |
| ++rightIter; |
| } |
| } |
| return FromArrayConstructor(context, std::move(result), shape); |
| } |
| |
| // array * scalar case |
| template <typename RESULT, typename LEFT, typename RIGHT> |
| auto MapOperation(FoldingContext &context, |
| std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f, |
| const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length, |
| Expr<LEFT> &&leftValues, const Expr<RIGHT> &rightScalar) |
| -> std::optional<Expr<RESULT>> { |
| auto result{ArrayConstructorFromMold<RESULT>(leftValues, std::move(length))}; |
| auto &leftArrConst{std::get<ArrayConstructor<LEFT>>(leftValues.u)}; |
| for (auto &leftValue : leftArrConst) { |
| auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)}; |
| result.Push( |
| Fold(context, f(std::move(leftScalar), Expr<RIGHT>{rightScalar}))); |
| } |
| return FromArrayConstructor(context, std::move(result), shape); |
| } |
| |
| // scalar * array case |
| template <typename RESULT, typename LEFT, typename RIGHT> |
| auto MapOperation(FoldingContext &context, |
| std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f, |
| const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length, |
| const Expr<LEFT> &leftScalar, Expr<RIGHT> &&rightValues) |
| -> std::optional<Expr<RESULT>> { |
| auto result{ArrayConstructorFromMold<RESULT>(leftScalar, std::move(length))}; |
| if constexpr (common::HasMember<RIGHT, AllIntrinsicCategoryTypes>) { |
| common::visit( |
| [&](auto &&kindExpr) { |
| using kindType = ResultType<decltype(kindExpr)>; |
| auto &rightArrConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)}; |
| for (auto &rightValue : rightArrConst) { |
| auto &rightScalar{std::get<Expr<kindType>>(rightValue.u)}; |
| result.Push(Fold(context, |
| f(Expr<LEFT>{leftScalar}, |
| Expr<RIGHT>{std::move(rightScalar)}))); |
| } |
| }, |
| std::move(rightValues.u)); |
| } else { |
| auto &rightArrConst{std::get<ArrayConstructor<RIGHT>>(rightValues.u)}; |
| for (auto &rightValue : rightArrConst) { |
| auto &rightScalar{std::get<Expr<RIGHT>>(rightValue.u)}; |
| result.Push( |
| Fold(context, f(Expr<LEFT>{leftScalar}, std::move(rightScalar)))); |
| } |
| } |
| return FromArrayConstructor(context, std::move(result), shape); |
| } |
| |
| template <typename DERIVED, typename RESULT, typename... OPD> |
| std::optional<Expr<SubscriptInteger>> ComputeResultLength( |
| Operation<DERIVED, RESULT, OPD...> &operation) { |
| if constexpr (RESULT::category == TypeCategory::Character) { |
| return Expr<RESULT>{operation.derived()}.LEN(); |
| } |
| return std::nullopt; |
| } |
| |
| // ApplyElementwise() recursively folds the operand expression(s) of an |
| // operation, then attempts to apply the operation to the (corresponding) |
| // scalar element(s) of those operands. Returns std::nullopt for scalars |
| // or unlinearizable operands. |
| template <typename DERIVED, typename RESULT, typename OPERAND> |
| auto ApplyElementwise(FoldingContext &context, |
| Operation<DERIVED, RESULT, OPERAND> &operation, |
| std::function<Expr<RESULT>(Expr<OPERAND> &&)> &&f) |
| -> std::optional<Expr<RESULT>> { |
| auto &expr{operation.left()}; |
| expr = Fold(context, std::move(expr)); |
| if (expr.Rank() > 0) { |
| if (std::optional<Shape> shape{GetShape(context, expr)}) { |
| if (auto values{AsFlatArrayConstructor(expr)}) { |
| return MapOperation(context, std::move(f), *shape, |
| ComputeResultLength(operation), std::move(*values)); |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename DERIVED, typename RESULT, typename OPERAND> |
| auto ApplyElementwise( |
| FoldingContext &context, Operation<DERIVED, RESULT, OPERAND> &operation) |
| -> std::optional<Expr<RESULT>> { |
| return ApplyElementwise(context, operation, |
| std::function<Expr<RESULT>(Expr<OPERAND> &&)>{ |
| [](Expr<OPERAND> &&operand) { |
| return Expr<RESULT>{DERIVED{std::move(operand)}}; |
| }}); |
| } |
| |
| template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT> |
| auto ApplyElementwise(FoldingContext &context, |
| Operation<DERIVED, RESULT, LEFT, RIGHT> &operation, |
| std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f) |
| -> std::optional<Expr<RESULT>> { |
| auto resultLength{ComputeResultLength(operation)}; |
| auto &leftExpr{operation.left()}; |
| leftExpr = Fold(context, std::move(leftExpr)); |
| auto &rightExpr{operation.right()}; |
| rightExpr = Fold(context, std::move(rightExpr)); |
| if (leftExpr.Rank() > 0) { |
| if (std::optional<Shape> leftShape{GetShape(context, leftExpr)}) { |
| if (auto left{AsFlatArrayConstructor(leftExpr)}) { |
| if (rightExpr.Rank() > 0) { |
| if (std::optional<Shape> rightShape{GetShape(context, rightExpr)}) { |
| if (auto right{AsFlatArrayConstructor(rightExpr)}) { |
| if (CheckConformance(context.messages(), *leftShape, *rightShape, |
| CheckConformanceFlags::EitherScalarExpandable) |
| .value_or(false /*fail if not known now to conform*/)) { |
| return MapOperation(context, std::move(f), *leftShape, |
| std::move(resultLength), std::move(*left), |
| std::move(*right)); |
| } else { |
| return std::nullopt; |
| } |
| return MapOperation(context, std::move(f), *leftShape, |
| std::move(resultLength), std::move(*left), std::move(*right)); |
| } |
| } |
| } else if (IsExpandableScalar(rightExpr, context, *leftShape)) { |
| return MapOperation(context, std::move(f), *leftShape, |
| std::move(resultLength), std::move(*left), rightExpr); |
| } |
| } |
| } |
| } else if (rightExpr.Rank() > 0) { |
| if (std::optional<Shape> rightShape{GetShape(context, rightExpr)}) { |
| if (IsExpandableScalar(leftExpr, context, *rightShape)) { |
| if (auto right{AsFlatArrayConstructor(rightExpr)}) { |
| return MapOperation(context, std::move(f), *rightShape, |
| std::move(resultLength), leftExpr, std::move(*right)); |
| } |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT> |
| auto ApplyElementwise( |
| FoldingContext &context, Operation<DERIVED, RESULT, LEFT, RIGHT> &operation) |
| -> std::optional<Expr<RESULT>> { |
| return ApplyElementwise(context, operation, |
| std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)>{ |
| [](Expr<LEFT> &&left, Expr<RIGHT> &&right) { |
| return Expr<RESULT>{DERIVED{std::move(left), std::move(right)}}; |
| }}); |
| } |
| |
| // Unary operations |
| |
| template <typename TO, typename FROM> |
| common::IfNoLvalue<std::optional<TO>, FROM> ConvertString(FROM &&s) { |
| if constexpr (std::is_same_v<TO, FROM>) { |
| return std::make_optional<TO>(std::move(s)); |
| } else { |
| // Fortran character conversion is well defined between distinct kinds |
| // only when the actual characters are valid 7-bit ASCII. |
| TO str; |
| for (auto iter{s.cbegin()}; iter != s.cend(); ++iter) { |
| if (static_cast<std::uint64_t>(*iter) > 127) { |
| return std::nullopt; |
| } |
| str.push_back(*iter); |
| } |
| return std::make_optional<TO>(std::move(str)); |
| } |
| } |
| |
| template <typename TO, TypeCategory FROMCAT> |
| Expr<TO> FoldOperation( |
| FoldingContext &context, Convert<TO, FROMCAT> &&convert) { |
| if (auto array{ApplyElementwise(context, convert)}) { |
| return *array; |
| } |
| struct { |
| FoldingContext &context; |
| Convert<TO, FROMCAT> &convert; |
| } msvcWorkaround{context, convert}; |
| return common::visit( |
| [&msvcWorkaround](auto &kindExpr) -> Expr<TO> { |
| using Operand = ResultType<decltype(kindExpr)>; |
| // This variable is a workaround for msvc which emits an error when |
| // using the FROMCAT template parameter below. |
| TypeCategory constexpr FromCat{FROMCAT}; |
| static_assert(FromCat == Operand::category); |
| auto &convert{msvcWorkaround.convert}; |
| if (auto value{GetScalarConstantValue<Operand>(kindExpr)}) { |
| FoldingContext &ctx{msvcWorkaround.context}; |
| if constexpr (TO::category == TypeCategory::Integer) { |
| if constexpr (FromCat == TypeCategory::Integer) { |
| auto converted{Scalar<TO>::ConvertSigned(*value)}; |
| if (converted.overflow && |
| msvcWorkaround.context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| ctx.messages().Say(common::UsageWarning::FoldingException, |
| "conversion of %s_%d to INTEGER(%d) overflowed; result is %s"_warn_en_US, |
| value->SignedDecimal(), Operand::kind, TO::kind, |
| converted.value.SignedDecimal()); |
| } |
| return ScalarConstantToExpr(std::move(converted.value)); |
| } else if constexpr (FromCat == TypeCategory::Real) { |
| auto converted{value->template ToInteger<Scalar<TO>>()}; |
| if (msvcWorkaround.context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| if (converted.flags.test(RealFlag::InvalidArgument)) { |
| ctx.messages().Say(common::UsageWarning::FoldingException, |
| "REAL(%d) to INTEGER(%d) conversion: invalid argument"_warn_en_US, |
| Operand::kind, TO::kind); |
| } else if (converted.flags.test(RealFlag::Overflow)) { |
| ctx.messages().Say( |
| "REAL(%d) to INTEGER(%d) conversion overflowed"_warn_en_US, |
| Operand::kind, TO::kind); |
| } |
| } |
| return ScalarConstantToExpr(std::move(converted.value)); |
| } |
| } else if constexpr (TO::category == TypeCategory::Real) { |
| if constexpr (FromCat == TypeCategory::Integer) { |
| auto converted{Scalar<TO>::FromInteger(*value)}; |
| if (!converted.flags.empty()) { |
| char buffer[64]; |
| std::snprintf(buffer, sizeof buffer, |
| "INTEGER(%d) to REAL(%d) conversion", Operand::kind, |
| TO::kind); |
| RealFlagWarnings(ctx, converted.flags, buffer); |
| } |
| return ScalarConstantToExpr(std::move(converted.value)); |
| } else if constexpr (FromCat == TypeCategory::Real) { |
| auto converted{Scalar<TO>::Convert(*value)}; |
| char buffer[64]; |
| if (!converted.flags.empty()) { |
| std::snprintf(buffer, sizeof buffer, |
| "REAL(%d) to REAL(%d) conversion", Operand::kind, TO::kind); |
| RealFlagWarnings(ctx, converted.flags, buffer); |
| } |
| if (ctx.targetCharacteristics().areSubnormalsFlushedToZero()) { |
| converted.value = converted.value.FlushSubnormalToZero(); |
| } |
| return ScalarConstantToExpr(std::move(converted.value)); |
| } |
| } else if constexpr (TO::category == TypeCategory::Complex) { |
| if constexpr (FromCat == TypeCategory::Complex) { |
| return FoldOperation(ctx, |
| ComplexConstructor<TO::kind>{ |
| AsExpr(Convert<typename TO::Part>{AsCategoryExpr( |
| Constant<typename Operand::Part>{value->REAL()})}), |
| AsExpr(Convert<typename TO::Part>{AsCategoryExpr( |
| Constant<typename Operand::Part>{value->AIMAG()})})}); |
| } |
| } else if constexpr (TO::category == TypeCategory::Character && |
| FromCat == TypeCategory::Character) { |
| if (auto converted{ConvertString<Scalar<TO>>(std::move(*value))}) { |
| return ScalarConstantToExpr(std::move(*converted)); |
| } |
| } else if constexpr (TO::category == TypeCategory::Logical && |
| FromCat == TypeCategory::Logical) { |
| return Expr<TO>{value->IsTrue()}; |
| } |
| } else if constexpr (TO::category == FromCat && |
| FromCat != TypeCategory::Character) { |
| // Conversion of non-constant in same type category |
| if constexpr (std::is_same_v<Operand, TO>) { |
| return std::move(kindExpr); // remove needless conversion |
| } else if constexpr (TO::category == TypeCategory::Logical || |
| TO::category == TypeCategory::Integer) { |
| if (auto *innerConv{ |
| std::get_if<Convert<Operand, TO::category>>(&kindExpr.u)}) { |
| // Conversion of conversion of same category & kind |
| if (auto *x{std::get_if<Expr<TO>>(&innerConv->left().u)}) { |
| if constexpr (TO::category == TypeCategory::Logical || |
| TO::kind <= Operand::kind) { |
| return std::move(*x); // no-op Logical or Integer |
| // widening/narrowing conversion pair |
| } else if constexpr (std::is_same_v<TO, |
| DescriptorInquiry::Result>) { |
| if (std::holds_alternative<DescriptorInquiry>(x->u) || |
| std::holds_alternative<TypeParamInquiry>(x->u)) { |
| // int(int(size(...),kind=k),kind=8) -> size(...) |
| return std::move(*x); |
| } |
| } |
| } |
| } |
| } |
| } |
| return Expr<TO>{std::move(convert)}; |
| }, |
| convert.left().u); |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Parentheses<T> &&x) { |
| auto &operand{x.left()}; |
| operand = Fold(context, std::move(operand)); |
| if (auto value{GetScalarConstantValue<T>(operand)}) { |
| // Preserve parentheses, even around constants. |
| return Expr<T>{Parentheses<T>{Expr<T>{Constant<T>{*value}}}}; |
| } else if (std::holds_alternative<Parentheses<T>>(operand.u)) { |
| // ((x)) -> (x) |
| return std::move(operand); |
| } else { |
| return Expr<T>{Parentheses<T>{std::move(operand)}}; |
| } |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Negate<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| auto &operand{x.left()}; |
| if (auto *nn{std::get_if<Negate<T>>(&x.left().u)}) { |
| // -(-x) -> (x) |
| if (IsVariable(nn->left())) { |
| return FoldOperation(context, Parentheses<T>{std::move(nn->left())}); |
| } else { |
| return std::move(nn->left()); |
| } |
| } else if (auto value{GetScalarConstantValue<T>(operand)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| auto negated{value->Negate()}; |
| if (negated.overflow && |
| context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) negation overflowed"_warn_en_US, T::kind); |
| } |
| return Expr<T>{Constant<T>{std::move(negated.value)}}; |
| } else { |
| // REAL & COMPLEX negation: no exceptions possible |
| return Expr<T>{Constant<T>{value->Negate()}}; |
| } |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| // Binary (dyadic) operations |
| |
| template <typename LEFT, typename RIGHT> |
| std::optional<std::pair<Scalar<LEFT>, Scalar<RIGHT>>> OperandsAreConstants( |
| const Expr<LEFT> &x, const Expr<RIGHT> &y) { |
| if (auto xvalue{GetScalarConstantValue<LEFT>(x)}) { |
| if (auto yvalue{GetScalarConstantValue<RIGHT>(y)}) { |
| return {std::make_pair(*xvalue, *yvalue)}; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT> |
| std::optional<std::pair<Scalar<LEFT>, Scalar<RIGHT>>> OperandsAreConstants( |
| const Operation<DERIVED, RESULT, LEFT, RIGHT> &operation) { |
| return OperandsAreConstants(operation.left(), operation.right()); |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Add<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| if (auto folded{OperandsAreConstants(x)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| auto sum{folded->first.AddSigned(folded->second)}; |
| if (sum.overflow && |
| context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) addition overflowed"_warn_en_US, T::kind); |
| } |
| return Expr<T>{Constant<T>{sum.value}}; |
| } else { |
| auto sum{folded->first.Add( |
| folded->second, context.targetCharacteristics().roundingMode())}; |
| RealFlagWarnings(context, sum.flags, "addition"); |
| if (context.targetCharacteristics().areSubnormalsFlushedToZero()) { |
| sum.value = sum.value.FlushSubnormalToZero(); |
| } |
| return Expr<T>{Constant<T>{sum.value}}; |
| } |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Subtract<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| if (auto folded{OperandsAreConstants(x)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| auto difference{folded->first.SubtractSigned(folded->second)}; |
| if (difference.overflow && |
| context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) subtraction overflowed"_warn_en_US, T::kind); |
| } |
| return Expr<T>{Constant<T>{difference.value}}; |
| } else { |
| auto difference{folded->first.Subtract( |
| folded->second, context.targetCharacteristics().roundingMode())}; |
| RealFlagWarnings(context, difference.flags, "subtraction"); |
| if (context.targetCharacteristics().areSubnormalsFlushedToZero()) { |
| difference.value = difference.value.FlushSubnormalToZero(); |
| } |
| return Expr<T>{Constant<T>{difference.value}}; |
| } |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Multiply<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| if (auto folded{OperandsAreConstants(x)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| auto product{folded->first.MultiplySigned(folded->second)}; |
| if (product.SignedMultiplicationOverflowed() && |
| context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) multiplication overflowed"_warn_en_US, T::kind); |
| } |
| return Expr<T>{Constant<T>{product.lower}}; |
| } else { |
| auto product{folded->first.Multiply( |
| folded->second, context.targetCharacteristics().roundingMode())}; |
| RealFlagWarnings(context, product.flags, "multiplication"); |
| if (context.targetCharacteristics().areSubnormalsFlushedToZero()) { |
| product.value = product.value.FlushSubnormalToZero(); |
| } |
| return Expr<T>{Constant<T>{product.value}}; |
| } |
| } else if constexpr (T::category == TypeCategory::Integer) { |
| if (auto c{GetScalarConstantValue<T>(x.right())}) { |
| x.right() = std::move(x.left()); |
| x.left() = Expr<T>{std::move(*c)}; |
| } |
| if (auto c{GetScalarConstantValue<T>(x.left())}) { |
| if (c->IsZero() && x.right().Rank() == 0) { |
| return std::move(x.left()); |
| } else if (c->CompareSigned(Scalar<T>{1}) == Ordering::Equal) { |
| if (IsVariable(x.right())) { |
| return FoldOperation(context, Parentheses<T>{std::move(x.right())}); |
| } else { |
| return std::move(x.right()); |
| } |
| } else if (c->CompareSigned(Scalar<T>{-1}) == Ordering::Equal) { |
| return FoldOperation(context, Negate<T>{std::move(x.right())}); |
| } |
| } |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Divide<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| if (auto folded{OperandsAreConstants(x)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| auto quotAndRem{folded->first.DivideSigned(folded->second)}; |
| if (quotAndRem.divisionByZero) { |
| if (context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) division by zero"_warn_en_US, T::kind); |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| if (quotAndRem.overflow && |
| context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) division overflowed"_warn_en_US, T::kind); |
| } |
| return Expr<T>{Constant<T>{quotAndRem.quotient}}; |
| } else { |
| auto quotient{folded->first.Divide( |
| folded->second, context.targetCharacteristics().roundingMode())}; |
| // Don't warn about -1./0., 0./0., or 1./0. from a module file |
| // they are interpreted as canonical Fortran representations of -Inf, |
| // NaN, and Inf respectively. |
| bool isCanonicalNaNOrInf{false}; |
| if constexpr (T::category == TypeCategory::Real) { |
| if (folded->second.IsZero() && context.moduleFileName().has_value()) { |
| using IntType = typename T::Scalar::Word; |
| auto intNumerator{folded->first.template ToInteger<IntType>()}; |
| isCanonicalNaNOrInf = intNumerator.flags == RealFlags{} && |
| intNumerator.value >= IntType{-1} && |
| intNumerator.value <= IntType{1}; |
| } |
| } |
| if (!isCanonicalNaNOrInf) { |
| RealFlagWarnings(context, quotient.flags, "division"); |
| } |
| if (context.targetCharacteristics().areSubnormalsFlushedToZero()) { |
| quotient.value = quotient.value.FlushSubnormalToZero(); |
| } |
| return Expr<T>{Constant<T>{quotient.value}}; |
| } |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Power<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| if (auto folded{OperandsAreConstants(x)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| auto power{folded->first.Power(folded->second)}; |
| if (context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingException)) { |
| if (power.divisionByZero) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) zero to negative power"_warn_en_US, T::kind); |
| } else if (power.overflow) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) power overflowed"_warn_en_US, T::kind); |
| } else if (power.zeroToZero) { |
| context.messages().Say(common::UsageWarning::FoldingException, |
| "INTEGER(%d) 0**0 is not defined"_warn_en_US, T::kind); |
| } |
| } |
| return Expr<T>{Constant<T>{power.power}}; |
| } else { |
| if (auto callable{GetHostRuntimeWrapper<T, T, T>("pow")}) { |
| return Expr<T>{ |
| Constant<T>{(*callable)(context, folded->first, folded->second)}}; |
| } else if (context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingFailure)) { |
| context.messages().Say(common::UsageWarning::FoldingFailure, |
| "Power for %s cannot be folded on host"_warn_en_US, |
| T{}.AsFortran()); |
| } |
| } |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, RealToIntPower<T> &&x) { |
| if (auto array{ApplyElementwise(context, x)}) { |
| return *array; |
| } |
| return common::visit( |
| [&](auto &y) -> Expr<T> { |
| if (auto folded{OperandsAreConstants(x.left(), y)}) { |
| auto power{evaluate::IntPower(folded->first, folded->second)}; |
| RealFlagWarnings(context, power.flags, "power with INTEGER exponent"); |
| if (context.targetCharacteristics().areSubnormalsFlushedToZero()) { |
| power.value = power.value.FlushSubnormalToZero(); |
| } |
| return Expr<T>{Constant<T>{power.value}}; |
| } else { |
| return Expr<T>{std::move(x)}; |
| } |
| }, |
| x.right().u); |
| } |
| |
| template <typename T> |
| Expr<T> FoldOperation(FoldingContext &context, Extremum<T> &&x) { |
| if (auto array{ApplyElementwise(context, x, |
| std::function<Expr<T>(Expr<T> &&, Expr<T> &&)>{[=](Expr<T> &&l, |
| Expr<T> &&r) { |
| return Expr<T>{Extremum<T>{x.ordering, std::move(l), std::move(r)}}; |
| }})}) { |
| return *array; |
| } |
| if (auto folded{OperandsAreConstants(x)}) { |
| if constexpr (T::category == TypeCategory::Integer) { |
| if (folded->first.CompareSigned(folded->second) == x.ordering) { |
| return Expr<T>{Constant<T>{folded->first}}; |
| } |
| } else if constexpr (T::category == TypeCategory::Real) { |
| if (folded->first.IsNotANumber() || |
| (folded->first.Compare(folded->second) == Relation::Less) == |
| (x.ordering == Ordering::Less)) { |
| return Expr<T>{Constant<T>{folded->first}}; |
| } |
| } else { |
| static_assert(T::category == TypeCategory::Character); |
| // Result of MIN and MAX on character has the length of |
| // the longest argument. |
| auto maxLen{std::max(folded->first.length(), folded->second.length())}; |
| bool isFirst{x.ordering == Compare(folded->first, folded->second)}; |
| auto res{isFirst ? std::move(folded->first) : std::move(folded->second)}; |
| res = res.length() == maxLen |
| ? std::move(res) |
| : CharacterUtils<T::kind>::Resize(res, maxLen); |
| return Expr<T>{Constant<T>{std::move(res)}}; |
| } |
| return Expr<T>{Constant<T>{folded->second}}; |
| } |
| return Expr<T>{std::move(x)}; |
| } |
| |
| template <int KIND> |
| Expr<Type<TypeCategory::Real, KIND>> ToReal( |
| FoldingContext &context, Expr<SomeType> &&expr) { |
| using Result = Type<TypeCategory::Real, KIND>; |
| std::optional<Expr<Result>> result; |
| common::visit( |
| [&](auto &&x) { |
| using From = std::decay_t<decltype(x)>; |
| if constexpr (std::is_same_v<From, BOZLiteralConstant>) { |
| // Move the bits without any integer->real conversion |
| From original{x}; |
| result = ConvertToType<Result>(std::move(x)); |
| const auto *constant{UnwrapExpr<Constant<Result>>(*result)}; |
| CHECK(constant); |
| Scalar<Result> real{constant->GetScalarValue().value()}; |
| From converted{From::ConvertUnsigned(real.RawBits()).value}; |
| if (original != converted && |
| context.languageFeatures().ShouldWarn( |
| common::UsageWarning::FoldingValueChecks)) { // C1601 |
| context.messages().Say(common::UsageWarning::FoldingValueChecks, |
| "Nonzero bits truncated from BOZ literal constant in REAL intrinsic"_warn_en_US); |
| } |
| } else if constexpr (IsNumericCategoryExpr<From>()) { |
| result = Fold(context, ConvertToType<Result>(std::move(x))); |
| } else { |
| common::die("ToReal: bad argument expression"); |
| } |
| }, |
| std::move(expr.u)); |
| return result.value(); |
| } |
| |
| // REAL(z) and AIMAG(z) |
| template <int KIND> |
| Expr<Type<TypeCategory::Real, KIND>> FoldOperation( |
| FoldingContext &context, ComplexComponent<KIND> &&x) { |
| using Operand = Type<TypeCategory::Complex, KIND>; |
| using Result = Type<TypeCategory::Real, KIND>; |
| if (auto array{ApplyElementwise(context, x, |
| std::function<Expr<Result>(Expr<Operand> &&)>{ |
| [=](Expr<Operand> &&operand) { |
| return Expr<Result>{ComplexComponent<KIND>{ |
| x.isImaginaryPart, std::move(operand)}}; |
| }})}) { |
| return *array; |
| } |
| auto &operand{x.left()}; |
| if (auto value{GetScalarConstantValue<Operand>(operand)}) { |
| if (x.isImaginaryPart) { |
| return Expr<Result>{Constant<Result>{value->AIMAG()}}; |
| } else { |
| return Expr<Result>{Constant<Result>{value->REAL()}}; |
| } |
| } |
| return Expr<Result>{std::move(x)}; |
| } |
| |
| template <typename T> |
| Expr<T> ExpressionBase<T>::Rewrite(FoldingContext &context, Expr<T> &&expr) { |
| return common::visit( |
| [&](auto &&x) -> Expr<T> { |
| if constexpr (IsSpecificIntrinsicType<T>) { |
| return FoldOperation(context, std::move(x)); |
| } else if constexpr (std::is_same_v<T, SomeDerived>) { |
| return FoldOperation(context, std::move(x)); |
| } else if constexpr (common::HasMember<decltype(x), |
| TypelessExpression>) { |
| return std::move(expr); |
| } else { |
| return Expr<T>{Fold(context, std::move(x))}; |
| } |
| }, |
| std::move(expr.u)); |
| } |
| |
| FOR_EACH_TYPE_AND_KIND(extern template class ExpressionBase, ) |
| } // namespace Fortran::evaluate |
| #endif // FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_ |