-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang] Use constant rounding mode for floating literals #90877
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
Conversation
Conversion of floating-point literal to binary representation must be made using constant rounding mode, which can be changed using pragma FENV_ROUND. For example, the literal "0.1F" should be representes by either 0.099999994 or 0.100000001 depending on the rounding direction.
@llvm/pr-subscribers-clang Author: Serge Pavlov (spavloff) ChangesConversion of floating-point literal to binary representation must be made using constant rounding mode, which can be changed using pragma FENV_ROUND. For example, the literal "0.1F" should be representes by either 0.099999994 or 0.100000001 depending on the rounding direction. Full diff: https://github.com/llvm/llvm-project/pull/90877.diff 4 Files Affected:
diff --git a/clang/include/clang/Lex/LiteralSupport.h b/clang/include/clang/Lex/LiteralSupport.h
index 2ed42d1c5f9aae..705021fcfa5b11 100644
--- a/clang/include/clang/Lex/LiteralSupport.h
+++ b/clang/include/clang/Lex/LiteralSupport.h
@@ -118,12 +118,10 @@ class NumericLiteralParser {
/// bits of the result and return true. Otherwise, return false.
bool GetIntegerValue(llvm::APInt &Val);
- /// GetFloatValue - Convert this numeric literal to a floating value, using
- /// the specified APFloat fltSemantics (specifying float, double, etc).
- /// The optional bool isExact (passed-by-reference) has its value
- /// set to true if the returned APFloat can represent the number in the
- /// literal exactly, and false otherwise.
- llvm::APFloat::opStatus GetFloatValue(llvm::APFloat &Result);
+ /// Convert this numeric literal to a floating value, using the specified
+ /// APFloat fltSemantics (specifying float, double, etc) and rounding mode.
+ llvm::APFloat::opStatus GetFloatValue(llvm::APFloat &Result,
+ llvm::RoundingMode RM);
/// GetFixedPointValue - Convert this numeric literal value into a
/// scaled integer that represents this value. Returns true if an overflow
diff --git a/clang/lib/Lex/LiteralSupport.cpp b/clang/lib/Lex/LiteralSupport.cpp
index 9c0cbea5052cb2..3df0391bdda772 100644
--- a/clang/lib/Lex/LiteralSupport.cpp
+++ b/clang/lib/Lex/LiteralSupport.cpp
@@ -1520,7 +1520,8 @@ bool NumericLiteralParser::GetIntegerValue(llvm::APInt &Val) {
}
llvm::APFloat::opStatus
-NumericLiteralParser::GetFloatValue(llvm::APFloat &Result) {
+NumericLiteralParser::GetFloatValue(llvm::APFloat &Result,
+ llvm::RoundingMode RM) {
using llvm::APFloat;
unsigned n = std::min(SuffixBegin - ThisTokBegin, ThisTokEnd - ThisTokBegin);
@@ -1534,8 +1535,7 @@ NumericLiteralParser::GetFloatValue(llvm::APFloat &Result) {
Str = Buffer;
}
- auto StatusOrErr =
- Result.convertFromString(Str, APFloat::rmNearestTiesToEven);
+ auto StatusOrErr = Result.convertFromString(Str, RM);
assert(StatusOrErr && "Invalid floating point representation");
return !errorToBool(StatusOrErr.takeError()) ? *StatusOrErr
: APFloat::opInvalidOp;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 0cca02c338954a..db96c8692f2516 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -3858,7 +3858,8 @@ static Expr *BuildFloatingLiteral(Sema &S, NumericLiteralParser &Literal,
using llvm::APFloat;
APFloat Val(Format);
- APFloat::opStatus result = Literal.GetFloatValue(Val);
+ APFloat::opStatus result =
+ Literal.GetFloatValue(Val, S.CurFPFeatures.getRoundingMode());
// Overflow is always an error, but underflow is only an error if
// we underflowed to zero (APFloat reports denormals as underflow).
diff --git a/clang/test/AST/const-fpfeatures.c b/clang/test/AST/const-fpfeatures.c
index 6600ea27405d9c..247245c776fed1 100644
--- a/clang/test/AST/const-fpfeatures.c
+++ b/clang/test/AST/const-fpfeatures.c
@@ -19,6 +19,9 @@ float FI1u = 0xFFFFFFFFU;
float _Complex C1u = C0;
// CHECK: @C1u = {{.*}} { float, float } { float 0x3FF0000020000000, float 0x3FF0000020000000 }
+float FLu = 0.1F;
+// CHECK: @FLu = {{.*}} float 0x3FB9999980000000
+
#pragma STDC FENV_ROUND FE_DOWNWARD
@@ -35,3 +38,6 @@ float FI1d = 0xFFFFFFFFU;
float _Complex C1d = C0;
// CHECK: @C1d = {{.*}} { float, float } { float 1.000000e+00, float 1.000000e+00 }
+
+float FLd = 0.1F;
+// CHECK: @FLd = {{.*}} float 0x3FB99999A0000000
|
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.
probably needs a release note
clang/lib/Sema/SemaExpr.cpp
Outdated
@@ -3858,7 +3858,8 @@ static Expr *BuildFloatingLiteral(Sema &S, NumericLiteralParser &Literal, | |||
using llvm::APFloat; | |||
APFloat Val(Format); | |||
|
|||
APFloat::opStatus result = Literal.GetFloatValue(Val); | |||
APFloat::opStatus result = |
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.
Does this work right in templates? Can we get a test that validates that a NTTP or Dependent value where the rounding mode is set either around or inside the function does the math right?
Also, test failures are related to this patch! |
float value; | ||
}; | ||
|
||
#pragma STDC FENV_ROUND FE_DOWNWARD |
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 meant something like:
template<typename T, T V> void foo() {
#pragma STDC FENV_ROUND FE_DOWNWARD
T Val = V;
// Should be: { float 0x3FB9999980000000 }
}
#pragma STDC FENV_ROUND FE_UPDWARD
foo<float, 0.1F>();
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.
Ah, I see that github ate the first line of my comment, see above, i'v eedited it.
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.
In this snippet Val
in foo<float, 0.1F>()
should be float 0x3FB99999A0000000
(rounded upward), because the literal 0.1F
is in the region, where #pragma STDC FENV_ROUND FE_UPDWARD
is in effect. The body of foo
does not contain literals.
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.
In this snippet
Val
infoo<float, 0.1F>()
should befloat 0x3FB99999A0000000
(rounded upward), because the literal0.1F
is in the region, where#pragma STDC FENV_ROUND FE_UPDWARD
is in effect. The body offoo
does not contain literals.
Hmm... ok, then the feature isn't quite what I expected it was. Does the FENV_ROUND
exclusively change the behavior of literals? I thought it messed with math as well? (I thought it was on assignment, but perhaps not!).
I'm more concerned about a case where the point-of-instantiation and the template have different rounding modes. But I see this patch is exclusively for literals, so we can let that go.
One thing to ensure: can we have an AST test (like your 'foo' example) that shows that they are different instantiations? Are there any cases where we could get two instantiation-requests spelled differently, but because of different rounding modes, are the same instantiation?
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.
Does the FENV_ROUND exclusively change the behavior of literals? I thought it messed with math as well?
Of course, FENV_ROUND affects any math, but this patch fixes misbehavior of literals only. And the note was about different expectation.
Assignment isn't an operation that depends on rounding mode, but changing the snippet to such:
template<typename T, T V> void foo() {
#pragma STDC FENV_ROUND FE_DOWNWARD
T Val = V + 1;
}
would make use of the rounding mode. In this case the addition in foo
would be evaluated using rounding down, but the literal is converted to AST object using rounding up.
I'm more concerned about a case where the point-of-instantiation and the template have different rounding modes.
Does this test address your concern: https://github.com/spavloff/llvm-project/blob/aeb6074444513587924106081213335f73ba6eb0/clang/test/AST/ast-dump-fpfeatures.cpp#L124-L143 ?
One thing to ensure: can we have an AST test (like your 'foo' example) that shows that they are different instantiations? Are there any cases where we could get two instantiation-requests spelled differently, but because of different rounding modes, are the same instantiation?
All instantiations of a function template use the rounding mode specified in the template definition. Otherwise we would have bad things like two instantiations in different translation units that use different rounding modes. The rounding mode is attached to AST nodes in the AST representation for function templates, tests in AST/ast-dump-fpfeatures.cpp
check this property.
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.
Does this test address your concern: https://github.com/spavloff/llvm-project/blob/aeb6074444513587924106081213335f73ba6eb0/clang/test/AST/ast-dump-fpfeatures.cpp#L124-L143 ?
That one does not. I'm more looking for:
template<typename T, T f> void foo(){}
#pragma STDC FENV_ROUND FE_DOWNWARD
foo<float, 0.1f>();
#pragma STDC FENV_ROUND FE_UPWARD
foo<float, 0.1f>();
Then confirming that there are two separate instantiations of foo
.
All instantiations of a function template use the rounding mode specified in the template definition. Otherwise we would have bad things like two instantiations in different translation units that use different rounding modes.
Right, that was very much my concern, the ODR implications of that are scary. So jsut the 1 test above, and I think I am good about this, though having @jcranmer-intel and/or @zahiraam review would also be appreciated.
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.
The interaction of these pragmas with C++ is underspecified, although for the most part, you can make pretty reasonable expectations about what the results should be. I'm also planning on bringing a paper to C++ to clarify the semantics somewhat, with the intent mostly being "just don't allow the pragma in most places."
That said, templates do add in all sorts of fun extra complexity, and there are at least three scenarios worth testing (with regards to interpretation of constants within the template body):
- Implicit template instantiation (you already test this)
- Explicit template instantiation
- Template specialization
I can definitely agree that implicit template instantiation should inherit from the rounding mode of the definition. Template specialization probably shouldn't inherit from the original template definition. Explicit instantiation... I'm not sure? I can argue that one either way.
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.
Explicit instantiation also should use the rounding mode at the point of definition. It is possible that the template is instantiated in two translation units and rounding mode is different at the points of instantiation.
I've been doing some testing, and I do want to confirm my understanding of the C standard here. From what I can tell, macro expansion (phase 4) happens before constants are parsed (phase 7). As a result, if you have code like this: #define CONSTANT 0.1f the interpretation of #define rendevous(x) _Pragma(#x)
#define CONSTANT(RM, x) ([](){ rendevous(STDC FENV_ROUND RM); return x; })()) This does seem to indeed be the behavior of the current implementation, but I would like to see some tests in the test code confirming that it's the macro use, not the |
✅ With the latest revision this PR passed the C/C++ code formatter. |
You can test this locally with the following command:git-clang-format --diff 1aeb64c8ec7b96b2301929d8a325a6e1d9ddaa2f cf6ff55ad4a45875a821b3ac82c22bb7917b4d67 -- clang/include/clang/Lex/LiteralSupport.h clang/lib/Lex/LiteralSupport.cpp clang/lib/Sema/SemaExpr.cpp clang/test/AST/const-fpfeatures.c clang/test/AST/const-fpfeatures.cpp View the diff from clang-format here.diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index da107a6844..a344d1281e 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -3861,8 +3861,7 @@ static Expr *BuildFloatingLiteral(Sema &S, NumericLiteralParser &Literal,
llvm::RoundingMode RM = S.CurFPFeatures.getRoundingMode();
if (RM == llvm::RoundingMode::Dynamic)
RM = llvm::RoundingMode::NearestTiesToEven;
- APFloat::opStatus result =
- Literal.GetFloatValue(Val, RM);
+ APFloat::opStatus result = Literal.GetFloatValue(Val, RM);
// Overflow is always an error, but underflow is only an error if
// we underflowed to zero (APFloat reports denormals as underflow).
|
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 generally happy with the testing and semantics at this point.
Are there any other comments? Can this PR be considered as approved? |
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.
The changes should come with a release note so that users know about the behavioral change, but otherwise LGTM.
Pragma FENV_ROUND is still officially unsupported, compiler issues a warning about this. It is #89617 that makes FENV_ROUND supported, it requires release note. |
Conversion of floating-point literal to binary representation must be made using constant rounding mode, which can be changed using pragma FENV_ROUND. For example, the literal "0.1F" should be representes by either 0.099999994 or 0.100000001 depending on the rounding direction.