[clang] 3ec748c - [C++20] [Modules] Preserve placement new and operator for ADL in Reduced BMI (#219145)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 27 02:19:15 PDT 2026
Author: Chuanqi Xu
Date: 2026-08-27T09:19:09Z
New Revision: 3ec748cea37b991d6f557310b1230d6a9011e064
URL: https://github.com/llvm/llvm-project/commit/3ec748cea37b991d6f557310b1230d6a9011e064
DIFF: https://github.com/llvm/llvm-project/commit/3ec748cea37b991d6f557310b1230d6a9011e064.diff
LOG: [C++20] [Modules] Preserve placement new and operator for ADL in Reduced BMI (#219145)
Close https://github.com/llvm/llvm-project/issues/218246
The root cause of the issue is, the placement new and operator for ADL
is not directly referenced and they are removed in reduced BMI. This is
not correct. This patch fixes it.
Added:
clang/test/Modules/reduced-bmi-dependent-lookup.cpp
Modified:
clang/lib/Serialization/ASTWriterStmt.cpp
Removed:
################################################################################
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 782fecdbd0c80..513c832c38ffd 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1140,6 +1140,44 @@ void ASTStmtWriter::VisitBinaryOperator(BinaryOperator *E) {
E->getObjectKind() == OK_Ordinary)
AbbrevToUse = Writer.getBinaryOperatorAbbrev();
+ // When emitting reduced BMI, some necessary operators may be removed for ADL.
+ // Here we tries to save such operators.
+ if (Writer.isGeneratingReducedBMI() &&
+ // Assign doesn't take part in ADL.
+ E->getOpcode() != BO_Assign &&
+ (E->getLHS()->isTypeDependent() || E->getRHS()->isTypeDependent())) {
+ OverloadedOperatorKind Op =
+ BinaryOperator::getOverloadedOperator(E->getOpcode());
+
+ // [module.global.frag] performs a synthetic lookup in which each
+ // type-dependent operand has no associated namespaces or entities.
+ DeclarationName Name =
+ Record.getASTContext().DeclarationNames.getCXXOperatorName(Op);
+
+ auto PreserveAssociatedCandidates = [&](Expr *Operand) {
+ const auto *RT = Operand->getType()->getAs<RecordType>();
+ if (!RT)
+ return;
+
+ // Find the associated namespace and perform a synthetic lookup in it.
+ DeclContext *DC = RT->getDecl()->getDeclContext();
+ while (DC && !DC->isFileContext())
+ DC = DC->getParent();
+ if (auto *NS = dyn_cast_or_null<NamespaceDecl>(DC))
+ for (NamedDecl *D : NS->noload_lookup(Name))
+ Writer.GetDeclRef(D);
+ };
+
+ if (!E->getLHS()->isTypeDependent())
+ PreserveAssociatedCandidates(E->getLHS());
+ if (!E->getRHS()->isTypeDependent())
+ PreserveAssociatedCandidates(E->getRHS());
+
+ for (NamedDecl *D :
+ Record.getASTContext().getTranslationUnitDecl()->noload_lookup(Name))
+ Writer.GetDeclRef(D);
+ }
+
Code = serialization::EXPR_BINARY_OPERATOR;
}
@@ -2045,6 +2083,24 @@ void ASTStmtWriter::VisitCXXNewExpr(CXXNewExpr *E) {
Record.AddDeclRef(E->getOperatorNew());
Record.AddDeclRef(E->getOperatorDelete());
+
+ // Preserve the global candidates that the lookup at instantiation can find;
+ // otherwise a reduced BMI can elide them because the dependent CXXNewExpr has
+ // no direct reference to an allocation function.
+ if (Writer.isGeneratingReducedBMI() && !E->getOperatorNew()) {
+ auto PreserveGlobalCandidates = [&](OverloadedOperatorKind Kind) {
+ DeclarationName Name =
+ Record.getASTContext().DeclarationNames.getCXXOperatorName(Kind);
+ for (NamedDecl *Found :
+ Record.getASTContext().getTranslationUnitDecl()->noload_lookup(Name))
+ if (!Found->isImplicit())
+ Writer.GetDeclRef(Found);
+ };
+
+ PreserveGlobalCandidates(E->isArray() ? OO_Array_New : OO_New);
+ PreserveGlobalCandidates(E->isArray() ? OO_Array_Delete : OO_Delete);
+ }
+
Record.AddTypeSourceInfo(E->getAllocatedTypeSourceInfo());
if (E->isParenTypeId())
Record.AddSourceRange(E->getTypeIdParens());
diff --git a/clang/test/Modules/reduced-bmi-dependent-lookup.cpp b/clang/test/Modules/reduced-bmi-dependent-lookup.cpp
new file mode 100644
index 0000000000000..4fd70b851db3d
--- /dev/null
+++ b/clang/test/Modules/reduced-bmi-dependent-lookup.cpp
@@ -0,0 +1,57 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t \
+// RUN: -fsyntax-only -verify
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-obj -fmodules-reduced-bmi \
+// RUN: -fmodule-output=%t/M.pcm -o %t/M.o
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t \
+// RUN: -fsyntax-only -verify
+
+//--- support.h
+using size_t = decltype(sizeof(0));
+
+struct placement_tag {};
+inline void *operator new(size_t, void *p) { return p; }
+inline void *operator new(size_t, void *p, placement_tag) { return p; }
+
+namespace ranges {
+struct reverse_fn {};
+inline constexpr reverse_fn reverse;
+
+template <class Range>
+bool operator|(Range &&, reverse_fn) {
+ return true;
+}
+} // namespace ranges
+
+//--- M.cppm
+module;
+#include "support.h"
+export module M;
+
+export template <class T>
+struct box {
+ alignas(T) unsigned char storage[sizeof(T)];
+
+ void construct(T value) { ::new (static_cast<void *>(storage)) T(value); }
+
+ void construct_tagged(T value) {
+ ::new (static_cast<void *>(storage), placement_tag{}) T(value);
+ }
+
+ bool reverse() { return *this | ranges::reverse; }
+};
+
+//--- use.cpp
+// expected-no-diagnostics
+import M;
+
+void use() {
+ box<int> b;
+ b.construct(42);
+ b.construct_tagged(43);
+ (void)b.reverse();
+}
More information about the cfe-commits
mailing list