[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