diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 106f1e63cd904..2c968d92e859f 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5365,6 +5365,23 @@ llvm::Constant *CodeGenModule::GetOrCreateLLVMFunction( // Lookup the entry, lazily creating it if necessary. llvm::GlobalValue *Entry = GetGlobalValue(MangledName); + auto DeferDefinitionIfBodyIsAvailable = [&] { + const auto *DAsFunction = dyn_cast_or_null(D); + if (!getLangOpts().CPlusPlus || !DAsFunction) + return; + + // Look for a declaration that's lexically in a record. + for (const auto *FD = DAsFunction->getMostRecentDecl(); FD; + FD = FD->getPreviousDecl()) { + if (isa(FD->getLexicalDeclContext()) && + FD->doesThisDeclarationHaveABody()) { + GlobalDecl DefGD = GD.getWithDecl(FD); + if (!llvm::is_contained(DeferredDeclsToEmit, DefGD)) + addDeferredDeclToEmit(DefGD); + break; + } + } + }; if (Entry) { if (WeakRefReferences.erase(Entry)) { const FunctionDecl *FD = cast_or_null(D); @@ -5397,6 +5414,11 @@ llvm::Constant *CodeGenModule::GetOrCreateLLVMFunction( if ((isa(Entry) || isa(Entry)) && (Entry->getValueType() == Ty)) { + // The LLVM declaration can be created before Sema synthesizes the body + // for an inline member, for example an implicit destructor. If this is a + // later reference after the body became available, queue it for emission. + if (!DontDefer && !IsForDefinition && Entry->isDeclaration()) + DeferDefinitionIfBodyIsAvailable(); return Entry; } @@ -5492,16 +5514,7 @@ llvm::Constant *CodeGenModule::GetOrCreateLLVMFunction( // We also don't emit a definition for a function if it's going to be an // entry in a vtable, unless it's already marked as used. } else if (getLangOpts().CPlusPlus && D) { - // Look for a declaration that's lexically in a record. - for (const auto *FD = cast(D)->getMostRecentDecl(); FD; - FD = FD->getPreviousDecl()) { - if (isa(FD->getLexicalDeclContext())) { - if (FD->doesThisDeclarationHaveABody()) { - addDeferredDeclToEmit(GD.getWithDecl(FD)); - break; - } - } - } + DeferDefinitionIfBodyIsAvailable(); } } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 98062afae4577..e75f2843fdf8f 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5703,11 +5703,54 @@ struct ImmediateCallVisitor : DynamicRecursiveASTVisitor { struct EnsureImmediateInvocationInDefaultArgs : TreeTransform { + using Base = TreeTransform; + EnsureImmediateInvocationInDefaultArgs(Sema &SemaRef) : TreeTransform(SemaRef) {} bool AlwaysRebuild() { return true; } + bool rebuiltInitNeedsCleanups() const { return RebuiltInitNeedsCleanups; } + + ExprResult TransformCXXBindTemporaryExpr(CXXBindTemporaryExpr *E) { + // TransformInitializer normally strips CXXBindTemporaryExpr. In default + // member initializers, rebuild the binding explicitly so CodeGen still + // knows which rebuilt temporaries need end-of-full-expression destruction. + ExprResult SubExpr = Base::TransformExpr(E->getSubExpr()); + if (SubExpr.isInvalid()) + return ExprError(); + if (SubExpr.get() == E->getSubExpr()) { + if (!SuppressRebuiltTemporaryCleanup) + RebuiltInitNeedsCleanups = true; + return E; + } + + ExprResult Res = SemaRef.MaybeBindToTemporary(SubExpr.get()); + if (!SuppressRebuiltTemporaryCleanup && !Res.isInvalid()) + RebuiltInitNeedsCleanups = true; + return Res; + } + + ExprResult TransformInitializer(Expr *Init, bool NotCopyInit) { + Expr *UnwrappedInit = Init; + if (auto *FE = dyn_cast_if_present(UnwrappedInit)) + UnwrappedInit = FE->getSubExpr(); + + if (auto *MTE = + dyn_cast_if_present(UnwrappedInit); + MTE && MTE->getExtendingDecl()) { + // The lifetime-extended temporary is intentionally not cleaned up at the + // end of the default member initializer full-expression. + llvm::SaveAndRestore SaveSuppress(SuppressRebuiltTemporaryCleanup, true); + return Base::TransformInitializer(Init, NotCopyInit); + } + + if (auto *E = dyn_cast_if_present(UnwrappedInit)) + return TransformCXXBindTemporaryExpr(E); + + return Base::TransformInitializer(Init, NotCopyInit); + } + // Lambda can only have immediate invocations in the default // args of their parameters, which is transformed upon calling the closure. // The body is not a subexpression, so we have nothing to do. @@ -5741,6 +5784,10 @@ struct EnsureImmediateInvocationInDefaultArgs return getDerived().RebuildSourceLocExpr( E->getIdentKind(), E->getType(), E->getBeginLoc(), E->getEndLoc(), DC); } + +private: + bool RebuiltInitNeedsCleanups = false; + bool SuppressRebuiltTemporaryCleanup = false; }; ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, @@ -5882,6 +5929,8 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { if (!NestedDefaultChecking) V.TraverseDecl(Field); + bool RebuiltInitNeedsCleanups = false; + // CWG1815 // Support lifetime extension of temporary created by aggregate // initialization using a default member initializer. We should rebuild @@ -5914,6 +5963,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { Field->setInvalidDecl(); return ExprError(); } + RebuiltInitNeedsCleanups = Immediate.rebuiltInitNeedsCleanups(); Init = Res.get(); } @@ -5923,8 +5973,11 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { runWithSufficientStackSpace(Loc, [&] { MarkDeclarationsReferencedInExpr(E, /*SkipLocalVariables=*/false); }); - if (isInLifetimeExtendingContext()) + if (isInLifetimeExtendingContext()) { DiscardCleanupsInEvaluationContext(); + if (RebuiltInitNeedsCleanups) + Cleanup.setExprNeedsCleanups(true); + } // C++11 [class.base.init]p7: // The initialization of each base and member constitutes a // full-expression. diff --git a/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp b/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp new file mode 100644 index 0000000000000..f0996cd91ec51 --- /dev/null +++ b/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s + +using Size = decltype(sizeof(0)); +void *operator new(Size); +void operator delete(void *) noexcept; +void operator delete(void *, Size) noexcept; + +struct Noisy { + Noisy(); + Noisy(const Noisy &); + ~Noisy(); +}; + +class Function { +public: + template + explicit Function(F &&f) : callable_{new Callable{static_cast(f)}} {} + + ~Function() { delete callable_; } + +private: + struct CallableBase { + virtual ~CallableBase() = default; + }; + + template struct Callable final : CallableBase { + explicit Callable(F f) : function{static_cast(f)} {} + + F function; + }; + + CallableBase *callable_; +}; + +struct Options { + Function function{[noisy = Noisy{}] {}}; +}; + +Options kOptions{}; + +// CHECK-LABEL: define internal void @__cxx_global_var_init +// CHECK: call void @_ZN5NoisyC1Ev +// CHECK: call void @_ZN8FunctionC1IN7Options8functionMUlvE_EEEOT_ +// CHECK: call void @_ZN7Options8functionMUlvE_D1Ev + +// CHECK-LABEL: define {{.*}} @_ZN7Options8functionMUlvE_D1Ev +// CHECK: call void @_ZN7Options8functionMUlvE_D2Ev + +// CHECK-LABEL: define {{.*}} @_ZN7Options8functionMUlvE_D2Ev +// CHECK: call void @_ZN5NoisyD1Ev