Author: Chuanqi Xu Date: 2022-11-15T17:21:48+08:00 New Revision: cb2289f39240a0fdccc9a853a02ae9751578a0fd
URL: https://github.com/llvm/llvm-project/commit/cb2289f39240a0fdccc9a853a02ae9751578a0fd DIFF: https://github.com/llvm/llvm-project/commit/cb2289f39240a0fdccc9a853a02ae9751578a0fd.diff LOG: [C++20] [Modules] Attach implicitly declared allocation funcitons to global module fragment [basic.stc.dynamic.general]p2 says: > The library provides default definitions for the global allocation > and deallocation functions. Some global allocation and > deallocation > functions are replaceable ([new.delete]); these are attached to > the global module ([module.unit]). But we didn't take this before and the implicitly generated functions will live in the module purview if we're compiling a module unit. This is bad since the owning module will affect the linkage of the declarations. This patch addresses this. Closes https://github.com/llvm/llvm-project/issues/58560 Added: clang/test/Modules/implicit-declared-allocation-functions.cppm Modified: clang/lib/Basic/Module.cpp clang/lib/Sema/SemaExprCXX.cpp clang/unittests/AST/DeclTest.cpp Removed: ################################################################################ diff --git a/clang/lib/Basic/Module.cpp b/clang/lib/Basic/Module.cpp index 9a58faee1fdd8..2f30fa0f02adf 100644 --- a/clang/lib/Basic/Module.cpp +++ b/clang/lib/Basic/Module.cpp @@ -633,7 +633,9 @@ LLVM_DUMP_METHOD void Module::dump() const { void VisibleModuleSet::setVisible(Module *M, SourceLocation Loc, VisibleCallback Vis, ConflictCallback Cb) { - assert(Loc.isValid() && "setVisible expects a valid import location"); + // We can't import a global module fragment so the location can be invalid. + assert((M->isGlobalModule() || Loc.isValid()) && + "setVisible expects a valid import location"); if (isVisible(M)) return; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 0cf79265b7448..8b3cd4211f88c 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2979,6 +2979,14 @@ void Sema::DeclareGlobalNewDelete() { if (getLangOpts().OpenCLCPlusPlus) return; + // C++ [basic.stc.dynamic.general]p2: + // The library provides default definitions for the global allocation + // and deallocation functions. Some global allocation and deallocation + // functions are replaceable ([new.delete]); these are attached to the + // global module ([module.unit]). + if (getLangOpts().CPlusPlusModules && getCurrentModule()) + PushGlobalModuleFragment(SourceLocation(), /*IsImplicit=*/true); + // C++ [basic.std.dynamic]p2: // [...] The following allocation and deallocation functions (18.4) are // implicitly declared in global scope in each translation unit of a @@ -3018,6 +3026,14 @@ void Sema::DeclareGlobalNewDelete() { &PP.getIdentifierTable().get("bad_alloc"), nullptr); getStdBadAlloc()->setImplicit(true); + + // The implicitly declared "std::bad_alloc" should live in global module + // fragment. + if (GlobalModuleFragment) { + getStdBadAlloc()->setModuleOwnershipKind( + Decl::ModuleOwnershipKind::ReachableWhenImported); + getStdBadAlloc()->setLocalOwningModule(GlobalModuleFragment); + } } if (!StdAlignValT && getLangOpts().AlignedAllocation) { // The "std::align_val_t" enum class has not yet been declared, so build it @@ -3025,9 +3041,19 @@ void Sema::DeclareGlobalNewDelete() { auto *AlignValT = EnumDecl::Create( Context, getOrCreateStdNamespace(), SourceLocation(), SourceLocation(), &PP.getIdentifierTable().get("align_val_t"), nullptr, true, true, true); + + // The implicitly declared "std::align_val_t" should live in global module + // fragment. + if (GlobalModuleFragment) { + AlignValT->setModuleOwnershipKind( + Decl::ModuleOwnershipKind::ReachableWhenImported); + AlignValT->setLocalOwningModule(GlobalModuleFragment); + } + AlignValT->setIntegerType(Context.getSizeType()); AlignValT->setPromotionType(Context.getSizeType()); AlignValT->setImplicit(true); + StdAlignValT = AlignValT; } @@ -3069,6 +3095,9 @@ void Sema::DeclareGlobalNewDelete() { DeclareGlobalAllocationFunctions(OO_Array_New, VoidPtr, SizeT); DeclareGlobalAllocationFunctions(OO_Delete, Context.VoidTy, VoidPtr); DeclareGlobalAllocationFunctions(OO_Array_Delete, Context.VoidTy, VoidPtr); + + if (getLangOpts().CPlusPlusModules && getCurrentModule()) + PopGlobalModuleFragment(); } /// DeclareGlobalAllocationFunction - Declares a single implicit global @@ -3137,6 +3166,22 @@ void Sema::DeclareGlobalAllocationFunction(DeclarationName Name, Alloc->addAttr( ReturnsNonNullAttr::CreateImplicit(Context, Alloc->getLocation())); + // C++ [basic.stc.dynamic.general]p2: + // The library provides default definitions for the global allocation + // and deallocation functions. Some global allocation and deallocation + // functions are replaceable ([new.delete]); these are attached to the + // global module ([module.unit]). + // + // In the language wording, these functions are attched to the global + // module all the time. But in the implementation, the global module + // is only meaningful when we're in a module unit. So here we attach + // these allocation functions to global module conditionally. + if (GlobalModuleFragment) { + Alloc->setModuleOwnershipKind( + Decl::ModuleOwnershipKind::ReachableWhenImported); + Alloc->setLocalOwningModule(GlobalModuleFragment); + } + Alloc->addAttr(VisibilityAttr::CreateImplicit( Context, LangOpts.GlobalAllocationFunctionVisibilityHidden ? VisibilityAttr::Hidden diff --git a/clang/test/Modules/implicit-declared-allocation-functions.cppm b/clang/test/Modules/implicit-declared-allocation-functions.cppm new file mode 100644 index 0000000000000..b378a1d1365ee --- /dev/null +++ b/clang/test/Modules/implicit-declared-allocation-functions.cppm @@ -0,0 +1,64 @@ +// Tests that the implicit declared allocation functions +// are attached to the global module fragment. +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -std=c++20 %t/foo.cppm -fsyntax-only -verify +// RUN: %clang_cc1 -std=c++20 %t/foo2.cppm -fsyntax-only -verify + +//--- foo.cppm +export module foo; +export void alloc_wrapper() { + void *a = ::operator new(32); + // [basic.stc.dynamic.general]Note2 + // The implicit declarations do not introduce the names std, std::size_t, + // std::align_val_t, ..., However, referring to std or std::size_t or + // std::align_val_t is ill-formed unless a standard library declaration + // ([cstddef.syn], [new.syn], [std.modules]) of that name precedes + // ([basic.lookup.general]) the use of that name. + void *b = ::operator new((std::size_t)32); // expected-error {{use of undeclared identifier 'std'}} + void *c = ::operator new((std::size_t)32, // expected-error {{use of undeclared identifier 'std'}} + (std::align_val_t)64); // expected-error {{use of undeclared identifier 'std'}} + + ::operator delete(a); + ::operator delete(b, (std::size_t)32); // expected-error {{use of undeclared identifier 'std'}} + ::operator delete(c, (std::size_t)32, // expected-error {{use of undeclared identifier 'std'}} + (std::align_val_t)64); // expected-error {{use of undeclared identifier 'std'}} +} + +//--- new +namespace std { + using size_t = decltype(sizeof(0)); + enum class align_val_t : size_t {}; +} + +[[nodiscard]] void *operator new(std::size_t); +[[nodiscard]] void *operator new(std::size_t, std::align_val_t); +[[nodiscard]] void *operator new[](std::size_t); +[[nodiscard]] void *operator new[](std::size_t, std::align_val_t); +void operator delete(void*) noexcept; +void operator delete(void*, std::size_t) noexcept; +void operator delete(void*, std::align_val_t) noexcept; +void operator delete(void*, std::size_t, std::align_val_t) noexcept; +void operator delete[](void*, std::size_t, std::align_val_t) noexcept; +void operator delete[](void*, std::size_t) noexcept; +void operator delete[](void*, std::align_val_t) noexcept; +void operator delete[](void*, std::size_t, std::align_val_t) noexcept; + +//--- foo2.cppm +// expected-no-diagnostics +module; +#include "new" +export module foo2; +export void alloc_wrapper() { + void *a = ::operator new(32); + void *b = ::operator new((std::size_t)32); + void *c = ::operator new((std::size_t)32, + (std::align_val_t)64); + + ::operator delete(a); + ::operator delete(b, (std::size_t)32); + ::operator delete(c, (std::size_t)32, + (std::align_val_t)64); +} diff --git a/clang/unittests/AST/DeclTest.cpp b/clang/unittests/AST/DeclTest.cpp index 8d55f13e1f5c3..6d525dc8ac3fe 100644 --- a/clang/unittests/AST/DeclTest.cpp +++ b/clang/unittests/AST/DeclTest.cpp @@ -376,3 +376,117 @@ TEST(Decl, NoProtoFunctionDeclAttributes) { EXPECT_FALSE(FPT->isVolatile()); EXPECT_FALSE(FPT->isRestrict()); } + +TEST(Decl, ImplicitlyDeclaredAllocationFunctionsInModules) { + // C++ [basic.stc.dynamic.general]p2: + // The library provides default definitions for the global allocation + // and deallocation functions. Some global allocation and deallocation + // functions are replaceable ([new.delete]); these are attached to the + // global module ([module.unit]). + + llvm::Annotations Code(R"( + export module base; + + export struct Base { + virtual void hello() = 0; + virtual ~Base() = default; + }; + )"); + + auto AST = + tooling::buildASTFromCodeWithArgs(Code.code(), /*Args=*/{"-std=c++20"}); + ASTContext &Ctx = AST->getASTContext(); + + // void* operator new(std::size_t); + auto *SizedOperatorNew = selectFirst<FunctionDecl>( + "operator new", + match(functionDecl(hasName("operator new"), parameterCountIs(1), + hasParameter(0, hasType(isUnsignedInteger()))) + .bind("operator new"), + Ctx)); + EXPECT_TRUE(SizedOperatorNew->getOwningModule()); + EXPECT_TRUE(SizedOperatorNew->getOwningModule()->isGlobalModule()); + + // void* operator new(std::size_t, std::align_val_t); + auto *SizedAlignedOperatorNew = selectFirst<FunctionDecl>( + "operator new", + match(functionDecl( + hasName("operator new"), parameterCountIs(2), + hasParameter(0, hasType(isUnsignedInteger())), + hasParameter(1, hasType(enumDecl(hasName("std::align_val_t"))))) + .bind("operator new"), + Ctx)); + EXPECT_TRUE(SizedAlignedOperatorNew->getOwningModule()); + EXPECT_TRUE(SizedAlignedOperatorNew->getOwningModule()->isGlobalModule()); + + // void* operator new[](std::size_t); + auto *SizedArrayOperatorNew = selectFirst<FunctionDecl>( + "operator new[]", + match(functionDecl(hasName("operator new[]"), parameterCountIs(1), + hasParameter(0, hasType(isUnsignedInteger()))) + .bind("operator new[]"), + Ctx)); + EXPECT_TRUE(SizedArrayOperatorNew->getOwningModule()); + EXPECT_TRUE(SizedArrayOperatorNew->getOwningModule()->isGlobalModule()); + + // void* operator new[](std::size_t, std::align_val_t); + auto *SizedAlignedArrayOperatorNew = selectFirst<FunctionDecl>( + "operator new[]", + match(functionDecl( + hasName("operator new[]"), parameterCountIs(2), + hasParameter(0, hasType(isUnsignedInteger())), + hasParameter(1, hasType(enumDecl(hasName("std::align_val_t"))))) + .bind("operator new[]"), + Ctx)); + EXPECT_TRUE(SizedAlignedArrayOperatorNew->getOwningModule()); + EXPECT_TRUE( + SizedAlignedArrayOperatorNew->getOwningModule()->isGlobalModule()); + + // void operator delete(void*) noexcept; + auto *Delete = selectFirst<FunctionDecl>( + "operator delete", + match(functionDecl( + hasName("operator delete"), parameterCountIs(1), + hasParameter(0, hasType(pointerType(pointee(voidType()))))) + .bind("operator delete"), + Ctx)); + EXPECT_TRUE(Delete->getOwningModule()); + EXPECT_TRUE(Delete->getOwningModule()->isGlobalModule()); + + // void operator delete(void*, std::align_val_t) noexcept; + auto *AlignedDelete = selectFirst<FunctionDecl>( + "operator delete", + match(functionDecl( + hasName("operator delete"), parameterCountIs(2), + hasParameter(0, hasType(pointerType(pointee(voidType())))), + hasParameter(1, hasType(enumDecl(hasName("std::align_val_t"))))) + .bind("operator delete"), + Ctx)); + EXPECT_TRUE(AlignedDelete->getOwningModule()); + EXPECT_TRUE(AlignedDelete->getOwningModule()->isGlobalModule()); + + // Sized deallocation is not enabled by default. So we skip it here. + + // void operator delete[](void*) noexcept; + auto *ArrayDelete = selectFirst<FunctionDecl>( + "operator delete[]", + match(functionDecl( + hasName("operator delete[]"), parameterCountIs(1), + hasParameter(0, hasType(pointerType(pointee(voidType()))))) + .bind("operator delete[]"), + Ctx)); + EXPECT_TRUE(ArrayDelete->getOwningModule()); + EXPECT_TRUE(ArrayDelete->getOwningModule()->isGlobalModule()); + + // void operator delete[](void*, std::align_val_t) noexcept; + auto *AlignedArrayDelete = selectFirst<FunctionDecl>( + "operator delete[]", + match(functionDecl( + hasName("operator delete[]"), parameterCountIs(2), + hasParameter(0, hasType(pointerType(pointee(voidType())))), + hasParameter(1, hasType(enumDecl(hasName("std::align_val_t"))))) + .bind("operator delete[]"), + Ctx)); + EXPECT_TRUE(AlignedArrayDelete->getOwningModule()); + EXPECT_TRUE(AlignedArrayDelete->getOwningModule()->isGlobalModule()); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits