[clang] 6314f41 - [FPEnv] Evaluate constant expressions under non-default rounding modes

Serge Pavlov via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 26 04:57:07 PDT 2020


Author: Serge Pavlov
Date: 2020-09-26T17:59:39+07:00
New Revision: 6314f412a83d61e293e3696d4afe1797a4001ac4

URL: https://github.com/llvm/llvm-project/commit/6314f412a83d61e293e3696d4afe1797a4001ac4
DIFF: https://github.com/llvm/llvm-project/commit/6314f412a83d61e293e3696d4afe1797a4001ac4.diff

LOG: [FPEnv] Evaluate constant expressions under non-default rounding modes

The change implements evaluation of constant floating point expressions
under non-default rounding modes. The main objective was to support
evaluation of global variable initializers, where constant rounding mode
may be specified by `#pragma STDC FENV_ROUND`.

Differential Revision: https://reviews.llvm.org/D87822

Added: 
    clang/test/AST/const-fpfeatures-diag.c
    clang/test/AST/const-fpfeatures.c
    clang/test/AST/const-fpfeatures.cpp

Modified: 
    clang/include/clang/AST/Expr.h
    clang/include/clang/Basic/DiagnosticASTKinds.td
    clang/lib/AST/Expr.cpp
    clang/lib/AST/ExprConstant.cpp
    clang/lib/AST/TextNodeDumper.cpp
    clang/lib/Sema/SemaAttr.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 1ea454514b2f..c14143c3f336 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -413,6 +413,10 @@ class Expr : public ValueStmt {
     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
index 6a9ff309e49c..231d597fe36a 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -72,6 +72,8 @@ def note_constexpr_array_index : Note<"cannot refer to element %0 of "
   "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
index b664224aa732..7fc09edddf98 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3577,6 +3577,18 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   return false;
 }
 
+FPOptions Expr::getFPFeaturesInEffect(const LangOptions &LO) const {
+  if (auto Call = dyn_cast<CallExpr>(this))
+    return Call->getFPFeaturesInEffect(LO);
+  if (auto UO = dyn_cast<UnaryOperator>(this))
+    return UO->getFPFeaturesInEffect(LO);
+  if (auto BO = dyn_cast<BinaryOperator>(this))
+    return BO->getFPFeaturesInEffect(LO);
+  if (auto Cast = dyn_cast<CastExpr>(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<NonTrivialCallFinder>

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e8f132dd4803..9bc4afd0b9f8 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2418,14 +2418,60 @@ static bool HandleFloatToIntCast(EvalInfo &Info, const Expr *E,
   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<CastExpr>(E) || isa<CompoundAssignOperator>(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 @@ static bool handleIntIntBinOp(EvalInfo &Info, const Expr *E, const APSInt &LHS,
 }
 
 /// 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 @@ static bool handleFloatFloatBinOp(EvalInfo &Info, const Expr *E,
     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 @@ static bool handleCompareOpForVector(const APValue &LHSValue,
 }
 
 // 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 @@ static bool handleAssignment(EvalInfo &Info, const Expr *E, const LValue &LVal,
 namespace {
 struct CompoundAssignSubobjectHandler {
   EvalInfo &Info;
-  const Expr *E;
+  const CompoundAssignOperator *E;
   QualType PromotedLHSType;
   BinaryOperatorKind Opcode;
   const APValue &RHS;
@@ -4197,10 +4247,12 @@ struct CompoundAssignSubobjectHandler {
 const AccessKinds CompoundAssignSubobjectHandler::AccessKind;
 
 /// Perform a compound assignment of LVal <op>= 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
index acbc0434931d..e3132752546f 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1105,6 +1105,8 @@ void TextNodeDumper::VisitCompoundAssignOperator(
   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
index bd5fc586b6af..180c7b21eecc 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -981,7 +981,9 @@ void Sema::ActOnPragmaFPReassociate(SourceLocation Loc, bool IsEnabled) {
 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
index 000000000000..c957459c05a5
--- /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
index 000000000000..bf512ff70658
--- /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
index 000000000000..9c807b34625f
--- /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 
diff er.
+
+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


        


More information about the cfe-commits mailing list