-
Notifications
You must be signed in to change notification settings - Fork 10.8k
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
[FPEnv] Add strictfp in some C++ constructors lacking a FunctionDecl. #74883
Conversation
Some C++ constructors require the strictfp attribute on all function calls but don't get it because the comstructor lacks a FunctionDecl. Use the IRBuilder's strictfp mode to communicate that the strictfp attribute is required. Note that the function calls that were missing the attribute were getting it from the IRBuilder but it then was getting lost when CGCall.cpp replaced all the attributes for the function call. Verified with "https://reviews.llvm.org/D146845".
@llvm/pr-subscribers-clang-codegen @llvm/pr-subscribers-clang Author: Kevin P. Neal (kpneal) ChangesSome C++ constructors require the strictfp attribute on all function calls but don't get it because the comstructor lacks a FunctionDecl. Use the IRBuilder's strictfp mode to communicate that the strictfp attribute is required. Note that the function calls that were missing the attribute were getting it from the IRBuilder but it then was getting lost when CGCall.cpp replaced all the attributes for the function call. Verified with "https://reviews.llvm.org/D146845". Full diff: https://github.com/llvm/llvm-project/pull/74883.diff 4 Files Affected:
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 24278016431837..f9269082c32475 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -1698,6 +1698,10 @@ class CXXConstructExpr : public Expr {
SourceRange getParenOrBraceRange() const { return ParenOrBraceRange; }
void setParenOrBraceRange(SourceRange Range) { ParenOrBraceRange = Range; }
+ /// Determine whether the function was declared in source context
+ /// that requires constrained FP intrinsics
+ bool UsesFPIntrin() const { return Constructor->UsesFPIntrin(); }
+
static bool classof(const Stmt *T) {
return T->getStmtClass() == CXXConstructExprClass ||
T->getStmtClass() == CXXTemporaryObjectExprClass;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index a24aeea7ae32bf..7d1bb4dc75ab45 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5520,6 +5520,12 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
CGM.AdjustMemoryAttribute(CalleePtr->getName(), Callee.getAbstractInfo(),
Attrs);
}
+ // We may not have a FunctionDecl*, but we still need to support strictfp.
+ if (Builder.getIsFPConstrained()) {
+ // All calls within a strictfp function are marked strictfp
+ Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::StrictFP);
+ }
+
// Add call-site nomerge attribute if exists.
if (InNoMergeAttributedStmt)
Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::NoMerge);
@@ -5587,10 +5593,12 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
!isa_and_nonnull<FunctionDecl>(TargetDecl))
EmitKCFIOperandBundle(ConcreteCallee, BundleList);
+#if 0 // XXX Why is this here? Duplicate!
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CurFuncDecl))
if (FD->hasAttr<StrictFPAttr>())
// All calls within a strictfp function are marked strictfp
Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::StrictFP);
+#endif
AssumeAlignedAttrEmitter AssumeAlignedAttrEmitter(*this, TargetDecl);
Attrs = AssumeAlignedAttrEmitter.TryEmitAsCallSiteAttribute(Attrs);
diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp
index e08a1e5f42df20..5700b7fcb49d32 100644
--- a/clang/lib/CodeGen/CGDeclCXX.cpp
+++ b/clang/lib/CodeGen/CGDeclCXX.cpp
@@ -1012,6 +1012,11 @@ void CodeGenFunction::GenerateCXXGlobalVarDeclInitFunc(llvm::Function *Fn,
CurEHLocation = D->getBeginLoc();
+ if (const auto *CE = dyn_cast<CXXConstructExpr>(D->getInit())) {
+ if (CE->UsesFPIntrin())
+ Builder.setIsFPConstrained(true);
+ }
+
StartFunction(GlobalDecl(D, DynamicInitKind::Initializer),
getContext().VoidTy, Fn, getTypes().arrangeNullaryFunction(),
FunctionArgList());
diff --git a/clang/test/CodeGen/fp-floatcontrol-class.cpp b/clang/test/CodeGen/fp-floatcontrol-class.cpp
index 83a27cb206eb82..c9953119fd3a22 100644
--- a/clang/test/CodeGen/fp-floatcontrol-class.cpp
+++ b/clang/test/CodeGen/fp-floatcontrol-class.cpp
@@ -1,3 +1,4 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --include-generated-funcs --version 4
// RUN: %clang_cc1 -ffp-contract=on -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
// Verify that float_control does not pertain to initializer expressions
@@ -6,14 +7,80 @@ float z();
#pragma float_control(except, on)
class ON {
float w = 2 + y() * z();
- // CHECK-LABEL: define {{.*}} @_ZN2ONC2Ev{{.*}}
- // CHECK: llvm.experimental.constrained.fmul{{.*}}tonearest{{.*}}strict
};
ON on;
#pragma float_control(except, off)
class OFF {
float w = 2 + y() * z();
- // CHECK-LABEL: define {{.*}} @_ZN3OFFC2Ev{{.*}}
- // CHECK-NOT: llvm.experimental.constrained.fmul{{.*}}tonearest{{.*}}strict
};
OFF off;
+// CHECK-LABEL: define internal void @__cxx_global_var_init(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] section ".text.startup" {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @_ZN2ONC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @on) #[[ATTR6:[0-9]+]]
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN2ONC1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]]) unnamed_addr #[[ATTR1:[0-9]+]] comdat align 2 {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[THIS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[THIS]], ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: call void @_ZN2ONC2Ev(ptr noundef nonnull align 4 dereferenceable(4) [[THIS1]]) #[[ATTR6]]
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal void @__cxx_global_var_init.1(
+// CHECK-SAME: ) #[[ATTR0]] section ".text.startup" {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @_ZN3OFFC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @off)
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN3OFFC1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]]) unnamed_addr #[[ATTR2:[0-9]+]] comdat align 2 {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[THIS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[THIS]], ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: call void @_ZN3OFFC2Ev(ptr noundef nonnull align 4 dereferenceable(4) [[THIS1]])
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN2ONC2Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]]) unnamed_addr #[[ATTR1]] comdat align 2 {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[THIS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[THIS]], ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: [[W:%.*]] = getelementptr inbounds [[CLASS_ON:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK-NEXT: [[CONV:%.*]] = call float @llvm.experimental.constrained.sitofp.f32.i32(i32 2, metadata !"round.tonearest", metadata !"fpexcept.strict") #[[ATTR6]]
+// CHECK-NEXT: [[CALL:%.*]] = call noundef float @_Z1yv() #[[ATTR6]]
+// CHECK-NEXT: [[CALL2:%.*]] = call noundef float @_Z1zv() #[[ATTR6]]
+// CHECK-NEXT: [[TMP0:%.*]] = call float @llvm.experimental.constrained.fmuladd.f32(float [[CALL]], float [[CALL2]], float [[CONV]], metadata !"round.tonearest", metadata !"fpexcept.strict") #[[ATTR6]]
+// CHECK-NEXT: store float [[TMP0]], ptr [[W]], align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN3OFFC2Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]]) unnamed_addr #[[ATTR2]] comdat align 2 {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[THIS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[THIS]], ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT: [[W:%.*]] = getelementptr inbounds [[CLASS_OFF:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK-NEXT: [[CALL:%.*]] = call noundef float @_Z1yv()
+// CHECK-NEXT: [[CALL2:%.*]] = call noundef float @_Z1zv()
+// CHECK-NEXT: [[TMP0:%.*]] = call float @llvm.fmuladd.f32(float [[CALL]], float [[CALL2]], float 2.000000e+00)
+// CHECK-NEXT: store float [[TMP0]], ptr [[W]], align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal void @_GLOBAL__sub_I_fp_floatcontrol_class.cpp(
+// CHECK-SAME: ) #[[ATTR0]] section ".text.startup" {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @__cxx_global_var_init()
+// CHECK-NEXT: call void @__cxx_global_var_init.1()
+// CHECK-NEXT: ret void
+//
|
clang/lib/CodeGen/CGCall.cpp
Outdated
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CurFuncDecl)) | ||
if (FD->hasAttr<StrictFPAttr>()) | ||
// All calls within a strictfp function are marked strictfp | ||
Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::StrictFP); | ||
#endif |
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.
Please remove this instead of #if 0
ing 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.
Done.
}; | ||
OFF off; | ||
// CHECK-LABEL: define internal void @__cxx_global_var_init( | ||
// CHECK-SAME: ) #[[ATTR0:[0-9]+]] section ".text.startup" { |
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.
- Are all of these checks necessary? If so, please explain why in the test so that maintainers will understand what you're testing for.
- Do you need to be checking the actual content of the attributes 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.
I missed the argument to update_cc_test_checks.py that includes the attribute comments. Fixed. Is the new comment about checking for strictfp sufficient? Or do I need to include a mention of the documentation in the LangRef?
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.
So here's the problem with these autogenerated checks: you're testing for a bunch of stuff you don't really care about and which we'll probably regret testing for in the future, and you've also lost some of the expressive intent of the old test. Like, you don't actually care that the this
argument is noundef nonnull align 4 dereferenceable(4)
, and when we inevitably come up with yet another attribute to slap on this
, we'll have to modify this test for no good reason. Meanwhile, the old test was pretty clear that it was testing whether the arithmetic in the constructor is done with constrained FP intrinsics, because the check lines were right next to the code involved, and now that isn't obvious at all.
In general:
- Please position checks in the test file so that they're clearly associated with the code that they're testing.
- Please edit the autogenerated checks down to make sure they're just checking the things you actually care about.
- Please think about how other programmers will read this test and understand what you're testing for.
In specific, the only new thing you're really testing for here is, what, that the strictfp
attribute is on _ZN2ONC2Ev
and not on _ZN3OFFC2Ev
? Can you test for that?
clang/lib/CodeGen/CGCall.cpp
Outdated
@@ -5520,6 +5520,12 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, | |||
CGM.AdjustMemoryAttribute(CalleePtr->getName(), Callee.getAbstractInfo(), | |||
Attrs); | |||
} | |||
// We may not have a FunctionDecl*, but we still need to support strictfp. |
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.
This comment is explaining the patch, not explaining the code as it stands after the patch. I suggest removing this and just pulling the comment inside the if
up here, since it explains why this block has to exist.
}; | ||
OFF off; | ||
// CHECK-LABEL: define internal void @__cxx_global_var_init( | ||
// CHECK-SAME: ) #[[ATTR0:[0-9]+]] section ".text.startup" { |
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.
So here's the problem with these autogenerated checks: you're testing for a bunch of stuff you don't really care about and which we'll probably regret testing for in the future, and you've also lost some of the expressive intent of the old test. Like, you don't actually care that the this
argument is noundef nonnull align 4 dereferenceable(4)
, and when we inevitably come up with yet another attribute to slap on this
, we'll have to modify this test for no good reason. Meanwhile, the old test was pretty clear that it was testing whether the arithmetic in the constructor is done with constrained FP intrinsics, because the check lines were right next to the code involved, and now that isn't obvious at all.
In general:
- Please position checks in the test file so that they're clearly associated with the code that they're testing.
- Please edit the autogenerated checks down to make sure they're just checking the things you actually care about.
- Please think about how other programmers will read this test and understand what you're testing for.
In specific, the only new thing you're really testing for here is, what, that the strictfp
attribute is on _ZN2ONC2Ev
and not on _ZN3OFFC2Ev
? Can you test for that?
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 sorry for failing to notice this in my previous review, but I don't think this patch is right. A global initializer should be FP-constrained based on whether the variable is defined in an FP-constrained context. This is testing whether the outermost level of the initializer is a call to an FP-constrained constructor. That means that we will not treat the initializer as FP constrained if it's not of class type or if it calls a non-FP-constrained constructor but passes arguments that require FP-constrained code emission. (It will also be wrong if the expression requires temporaries, because then the expression will not be directly a CXXConstructExpr
.)
If we don't currently capture in the AST whether a global variable definition is in an FP-constrained context, that seems like something we should fix.
OK, I'll head in the direction you've pointed. Thanks for your time! |
Some C++ constructors require the strictfp attribute on all function calls but don't get it because the comstructor lacks a FunctionDecl. Use the IRBuilder's strictfp mode to communicate that the strictfp attribute is required. Note that the function calls that were missing the attribute were getting it from the IRBuilder but it then was getting lost when CGCall.cpp replaced all the attributes for the function call.
Verified with "https://reviews.llvm.org/D146845".