Skip to content

[flang] Warn about inexact real literal implicit widening pitfall #152799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions flang/include/flang/Common/enum-set.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,8 @@ template <typename ENUM, std::size_t BITS> class EnumSet {
constexpr bool empty() const { return none(); }
void clear() { reset(); }
void insert(enumerationType x) { set(x); }
void insert(enumerationType &&x) { set(x); }
void emplace(enumerationType &&x) { set(x); }
void emplace(enumerationType x) { set(x); }
void erase(enumerationType x) { reset(x); }
void erase(enumerationType &&x) { reset(x); }

constexpr std::optional<enumerationType> LeastElement() const {
if (empty()) {
Expand Down
7 changes: 7 additions & 0 deletions flang/include/flang/Evaluate/check-expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ bool IsInitialProcedureTarget(const Symbol &);
bool IsInitialProcedureTarget(const ProcedureDesignator &);
bool IsInitialProcedureTarget(const Expr<SomeType> &);

// Emit warnings about default REAL literal constants in contexts that
// will be converted to a higher precision REAL kind than the default.
void CheckRealWidening(
const Expr<SomeType> &, const DynamicType &toType, FoldingContext &);
void CheckRealWidening(const Expr<SomeType> &,
const std::optional<DynamicType> &, FoldingContext &);

// Validate the value of a named constant, the static initial
// value of a non-pointer non-allocatable non-dummy variable, or the
// default initializer of a component of a derived type (or instantiation
Expand Down
7 changes: 5 additions & 2 deletions flang/include/flang/Evaluate/constant.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,19 @@ class ConstantBase : public ConstantBounds {
bool empty() const { return values_.empty(); }
std::size_t size() const { return values_.size(); }
const std::vector<Element> &values() const { return values_; }
constexpr Result result() const { return result_; }
Result &result() { return result_; }
const Result &result() const { return result_; }

constexpr DynamicType GetType() const { return result_.GetType(); }
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::string AsFortran() const;

protected:
std::vector<Element> Reshape(const ConstantSubscripts &) const;
std::size_t CopyFrom(const ConstantBase &source, std::size_t count,
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);

Result result_;
Result result_; // usually empty except for Real & Complex
std::vector<Element> values_;
};

Expand Down Expand Up @@ -209,6 +211,7 @@ class Constant<Type<TypeCategory::Character, KIND>> : public ConstantBounds {

Constant Reshape(ConstantSubscripts &&) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::string AsFortran() const;
DynamicType GetType() const { return {KIND, length_}; }
std::size_t CopyFrom(const Constant &source, std::size_t count,
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);
Expand Down
1 change: 1 addition & 0 deletions flang/include/flang/Evaluate/real.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ template <typename WORD, int PREC> class Real {
// or parenthesized constant expression that produces this value.
llvm::raw_ostream &AsFortran(
llvm::raw_ostream &, int kind, bool minimal = false) const;
std::string AsFortran(int kind, bool minimal = false) const;

private:
using Significand = Integer<significandBits>; // no implicit bit
Expand Down
22 changes: 20 additions & 2 deletions flang/include/flang/Evaluate/type.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,26 @@ class Type<TypeCategory::Unsigned, KIND>
using Scalar = value::Integer<8 * KIND>;
};

// Records when a default REAL literal constant is inexactly converted to binary
// (e.g., 0.1 but not 0.125) to enable a usage warning if the expression in
// which it appears undergoes an implicit widening conversion.
class TrackInexactLiteralConversion {
public:
constexpr bool isFromInexactLiteralConversion() const {
return isFromInexactLiteralConversion_;
}
void set_isFromInexactLiteralConversion(bool yes = true) {
isFromInexactLiteralConversion_ = yes;
}

private:
bool isFromInexactLiteralConversion_{false};
};

template <int KIND>
class Type<TypeCategory::Real, KIND>
: public TypeBase<TypeCategory::Real, KIND> {
: public TypeBase<TypeCategory::Real, KIND>,
public TrackInexactLiteralConversion {
public:
static constexpr int precision{common::PrecisionOfRealKind(KIND)};
static constexpr int bits{common::BitsForBinaryPrecision(precision)};
Expand All @@ -289,7 +306,8 @@ class Type<TypeCategory::Real, KIND>
// The KIND type parameter on COMPLEX is the kind of each of its components.
template <int KIND>
class Type<TypeCategory::Complex, KIND>
: public TypeBase<TypeCategory::Complex, KIND> {
: public TypeBase<TypeCategory::Complex, KIND>,
public TrackInexactLiteralConversion {
public:
using Part = Type<TypeCategory::Real, KIND>;
using Scalar = value::Complex<typename Part::Scalar>;
Expand Down
3 changes: 2 additions & 1 deletion flang/include/flang/Support/Fortran-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
MismatchingDummyProcedure, SubscriptedEmptyArray, UnsignedLiteralTruncation,
CompatibleDeclarationsFromDistinctModules,
NullActualForDefaultIntentAllocatable, UseAssociationIntoSameNameSubprogram,
HostAssociatedIntentOutInSpecExpr, NonVolatilePointerToVolatile)
HostAssociatedIntentOutInSpecExpr, NonVolatilePointerToVolatile,
RealConstantWidening)

using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
using UsageWarnings = EnumSet<UsageWarning, UsageWarning_enumSize>;
Expand Down
84 changes: 84 additions & 0 deletions flang/lib/Evaluate/check-expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,88 @@ bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
}
}

class SuspiciousRealLiteralFinder
: public AnyTraverse<SuspiciousRealLiteralFinder> {
public:
using Base = AnyTraverse<SuspiciousRealLiteralFinder>;
SuspiciousRealLiteralFinder(int kind, FoldingContext &c)
: Base{*this}, kind_{kind}, context_{c} {}
using Base::operator();
template <int KIND>
bool operator()(const Constant<Type<TypeCategory::Real, KIND>> &x) const {
if (kind_ > KIND && x.result().isFromInexactLiteralConversion()) {
context_.messages().Say(common::UsageWarning::RealConstantWidening,
"Default real literal in REAL(%d) context might need a kind suffix, as its rounded value %s is inexact"_warn_en_US,
kind_, x.AsFortran());
return true;
} else {
return false;
}
}
template <int KIND>
bool operator()(const Constant<Type<TypeCategory::Complex, KIND>> &x) const {
if (kind_ > KIND && x.result().isFromInexactLiteralConversion()) {
context_.messages().Say(common::UsageWarning::RealConstantWidening,
"Default real literal in COMPLEX(%d) context might need a kind suffix, as its rounded value %s is inexact"_warn_en_US,
kind_, x.AsFortran());
return true;
} else {
return false;
}
}
template <TypeCategory TOCAT, int TOKIND, TypeCategory FROMCAT>
bool operator()(const Convert<Type<TOCAT, TOKIND>, FROMCAT> &x) const {
if constexpr ((TOCAT == TypeCategory::Real ||
TOCAT == TypeCategory::Complex) &&
(FROMCAT == TypeCategory::Real || FROMCAT == TypeCategory::Complex)) {
auto fromType{x.left().GetType()};
if (!fromType || fromType->kind() < TOKIND) {
return false;
}
}
return (*this)(x.left());
}

private:
int kind_;
FoldingContext &context_;
};

void CheckRealWidening(const Expr<SomeType> &expr, const DynamicType &toType,
FoldingContext &context) {
if (toType.category() == TypeCategory::Real ||
toType.category() == TypeCategory::Complex) {
if (auto fromType{expr.GetType()}) {
if ((fromType->category() == TypeCategory::Real ||
fromType->category() == TypeCategory::Complex) &&
toType.kind() > fromType->kind()) {
SuspiciousRealLiteralFinder{toType.kind(), context}(expr);
}
}
}
}

void CheckRealWidening(const Expr<SomeType> &expr,
const std::optional<DynamicType> &toType, FoldingContext &context) {
if (toType) {
CheckRealWidening(expr, *toType, context);
}
}

class InexactLiteralConversionFlagClearer
: public AnyTraverse<InexactLiteralConversionFlagClearer> {
public:
using Base = AnyTraverse<InexactLiteralConversionFlagClearer>;
InexactLiteralConversionFlagClearer() : Base(*this) {}
using Base::operator();
template <int KIND>
bool operator()(const Constant<Type<TypeCategory::Real, KIND>> &x) const {
auto &mut{const_cast<Type<TypeCategory::Real, KIND> &>(x.result())};
mut.set_isFromInexactLiteralConversion(false);
return false;
}
};

// Converts, folds, and then checks type, rank, and shape of an
// initialization expression for a named constant, a non-pointer
// variable static initialization, a component default initializer,
Expand All @@ -416,6 +498,7 @@ std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol,
if (auto symTS{
characteristics::TypeAndShape::Characterize(symbol, context)}) {
auto xType{x.GetType()};
CheckRealWidening(x, symTS->type(), context);
auto converted{ConvertToType(symTS->type(), Expr<SomeType>{x})};
if (!converted &&
symbol.owner().context().IsEnabled(
Expand All @@ -433,6 +516,7 @@ std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol,
if (converted) {
auto folded{Fold(context, std::move(*converted))};
if (IsActuallyConstant(folded)) {
InexactLiteralConversionFlagClearer{}(folded);
int symRank{symTS->Rank()};
if (IsImpliedShape(symbol)) {
if (folded.Rank() == symRank) {
Expand Down
17 changes: 13 additions & 4 deletions flang/lib/Evaluate/fold-complex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,21 @@ Expr<Type<TypeCategory::Complex, KIND>> FoldOperation(
if (auto array{ApplyElementwise(context, x)}) {
return *array;
}
using Result = Type<TypeCategory::Complex, KIND>;
using ComplexType = Type<TypeCategory::Complex, KIND>;
if (auto folded{OperandsAreConstants(x)}) {
return Expr<Result>{
Constant<Result>{Scalar<Result>{folded->first, folded->second}}};
using RealType = typename ComplexType::Part;
Constant<ComplexType> result{
Scalar<ComplexType>{folded->first, folded->second}};
if (const auto *re{UnwrapConstantValue<RealType>(x.left())};
re && re->result().isFromInexactLiteralConversion()) {
result.result().set_isFromInexactLiteralConversion();
} else if (const auto *im{UnwrapConstantValue<RealType>(x.right())};
im && im->result().isFromInexactLiteralConversion()) {
result.result().set_isFromInexactLiteralConversion();
}
return Expr<ComplexType>{std::move(result)};
}
return Expr<Result>{std::move(x)};
return Expr<ComplexType>{std::move(x)};
}

#ifdef _MSC_VER // disable bogus warning about missing definitions
Expand Down
10 changes: 8 additions & 2 deletions flang/lib/Evaluate/fold-implementation.h
Original file line number Diff line number Diff line change
Expand Up @@ -1321,8 +1321,8 @@ template <typename T> class ArrayConstructorFolder {
*charLength_, std::move(elements_), ConstantSubscripts{n}}};
}
} else {
return Expr<T>{
Constant<T>{std::move(elements_), ConstantSubscripts{n}}};
return Expr<T>{Constant<T>{
std::move(elements_), ConstantSubscripts{n}, resultInfo_}};
}
}
return Expr<T>{std::move(array)};
Expand All @@ -1343,6 +1343,11 @@ template <typename T> class ArrayConstructorFolder {
if (!knownCharLength_) {
charLength_ = std::max(c->LEN(), charLength_.value_or(-1));
}
} else if constexpr (T::category == TypeCategory::Real ||
T::category == TypeCategory::Complex) {
if (c->result().isFromInexactLiteralConversion()) {
resultInfo_.set_isFromInexactLiteralConversion();
}
}
return true;
} else {
Expand Down Expand Up @@ -1395,6 +1400,7 @@ template <typename T> class ArrayConstructorFolder {
std::vector<Scalar<T>> elements_;
std::optional<ConstantSubscript> charLength_;
bool knownCharLength_{false};
typename Constant<T>::Result resultInfo_;
};

template <typename T>
Expand Down
16 changes: 16 additions & 0 deletions flang/lib/Evaluate/formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ llvm::raw_ostream &ConstantBase<RESULT, VALUE>::AsFortran(
return o;
}

template <typename RESULT, typename VALUE>
std::string ConstantBase<RESULT, VALUE>::AsFortran() const {
std::string result;
llvm::raw_string_ostream sstream(result);
AsFortran(sstream);
return result;
}

template <int KIND>
llvm::raw_ostream &Constant<Type<TypeCategory::Character, KIND>>::AsFortran(
llvm::raw_ostream &o) const {
Expand Down Expand Up @@ -126,6 +134,14 @@ llvm::raw_ostream &Constant<Type<TypeCategory::Character, KIND>>::AsFortran(
return o;
}

template <int KIND>
std::string Constant<Type<TypeCategory::Character, KIND>>::AsFortran() const {
std::string result;
llvm::raw_string_ostream sstream(result);
AsFortran(sstream);
return result;
}

llvm::raw_ostream &EmitVar(llvm::raw_ostream &o, const Symbol &symbol,
std::optional<parser::CharBlock> name = std::nullopt) {
const auto &renamings{symbol.owner().context().moduleFileOutputRenamings()};
Expand Down
8 changes: 8 additions & 0 deletions flang/lib/Evaluate/real.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,14 @@ llvm::raw_ostream &Real<W, P>::AsFortran(
return o;
}

template <typename W, int P>
std::string Real<W, P>::AsFortran(int kind, bool minimal) const {
std::string result;
llvm::raw_string_ostream sstream(result);
AsFortran(sstream, kind, minimal);
return result;
}

// 16.9.180
template <typename W, int P> Real<W, P> Real<W, P>::RRSPACING() const {
if (IsNotANumber()) {
Expand Down
9 changes: 5 additions & 4 deletions flang/lib/Semantics/data-to-inits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,21 +285,22 @@ template <typename DSV>
std::optional<std::pair<SomeExpr, bool>>
DataInitializationCompiler<DSV>::ConvertElement(
const SomeExpr &expr, const evaluate::DynamicType &type) {
evaluate::FoldingContext &foldingContext{exprAnalyzer_.GetFoldingContext()};
evaluate::CheckRealWidening(expr, type, foldingContext);
if (auto converted{evaluate::ConvertToType(type, SomeExpr{expr})}) {
return {std::make_pair(std::move(*converted), false)};
}
// Allow DATA initialization with Hollerith and kind=1 CHARACTER like
// (most) other Fortran compilers do.
if (auto converted{evaluate::HollerithToBOZ(
exprAnalyzer_.GetFoldingContext(), expr, type)}) {
if (auto converted{evaluate::HollerithToBOZ(foldingContext, expr, type)}) {
return {std::make_pair(std::move(*converted), true)};
}
SemanticsContext &context{exprAnalyzer_.context()};
if (context.IsEnabled(common::LanguageFeature::LogicalIntegerAssignment)) {
if (MaybeExpr converted{evaluate::DataConstantConversionExtension(
exprAnalyzer_.GetFoldingContext(), type, expr)}) {
foldingContext, type, expr)}) {
context.Warn(common::LanguageFeature::LogicalIntegerAssignment,
exprAnalyzer_.GetFoldingContext().messages().at(),
foldingContext.messages().at(),
"nonstandard usage: initialization of %s with %s"_port_en_US,
type.AsFortran(), expr.GetType().value().AsFortran());
return {std::make_pair(std::move(*converted), false)};
Expand Down
Loading
Loading