diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -413,6 +413,10 @@ return ClassifyImpl(Ctx, &Loc); } + /// Returns the set of floating point options that apply to this expression. + /// Only meaningful for operations on floating point values. + FPOptions getFPFeaturesInEffect(const LangOptions &LO) const; + /// getValueKindForType - Given a formal return or parameter type, /// give its value kind. static ExprValueKind getValueKindForType(QualType T) { diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -72,6 +72,8 @@ "in a constant expression">; def note_constexpr_float_arithmetic : Note< "floating point arithmetic produces %select{an infinity|a NaN}0">; +def note_constexpr_dynamic_rounding : Note< + "cannot evaluate this expression if rounding mode is dynamic">; def note_constexpr_pointer_subtraction_not_same_array : Note< "subtracted pointers are not elements of the same array">; def note_constexpr_pointer_subtraction_zero_size : Note< diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -3577,6 +3577,18 @@ return false; } +FPOptions Expr::getFPFeaturesInEffect(const LangOptions &LO) const { + if (auto Call = dyn_cast(this)) + return Call->getFPFeaturesInEffect(LO); + if (auto UO = dyn_cast(this)) + return UO->getFPFeaturesInEffect(LO); + if (auto BO = dyn_cast(this)) + return BO->getFPFeaturesInEffect(LO); + if (auto Cast = dyn_cast(this)) + return Cast->getFPFeaturesInEffect(LO); + return FPOptions::defaultWithoutTrailingStorage(LO); +} + namespace { /// Look for a call to a non-trivial function within an expression. class NonTrivialCallFinder : public ConstEvaluatedExprVisitor diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2418,14 +2418,60 @@ return true; } +/// Get rounding mode used for evaluation of the specified expression. +/// \param[out] DynamicRM Is set to true is the requested rounding mode is +/// dynamic. +/// If rounding mode is unknown at compile time, still try to evaluate the +/// expression. If the result is exact, it does not depend on rounding mode. +/// So return "tonearest" mode instead of "dynamic". +static llvm::RoundingMode getActiveRoundingMode(EvalInfo &Info, const Expr *E, + bool &DynamicRM) { + llvm::RoundingMode RM = + E->getFPFeaturesInEffect(Info.Ctx.getLangOpts()).getRoundingMode(); + DynamicRM = (RM == llvm::RoundingMode::Dynamic); + if (DynamicRM) + RM = llvm::RoundingMode::NearestTiesToEven; + return RM; +} + +/// Check if the given evaluation result is allowed for constant evaluation. +static bool checkFloatingPointResult(EvalInfo &Info, const Expr *E, + APFloat::opStatus St) { + FPOptions FPO = E->getFPFeaturesInEffect(Info.Ctx.getLangOpts()); + if ((St & APFloat::opInexact) && + FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { + // Inexact result means that it depends on rounding mode. If the requested + // mode is dynamic, the evaluation cannot be made in compile time. + Info.FFDiag(E, diag::note_constexpr_dynamic_rounding); + return false; + } + + if (St & APFloat::opStatus::opInvalidOp) { + // There is no usefully definable result. + Info.FFDiag(E); + return false; + } + + // FIXME: if: + // - evaluation triggered other FP exception, and + // - exception mode is not "ignore", and + // - the expression being evaluated is not a part of global variable + // initializer, + // the evaluation probably need to be rejected. + return true; +} + static bool HandleFloatToFloatCast(EvalInfo &Info, const Expr *E, QualType SrcType, QualType DestType, APFloat &Result) { + assert(isa(E) || isa(E)); + bool DynamicRM; + llvm::RoundingMode RM = getActiveRoundingMode(Info, E, DynamicRM); + APFloat::opStatus St; APFloat Value = Result; bool ignored; - Result.convert(Info.Ctx.getFloatTypeSemantics(DestType), - APFloat::rmNearestTiesToEven, &ignored); - return true; + St = Result.convert(Info.Ctx.getFloatTypeSemantics(DestType), RM, &ignored); + return checkFloatingPointResult(Info, E, St); } static APSInt HandleIntToIntCast(EvalInfo &Info, const Expr *E, @@ -2647,28 +2693,31 @@ } /// Perform the given binary floating-point operation, in-place, on LHS. -static bool handleFloatFloatBinOp(EvalInfo &Info, const Expr *E, +static bool handleFloatFloatBinOp(EvalInfo &Info, const BinaryOperator *E, APFloat &LHS, BinaryOperatorKind Opcode, const APFloat &RHS) { + bool DynamicRM; + llvm::RoundingMode RM = getActiveRoundingMode(Info, E, DynamicRM); + APFloat::opStatus St; switch (Opcode) { default: Info.FFDiag(E); return false; case BO_Mul: - LHS.multiply(RHS, APFloat::rmNearestTiesToEven); + St = LHS.multiply(RHS, RM); break; case BO_Add: - LHS.add(RHS, APFloat::rmNearestTiesToEven); + St = LHS.add(RHS, RM); break; case BO_Sub: - LHS.subtract(RHS, APFloat::rmNearestTiesToEven); + St = LHS.subtract(RHS, RM); break; case BO_Div: // [expr.mul]p4: // If the second operand of / or % is zero the behavior is undefined. if (RHS.isZero()) Info.CCEDiag(E, diag::note_expr_divide_by_zero); - LHS.divide(RHS, APFloat::rmNearestTiesToEven); + St = LHS.divide(RHS, RM); break; } @@ -2680,7 +2729,8 @@ Info.CCEDiag(E, diag::note_constexpr_float_arithmetic) << LHS.isNaN(); return Info.noteUndefinedBehavior(); } - return true; + + return checkFloatingPointResult(Info, E, St); } static bool handleLogicalOpForVector(const APInt &LHSValue, @@ -2763,7 +2813,7 @@ } // Perform binary operations for vector types, in place on the LHS. -static bool handleVectorVectorBinOp(EvalInfo &Info, const Expr *E, +static bool handleVectorVectorBinOp(EvalInfo &Info, const BinaryOperator *E, BinaryOperatorKind Opcode, APValue &LHSValue, const APValue &RHSValue) { @@ -4077,7 +4127,7 @@ namespace { struct CompoundAssignSubobjectHandler { EvalInfo &Info; - const Expr *E; + const CompoundAssignOperator *E; QualType PromotedLHSType; BinaryOperatorKind Opcode; const APValue &RHS; @@ -4197,10 +4247,12 @@ const AccessKinds CompoundAssignSubobjectHandler::AccessKind; /// Perform a compound assignment of LVal = RVal. -static bool handleCompoundAssignment( - EvalInfo &Info, const Expr *E, - const LValue &LVal, QualType LValType, QualType PromotedLValType, - BinaryOperatorKind Opcode, const APValue &RVal) { +static bool handleCompoundAssignment(EvalInfo &Info, + const CompoundAssignOperator *E, + const LValue &LVal, QualType LValType, + QualType PromotedLValType, + BinaryOperatorKind Opcode, + const APValue &RVal) { if (LVal.Designator.Invalid) return false; diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -1105,6 +1105,8 @@ dumpBareType(Node->getComputationLHSType()); OS << " ComputeResultTy="; dumpBareType(Node->getComputationResultType()); + if (Node->hasStoredFPFeatures()) + printFPOptions(Node->getStoredFPFeatures()); } void TextNodeDumper::VisitAddrLabelExpr(const AddrLabelExpr *Node) { diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -981,7 +981,9 @@ void Sema::setRoundingMode(SourceLocation Loc, llvm::RoundingMode FPR) { // C2x: 7.6.2p3 If the FE_DYNAMIC mode is specified and FENV_ACCESS is "off", // the translator may assume that the default rounding mode is in effect. - if (FPR == llvm::RoundingMode::Dynamic && !CurFPFeatures.getAllowFEnvAccess()) + if (FPR == llvm::RoundingMode::Dynamic && + !CurFPFeatures.getAllowFEnvAccess() && + CurFPFeatures.getFPExceptionMode() == LangOptions::FPE_Ignore) FPR = llvm::RoundingMode::NearestTiesToEven; FPOptionsOverride NewFPFeatures = CurFPFeatureOverrides(); diff --git a/clang/test/AST/const-fpfeatures-diag.c b/clang/test/AST/const-fpfeatures-diag.c new file mode 100644 --- /dev/null +++ b/clang/test/AST/const-fpfeatures-diag.c @@ -0,0 +1,8 @@ +// RUN: %clang_cc1 -verify -ffp-exception-behavior=strict -Wno-unknown-pragmas %s + +#pragma STDC FENV_ROUND FE_DYNAMIC + +// nextUp(1.F) == 0x1.000002p0F + +float F1 = 0x1.000000p0F + 0x0.000002p0F; +float F2 = 0x1.000000p0F + 0x0.000001p0F; // expected-error{{initializer element is not a compile-time constant}} diff --git a/clang/test/AST/const-fpfeatures.c b/clang/test/AST/const-fpfeatures.c new file mode 100644 --- /dev/null +++ b/clang/test/AST/const-fpfeatures.c @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -S -emit-llvm -Wno-unknown-pragmas %s -o - | FileCheck %s + +// nextUp(1.F) == 0x1.000002p0F + +const double _Complex C0 = 0x1.000001p0 + 0x1.000001p0I; + +#pragma STDC FENV_ROUND FE_UPWARD + +float F1u = 1.0F + 0x0.000002p0F; +float F2u = 1.0F + 0x0.000001p0F; +float F3u = 0x1.000001p0; +// CHECK: @F1u = {{.*}} float 0x3FF0000020000000 +// CHECK: @F2u = {{.*}} float 0x3FF0000020000000 +// CHECK: @F3u = {{.*}} float 0x3FF0000020000000 + +float _Complex C1u = C0; +// CHECK: @C1u = {{.*}} { float, float } { float 0x3FF0000020000000, float 0x3FF0000020000000 } + + +#pragma STDC FENV_ROUND FE_DOWNWARD + +float F1d = 1.0F + 0x0.000002p0F; +float F2d = 1.0F + 0x0.000001p0F; +float F3d = 0x1.000001p0; + +// CHECK: @F1d = {{.*}} float 0x3FF0000020000000 +// CHECK: @F2d = {{.*}} float 1.000000e+00 +// CHECK: @F3d = {{.*}} float 1.000000e+00 + +float _Complex C1d = C0; +// CHECK: @C1d = {{.*}} { float, float } { float 1.000000e+00, float 1.000000e+00 } diff --git a/clang/test/AST/const-fpfeatures.cpp b/clang/test/AST/const-fpfeatures.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/const-fpfeatures.cpp @@ -0,0 +1,81 @@ +// RUN: %clang_cc1 -S -emit-llvm -triple i386-linux -std=c++2a -Wno-unknown-pragmas %s -o - | FileCheck %s + +// nextUp(1.F) == 0x1.000002p0F + +constexpr float add_round_down(float x, float y) { + #pragma STDC FENV_ROUND FE_DOWNWARD + float res = x; + res += y; + return res; +} + +constexpr float add_round_up(float x, float y) { + #pragma STDC FENV_ROUND FE_UPWARD + float res = x; + res += y; + return res; +} + +float V1 = add_round_down(1.0F, 0x0.000001p0F); +float V2 = add_round_up(1.0F, 0x0.000001p0F); +// CHECK: @V1 = {{.*}} float 1.000000e+00 +// CHECK: @V2 = {{.*}} float 0x3FF0000020000000 + +constexpr float add_cast_round_down(float x, double y) { + #pragma STDC FENV_ROUND FE_DOWNWARD + float res = x; + res += y; + return res; +} + +constexpr float add_cast_round_up(float x, double y) { + #pragma STDC FENV_ROUND FE_UPWARD + float res = x; + res += y; + return res; +} + +float V3 = add_cast_round_down(1.0F, 0x0.000001p0F); +float V4 = add_cast_round_up(1.0F, 0x0.000001p0F); + +// CHECK: @V3 = {{.*}} float 1.000000e+00 +// CHECK: @V4 = {{.*}} float 0x3FF0000020000000 + +// The next three variables use the same function as initializer, only rounding +// modes differ. + +float V5 = []() -> float { + return [](float x, float y)->float { + #pragma STDC FENV_ROUND FE_UPWARD + return x + y; + }([](float x, float y) -> float { + #pragma STDC FENV_ROUND FE_UPWARD + return x + y; + }(1.0F, 0x0.000001p0F), + 0x0.000001p0F); +}(); +// CHECK: @V5 = {{.*}} float 0x3FF0000040000000 + +float V6 = []() -> float { + return [](float x, float y)->float { + #pragma STDC FENV_ROUND FE_DOWNWARD + return x + y; + }([](float x, float y) -> float { + #pragma STDC FENV_ROUND FE_UPWARD + return x + y; + }(1.0F, 0x0.000001p0F), + 0x0.000001p0F); +}(); +// CHECK: @V6 = {{.*}} float 0x3FF0000020000000 + +float V7 = []() -> float { + return [](float x, float y)->float { + #pragma STDC FENV_ROUND FE_DOWNWARD + return x + y; + }([](float x, float y) -> float { + #pragma STDC FENV_ROUND FE_DOWNWARD + return x + y; + }(1.0F, 0x0.000001p0F), + 0x0.000001p0F); +}(); +// CHECK: @V7 = {{.*}} float 1.000000e+00