https://github.com/ChuanqiXu9 created 
https://github.com/llvm/llvm-project/pull/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.

>From 63ad6091cdeaf182341ee971b88c8d106a0a908e Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <[email protected]>
Date: Thu, 27 Aug 2026 16:38:28 +0800
Subject: [PATCH] [C++20] [Modules] Preserve placement new and operator for ADL
 in Reduced BMI

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.
---
 clang/lib/Serialization/ASTWriterStmt.cpp     | 56 ++++++++++++++++++
 .../Modules/reduced-bmi-dependent-lookup.cpp  | 57 +++++++++++++++++++
 2 files changed, 113 insertions(+)
 create mode 100644 clang/test/Modules/reduced-bmi-dependent-lookup.cpp

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();
+}

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

Reply via email to