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
