Author: Nico Weber
Date: 2026-03-19T07:34:12-04:00
New Revision: c3e7624ac4bdfaa583f2d7f76fd780ea6bfd4a84

URL: 
https://github.com/llvm/llvm-project/commit/c3e7624ac4bdfaa583f2d7f76fd780ea6bfd4a84
DIFF: 
https://github.com/llvm/llvm-project/commit/c3e7624ac4bdfaa583f2d7f76fd780ea6bfd4a84.diff

LOG: [clang] Add implicit std::align_val_t to std namespace DeclContext for 
module merging (#187347)

When a virtual destructor is encountered before any module providing
std::align_val_t is loaded, DeclareGlobalNewDelete() implicitly creates
a std::align_val_t EnumDecl. However, this EnumDecl was not added to the
std namespace's DeclContext -- it was only stored in the
Sema::StdAlignValT field.

Later, when a module containing an explicit std::align_val_t definition
is loaded, ASTReaderDecl::findExisting() attempts to find the implicit
decl via DeclContext::noload_lookup() on the std namespace. Since the
implicit EnumDecl was never added to that DeclContext, the lookup fails,
and the two align_val_t declarations are not merged into a single
redeclaration chain. This results in two distinct types both named
std::align_val_t.

The implicitly declared operator delete overloads (also created by
DeclareGlobalNewDelete) use the implicit align_val_t type for their
aligned-deallocation parameter. When module code (e.g. std::allocator::
deallocate) calls __builtin_operator_delete with the module's
align_val_t, overload resolution fails because the two align_val_t types
are not the same, producing:

  error: no matching function for call to 'operator delete'
note: no known conversion from 'std::align_val_t' to 'std::align_val_t'

The fix adds the implicit align_val_t EnumDecl to the std namespace
DeclContext via getOrCreateStdNamespace()->addDecl(AlignValT), so the
module merger can find it via noload_lookup and merge the two
declarations.

This bug was exposed by a libc++ change (2b01e7cf2b70) that removed the
#include <__new/global_new_delete.h> line from allocate.h, which meant
modules no longer had explicit operator delete declarations to paper
over the type mismatch.

Assisted-by: Claude Code

Added: 
    clang/test/Modules/align-val-t-merge.cpp

Modified: 
    clang/lib/Sema/SemaExprCXX.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 5de4a1e7475f2..5553f25546c38 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -3445,6 +3445,13 @@ void Sema::DeclareGlobalNewDelete() {
     AlignValT->setPromotionType(Context.getSizeType());
     AlignValT->setImplicit(true);
 
+    // Add to the std namespace so that the module merger can find it via
+    // noload_lookup and merge it with the module's explicit definition.
+    // We want the created EnumDecl to be available for redeclaration lookups,
+    // but not for regular name lookups (same pattern as
+    // getOrCreateStdNamespace).
+    getOrCreateStdNamespace()->addDecl(AlignValT);
+
     StdAlignValT = AlignValT;
   }
 

diff  --git a/clang/test/Modules/align-val-t-merge.cpp 
b/clang/test/Modules/align-val-t-merge.cpp
new file mode 100644
index 0000000000000..9bac6d5aafc92
--- /dev/null
+++ b/clang/test/Modules/align-val-t-merge.cpp
@@ -0,0 +1,75 @@
+// Tests that an implicitly-declared std::align_val_t (created by
+// DeclareGlobalNewDelete when a virtual destructor is seen) merges correctly
+// with the module's explicit definition of std::align_val_t.
+//
+// Without the fix, the implicit align_val_t was not added to the std namespace
+// DeclContext, so the ASTReader's noload_lookup couldn't find it during module
+// deserialization. This resulted in two unmerged align_val_t types and a
+// "no matching function for call to '__builtin_operator_delete'" error when
+// module code passed the module's align_val_t to the implicitly declared
+// operator delete (which uses the implicit align_val_t).
+//
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// Build the module that provides std::align_val_t and a template using
+// __builtin_operator_delete. Crucially, the module does NOT declare
+// operator delete -- only the implicit declarations exist.
+// RUN: %clang_cc1 -std=c++17 -x c++ -fmodules -fno-implicit-modules \
+// RUN:   -faligned-allocation -fsized-deallocation \
+// RUN:   -emit-module -fmodule-name=alloc \
+// RUN:   -fmodule-map-file=%t/alloc.modulemap \
+// RUN:   %t/alloc.modulemap -o %t/alloc.pcm
+//
+// Compile a TU that has a virtual destructor (triggers DeclareGlobalNewDelete
+// and implicit align_val_t) BEFORE importing the module.
+// RUN: %clang_cc1 -std=c++17 -x c++ -fmodules -fno-implicit-modules \
+// RUN:   -faligned-allocation -fsized-deallocation \
+// RUN:   -fmodule-map-file=%t/alloc.modulemap \
+// RUN:   -fmodule-file=alloc=%t/alloc.pcm \
+// RUN:   -fsyntax-only -verify %t/use.cpp
+
+//--- alloc.modulemap
+module alloc {
+  header "alloc.h"
+}
+
+//--- alloc.h
+#ifndef ALLOC_H
+#define ALLOC_H
+
+namespace std {
+  using size_t = decltype(sizeof(0));
+  enum class align_val_t : size_t {};
+}
+
+// No explicit operator delete declarations here -- we rely on the implicit
+// ones from DeclareGlobalNewDelete. This mirrors the libc++ setup after
+// the removal of the global_new_delete.h include from allocate.h.
+
+template <class T>
+void dealloc(T *p) {
+  // __builtin_operator_delete resolves against the usual (implicit)
+  // deallocation functions. Those use the implicit align_val_t.
+  // The argument here uses the module's align_val_t.
+  // If they're not merged, this fails with a type mismatch.
+  __builtin_operator_delete(p, std::align_val_t(alignof(T)));
+}
+
+#endif
+
+//--- use.cpp
+// expected-no-diagnostics
+
+// Virtual destructor triggers DeclareGlobalNewDelete(), which implicitly
+// creates std::align_val_t before the module is loaded.
+class Foo {
+  virtual ~Foo() {}
+};
+
+#include "alloc.h"
+
+void test() {
+  dealloc<Foo>(nullptr);
+}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to