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 <[email protected]> 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 <[email protected]> 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; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
