[clang] cb2289f - [C++20] [Modules] Attach implicitly declared allocation funcitons to
Chuanqi Xu via cfe-commits
cfe-commits at lists.llvm.org
Tue Nov 15 01:27:41 PST 2022
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());
+}
More information about the cfe-commits
mailing list