[clang] [Clang][Sema] Ensure explicitly defaulted functions respect FP pragmas from their declaration site (PR #207429)
Kyungtak Woo via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 3 10:45:40 PDT 2026
https://github.com/kevinwkt updated https://github.com/llvm/llvm-project/pull/207429
>From ae7336d03baf6a77e7a02f2d67b8c28c8964fe04 Mon Sep 17 00:00:00 2001
From: Kyungtak Woo <kevinwkt at google.com>
Date: Fri, 3 Jul 2026 17:05:36 +0000
Subject: [PATCH 1/2] [Clang][Sema] Ensure explicitly defaulted functions
respect FP pragmas from their declaration site
---
clang/include/clang/AST/Decl.h | 10 +-
clang/lib/AST/Decl.cpp | 3 +-
clang/lib/Sema/SemaDeclCXX.cpp | 45 +++++++-
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 5 +-
clang/lib/Serialization/ASTReaderDecl.cpp | 4 +-
clang/lib/Serialization/ASTWriterDecl.cpp | 2 +
.../defaulted-function-fp-features.cpp | 101 ++++++++++++++++++
7 files changed, 160 insertions(+), 10 deletions(-)
create mode 100644 clang/test/CodeGenCXX/defaulted-function-fp-features.cpp
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 88c900203630a..7f4ea059f699e 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2049,13 +2049,16 @@ class FunctionDecl : public DeclaratorDecl,
};
- /// Stashed information about a defaulted/deleted function body.
+ /// Stashed information about a defaulted/deleted function body, including
+ /// the active FP pragma overrides (FPOptionsOverride) from the declaration
+ /// site. These overrides are required to correctly synthesize the function body.
class DefaultedOrDeletedFunctionInfo final
: llvm::TrailingObjects<DefaultedOrDeletedFunctionInfo, DeclAccessPair,
StringLiteral *> {
friend TrailingObjects;
unsigned NumLookups;
bool HasDeletedMessage;
+ uint64_t FPFeatures;
size_t numTrailingObjects(OverloadToken<DeclAccessPair>) const {
return NumLookups;
@@ -2064,7 +2067,10 @@ class FunctionDecl : public DeclaratorDecl,
public:
static DefaultedOrDeletedFunctionInfo *
Create(ASTContext &Context, ArrayRef<DeclAccessPair> Lookups,
- StringLiteral *DeletedMessage = nullptr);
+ StringLiteral *DeletedMessage = nullptr,
+ uint64_t FPFeatures = 0);
+
+ uint64_t getFPFeatures() const { return FPFeatures; }
/// Get the unqualified lookup results that should be used in this
/// defaulted function definition.
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b23bf73ae803c..1b3976172db80 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3116,7 +3116,7 @@ bool FunctionDecl::isVariadic() const {
FunctionDecl::DefaultedOrDeletedFunctionInfo *
FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
ASTContext &Context, ArrayRef<DeclAccessPair> Lookups,
- StringLiteral *DeletedMessage) {
+ StringLiteral *DeletedMessage, uint64_t FPFeatures) {
static constexpr size_t Alignment =
std::max({alignof(DefaultedOrDeletedFunctionInfo),
alignof(DeclAccessPair), alignof(StringLiteral *)});
@@ -3127,6 +3127,7 @@ FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
new (Context.Allocate(Size, Alignment)) DefaultedOrDeletedFunctionInfo;
Info->NumLookups = Lookups.size();
Info->HasDeletedMessage = DeletedMessage != nullptr;
+ Info->FPFeatures = FPFeatures;
llvm::uninitialized_copy(Lookups, Info->getTrailingObjects<DeclAccessPair>());
if (DeletedMessage)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c1caf9a58d650..5969af4da3043 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6906,6 +6906,25 @@ Sema::getDefaultedFunctionKind(const FunctionDecl *FD) {
return DefaultedFunctionKind();
}
+namespace {
+/// RAII object to restore the floating-point (FP) features active at the time
+/// a defaulted function was declared. This ensures that the synthesized body
+/// of the function respects the FP pragmas (e.g., #pragma STDC FENV_ACCESS)
+/// that were in effect when the function was explicitly defaulted.
+struct DefaultedFunctionFPFeaturesRAII {
+ Sema::FPFeaturesStateRAII SavedFPFeatures;
+ DefaultedFunctionFPFeaturesRAII(Sema &S, FunctionDecl *FD)
+ : SavedFPFeatures(S) {
+ auto *Info = FD->getDefaultedOrDeletedInfo();
+ FPOptionsOverride FPO =
+ Info ? FPOptionsOverride::getFromOpaqueInt(Info->getFPFeatures())
+ : FPOptionsOverride();
+ S.CurFPFeatures = FPO.applyOverrides(S.LangOpts);
+ S.FpPragmaStack.CurrentValue = FPO;
+ }
+};
+} // namespace
+
static void DefineDefaultedFunction(Sema &S, FunctionDecl *FD,
SourceLocation DefaultLoc) {
Sema::DefaultedFunctionKind DFK = S.getDefaultedFunctionKind(FD);
@@ -9027,9 +9046,10 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
UnresolvedSet<32> Operators;
lookupOperatorsForDefaultedComparison(*this, S, Operators,
FD->getOverloadedOperator());
- FD->setDefaultedOrDeletedInfo(
- FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
- Context, Operators.pairs()));
+ auto *Info = FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
+ Context, Operators.pairs(), /*DeletedMessage=*/nullptr,
+ CurFPFeatureOverrides().getAsOpaqueInt());
+ FD->setDefaultedOrDeletedInfo(Info);
}
// C++2a [class.compare.default]p1:
@@ -9374,6 +9394,8 @@ void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
// Add a context note for diagnostics produced after this point.
Scope.addContextNote(UseLoc);
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, FD);
+
{
// Build and set up the function body.
// The first parameter has type maybe-ref-to maybe-const T, use that to get
@@ -14366,6 +14388,7 @@ CXXConstructorDecl *Sema::DeclareImplicitDefaultConstructor(
void Sema::DefineImplicitDefaultConstructor(SourceLocation CurrentLocation,
CXXConstructorDecl *Constructor) {
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, Constructor);
assert((Constructor->isDefaulted() && Constructor->isDefaultConstructor() &&
!Constructor->doesThisDeclarationHaveABody() &&
!Constructor->isDeleted()) &&
@@ -14665,6 +14688,7 @@ CXXDestructorDecl *Sema::DeclareImplicitDestructor(CXXRecordDecl *ClassDecl) {
void Sema::DefineImplicitDestructor(SourceLocation CurrentLocation,
CXXDestructorDecl *Destructor) {
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, Destructor);
assert((Destructor->isDefaulted() &&
!Destructor->doesThisDeclarationHaveABody() &&
!Destructor->isDeleted()) &&
@@ -15355,6 +15379,7 @@ static void diagnoseDeprecatedCopyOperation(Sema &S, CXXMethodDecl *CopyOp) {
void Sema::DefineImplicitCopyAssignment(SourceLocation CurrentLocation,
CXXMethodDecl *CopyAssignOperator) {
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, CopyAssignOperator);
assert((CopyAssignOperator->isDefaulted() &&
CopyAssignOperator->isOverloadedOperator() &&
CopyAssignOperator->getOverloadedOperator() == OO_Equal &&
@@ -15742,6 +15767,7 @@ static void checkMoveAssignmentForRepeatedMove(Sema &S, CXXRecordDecl *Class,
void Sema::DefineImplicitMoveAssignment(SourceLocation CurrentLocation,
CXXMethodDecl *MoveAssignOperator) {
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, MoveAssignOperator);
assert((MoveAssignOperator->isDefaulted() &&
MoveAssignOperator->isOverloadedOperator() &&
MoveAssignOperator->getOverloadedOperator() == OO_Equal &&
@@ -16075,6 +16101,7 @@ CXXConstructorDecl *Sema::DeclareImplicitCopyConstructor(
void Sema::DefineImplicitCopyConstructor(SourceLocation CurrentLocation,
CXXConstructorDecl *CopyConstructor) {
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, CopyConstructor);
assert((CopyConstructor->isDefaulted() &&
CopyConstructor->isCopyConstructor() &&
!CopyConstructor->doesThisDeclarationHaveABody() &&
@@ -16213,6 +16240,7 @@ CXXConstructorDecl *Sema::DeclareImplicitMoveConstructor(
void Sema::DefineImplicitMoveConstructor(SourceLocation CurrentLocation,
CXXConstructorDecl *MoveConstructor) {
+ DefaultedFunctionFPFeaturesRAII RestoreFP(*this, MoveConstructor);
assert((MoveConstructor->isDefaulted() &&
MoveConstructor->isMoveConstructor() &&
!MoveConstructor->doesThisDeclarationHaveABody() &&
@@ -18775,6 +18803,17 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
return;
}
+ // Only allocate DefaultedOrDeletedFunctionInfo if we actually have
+ // non-default FP features to stash. This avoids memory overhead for
+ // the vast majority of defaulted functions.
+ if (!FD->getDefaultedOrDeletedInfo() &&
+ CurFPFeatureOverrides().requiresTrailingStorage()) {
+ FD->setDefaultedOrDeletedInfo(
+ FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
+ Context, /*Lookups=*/{}, /*DeletedMessage=*/nullptr,
+ CurFPFeatureOverrides().getAsOpaqueInt()));
+ }
+
if (DefKind.isComparison()) {
if (CheckExplicitlyDefaultedComparison(nullptr, FD, DefKind.asComparison()))
FD->setInvalidDecl();
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index c56203f10ac3c..d4fc17073d2e9 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -5515,11 +5515,10 @@ bool TemplateDeclInstantiator::SubstDefaultedFunction(FunctionDecl *New,
Lookups.push_back(DeclAccessPair::make(D, DA.getAccess()));
}
- // It's unlikely that substitution will change any declarations. Don't
- // store an unnecessary copy in that case.
New->setDefaultedOrDeletedInfo(
AnyChanged ? FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
- SemaRef.Context, Lookups)
+ SemaRef.Context, Lookups, /*DeletedMessage=*/nullptr,
+ DFI->getFPFeatures())
: DFI);
}
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index a25e58e434bf2..b3ebbc752abf8 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1094,6 +1094,8 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
StringLiteral *DeletedMessage =
HasMessage ? cast<StringLiteral>(Record.readExpr()) : nullptr;
+ uint64_t FPFeatures = Record.readInt();
+
unsigned NumLookups = Record.readInt();
SmallVector<DeclAccessPair, 8> Lookups;
for (unsigned I = 0; I != NumLookups; ++I) {
@@ -1104,7 +1106,7 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
FD->setDefaultedOrDeletedInfo(
FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
- Reader.getContext(), Lookups, DeletedMessage));
+ Reader.getContext(), Lookups, DeletedMessage, FPFeatures));
}
}
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index f271769d8edf6..52bd5bc88848f 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -898,6 +898,8 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
if (DeletedMessage)
Record.AddStmt(DeletedMessage);
+ Record.push_back(FDI->getFPFeatures());
+
Record.push_back(FDI->getUnqualifiedLookups().size());
for (DeclAccessPair P : FDI->getUnqualifiedLookups()) {
Record.AddDeclRef(P.getDecl());
diff --git a/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp b/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp
new file mode 100644
index 0000000000000..809c1fc864618
--- /dev/null
+++ b/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp
@@ -0,0 +1,101 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s
+
+// CHECK-DAG: define {{.*}} @_ZeqRK6TargetS1_({{.*}}) [[NORMAL_ATTRS:#[0-9]+]]
+// CHECK-DAG: define {{.*}} @_ZeqRK12StrictTargetS1_({{.*}}) [[STRICT_ATTRS:#[0-9]+]]
+// CHECK-DAG: define {{.*}} @_ZN12AssignTargetaSERKS_({{.*}}) [[NORMAL_ATTRS]]
+// CHECK-DAG: define {{.*}} @_ZN18StrictAssignTargetaSERKS_({{.*}}) [[STRICT_ATTRS]]
+
+// Templates
+// CHECK-DAG: define {{.*}} @_ZeqRK14TemplateTargetIdES2_({{.*}}) [[NORMAL_ATTRS]]
+// CHECK-DAG: define {{.*}} @_ZeqRK14TemplateTargetIfES2_({{.*}}) [[NORMAL_ATTRS]]
+// CHECK-DAG: define {{.*}} @_ZeqRK20StrictTemplateTargetIdES2_({{.*}}) [[STRICT_ATTRS]]
+
+// --- NON-STRICT DECLARATIONS (at top of file, default FP is non-strict) ---
+
+struct Target {
+ double d;
+ friend bool operator==(const Target&, const Target&) = default;
+};
+
+struct Member {
+ double d;
+ Member& operator=(const Member& Other) {
+ d = Other.d + 1.0;
+ return *this;
+ }
+};
+
+struct AssignTarget {
+ Member m;
+};
+
+template <typename T>
+struct TemplateTarget {
+ T d;
+ friend bool operator==(const TemplateTarget&, const TemplateTarget&) = default;
+};
+
+// Trigger instantiation of TemplateTarget<double>::operator== in non-strict context.
+bool test_template_non_strict(TemplateTarget<double> a, TemplateTarget<double> b) {
+ return a == b;
+}
+
+
+// --- STRICT CONTEXT (pragmas enabled) ---
+#pragma STDC FENV_ACCESS ON
+
+// Use-sites triggering synthesis of non-strict defaulted functions in strict context.
+
+bool test_non_strict_cmp(Target a, Target b) {
+ return a == b;
+}
+
+void test_non_strict_assign(AssignTarget& a, AssignTarget& b) {
+ a = b;
+}
+
+// Trigger instantiation of TemplateTarget<float>::operator== in strict context.
+// It should still be non-strict because the template was defined in non-strict context.
+bool test_template_strict(TemplateTarget<float> a, TemplateTarget<float> b) {
+ return a == b;
+}
+
+// Strict declarations (must get strictfp)
+
+struct StrictTarget {
+ double d;
+ friend bool operator==(const StrictTarget&, const StrictTarget&) = default;
+};
+
+bool test_strict_cmp(StrictTarget a, StrictTarget b) {
+ return a == b;
+}
+
+struct StrictAssignTarget {
+ Member m;
+};
+
+void test_strict_assign(StrictAssignTarget& a, StrictAssignTarget& b) {
+ a = b;
+}
+
+template <typename T>
+struct StrictTemplateTarget {
+ T d;
+ friend bool operator==(const StrictTemplateTarget&, const StrictTemplateTarget&) = default;
+};
+
+
+// --- NON-STRICT CONTEXT AGAIN ---
+#pragma STDC FENV_ACCESS OFF
+
+// Trigger instantiation of StrictTemplateTarget<double>::operator== in non-strict context.
+// It should be strict because the template was defined in strict context.
+bool test_strict_template_non_strict(StrictTemplateTarget<double> a, StrictTemplateTarget<double> b) {
+ return a == b;
+}
+
+
+// CHECK-DAG: attributes [[STRICT_ATTRS]] = { {{.*}}strictfp{{.*}} }
+// CHECK-DAG: attributes [[NORMAL_ATTRS]] = { {{.*}} }
+// CHECK-NOT: attributes [[NORMAL_ATTRS]] = { {{.*}}strictfp
>From 08a1731ffb88ebb3f7f736fffb73fe8275b9cfaa Mon Sep 17 00:00:00 2001
From: Kyungtak Woo <kevinwkt at google.com>
Date: Fri, 3 Jul 2026 17:45:21 +0000
Subject: [PATCH 2/2] fix: run clang tidy
---
clang/include/clang/AST/Decl.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 7f4ea059f699e..fb896390a8bea 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2051,7 +2051,8 @@ class FunctionDecl : public DeclaratorDecl,
/// Stashed information about a defaulted/deleted function body, including
/// the active FP pragma overrides (FPOptionsOverride) from the declaration
- /// site. These overrides are required to correctly synthesize the function body.
+ /// site. These overrides are required to correctly synthesize the function
+ /// body.
class DefaultedOrDeletedFunctionInfo final
: llvm::TrailingObjects<DefaultedOrDeletedFunctionInfo, DeclAccessPair,
StringLiteral *> {
@@ -2067,8 +2068,7 @@ class FunctionDecl : public DeclaratorDecl,
public:
static DefaultedOrDeletedFunctionInfo *
Create(ASTContext &Context, ArrayRef<DeclAccessPair> Lookups,
- StringLiteral *DeletedMessage = nullptr,
- uint64_t FPFeatures = 0);
+ StringLiteral *DeletedMessage = nullptr, uint64_t FPFeatures = 0);
uint64_t getFPFeatures() const { return FPFeatures; }
More information about the cfe-commits
mailing list