-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Implementation of '#pragma STDC FENV_ROUND' #89617
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
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-backend-risc-v @llvm/pr-subscribers-clang-codegen Author: Serge Pavlov (spavloff) ChangesThis pragma is introduced by forthcoming C2x standard and can be used to set particular rounding mode without need to call 'fesetmode' or accessing control mode registers directly. Previously this pragma was implemented in clang partially, only for the purpose of using in constant expressions and making tests. This change implements the pragma according to the standard draft. It sets up dynamic rounding mode in the compound statement where the pragma acts. This is inevitable for targets that set rounding mode by changing some control register. Targets that support static rounding mode encoded in instructions can have more efficient implementation, it is not implemented in this change. The implementation uses intrinsic functions 'get_rounding' and 'set_rounding' to save/restore dynamic rounding mode. In some cases using functions that operate entire set of control modes or even FP environment may give more efficient implementation. This optimization is not a part of this change. Patch is 33.34 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/89617.diff 11 Files Affected:
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 1b9c9231047717..0ee9c13df75e41 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -1663,6 +1663,15 @@ class CompoundStmt final
return *getTrailingObjects<FPOptionsOverride>();
}
+ /// Get FPOptions inside this statement. They may differ from the outer
+ /// options due to pragmas.
+ /// \param CurFPOptions FPOptions outside this statement.
+ FPOptions getNewFPOptions(FPOptions CurFPOptions) const {
+ return hasStoredFPFeatures()
+ ? getStoredFPFeatures().applyOverrides(CurFPOptions)
+ : CurFPOptions;
+ }
+
using body_iterator = Stmt **;
using body_range = llvm::iterator_range<body_iterator>;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index bb9ca2a50cc06c..cbda8975717d2e 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1255,9 +1255,6 @@ def ext_stdc_pragma_ignored : ExtWarn<"unknown pragma in STDC namespace">,
// The C standard 7.6.1p2 says "The [FENV_ACCESS] pragma shall occur either
// outside external declarations or preceding all explicit declarations and
// statements inside a compound statement.
-def warn_stdc_fenv_round_not_supported :
- Warning<"pragma STDC FENV_ROUND is not supported">,
- InGroup<UnknownPragmas>;
def warn_stdc_unknown_rounding_mode : Warning<
"invalid or unsupported rounding mode in '#pragma STDC FENV_ROUND' - ignored">,
InGroup<IgnoredPragmas>;
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 24b109e32cdd3e..2ec642e25ddcc6 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -834,6 +834,12 @@ class FPOptions {
getAllowFEnvAccess();
}
+ /// Checks if the rounding mode is unknown at compile-time.
+ bool isRoundingModeDynamic() const {
+ return (getConstRoundingMode() == RoundingMode::Dynamic) &&
+ (getAllowFEnvAccess() || getRoundingMath());
+ }
+
RoundingMode getRoundingMode() const {
RoundingMode RM = getConstRoundingMode();
if (RM == RoundingMode::Dynamic) {
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 576fe2f7a2d46f..4fbc906afaeed5 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -486,6 +486,56 @@ bool CodeGenFunction::EmitSimpleStmt(const Stmt *S,
return true;
}
+namespace {
+/// Cleanup action that restores floating-point control modes upon leaving
+/// a scope.
+struct FPControlModesCleanup final : EHScopeStack::Cleanup {
+ llvm::Value *PreviousModes;
+ FPControlModesCleanup(llvm::Value *M) : PreviousModes(M) {}
+ void Emit(CodeGenFunction &CGF, Flags flags) override {
+ CGF.Builder.CreateIntrinsic(llvm::Intrinsic::set_rounding, {},
+ {PreviousModes});
+ }
+};
+} // namespace
+
+void CodeGenFunction::emitSetFPControlModes(FPOptions NewFP) {
+ if (NewFP == CurFPFeatures)
+ return;
+
+ // For now only rounding mode is handled.
+
+ // If the new rounding mode is unknown in compile-time, it means that the
+ // compound statement contains `#pragma STDC FENV_ACCESS ON`. In this case all
+ // manipulations on FP environment, including setting and restoring control
+ // modes are made by the user.
+ if (NewFP.isRoundingModeDynamic())
+ return;
+
+ llvm::RoundingMode OldConstRM = CurFPFeatures.getConstRoundingMode();
+ llvm::RoundingMode NewConstRM = NewFP.getConstRoundingMode();
+ if (OldConstRM == NewConstRM)
+ return;
+
+ llvm::RoundingMode OldRM = CurFPFeatures.getRoundingMode();
+ if (OldRM == NewConstRM)
+ return;
+
+ llvm::Value *PreviousRM = nullptr;
+ if (CurFPFeatures.isRoundingModeDynamic()) {
+ llvm::Function *FGetRound = CGM.getIntrinsic(llvm::Intrinsic::get_rounding);
+ PreviousRM = Builder.CreateCall(FGetRound);
+ } else {
+ PreviousRM = llvm::ConstantInt::get(Int32Ty, static_cast<uint64_t>(OldRM));
+ }
+
+ llvm::RoundingMode NewRM = NewFP.getRoundingMode();
+ Builder.CreateIntrinsic(
+ llvm::Intrinsic::set_rounding, {},
+ llvm::ConstantInt::get(Int32Ty, static_cast<uint64_t>(NewRM)));
+ EHStack.pushCleanup<FPControlModesCleanup>(NormalAndEHCleanup, PreviousRM);
+}
+
/// EmitCompoundStmt - Emit a compound statement {..} node. If GetLast is true,
/// this captures the expression result of the last sub-statement and returns it
/// (for use by the statement expression extension).
@@ -509,6 +559,12 @@ CodeGenFunction::EmitCompoundStmtWithoutScope(const CompoundStmt &S,
assert((!GetLast || (GetLast && ExprResult)) &&
"If GetLast is true then the CompoundStmt must have a StmtExprResult");
+ // Optionally set up the new FP environment, if the compound statement
+ // contains a pragma that modifies it.
+ FPOptions NewFP = S.getNewFPOptions(CurFPFeatures);
+ emitSetFPControlModes(NewFP);
+ CGFPOptionsRAII SavedFPFeatues(*this, NewFP);
+
Address RetAlloca = Address::invalid();
for (auto *CurStmt : S.body()) {
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index c49e9fd00c8d3e..4964a1c6c0c5fb 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3323,6 +3323,9 @@ class CodeGenFunction : public CodeGenTypeCache {
/// Get the record field index as represented in debug info.
unsigned getDebugInfoFIndex(const RecordDecl *Rec, unsigned FieldIndex);
+ /// Optionally emit code that sets required floating-point control modes and
+ /// creates corresponding cleanup action.
+ void emitSetFPControlModes(FPOptions NewFP);
//===--------------------------------------------------------------------===//
// Declaration Emission
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 3979f75b6020db..56b15bda975a9a 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -3461,9 +3461,6 @@ void PragmaSTDC_FENV_ROUNDHandler::HandlePragma(Preprocessor &PP,
return;
}
- // Until the pragma is fully implemented, issue a warning.
- PP.Diag(Tok.getLocation(), diag::warn_stdc_fenv_round_not_supported);
-
MutableArrayRef<Token> Toks(PP.getPreprocessorAllocator().Allocate<Token>(1),
1);
Toks[0].startToken();
diff --git a/clang/test/CodeGen/complex-strictfp.c b/clang/test/CodeGen/complex-strictfp.c
index b0c84d5eebe725..e1f5405b921978 100644
--- a/clang/test/CodeGen/complex-strictfp.c
+++ b/clang/test/CodeGen/complex-strictfp.c
@@ -17,16 +17,18 @@ double D;
// CHECK-LABEL: @test3a(
// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3:[0-9]+]]
// CHECK-NEXT: [[TMP0:%.*]] = load double, ptr @D, align 8
// CHECK-NEXT: [[CF_REAL:%.*]] = load float, ptr @cf, align 4
// CHECK-NEXT: [[CF_IMAG:%.*]] = load float, ptr getelementptr inbounds ({ float, float }, ptr @cf, i32 0, i32 1), align 4
-// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_REAL]], metadata !"fpexcept.strict") #[[ATTR2:[0-9]+]]
-// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_IMAG]], metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[CONV]], double [[TMP0]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[CONV2:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[ADD_R]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[CONV3:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[CONV1]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_REAL]], metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_IMAG]], metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[CONV]], double [[TMP0]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CONV2:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[ADD_R]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CONV3:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[CONV1]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store float [[CONV2]], ptr @cf, align 4
// CHECK-NEXT: store float [[CONV3]], ptr getelementptr inbounds ({ float, float }, ptr @cf, i32 0, i32 1), align 4
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void test3a(void) {
@@ -35,13 +37,15 @@ void test3a(void) {
// CHECK-LABEL: @test3b(
// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
// CHECK-NEXT: [[CF_REAL:%.*]] = load float, ptr @cf, align 4
// CHECK-NEXT: [[CF_IMAG:%.*]] = load float, ptr getelementptr inbounds ({ float, float }, ptr @cf, i32 0, i32 1), align 4
-// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_REAL]], metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_IMAG]], metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_REAL]], metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_IMAG]], metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: [[TMP0:%.*]] = load double, ptr @D, align 8
-// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[TMP0]], double [[CONV]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[TMP0]], double [[CONV]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store double [[ADD_R]], ptr @D, align 8
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void test3b(void) {
@@ -50,19 +54,21 @@ void test3b(void) {
// CHECK-LABEL: @test3c(
// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
// CHECK-NEXT: [[G1_REAL:%.*]] = load double, ptr @g1, align 8
// CHECK-NEXT: [[G1_IMAG:%.*]] = load double, ptr getelementptr inbounds ({ double, double }, ptr @g1, i32 0, i32 1), align 8
// CHECK-NEXT: [[CF_REAL:%.*]] = load float, ptr @cf, align 4
// CHECK-NEXT: [[CF_IMAG:%.*]] = load float, ptr getelementptr inbounds ({ float, float }, ptr @cf, i32 0, i32 1), align 4
-// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_REAL]], metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_IMAG]], metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[CALL:%.*]] = call { double, double } @__divdc3(double noundef [[CONV]], double noundef [[CONV1]], double noundef [[G1_REAL]], double noundef [[G1_IMAG]]) #[[ATTR3:[0-9]+]]
+// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_REAL]], metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float [[CF_IMAG]], metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CALL:%.*]] = call { double, double } @__divdc3(double noundef [[CONV]], double noundef [[CONV1]], double noundef [[G1_REAL]], double noundef [[G1_IMAG]]) #[[ATTR4:[0-9]+]]
// CHECK-NEXT: [[TMP0:%.*]] = extractvalue { double, double } [[CALL]], 0
// CHECK-NEXT: [[TMP1:%.*]] = extractvalue { double, double } [[CALL]], 1
-// CHECK-NEXT: [[CONV2:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[TMP0]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
-// CHECK-NEXT: [[CONV3:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[TMP1]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV2:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[TMP0]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
+// CHECK-NEXT: [[CONV3:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double [[TMP1]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store float [[CONV2]], ptr @cf, align 4
// CHECK-NEXT: store float [[CONV3]], ptr getelementptr inbounds ({ float, float }, ptr @cf, i32 0, i32 1), align 4
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void test3c(void) {
@@ -71,12 +77,14 @@ void test3c(void) {
// CHECK-LABEL: @test3d(
// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
// CHECK-NEXT: [[G1_REAL:%.*]] = load double, ptr @g1, align 8
// CHECK-NEXT: [[G1_IMAG:%.*]] = load double, ptr getelementptr inbounds ({ double, double }, ptr @g1, i32 0, i32 1), align 8
// CHECK-NEXT: [[TMP0:%.*]] = load double, ptr @D, align 8
-// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[G1_REAL]], double [[TMP0]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[G1_REAL]], double [[TMP0]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store double [[ADD_R]], ptr @g1, align 8
// CHECK-NEXT: store double [[G1_IMAG]], ptr getelementptr inbounds ({ double, double }, ptr @g1, i32 0, i32 1), align 8
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void test3d(void) {
@@ -85,12 +93,14 @@ void test3d(void) {
// CHECK-LABEL: @test3e(
// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
// CHECK-NEXT: [[TMP0:%.*]] = load double, ptr @D, align 8
// CHECK-NEXT: [[G1_REAL:%.*]] = load double, ptr @g1, align 8
// CHECK-NEXT: [[G1_IMAG:%.*]] = load double, ptr getelementptr inbounds ({ double, double }, ptr @g1, i32 0, i32 1), align 8
-// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[TMP0]], double [[G1_REAL]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[ADD_R:%.*]] = call double @llvm.experimental.constrained.fadd.f64(double [[TMP0]], double [[G1_REAL]], metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store double [[ADD_R]], ptr @g1, align 8
// CHECK-NEXT: store double [[G1_IMAG]], ptr getelementptr inbounds ({ double, double }, ptr @g1, i32 0, i32 1), align 8
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void test3e(void) {
@@ -99,8 +109,10 @@ void test3e(void) {
// CHECK-LABEL: @t1(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[CONV:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double 4.000000e+00, metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
+// CHECK-NEXT: [[CONV:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double 4.000000e+00, metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store float [[CONV]], ptr @cf, align 4
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void t1(void) {
@@ -109,8 +121,10 @@ void t1(void) {
// CHECK-LABEL: @t2(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[CONV:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double 4.000000e+00, metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
+// CHECK-NEXT: [[CONV:%.*]] = call float @llvm.experimental.constrained.fptrunc.f32.f64(double 4.000000e+00, metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: store float [[CONV]], ptr getelementptr inbounds ({ float, float }, ptr @cf, i32 0, i32 1), align 4
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void t2(void) {
@@ -120,16 +134,18 @@ void t2(void) {
// CHECK-LABEL: @t91(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[C:%.*]] = alloca [0 x i8], align 1
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
// CHECK-NEXT: br i1 false, label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
// CHECK: cond.true:
-// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: br label [[COND_END:%.*]]
// CHECK: cond.false:
-// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: br label [[COND_END]]
// CHECK: cond.end:
// CHECK-NEXT: [[COND_R:%.*]] = phi double [ [[CONV]], [[COND_TRUE]] ], [ [[CONV1]], [[COND_FALSE]] ]
// CHECK-NEXT: [[COND_I:%.*]] = phi double [ 0.000000e+00, [[COND_TRUE]] ], [ 0.000000e+00, [[COND_FALSE]] ]
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void t91(void) {
@@ -142,16 +158,18 @@ void t91(void) {
// CHECK-LABEL: @t92(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[C:%.*]] = alloca [0 x i8], align 1
+// CHECK-NEXT: call void @llvm.set.rounding(i32 2) #[[ATTR3]]
// CHECK-NEXT: br i1 false, label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
// CHECK: cond.true:
-// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: br label [[COND_END:%.*]]
// CHECK: cond.false:
-// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR2]]
+// CHECK-NEXT: [[CONV1:%.*]] = call double @llvm.experimental.constrained.fpext.f64.f32(float 2.000000e+00, metadata !"fpexcept.strict") #[[ATTR3]]
// CHECK-NEXT: br label [[COND_END]]
// CHECK: cond.end:
// CHECK-NEXT: [[COND_R:%.*]] = phi double [ [[CONV]], [[COND_TRUE]] ], [ [[CONV1]], [[COND_FALSE]] ]
// CHECK-NEXT: [[COND_I:%.*]] = phi double [ 0.000000e+00, [[COND_TRUE]] ], [ 0.000000e+00, [[COND_FALSE]] ]
+// CHECK-NEXT: call void @llvm.set.rounding(i32 1) #[[ATTR3]]
// CHECK-NEXT: ret void
//
void t92(void) {
diff --git a/clang/test/CodeGen/math-errno.c b/clang/test/CodeGen/math-errno.c
index b5354e47e26b77..b8da81b585fa53 100644
--- a/clang/test/CodeGen/math-errno.c
+++ b/clang/test/CodeGen/math-errno.c
@@ -27,7 +27,7 @@ float f1(float x) {
// CHECK: tail call float @sqrtf(float noundef {{.*}}) #[[ATTR4_O2:[0-9]+]]
// FAST-LABEL: define {{.*}} nofpclass(nan inf) float @f1
-// FAST: call fast nofpclass(nan inf) float @sqrtf(float noundef nofpclass(nan inf) {{.*}}) #[[ATTR3_FAST:[0-9]+]]
+// FAST: call nofpclass(nan inf) float @sqrtf(float noundef nofpclass(nan inf) {{.*}}) #[[ATTR3_FAST:[0-9]+]]
// NOOPT-LABEL: define {{.*}} float @f1
// NOOPT...
[truncated]
|
You can test this locally with the following command:git-clang-format --diff 1aeb64c8ec7b96b2301929d8a325a6e1d9ddaa2f cf6539667a9f3917cd85ff611ed813fb46304b2b -- clang/test/CodeGen/pragma-fenv_round.c clang/include/clang/AST/Stmt.h clang/include/clang/Basic/LangOptions.h clang/include/clang/Basic/TargetInfo.h clang/lib/Basic/Targets/RISCV.h clang/lib/CodeGen/CGCall.cpp clang/lib/CodeGen/CGStmt.cpp clang/lib/CodeGen/CodeGenFunction.cpp clang/lib/CodeGen/CodeGenFunction.h clang/lib/Parse/ParsePragma.cpp clang/test/CodeGen/complex-strictfp.c clang/test/CodeGen/math-errno.c clang/test/CodeGen/pragma-fenv_access.c clang/test/Parser/pragma-fenv_round.c View the diff from clang-format here.diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 8489af7b3d..a5758f1c69 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3340,7 +3340,6 @@ public:
void restoreRoundingModeAfterCall();
bool requiresDynamicRounding(const CGCallee &Callee);
-
//===--------------------------------------------------------------------===//
// Declaration Emission
//===--------------------------------------------------------------------===//
|
fc7aab6
to
0fc5c57
Compare
How about adding tests with |
// CHECK: call void @llvm.set.rounding(i32 0) | ||
// CHECK: call float @llvm.experimental.constrained.fmul.f32({{.*}}, metadata !"round.towardzero", metadata !"fpexcept.ignore") | ||
// CHECK: call void @llvm.set.rounding(i32 1) | ||
// CHECK: call float @llvm.experimental.constrained.fadd.f32({{.*}}, metadata !"round.tonearest", metadata !"fpexcept.ignore") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the result += z
be dynamic here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't fully tested the changes yet, so right now I'm looking at the test to figure out how much is supported. Nevertheless, I can already tell that this is not complete support.
7.6.2p4 does clearly state that floating constants need to be evaluated according to the standard rounding mode, so that the constant 0.1
evaluates to a different value in FE_DOWNWARD
versus FE_UPWARD
.
I'm not seeing from the tests how the code is handling calls to functions. Calls to all functions outside of a finite list (see same paragraph) need to restore the saved dynamic rounding mode for the duration of the call.
I'd like to see tests covering casts and conversions better.
This pragma is introduced by forthcoming C2x standard and can be used to set particular rounding mode without need to call 'fesetmode' or accessing control mode registers directly. Previously this pragma was implemented in clang partially, only for the purpose of using in constant expressions and making tests. This change implements the pragma according to the standard draft. It sets up dynamic rounding mode in the compound statement where the pragma acts. This is inevitable for targets that set rounding mode by changing some control register. Targets that support static rounding mode encoded in instructions can have more efficient implementation, it is not implemented in this change. The implementation uses intrinsic functions 'get_rounding' and 'set_rounding' to save/restore dynamic rounding mode. In some cases using functions that operate entire set of control modes or even FP environment may give more efficient implementation. This optimization is not a part of this change.
Treatment of floating-point constants is fixed in #90877.
The latest update to this PR fixes this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for just thinking of this now, but we should also have tests for some of the builtins like __builtin_fma
or __builtin_sqrt
.
@@ -1232,6 +1232,14 @@ class TargetInfo : public TransferrableTargetInfo, | |||
return true; | |||
} | |||
|
|||
/// Returns true, if an operations that depends on rounding mode can be | |||
/// implemented without changing FP environment. In this case the rounding | |||
/// mode is encoded in the bits of implementing instruction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we actually have any (non-target-specific) IR constructs at the moment that actually implement static rounding mode. The documentation for constrained intrinsics says:
For values other than “round.dynamic” optimization passes may assume that the actual runtime rounding mode (as defined in a target-specific manner) matches the specified rounding mode, but this is not guaranteed.
It's also the case that static rounding mode may be a less-than-global decision. X86 AVX512/AVX10 has static rounding mode, which is a subtarget consideration, but even then, it's not entirely clear that they would be absolutely preferred.
// If a reserved identifier ends with rounding mode suffix preceded by | ||
// underscore, this function does not need the previous dynamic rounding | ||
// mode to be set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this rule coming from?
if (Target.hasStaticRounding() || Callee.isBuiltin() || | ||
!requiresDynamicRounding(Callee)) | ||
return; | ||
if (!CurrentRoundingIsStatic || !DynamicRoundingMode) | ||
return; | ||
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::set_rounding), | ||
DynamicRoundingMode); | ||
CurrentRoundingIsStatic = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing logic here to effect the proper handling of, e.g., sqrt
. Is this planned in a future patch? If so, add in a TODO note.
This pragma is introduced by forthcoming C2x standard and can be used to set particular rounding mode without need to call 'fesetmode' or accessing control mode registers directly. Previously this pragma was implemented in clang partially, only for the purpose of using in constant expressions and making tests.
The pragma sets rounding mode that should be used for all operations on floating-point values, both in compile and runtime. This mode is referenced as static rounding mode. Some targets allow encoding rounding mode in instructions, they will use this mode to set instruction fields. Rounding mode also can be specified by some hardware register, the content of this register determines dynamic rounding mode. Some targets support only dynamic mode, for them the requested static rounding mode should be implemented using the dynamic.
Within the scope of the pragma the operations dependent on rounding mode are represented by constrained intrinsic calls using the requested rounding mode attribute. If target does not support static rounding, the requested rounding mode is also set as dynamic in the affected compound statement and restored to the previous value upon exit from it.
The Standard draft requires execution of most function calls with dynamic rounding mode, which was set previously. If a target does not have static rounding, the dynamic rounding mode must be restored before the function call and then set to static rounding mode after the call is finished. It may cause unneeded rounding mode switches. So some functions are executed with the current dynamic rounding mode rather than initial. These are: