// Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "fold.h" #include "common.h" #include "expression.h" #include "int-power.h" #include "tools.h" #include "type.h" #include "../common/indirection.h" #include "../parser/message.h" #include #include // TODO pmk rm #include #include #include namespace Fortran::evaluate { // no-op base case template Expr> FoldOperation(FoldingContext &, A &&x) { return Expr>{std::move(x)}; } // Designators // At the moment, only substrings fold. // TODO: Parameters, KIND type parameters template Expr> FoldOperation(FoldingContext &context, Designator> &&designator) { using CHAR = Type; if (auto *substring{std::get_if(&designator.u)}) { if (auto folded{substring->Fold(context)}) { if (auto *string{std::get_if>(&*folded)}) { return Expr{Constant{std::move(*string)}}; } // A zero-length substring of an arbitrary data reference can // be folded, but the C++ string type of the empty value will be // std::string and that may not be right for multi-byte CHARACTER // kinds. if (auto length{ToInt64(Fold(context, substring->LEN()))}) { if (*length == 0) { return Expr{Constant{Scalar{}}}; } } } } return Expr{std::move(designator)}; } // TODO: Fold/rewrite intrinsic function references // Unary operations template Expr FoldOperation( FoldingContext &context, Convert &&convert) { return std::visit( [&](auto &kindExpr) -> Expr { kindExpr = Fold(context, std::move(kindExpr)); using Operand = ResultType; char buffer[64]; if (auto c{GetScalarConstantValue(kindExpr)}) { if constexpr (TO::category == TypeCategory::Integer) { if constexpr (Operand::category == TypeCategory::Integer) { auto converted{Scalar::ConvertSigned(c->value)}; if (converted.overflow) { context.messages.Say( "INTEGER(%d) to INTEGER(%d) conversion overflowed"_en_US, Operand::kind, TO::kind); } return Expr{Constant{std::move(converted.value)}}; } else if constexpr (Operand::category == TypeCategory::Real) { auto converted{c->value.template ToInteger>()}; if (converted.flags.test(RealFlag::InvalidArgument)) { context.messages.Say( "REAL(%d) to INTEGER(%d) conversion: invalid argument"_en_US, Operand::kind, TO::kind); } else if (converted.flags.test(RealFlag::Overflow)) { context.messages.Say( "REAL(%d) to INTEGER(%d) conversion overflowed"_en_US, Operand::kind, TO::kind); } return Expr{Constant{std::move(converted.value)}}; } } else if constexpr (TO::category == TypeCategory::Real) { if constexpr (Operand::category == TypeCategory::Integer) { auto converted{Scalar::FromInteger(c->value)}; if (!converted.flags.empty()) { std::snprintf(buffer, sizeof buffer, "INTEGER(%d) to REAL(%d) conversion", Operand::kind, TO::kind); RealFlagWarnings(context, converted.flags, buffer); } return Expr{Constant{std::move(converted.value)}}; } else if constexpr (Operand::category == TypeCategory::Real) { auto converted{Scalar::Convert(c->value)}; if (!converted.flags.empty()) { std::snprintf(buffer, sizeof buffer, "REAL(%d) to REAL(%d) conversion", Operand::kind, TO::kind); RealFlagWarnings(context, converted.flags, buffer); } if (context.flushDenormalsToZero) { converted.value = converted.value.FlushDenormalToZero(); } return Expr{Constant{std::move(converted.value)}}; } } else if constexpr (TO::category == TypeCategory::Logical && Operand::category == TypeCategory::Logical) { return Expr{Constant{c->value.IsTrue()}}; } } return Expr{std::move(convert)}; }, convert.left().u); } template Expr FoldOperation(FoldingContext &context, Parentheses &&x) { auto &operand{x.left()}; operand.Dump(std::cout << "pmk: Parentheses Fold operand: ") << '\n'; operand = Fold(context, std::move(operand)); if (auto c{GetScalarConstantValue(operand)}) { // Preserve parentheses, even around constants. // TODO pmk: Once parentheses around arguments are recorded, don't do this return Expr{Parentheses{Expr{Constant{std::move(c->value)}}}}; } return Expr{std::move(x)}; } template Expr FoldOperation(FoldingContext &context, Negate &&x) { auto &operand{x.left()}; operand = Fold(context, std::move(operand)); if (auto c{GetScalarConstantValue(operand)}) { if constexpr (T::category == TypeCategory::Integer) { auto negated{c->value.Negate()}; if (negated.overflow) { context.messages.Say("INTEGER(%d) negation overflowed"_en_US, T::kind); } return Expr{Constant{std::move(negated.value)}}; } else { // REAL & COMPLEX negation: no exceptions possible return Expr{Constant{c->value.Negate()}}; } } return Expr{std::move(x)}; } template Expr> FoldOperation( FoldingContext &context, ComplexComponent &&x) { using Part = Type; auto &operand{x.left()}; operand = Fold(context, std::move(operand)); if (auto z{GetScalarConstantValue(operand)}) { if (x.isImaginaryPart) { return Expr{Constant{z->value.AIMAG()}}; } else { return Expr{Constant{z->value.REAL()}}; } } return Expr{std::move(x)}; } template Expr> FoldOperation( FoldingContext &context, Not &&x) { using Ty = Type; auto &operand{x.left()}; operand = Fold(context, std::move(operand)); if (auto c{GetScalarConstantValue(operand)}) { return Expr{Constant{c->value.IsTrue()}}; } return Expr{x}; } // Binary (dyadic) operations template std::optional, Scalar>> FoldOperands( FoldingContext &context, Expr &x, Expr &y) { std::cout << "pmk: FoldOperands begin\n"; x = Fold(context, std::move(x)); y = Fold(context, std::move(y)); if (auto xc{GetScalarConstantValue(x)}) { if (auto yc{GetScalarConstantValue(y)}) { std::cout << "pmk: FoldOperands success\n"; return {std::make_pair(xc->value, yc->value)}; } } std::cout << "pmk: FoldOperands failure\n"; return std::nullopt; } template Expr FoldOperation(FoldingContext &context, Add &&x) { if (auto folded{FoldOperands(context, x.left(), x.right())}) { if constexpr (T::category == TypeCategory::Integer) { auto sum{folded->first.AddSigned(folded->second)}; if (sum.overflow) { context.messages.Say("INTEGER(%d) addition overflowed"_en_US, T::kind); } return Expr{Constant{sum.value}}; } else { auto sum{folded->first.Add(folded->second, context.rounding)}; RealFlagWarnings(context, sum.flags, "addition"); if (context.flushDenormalsToZero) { sum.value = sum.value.FlushDenormalToZero(); } return Expr{Constant{sum.value}}; } } return Expr{std::move(x)}; } template Expr FoldOperation(FoldingContext &context, Subtract &&x) { if (auto folded{FoldOperands(context, x.left(), x.right())}) { if constexpr (T::category == TypeCategory::Integer) { auto difference{folded->first.SubtractSigned(folded->second)}; if (difference.overflow) { context.messages.Say( "INTEGER(%d) subtraction overflowed"_en_US, T::kind); } return Expr{Constant{difference.value}}; } else { auto difference{folded->first.Subtract(folded->second, context.rounding)}; RealFlagWarnings(context, difference.flags, "subtraction"); if (context.flushDenormalsToZero) { difference.value = difference.value.FlushDenormalToZero(); } return Expr{Constant{difference.value}}; } } return Expr{std::move(x)}; } template Expr FoldOperation(FoldingContext &context, Multiply &&x) { if (auto folded{FoldOperands(context, x.left(), x.right())}) { if constexpr (T::category == TypeCategory::Integer) { auto product{folded->first.MultiplySigned(folded->second)}; if (product.SignedMultiplicationOverflowed()) { context.messages.Say( "INTEGER(%d) multiplication overflowed"_en_US, T::kind); } return Expr{Constant{product.lower}}; } else { auto product{folded->first.Multiply(folded->second, context.rounding)}; RealFlagWarnings(context, product.flags, "multiplication"); if (context.flushDenormalsToZero) { product.value = product.value.FlushDenormalToZero(); } return Expr{Constant{product.value}}; } } return Expr{std::move(x)}; } template Expr FoldOperation(FoldingContext &context, Divide &&x) { if (auto folded{FoldOperands(context, x.left(), x.right())}) { if constexpr (T::category == TypeCategory::Integer) { auto quotAndRem{folded->first.DivideSigned(folded->second)}; if (quotAndRem.divisionByZero) { context.messages.Say("INTEGER(%d) division by zero"_en_US, T::kind); } if (quotAndRem.overflow) { context.messages.Say("INTEGER(%d) division overflowed"_en_US, T::kind); } return Expr{Constant{quotAndRem.quotient}}; } else { auto quotient{folded->first.Divide(folded->second, context.rounding)}; RealFlagWarnings(context, quotient.flags, "division"); if (context.flushDenormalsToZero) { quotient.value = quotient.value.FlushDenormalToZero(); } return Expr{Constant{quotient.value}}; } } return Expr{std::move(x)}; } template Expr FoldOperation(FoldingContext &context, Power &&x) { if (auto folded{FoldOperands(context, x.left(), x.right())}) { if constexpr (T::category == TypeCategory::Integer) { auto power{folded->first.Power(folded->second)}; if (power.divisionByZero) { context.messages.Say( "INTEGER(%d) zero to negative power"_en_US, T::kind); } else if (power.overflow) { context.messages.Say("INTEGER(%d) power overflowed"_en_US, T::kind); } else if (power.zeroToZero) { context.messages.Say("INTEGER(%d) 0**0 is not defined"_en_US, T::kind); } return Expr{Constant{power.power}}; } else { // TODO: real & complex power with non-integral exponent } } return Expr{std::move(x)}; } template Expr FoldOperation(FoldingContext &context, RealToIntPower &&x) { return std::visit( [&](auto &y) -> Expr { if (auto folded{FoldOperands(context, x.left(), y)}) { auto power{evaluate::IntPower(folded->first, folded->second)}; RealFlagWarnings(context, power.flags, "power with INTEGER exponent"); if (context.flushDenormalsToZero) { power.value = power.value.FlushDenormalToZero(); } return Expr{Constant{power.value}}; } else { return Expr{std::move(x)}; } }, x.right().u); } template Expr FoldOperation(FoldingContext &context, Extremum &&x) { if (auto folded{FoldOperands(context, x.left(), x.right())}) { if constexpr (T::category == TypeCategory::Integer) { if (folded->first.CompareSigned(folded->second) == x.ordering) { return Expr{Constant{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{Constant{folded->first}}; } } else { if (x.ordering == Compare(folded->first, folded->second)) { return Expr{Constant{folded->first}}; } } return Expr{Constant{folded->second}}; } return Expr{std::move(x)}; } template Expr> FoldOperation( FoldingContext &context, ComplexConstructor &&x) { using COMPLEX = Type; if (auto folded{FoldOperands(context, x.left(), x.right())}) { return Expr{ Constant{Scalar{folded->first, folded->second}}}; } return Expr{std::move(x)}; } template Expr> FoldOperation( FoldingContext &context, Concat &&x) { using CHAR = Type; if (auto folded{FoldOperands(context, x.left(), x.right())}) { return Expr{Constant{folded->first + folded->second}}; } return Expr{std::move(x)}; } template Expr FoldOperation( FoldingContext &context, Relational &&relation) { if (auto folded{FoldOperands(context, relation.left(), relation.right())}) { bool result{}; if constexpr (T::category == TypeCategory::Integer) { result = Satisfies(relation.opr, folded->first.CompareSigned(folded->second)); } else if constexpr (T::category == TypeCategory::Real) { result = Satisfies(relation.opr, folded->first.Compare(folded->second)); } else if constexpr (T::category == TypeCategory::Character) { result = Satisfies(relation.opr, Compare(folded->first, folded->second)); } else { static_assert(T::category != TypeCategory::Complex && T::category != TypeCategory::Logical); } return Expr{Constant{result}}; } return Expr{Relational{std::move(relation)}}; } template<> inline Expr FoldOperation( FoldingContext &context, Relational &&relation) { return std::visit( [&](auto &&x) { return Expr{FoldOperation(context, std::move(x))}; }, std::move(relation.u)); } template Expr> FoldOperation( FoldingContext &context, LogicalOperation &&x) { using LOGICAL = Type; if (auto folded{FoldOperands(context, x.left(), x.right())}) { bool xt{folded->first.IsTrue()}, yt{folded->second.IsTrue()}, result{}; switch (x.logicalOperator) { case LogicalOperator::And: result = xt && yt; break; case LogicalOperator::Or: result = xt || yt; break; case LogicalOperator::Eqv: result = xt == yt; break; case LogicalOperator::Neqv: result = xt != yt; break; } return Expr{Constant{result}}; } return Expr{std::move(x)}; } // end per-operation folding functions template Expr FoldHelper::FoldExpr(FoldingContext &context, Expr &&expr) { return std::visit( [&](auto &&x) -> Expr { if constexpr (T::isSpecificIntrinsicType) { return FoldOperation(context, std::move(x)); } else if constexpr (std::is_same_v) { return FoldOperation(context, std::move(x)); } else if constexpr (std::is_same_v>) { return std::move(expr); } else { return Expr{Fold(context, std::move(x))}; } }, std::move(expr.u)); } FOR_EACH_TYPE_AND_KIND(template struct FoldHelper, ;) template std::optional> GetScalarConstantValueHelper::GetScalarConstantValue(const Expr &expr) { if (const auto *c{std::get_if>(&expr.u)}) { return {*c}; } else if (const auto *p{std::get_if>(&expr.u)}) { return GetScalarConstantValue(p->left()); } else { return std::nullopt; } } FOR_EACH_INTRINSIC_KIND(template struct GetScalarConstantValueHelper, ;) } // namespace Fortran::evaluate