[clang] [C++26] Implement P2752R3 'Static storage for braced initializers' (PR #197458)

Yihan Wang via cfe-commits cfe-commits at lists.llvm.org
Tue Jun 30 05:20:48 PDT 2026


https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/197458

>From 76fcb0a8d54b10d4e9204cf06cd3ef1d67b1215a Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 27 May 2026 09:39:03 -0700
Subject: [PATCH 1/5] [C++26] Implement P2752R3 'Static storage for braced
 initializers'

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/docs/ReleaseNotes.md                    |   1 +
 clang/include/clang/AST/ExprCXX.h             |  17 +-
 clang/include/clang/AST/Stmt.h                |  14 +
 clang/include/clang/AST/TypeBase.h            |   9 +
 .../include/clang/Basic/DiagnosticASTKinds.td |   2 +
 clang/lib/AST/ASTImporter.cpp                 |   2 +
 clang/lib/AST/ByteCode/Interp.cpp             | 112 ++++++
 clang/lib/AST/ByteCode/Interp.h               |  14 +-
 clang/lib/AST/ExprCXX.cpp                     |   1 +
 clang/lib/AST/ExprConstShared.h               |  31 +-
 clang/lib/AST/ExprConstant.cpp                | 331 ++++++++++++++++--
 clang/lib/AST/Type.cpp                        |  81 +++++
 clang/lib/Sema/CheckExprLifetime.cpp          |  19 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 101 +-----
 clang/lib/Sema/SemaInit.cpp                   |   1 +
 clang/lib/Serialization/ASTReaderStmt.cpp     |   1 +
 clang/lib/Serialization/ASTWriterStmt.cpp     |   1 +
 clang/test/AST/ByteCode/initializer_list.cpp  | 300 ++++++++++++++++
 .../dcl.decl/dcl.init/dcl.init.list/p5.cpp    | 139 ++++++++
 clang/test/CXX/drs/cwg27xx.cpp                | 167 +++++++--
 clang/test/CodeGenCXX/Inputs/jk.txt           |   1 +
 .../CodeGenCXX/p2752r3-initializer-list.cpp   |  93 +++++
 clang/www/cxx_dr_status.html                  |   2 +-
 clang/www/cxx_status.html                     |   2 +-
 24 files changed, 1277 insertions(+), 165 deletions(-)
 create mode 100644 clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
 create mode 100644 clang/test/CodeGenCXX/Inputs/jk.txt
 create mode 100644 clang/test/CodeGenCXX/p2752r3-initializer-list.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index b372e5b58068b..c990afd43ba59 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -214,6 +214,7 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
 #### C++2c Feature Support
 
 - Clang now propagates `constinit` and `constexpr` in structured bindings with tuple-like initializers.
+- Clang now supports `P2752R3 <https://wg21.link/p2752r3>`_ 'Static storage for braced initializers'. (#GH104487)
 
 #### C++23 Feature Support
 
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 36e5f8940228e..09c7423a2eb36 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4930,7 +4930,22 @@ class MaterializeTemporaryExpr : public Expr {
                            LifetimeExtendedTemporaryDecl *MTD = nullptr);
 
   MaterializeTemporaryExpr(EmptyShell Empty)
-      : Expr(MaterializeTemporaryExprClass, Empty) {}
+      : Expr(MaterializeTemporaryExprClass, Empty) {
+    MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false;
+  }
+
+  /// Whether this materialized temporary is the backing array of a
+  /// std::initializer_list.
+  ///
+  /// This used by [intro.object]/9 "potentially non-unique object" handling.
+  bool isBackingArrayForInitializerList() const {
+    return MaterializeTemporaryExprBits.IsBackingArrayForInitializerList;
+  }
+
+  /// This bitfield will set by SK_StdInitializerList step in Sema.
+  void setBackingArrayForInitializerList(bool V = true) {
+    MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = V;
+  }
 
   /// Retrieve the temporary-generating subexpression whose value will
   /// be materialized into a glvalue.
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index f07ba9205661b..e4eebe5986bd5 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -831,6 +831,19 @@ class alignas(void *) Stmt {
     SourceLocation Loc;
   };
 
+  class MaterializeTemporaryExprBitfields {
+    friend class ASTStmtReader;
+    friend class MaterializeTemporaryExpr;
+
+    LLVM_PREFERRED_TYPE(ExprBitfields)
+    unsigned : NumExprBits;
+
+    /// Whether the materialized temporary is the backing array of a
+    /// std::initializer_list.
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned IsBackingArrayForInitializerList : 1;
+  };
+
   class CXXThisExprBitfields {
     friend class CXXThisExpr;
 
@@ -1381,6 +1394,7 @@ class alignas(void *) Stmt {
     CXXRewrittenBinaryOperatorBitfields CXXRewrittenBinaryOperatorBits;
     CXXBoolLiteralExprBitfields CXXBoolLiteralExprBits;
     CXXNullPtrLiteralExprBitfields CXXNullPtrLiteralExprBits;
+    MaterializeTemporaryExprBitfields MaterializeTemporaryExprBits;
     CXXThisExprBitfields CXXThisExprBits;
     CXXThrowExprBitfields CXXThrowExprBits;
     CXXDefaultArgExprBitfields CXXDefaultArgExprBits;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 3a801e2857b13..ac3dc8e58a515 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -116,6 +116,7 @@ namespace clang {
 class ASTContext;
 template <typename> class CanQual;
 class CXXRecordDecl;
+class Decl;
 class DeclContext;
 class EnumDecl;
 class Expr;
@@ -2762,6 +2763,14 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
   bool isNothrowT() const;                      // C++   std::nothrow_t
   bool isAlignValT() const;                     // C++17 std::align_val_t
   bool isStdByteType() const;                   // C++17 std::byte
+  bool
+  isStdClassTemplateSpecialization(const ASTContext &Ctx, StringRef ClassName,
+                                   QualType *TypeArg = nullptr,
+                                   ClassTemplateDecl **CachedDecl = nullptr,
+                                   const Decl **MalformedDecl = nullptr) const;
+  bool isStdInitializerListType(
+      const ASTContext &Ctx,
+      QualType *Element = nullptr) const; // C++11 std::initializer_list<T>
   bool isAtomicType() const;                    // C11 _Atomic()
   bool isUndeducedAutoType() const;             // C++11 auto or
                                                 // C++14 decltype(auto)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index bde418695f647..f4f239a16a38f 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -99,6 +99,8 @@ def note_constexpr_pointer_constant_comparison : Note<
   "at runtime">;
 def note_constexpr_literal_comparison : Note<
   "comparison of addresses of potentially overlapping literals has unspecified value">;
+def note_constexpr_non_unique_object_comparison : Note<
+  "comparison of addresses of potentially non-unique objects has unspecified value">;
 def note_constexpr_literal_arith : Note<
   "arithmetic on addresses of potentially overlapping literals has unspecified value">;
 def note_constexpr_repeated_literal_eval : Note<
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 567d2d07298a3..0abb902bd1aba 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8577,6 +8577,8 @@ ASTNodeImporter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
   auto *ToMTE = new (Importer.getToContext()) MaterializeTemporaryExpr(
       ToType, ToTemporaryExpr, E->isBoundToLvalueReference(),
       ToMaterializedDecl);
+  ToMTE->setBackingArrayForInitializerList(
+      E->isBackingArrayForInitializerList());
 
   return ToMTE;
 }
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 4815828adb613..79711d2e6a21d 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2819,6 +2819,118 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
   return Shorter == Longer.take_front(Shorter.size());
 }
 
+/// Whether Ptr designates an object that is the backing array of a
+/// std::initializer_list.
+static bool isInitializerListBackingArray(const Pointer &Ptr) {
+  if (Ptr.isZero() || !Ptr.isBlockPointer() || Ptr.block()->isDynamic())
+    return false;
+
+  const auto *MTE =
+      dyn_cast_or_null<MaterializeTemporaryExpr>(Ptr.getDeclDesc()->asExpr());
+  return MTE && MTE->isBackingArrayForInitializerList();
+}
+
+namespace {
+/// Pairs an enclosing-array Pointer with the element-relative location of
+/// the original pointer within it.
+struct ArraySubobjectInfo {
+  Pointer Array;
+  ArraySubobjectLocation Loc;
+};
+} // namespace
+
+/// Returns the enclosing array and element-relative location if Ptr
+/// designates an element of an initializer_list backing array,
+/// one-past-the-end of it, or a subobject of an element. Returns
+/// std::nullopt otherwise.
+static std::optional<ArraySubobjectInfo>
+getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
+  if (Ptr.isZero() || !Ptr.isBlockPointer())
+    return std::nullopt;
+
+  Pointer Array = Ptr.getDeclPtr();
+  if (!isInitializerListBackingArray(Array))
+    return std::nullopt;
+
+  const auto *ArrayType =
+      Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+  if (!ArrayType)
+    return std::nullopt;
+
+  APValue PtrValue = Ptr.toAPValue(Ctx);
+  if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
+    return std::nullopt;
+
+  ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+  if (Path.empty())
+    return std::nullopt;
+
+  uint64_t Index = Path.front().getAsArrayIndex();
+  bool IsValidOnePastEnd = Path.size() == 1;
+  std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+      Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
+  if (!Loc)
+    return std::nullopt;
+  return ArraySubobjectInfo{std::move(Array), *Loc};
+}
+
+/// Returns true if \p LHS and \p RHS both designate elements of
+/// std::initializer_list backing arrays whose values agree on the overlapping
+/// region. Such backing arrays may be merged by the implementation, so
+/// comparing their addresses produces an unspecified result and is rejected
+/// as a constant expression.
+bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
+                                                    const Pointer &LHS,
+                                                    const Pointer &RHS) {
+  const ASTContext &Ctx = S.getASTContext();
+  std::optional<ArraySubobjectInfo> LHSInfo =
+      getArraySubobjectLocation(Ctx, LHS);
+  std::optional<ArraySubobjectInfo> RHSInfo =
+      getArraySubobjectLocation(Ctx, RHS);
+  if (!LHSInfo || !RHSInfo)
+    return false;
+
+  if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement)
+    return false;
+
+  const auto *LHSArrayType =
+      Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
+  const auto *RHSArrayType =
+      Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
+  if (!LHSArrayType || !RHSArrayType ||
+      !Ctx.hasSameType(LHSArrayType->getElementType(),
+                       RHSArrayType->getElementType()))
+    return false;
+
+  QualType ElementType = LHSArrayType->getElementType();
+  int64_t LHSSize = LHSInfo->Array.getNumElems();
+  int64_t RHSSize = RHSInfo->Array.getNumElems();
+  int64_t LHSOffset = LHSInfo->Loc.Index;
+  int64_t RHSOffset = RHSInfo->Loc.Index;
+  int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+  int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+  if (OverlapBegin >= OverlapEnd)
+    return false;
+
+  for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+    Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow();
+    Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow();
+    std::optional<APValue> LHSEltValue =
+        LHSElt.toRValue(S.getContext(), ElementType);
+    std::optional<APValue> RHSEltValue =
+        RHSElt.toRValue(S.getContext(), ElementType);
+    // Missing element data: be conservative and assume the backing arrays
+    // may share storage.
+    if (!LHSEltValue || !RHSEltValue)
+      return true;
+
+    if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx))
+      return false;
+  }
+
+  return true;
+}
+
 static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
                                 PrimType T) {
   if (T == PT_IntAPS) {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 97d9e7cc14e32..0b73ede59f7dd 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1292,6 +1292,9 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
 
 bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
                                              const Pointer &RHS);
+bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
+                                                    const Pointer &LHS,
+                                                    const Pointer &RHS);
 
 template <>
 inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1336,7 +1339,10 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     return true;
   }
 
-  // FIXME: The source check here isn't entirely correct.
+  // C++ [intro.object]/9:
+  //   An object is potentially non-unique if it is a string literal object,
+  //   the backing array of an initializer list, or a subobject thereof.
+  // FIXME: The string literal source check here isn't entirely correct.
   if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() &&
       LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) {
     if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
@@ -1358,6 +1364,12 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     return true;
   }
 
+  if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) {
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
+    return false;
+  }
+
   // Otherwise we need to do a bunch of extra checks before returning Unordered.
   if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
       RHS.getOffset() == 0) {
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 960691219be6d..bbe237f0ad205 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1825,6 +1825,7 @@ MaterializeTemporaryExpr::MaterializeTemporaryExpr(
     LifetimeExtendedTemporaryDecl *MTD)
     : Expr(MaterializeTemporaryExprClass, T,
            BoundToLvalueReference ? VK_LValue : VK_XValue, OK_Ordinary) {
+  MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false;
   if (MTD) {
     State = MTD;
     MTD->ExprWithTemporary = Temporary;
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index 619c79a1408f3..73c4ab76ade19 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -14,6 +14,7 @@
 #ifndef LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
 #define LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
 
+#include "clang/AST/CharUnits.h"
 #include "clang/Basic/TypeTraits.h"
 #include <cstdint>
 #include <optional>
@@ -27,7 +28,8 @@ namespace clang {
 class QualType;
 class LangOptions;
 class ASTContext;
-class CharUnits;
+class APValue;
+class ConstantArrayType;
 class Expr;
 } // namespace clang
 using namespace clang;
@@ -78,6 +80,12 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C,
 CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
                          UnaryExprOrTypeTrait ExprKind);
 
+/// Whether two APValues could be merged into a single storage location by
+/// the implementation (the relation [intro.object]/9 cares about for
+/// initializer_list backing arrays and string literals).
+bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
+                                     const ASTContext &Ctx);
+
 uint8_t GFNIMultiplicativeInverse(uint8_t Byte);
 uint8_t GFNIMul(uint8_t AByte, uint8_t BByte);
 uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword,
@@ -89,4 +97,23 @@ std::optional<llvm::APFloat>
 EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B,
                    std::optional<llvm::APSInt> RoundingMode, bool IsMin);
 
-#endif
+/// Where an lvalue into an array element lives: the element index within the
+/// array (or the array length for a one-past-the-end pointer), and the byte
+/// offset from the start of that element.
+struct ArraySubobjectLocation {
+  uint64_t Index;
+  CharUnits OffsetInElement;
+};
+
+/// Computes the array-element location designated by an lvalue whose first
+/// path entry indexes into ArrayType with the given Index and whose
+/// byte offset from the array base is LValueOffset. IsValidOnePastEnd
+/// must be true iff the lvalue is a valid one-past-the-end position of the
+/// array (which the caller determines from its own lvalue representation).
+/// Returns std::nullopt if the lvalue does not designate an element,
+/// one-past-the-end position, or subobject of an element.
+std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+    const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
+    CharUnits LValueOffset, bool IsValidOnePastEnd);
+
+#endif // LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 563d6b3bb0cf9..9e77febd9fff3 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -56,6 +56,7 @@
 #include "clang/Basic/TargetBuiltins.h"
 #include "clang/Basic/TargetInfo.h"
 #include "llvm/ADT/APFixedPoint.h"
+#include "llvm/ADT/FoldingSet.h"
 #include "llvm/ADT/Sequence.h"
 #include "llvm/ADT/SmallBitVector.h"
 #include "llvm/ADT/StringExtras.h"
@@ -2020,17 +2021,18 @@ struct LValueBaseString {
   int CharWidth;
 };
 
-// Gets the lvalue base of LVal as a string.
-static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
+// Gets the lvalue base as a string.
+static bool GetLValueBaseAsString(const ASTContext &Ctx,
+                                  APValue::LValueBase Base,
                                   LValueBaseString &AsString) {
-  const auto *BaseExpr = LVal.Base.dyn_cast<const Expr *>();
+  const auto *BaseExpr = Base.dyn_cast<const Expr *>();
   if (!BaseExpr)
     return false;
 
   // For ObjCEncodeExpr, we need to compute and store the string.
   if (const auto *EE = dyn_cast<ObjCEncodeExpr>(BaseExpr)) {
-    Info.Ctx.getObjCEncodingForType(EE->getEncodedType(),
-                                    AsString.ObjCEncodeStorage);
+    Ctx.getObjCEncodingForType(EE->getEncodedType(),
+                               AsString.ObjCEncodeStorage);
     AsString.Bytes = AsString.ObjCEncodeStorage;
     AsString.CharWidth = 1;
     return true;
@@ -2049,6 +2051,11 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
   return true;
 }
 
+static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
+                                  LValueBaseString &AsString) {
+  return GetLValueBaseAsString(Info.Ctx, LVal.Base, AsString);
+}
+
 // Determine whether two string literals potentially overlap. This will be the
 // case if they agree on the values of all the bytes on the overlapping region
 // between them.
@@ -2063,31 +2070,31 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
 // addresses onwards.
 //
 // See open core issue CWG2765 which is discussing the desired rule here.
-static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
-                                                    const LValue &LHS,
-                                                    const LValue &RHS) {
-  LValueBaseString LHSString, RHSString;
-  if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
-      !GetLValueBaseAsString(Info, RHS, RHSString))
-    return false;
+static bool ArePotentiallyOverlappingStringLiterals(
+    const LValueBaseString &LHSString, CharUnits LHSOffset,
+    const LValueBaseString &RHSString, CharUnits RHSOffset) {
+  assert(LHSString.Bytes.data() && RHSString.Bytes.data() &&
+         "passing a string with no underlying storage");
 
   // This is the byte offset to the location of the first character of LHS
   // within RHS. We don't need to look at the characters of one string that
   // would appear before the start of the other string if they were merged.
-  CharUnits Offset = RHS.Offset - LHS.Offset;
+  StringRef LHSBytes = LHSString.Bytes;
+  StringRef RHSBytes = RHSString.Bytes;
+  CharUnits Offset = RHSOffset - LHSOffset;
   if (Offset.isNegative()) {
-    if (LHSString.Bytes.size() < (size_t)-Offset.getQuantity())
+    if (LHSBytes.size() < (size_t)-Offset.getQuantity())
       return false;
-    LHSString.Bytes = LHSString.Bytes.drop_front(-Offset.getQuantity());
+    LHSBytes = LHSBytes.drop_front(-Offset.getQuantity());
   } else {
-    if (RHSString.Bytes.size() < (size_t)Offset.getQuantity())
+    if (RHSBytes.size() < (size_t)Offset.getQuantity())
       return false;
-    RHSString.Bytes = RHSString.Bytes.drop_front(Offset.getQuantity());
+    RHSBytes = RHSBytes.drop_front(Offset.getQuantity());
   }
 
-  bool LHSIsLonger = LHSString.Bytes.size() > RHSString.Bytes.size();
-  StringRef Longer = LHSIsLonger ? LHSString.Bytes : RHSString.Bytes;
-  StringRef Shorter = LHSIsLonger ? RHSString.Bytes : LHSString.Bytes;
+  bool LHSIsLonger = LHSBytes.size() > RHSBytes.size();
+  StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes;
+  StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes;
   int ShorterCharWidth = (LHSIsLonger ? RHSString : LHSString).CharWidth;
 
   // The null terminator isn't included in the string data, so check for it
@@ -2105,6 +2112,32 @@ static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
   return Shorter == Longer.take_front(Shorter.size());
 }
 
+static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
+                                                    const LValue &LHS,
+                                                    const LValue &RHS) {
+  LValueBaseString LHSString, RHSString;
+  if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
+      !GetLValueBaseAsString(Info, RHS, RHSString))
+    return false;
+
+  return ArePotentiallyOverlappingStringLiterals(LHSString, LHS.Offset,
+                                                 RHSString, RHS.Offset);
+}
+
+/// APValue overload of the string-literal overlap predicate. Returns nullopt
+/// if either operand is not a string-literal (or ObjC encoding) LValue.
+static std::optional<bool> ArePotentiallyOverlappingStringLiterals(
+    const ASTContext &Ctx, const APValue &LHS, const APValue &RHS) {
+  if (!LHS.isLValue() || !RHS.isLValue())
+    return std::nullopt;
+  LValueBaseString LHSString, RHSString;
+  if (!GetLValueBaseAsString(Ctx, LHS.getLValueBase(), LHSString) ||
+      !GetLValueBaseAsString(Ctx, RHS.getLValueBase(), RHSString))
+    return std::nullopt;
+  return ArePotentiallyOverlappingStringLiterals(
+      LHSString, LHS.getLValueOffset(), RHSString, RHS.getLValueOffset());
+}
+
 static bool IsWeakLValue(const LValue &Value) {
   const ValueDecl *Decl = GetLValueBaseDecl(Value);
   return Decl && Decl->isWeak();
@@ -4856,6 +4889,259 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
   return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType);
 }
 
+static const APValue *GetArrayInitializedElt(const APValue &Array,
+                                             uint64_t Index) {
+  if (!Array.isArray() || Index >= Array.getArraySize())
+    return nullptr;
+  if (Index < Array.getArrayInitializedElts())
+    return &Array.getArrayInitializedElt(Index);
+  return Array.hasArrayFiller() ? &Array.getArrayFiller() : nullptr;
+}
+
+/// True if the LValue base names a weak entity (whose link-time identity
+/// may be merged with another).
+static bool isWeakLValueBase(const APValue &V) {
+  if (!V.isLValue())
+    return false;
+  if (const auto *VD = V.getLValueBase().dyn_cast<const ValueDecl *>())
+    return VD->isWeak();
+  return false;
+}
+
+/// True if the member-pointer target decl is weak.
+static bool isWeakMemberPointer(const APValue &V) {
+  if (!V.isMemberPointer())
+    return false;
+  const ValueDecl *D = V.getMemberPointerDecl();
+  return D && D->isWeak();
+}
+
+/// AreAPValuesPotentiallyMergeableSlow - The slow path for
+/// AreAPValuesPotentiallyMergeable. Will be invoked when the two values'
+/// APValue::Profile already differs. Returns true if the disagreement is one
+/// the runtime can collapse (overlapping string literals, weak decls that might
+/// resolve to the same target), recursing into aggregate kinds so that
+/// aggregates containing such leaves are still reported as mergeable.
+static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
+                                                const APValue &RHS,
+                                                const ASTContext &Ctx) {
+  if (!LHS.hasValue() || !RHS.hasValue())
+    return true;
+  if (LHS.getKind() != RHS.getKind())
+    return false;
+
+  switch (LHS.getKind()) {
+  case APValue::LValue: {
+    // Distinct string-literal LValues whose content could be merged.
+    if (auto Overlap = ArePotentiallyOverlappingStringLiterals(Ctx, LHS, RHS))
+      return *Overlap;
+    // Either side names a weak entity -> link-time may resolve to one.
+    return isWeakLValueBase(LHS) || isWeakLValueBase(RHS);
+  }
+
+  case APValue::MemberPointer:
+    return isWeakMemberPointer(LHS) || isWeakMemberPointer(RHS);
+
+  case APValue::Struct: {
+    if (LHS.getStructNumBases() != RHS.getStructNumBases() ||
+        LHS.getStructNumFields() != RHS.getStructNumFields())
+      return false;
+    for (unsigned I = 0, N = LHS.getStructNumBases(); I != N; ++I)
+      if (!AreAPValuesPotentiallyMergeable(LHS.getStructBase(I),
+                                           RHS.getStructBase(I), Ctx))
+        return false;
+    for (unsigned I = 0, N = LHS.getStructNumFields(); I != N; ++I)
+      if (!AreAPValuesPotentiallyMergeable(LHS.getStructField(I),
+                                           RHS.getStructField(I), Ctx))
+        return false;
+    return true;
+  }
+
+  case APValue::Union: {
+    if (LHS.getUnionField() != RHS.getUnionField())
+      return false;
+    if (!LHS.getUnionField())
+      return true;
+    return AreAPValuesPotentiallyMergeable(LHS.getUnionValue(),
+                                           RHS.getUnionValue(), Ctx);
+  }
+
+  case APValue::Array: {
+    if (LHS.getArraySize() != RHS.getArraySize())
+      return false;
+    for (unsigned I = 0, N = LHS.getArraySize(); I != N; ++I) {
+      const APValue *LE = GetArrayInitializedElt(LHS, I);
+      const APValue *RE = GetArrayInitializedElt(RHS, I);
+      // Missing element data: be conservative.
+      if (!LE || !RE)
+        return true;
+      if (!AreAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
+        return false;
+    }
+    return true;
+  }
+
+  case APValue::Vector: {
+    if (LHS.getVectorLength() != RHS.getVectorLength())
+      return false;
+    for (unsigned I = 0, N = LHS.getVectorLength(); I != N; ++I)
+      if (!AreAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
+                                           RHS.getVectorElt(I), Ctx))
+        return false;
+    return true;
+  }
+
+  case APValue::Matrix: {
+    if (LHS.getMatrixNumRows() != RHS.getMatrixNumRows() ||
+        LHS.getMatrixNumColumns() != RHS.getMatrixNumColumns())
+      return false;
+    for (unsigned I = 0, N = LHS.getMatrixNumElements(); I != N; ++I)
+      if (!AreAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
+                                           RHS.getMatrixElt(I), Ctx))
+        return false;
+    return true;
+  }
+
+  // For every scalar kind (Int, Float, FixedPoint, ComplexInt, ComplexFloat,
+  // AddrLabelDiff, None, Indeterminate), Profile already captures the full
+  // observable content. If Profile disagreed, the values are observably
+  // distinct.
+  default:
+    return false;
+  }
+}
+
+bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
+                                     const ASTContext &Ctx) {
+  // Fast path: bitwise-identical APValues -> definitely mergeable.
+  llvm::FoldingSetNodeID LHSID, RHSID;
+  LHS.Profile(LHSID);
+  RHS.Profile(RHSID);
+  if (LHSID == RHSID)
+    return true;
+
+  return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
+}
+
+std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+    const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
+    CharUnits LValueOffset, bool IsValidOnePastEnd) {
+  uint64_t ArraySize = ArrayType->getZExtSize();
+  if (Index > ArraySize)
+    return std::nullopt;
+
+  if (Index == ArraySize) {
+    if (!IsValidOnePastEnd)
+      return std::nullopt;
+    return ArraySubobjectLocation{Index, CharUnits::Zero()};
+  }
+
+  if (LValueOffset.isNegative())
+    return std::nullopt;
+
+  uint64_t ElementSize =
+      Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+  if (ElementSize && Index > std::numeric_limits<uint64_t>::max() / ElementSize)
+    return std::nullopt;
+
+  uint64_t ElementOffset = ElementSize * Index;
+  uint64_t LValueOffsetQ = LValueOffset.getQuantity();
+  if (LValueOffsetQ < ElementOffset)
+    return std::nullopt;
+
+  uint64_t OffsetInElement = LValueOffsetQ - ElementOffset;
+  if (OffsetInElement > ElementSize)
+    return std::nullopt;
+
+  return ArraySubobjectLocation{Index,
+                                CharUnits::fromQuantity(OffsetInElement)};
+}
+
+/// Returns the array element and element-relative location that LV
+/// designates, or std::nullopt if LV is not an element, one-past-the-end
+/// position, or subobject of an element of its base array.
+static std::optional<ArraySubobjectLocation>
+getArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+  if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+    return std::nullopt;
+
+  const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+  if (!ArrayType)
+    return std::nullopt;
+
+  uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
+  bool IsValidOnePastEnd =
+      LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
+  return getArraySubobjectLocationImpl(Ctx, ArrayType, Index, LV.Offset,
+                                       IsValidOnePastEnd);
+}
+
+static const APValue *GetCompleteObjectValue(EvalInfo &Info, const Expr *E,
+                                             const LValue &LV) {
+  CompleteObject Obj =
+      findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
+  return Obj ? Obj.Value : nullptr;
+}
+
+static bool isInitializerListBackingArray(const LValue &LV) {
+  const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>();
+  const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr);
+  return MTE && MTE->isBackingArrayForInitializerList();
+}
+
+static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info,
+                                                           const Expr *E,
+                                                           const LValue &LHS,
+                                                           const LValue &RHS) {
+  if (!isInitializerListBackingArray(LHS) ||
+      !isInitializerListBackingArray(RHS))
+    return false;
+
+  std::optional<ArraySubobjectLocation> LHSLoc =
+      getArraySubobjectLocation(Info.Ctx, LHS);
+  std::optional<ArraySubobjectLocation> RHSLoc =
+      getArraySubobjectLocation(Info.Ctx, RHS);
+  if (!LHSLoc || !RHSLoc)
+    return false;
+
+  if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement)
+    return false;
+
+  const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base));
+  const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base));
+  if (!LHSArrayType || !RHSArrayType ||
+      !Info.Ctx.hasSameType(LHSArrayType->getElementType(),
+                            RHSArrayType->getElementType()))
+    return false;
+
+  const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS);
+  const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS);
+  if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray())
+    return false;
+
+  int64_t LHSSize = LHSArray->getArraySize();
+  int64_t RHSSize = RHSArray->getArraySize();
+  int64_t LHSOffset = LHSLoc->Index;
+  int64_t RHSOffset = RHSLoc->Index;
+  int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+  int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+  if (OverlapBegin >= OverlapEnd)
+    return false;
+
+  for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+    const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset);
+    const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset);
+    // Missing element data: be conservative and assume the arrays may share
+    // storage.
+    if (!LHSElt || !RHSElt)
+      return true;
+    if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+      return false;
+  }
+
+  return true;
+}
+
 /// Perform an lvalue-to-rvalue conversion on the given glvalue. This
 /// can also be used for 'lvalue-to-lvalue' conversions for looking up the
 /// glvalue referred to by an entity of reference type.
@@ -18877,9 +19163,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       // This makes the comparison result unspecified, so it's not a constant
       // expression.
       //
-      // TODO: Do we need to handle the initializer list case here?
       if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
         return DiagComparison(diag::note_constexpr_literal_comparison);
+      if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue,
+                                                         RHSValue))
+        return DiagComparison(
+            diag::note_constexpr_non_unique_object_comparison);
       if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
         return DiagComparison(diag::note_constexpr_opaque_call_comparison,
                               !IsOpaqueConstantCall(LHSValue));
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 19b85d0e0af69..13897198aae7e 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3327,6 +3327,87 @@ bool Type::isStdByteType() const {
   return false;
 }
 
+bool Type::isStdClassTemplateSpecialization(const ASTContext &Ctx,
+                                            StringRef ClassName,
+                                            QualType *TypeArg,
+                                            ClassTemplateDecl **CachedDecl,
+                                            const Decl **MalformedDecl) const {
+  QualType SugaredType(this, 0);
+  auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) {
+    if (!MalformedDecl)
+      return;
+    if (!D)
+      D = SugaredType->getAsTagDecl();
+    if (!D || !D->isInStdNamespace())
+      return;
+    IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo();
+    if (II && II->isStr(ClassName))
+      *MalformedDecl = D;
+  };
+
+  ClassTemplateDecl *Template = nullptr;
+  ArrayRef<TemplateArgument> Arguments;
+  if (const TemplateSpecializationType *TST =
+          SugaredType->getAsNonAliasTemplateSpecializationType()) {
+    Template = dyn_cast_or_null<ClassTemplateDecl>(
+        TST->getTemplateName().getAsTemplateDecl());
+    Arguments = TST->template_arguments();
+  } else if (const auto *TT = SugaredType->getAs<TagType>()) {
+    Template = TT->getTemplateDecl();
+    Arguments = TT->getTemplateArgs(Ctx);
+  }
+
+  if (!Template) {
+    ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl());
+    return false;
+  }
+
+  ClassTemplateDecl *Cached = CachedDecl ? *CachedDecl : nullptr;
+  if (!Cached) {
+    CXXRecordDecl *TemplateClass = Template->getTemplatedDecl();
+    IdentifierInfo *II = TemplateClass->getIdentifier();
+    if (!II || !II->isStr(ClassName) || !TemplateClass->isInStdNamespace())
+      return false;
+
+    TemplateParameterList *Params = Template->getTemplateParameters();
+    if (Params->getMinRequiredArguments() != 1 ||
+        !isa<TemplateTypeParmDecl>(Params->getParam(0)) ||
+        Params->getParam(0)->isTemplateParameterPack()) {
+      if (MalformedDecl)
+        *MalformedDecl = TemplateClass;
+      return false;
+    }
+
+    Cached = Template;
+    if (CachedDecl)
+      *CachedDecl = Template;
+  }
+
+  if (Template->getCanonicalDecl() != Cached->getCanonicalDecl())
+    return false;
+
+  if (TypeArg) {
+    if (Arguments.empty() || Arguments[0].getKind() != TemplateArgument::Type)
+      return false;
+
+    QualType ArgType = Arguments[0].getAsType();
+    if (Ctx.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() &&
+        !ArgType.getObjCLifetime()) {
+      Qualifiers Qs;
+      Qs.setObjCLifetime(Qualifiers::OCL_Strong);
+      ArgType = Ctx.getQualifiedType(ArgType, Qs);
+    }
+    *TypeArg = ArgType;
+  }
+
+  return true;
+}
+
+bool Type::isStdInitializerListType(const ASTContext &Ctx,
+                                    QualType *Element) const {
+  return isStdClassTemplateSpecialization(Ctx, "initializer_list", Element);
+}
+
 bool Type::isSpecifierType() const {
   // Note that this intentionally does not use the canonical type.
   switch (getTypeClass()) {
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index d15b2c6518cec..7b466ec94b70c 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -285,17 +285,11 @@ static bool isContainerOfOwner(const RecordDecl *Container) {
          isGslOwnerType(TAs[0].getAsType());
 }
 
-// Returns true if the given Record is `std::initializer_list<pointer>`.
-static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
-  if (const auto *CTSD =
-          dyn_cast_if_present<ClassTemplateSpecializationDecl>(RD)) {
-    const auto &TAs = CTSD->getTemplateArgs();
-    return lifetimes::isInStlNamespace(RD) && RD->getIdentifier() &&
-           RD->getName() == "initializer_list" && TAs.size() > 0 &&
-           TAs[0].getKind() == TemplateArgument::Type &&
-           lifetimes::isPointerLikeType(TAs[0].getAsType());
-  }
-  return false;
+// Returns true if the given type is `std::initializer_list<pointer>`.
+static bool isStdInitializerListOfPointer(QualType T, const ASTContext &Ctx) {
+  QualType ElementType;
+  return T.getNonReferenceType()->isStdInitializerListType(Ctx, &ElementType) &&
+         lifetimes::isPointerLikeType(ElementType);
 }
 
 // Returns true if the given constructor is a copy-like constructor, such as
@@ -351,7 +345,8 @@ shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
   // array. We perform analysis on it to determine if there are any dangling
   // temporaries in the backing array.
   // E.g. std::vector<string_view> abc = {string()};
-  if (isStdInitializerListOfPointer(RHSRD))
+  if (isStdInitializerListOfPointer(RHSArgType,
+                                    Ctor->getConstructor()->getASTContext()))
     return true;
 
   // RHS must be an owner.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c645d96da5c00..9f43916c4fecc 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12327,96 +12327,6 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() {
   return getStdNamespace();
 }
 
-static bool isStdClassTemplate(Sema &S, QualType SugaredType, QualType *TypeArg,
-                               const char *ClassName,
-                               ClassTemplateDecl **CachedDecl,
-                               const Decl **MalformedDecl) {
-  // We're looking for implicit instantiations of
-  // template <typename U> class std::{ClassName}.
-
-  if (!S.StdNamespace) // If we haven't seen namespace std yet, this can't be
-                       // it.
-    return false;
-
-  auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) {
-    if (!MalformedDecl)
-      return;
-    if (!D)
-      D = SugaredType->getAsTagDecl();
-    if (!D || !D->isInStdNamespace())
-      return;
-    IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo();
-    if (II && II == &S.PP.getIdentifierTable().get(ClassName))
-      *MalformedDecl = D;
-  };
-
-  ClassTemplateDecl *Template = nullptr;
-  ArrayRef<TemplateArgument> Arguments;
-  if (const TemplateSpecializationType *TST =
-          SugaredType->getAsNonAliasTemplateSpecializationType()) {
-    Template = dyn_cast_or_null<ClassTemplateDecl>(
-        TST->getTemplateName().getAsTemplateDecl());
-    Arguments = TST->template_arguments();
-  } else if (const auto *TT = SugaredType->getAs<TagType>()) {
-    Template = TT->getTemplateDecl();
-    Arguments = TT->getTemplateArgs(S.Context);
-  }
-
-  if (!Template) {
-    ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl());
-    return false;
-  }
-
-  if (!*CachedDecl) {
-    // Haven't recognized std::{ClassName} yet, maybe this is it.
-    // FIXME: It seems we should just reuse LookupStdClassTemplate but the
-    // semantics of this are slightly different, most notably the existing
-    // "lookup" semantics explicitly diagnose an invalid definition as an
-    // error.
-    CXXRecordDecl *TemplateClass = Template->getTemplatedDecl();
-    if (TemplateClass->getIdentifier() !=
-            &S.PP.getIdentifierTable().get(ClassName) ||
-        !S.getStdNamespace()->InEnclosingNamespaceSetOf(
-            TemplateClass->getNonTransparentDeclContext()))
-      return false;
-    // This is a template called std::{ClassName}, but is it the right
-    // template?
-    TemplateParameterList *Params = Template->getTemplateParameters();
-    if (Params->getMinRequiredArguments() != 1 ||
-        !isa<TemplateTypeParmDecl>(Params->getParam(0)) ||
-        Params->getParam(0)->isTemplateParameterPack()) {
-      if (MalformedDecl)
-        *MalformedDecl = TemplateClass;
-      return false;
-    }
-
-    // It's the right template.
-    *CachedDecl = Template;
-  }
-
-  if (Template->getCanonicalDecl() != (*CachedDecl)->getCanonicalDecl())
-    return false;
-
-  // This is an instance of std::{ClassName}. Find the argument type.
-  if (TypeArg) {
-    QualType ArgType = Arguments[0].getAsType();
-    // FIXME: Since TST only has as-written arguments, we have to perform the
-    // only kind of conversion applicable to type arguments; in Objective-C ARC:
-    // - If an explicitly-specified template argument type is a lifetime type
-    //   with no lifetime qualifier, the __strong lifetime qualifier is
-    //   inferred.
-    if (S.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() &&
-        !ArgType.getObjCLifetime()) {
-      Qualifiers Qs;
-      Qs.setObjCLifetime(Qualifiers::OCL_Strong);
-      ArgType = S.Context.getQualifiedType(ArgType, Qs);
-    }
-    *TypeArg = ArgType;
-  }
-
-  return true;
-}
-
 bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
   assert(getLangOpts().CPlusPlus &&
          "Looking for std::initializer_list outside of C++.");
@@ -12424,8 +12334,10 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
   // We're looking for implicit instantiations of
   // template <typename E> class std::initializer_list.
 
-  return isStdClassTemplate(*this, Ty, Element, "initializer_list",
-                            &StdInitializerList, /*MalformedDecl=*/nullptr);
+  return !Ty.isNull() &&
+         Ty->isStdClassTemplateSpecialization(Context, "initializer_list",
+                                              Element, &StdInitializerList,
+                                              /*MalformedDecl=*/nullptr);
 }
 
 bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element,
@@ -12436,8 +12348,9 @@ bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element,
   // We're looking for implicit instantiations of
   // template <typename T> struct std::type_identity.
 
-  return isStdClassTemplate(*this, Ty, Element, "type_identity",
-                            &StdTypeIdentity, MalformedDecl);
+  return !Ty.isNull() &&
+         Ty->isStdClassTemplateSpecialization(Context, "type_identity", Element,
+                                              &StdTypeIdentity, MalformedDecl);
 }
 
 static ClassTemplateDecl *LookupStdClassTemplate(Sema &S, SourceLocation Loc,
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index dad9c8c972dd9..e9849e286af16 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8690,6 +8690,7 @@ ExprResult InitializationSequence::Perform(Sema &S,
       MaterializeTemporaryExpr *MTE = S.CreateMaterializeTemporaryExpr(
           CurInit.get()->getType(), CurInit.get(),
           /*BoundToLvalueReference=*/false);
+      MTE->setBackingArrayForInitializerList();
 
       // Wrap it in a construction of a std::initializer_list<T>.
       CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 02ccf8d4d41c2..6e0c06e602a95 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2322,6 +2322,7 @@ void ASTStmtReader::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) {
 
 void ASTStmtReader::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
   VisitExpr(E);
+  E->setBackingArrayForInitializerList(Record.readInt());
   bool HasMaterialzedDecl = Record.readInt();
   if (HasMaterialzedDecl)
     E->State = cast<LifetimeExtendedTemporaryDecl>(Record.readDecl());
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 4e9eadd730a56..09fd79fd30010 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2332,6 +2332,7 @@ void ASTStmtWriter::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) {
 
 void ASTStmtWriter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
   VisitExpr(E);
+  Record.push_back(E->isBackingArrayForInitializerList());
   Record.push_back(static_cast<bool>(E->getLifetimeExtendedTemporaryDecl()));
   if (E->getLifetimeExtendedTemporaryDecl())
     Record.AddDeclRef(E->getLifetimeExtendedTemporaryDecl());
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index f882e4ff1b124..a6751453a7f3a 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -68,4 +68,304 @@ namespace rdar13395022 {
   }
 }
 
+namespace cwg2765 {
+  constexpr bool same(std::initializer_list<int> a,
+                      std::initializer_list<int> b) {
+    return a.begin() != b.begin(); // #cwg2765-init-list-compare
+  }
+  static_assert(same({1}, {1}), "");
+  // both-error at -1 {{static assertion expression is not an integral constant expression}}
+  // both-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  template <class T>
+  constexpr bool begins_equal(T a, T b) {
+    return a.begin() == b.begin(); // #cwg2765-template-compare
+  }
+  constexpr bool same_three =
+      begins_equal<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+  // both-error at -2 {{constexpr variable 'same_three' must be initialized by a constant expression}}
+  // both-note@#cwg2765-template-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool different_three =
+      begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
+  static_assert(!different_three, "");
+
+  constexpr bool same_object() {
+    std::initializer_list<int> il = {1, 1, 1};
+    return il.begin() == il.begin() && il.begin() != il.begin() + 1;
+  }
+  static_assert(same_object(), "");
+
+  constexpr bool same_pointer_value() {
+    std::initializer_list<int> il = {1, 1, 1};
+    const int *p = il.begin();
+    return p + 0 == p && p != p + 1;
+  }
+  static_assert(same_pointer_value(), "");
+
+  constexpr bool local_list(const int *p) {
+    std::initializer_list<int> il = {1, 2, 3};
+    return p ? (p == il.begin()) : local_list(il.begin()); // #cwg2765-local-compare
+  }
+  constexpr bool same_local = local_list(nullptr); // #cwg2765-local-call
+  // both-error at -1 {{constexpr variable 'same_local' must be initialized by a constant expression}}
+  // both-note@#cwg2765-local-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note@#cwg2765-local-compare {{in call to}}
+  // both-note@#cwg2765-local-call {{in call to}}
+
+  constexpr bool shifted(std::initializer_list<int> a,
+                         std::initializer_list<int> b) {
+    return a.begin() != b.begin() + 1; // #cwg2765-shifted-compare
+  }
+  constexpr bool annex_c = shifted({2, 3}, {1, 2, 3});
+  // both-error at -1 {{constexpr variable 'annex_c' must be initialized by a constant expression}}
+  // both-note@#cwg2765-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool end_shifted(std::initializer_list<int> a,
+                             std::initializer_list<int> b) {
+    return a.end() == b.begin() + 1; // #cwg2765-end-shifted-compare
+  }
+  constexpr bool same_end_shifted = end_shifted({1}, {1, 2});
+  // both-error at -1 {{constexpr variable 'same_end_shifted' must be initialized by a constant expression}}
+  // both-note@#cwg2765-end-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool different_end_shifted = end_shifted({1}, {2, 1});
+  static_assert(!different_end_shifted, "");
+
+  struct Box {
+    int n;
+  };
+  constexpr bool class_same(std::initializer_list<Box> a,
+                            std::initializer_list<Box> b) {
+    return a.begin() == b.begin(); // #cwg2765-class-compare
+  }
+  constexpr bool same_box = class_same({{1}}, {{1}});
+  // both-error at -1 {{constexpr variable 'same_box' must be initialized by a constant expression}}
+  // both-note@#cwg2765-class-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool different_box = class_same({{1}}, {{2}});
+  static_assert(!different_box, "");
+
+  constexpr bool class_field_same(std::initializer_list<Box> a,
+                                  std::initializer_list<Box> b) {
+    return &a.begin()->n == &b.begin()->n; // #cwg2765-field-compare
+  }
+  constexpr bool same_box_field = class_field_same({{1}}, {{1}});
+  // both-error at -1 {{constexpr variable 'same_box_field' must be initialized by a constant expression}}
+  // both-note@#cwg2765-field-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool different_box_field = class_field_same({{1}}, {{2}});
+  static_assert(!different_box_field, "");
+
+  struct WithPointer {
+    const int *p;
+    int n;
+  };
+  constexpr int pointer_anchor = 0;
+  constexpr int pointer_anchor_2 = 0;
+  constexpr bool class_with_pointer_same(std::initializer_list<WithPointer> a,
+                                         std::initializer_list<WithPointer> b) {
+    return a.begin() == b.begin(); // #cwg2765-with-pointer-compare
+  }
+  // Both elements compare equal in the scalar field; the pointer field
+  // points to the same global, so observable equality is Unknown overall
+  // and the address comparison is unspecified.
+  constexpr bool same_after_pointer_field =
+      class_with_pointer_same({{&pointer_anchor, 1}},
+                              {{&pointer_anchor, 1}});
+  // both-error at -3 {{constexpr variable 'same_after_pointer_field' must be initialized by a constant expression}}
+  // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -4 {{in call to}}
+
+  // The non-pointer field already disagrees, so we can conclude the
+  // backing arrays are distinct even though a pointer field is present.
+  constexpr bool different_after_pointer_field =
+      class_with_pointer_same({{&pointer_anchor, 1}},
+                              {{&pointer_anchor, 2}});
+  static_assert(!different_after_pointer_field, "");
+
+  // Distinct strong-symbol globals have distinct addresses, so the
+  // pointer field alone is enough to conclude the backing arrays differ.
+  constexpr bool different_pointer_field =
+      class_with_pointer_same({{&pointer_anchor, 1}},
+                              {{&pointer_anchor_2, 1}});
+  static_assert(!different_pointer_field, "");
+
+  // A null pointer is distinct from a pointer-to-object.
+  constexpr bool one_null_pointer =
+      class_with_pointer_same({{nullptr, 1}}, {{&pointer_anchor, 1}});
+  static_assert(!one_null_pointer, "");
 
+  // Both null and scalar fields agree -> potentially overlapping.
+  constexpr bool both_null_pointer =
+      class_with_pointer_same({{nullptr, 1}}, {{nullptr, 1}}); // #cwg2765-both-null-call
+  // both-error at -2 {{constexpr variable 'both_null_pointer' must be initialized by a constant expression}}
+  // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note@#cwg2765-both-null-call {{in call to}}
+
+  struct WithStringPointer { const char *p; };
+  constexpr bool class_string_pointer_same(
+      std::initializer_list<WithStringPointer> a,
+      std::initializer_list<WithStringPointer> b) {
+    return a.begin() == b.begin(); // #cwg2765-string-ptr-compare
+  }
+  constexpr bool different_string_pointer =
+      class_string_pointer_same({{"abc"}}, {{"def"}});
+  static_assert(!different_string_pointer, "");
+
+  constexpr bool same_string_pointer =
+      class_string_pointer_same({{"abc"}}, {{"abc"}}); // #cwg2765-same-string-ptr-call
+  // both-error at -2 {{constexpr variable 'same_string_pointer' must be initialized by a constant expression}}
+  // both-note@#cwg2765-string-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note@#cwg2765-same-string-ptr-call {{in call to}}
+
+  // Both pointers are at offset 0 and the literals have different sizes, so
+  // "abc" cannot be merged into the start of "abcd": that would require a
+  // null terminator at offset 3 of "abcd", which holds 'd'. The string-overlap
+  // predicate resolves this to NotEqual.
+  constexpr bool prefix_string_pointer =
+      class_string_pointer_same({{"abc"}}, {{"abcd"}});
+  static_assert(!prefix_string_pointer, "");
+
+  // A null const char * is distinct from any non-null string literal.
+  constexpr bool null_vs_string =
+      class_string_pointer_same({{nullptr}}, {{"abc"}});
+  static_assert(!null_vs_string, "");
+
+  // Floating-point uses bitwise equality: -0.0 is distinguishable from +0.0.
+  struct WithFloat { double d; };
+  constexpr bool class_float_same(std::initializer_list<WithFloat> a,
+                                  std::initializer_list<WithFloat> b) {
+    return a.begin() == b.begin();
+  }
+  constexpr bool different_signed_zero = class_float_same({{-0.0}}, {{0.0}});
+  static_assert(!different_signed_zero, "");
+
+  // Anonymous union field: the active member differs between LHS and RHS, so
+  // the backing arrays must have distinct addresses.
+  struct WithAnonUnion {
+    union { int i; float f; };
+    int tag;
+  };
+  constexpr bool class_anon_union_same(std::initializer_list<WithAnonUnion> a,
+                                       std::initializer_list<WithAnonUnion> b) {
+    return a.begin() == b.begin();
+  }
+  constexpr bool different_union_member =
+      class_anon_union_same({{.i = 0, .tag = 0}}, {{.f = 0.0f, .tag = 0}});
+  static_assert(!different_union_member, "");
+
+  // Member pointers compare structurally: distinct target fields imply
+  // distinct member-pointer values, so the backing arrays differ.
+  struct Holder { int x; int y; };
+  struct WithMemberPtr { int Holder::*p; };
+  constexpr bool class_member_ptr_same(
+      std::initializer_list<WithMemberPtr> a,
+      std::initializer_list<WithMemberPtr> b) {
+    return a.begin() == b.begin(); // #cwg2765-member-ptr-compare
+  }
+  constexpr bool different_member_ptr =
+      class_member_ptr_same({{&Holder::x}}, {{&Holder::y}});
+  static_assert(!different_member_ptr, "");
+
+  // Same member pointer in both arrays + only this field -> potentially
+  // overlapping, so address comparison is unspecified.
+  constexpr bool same_member_ptr =
+      class_member_ptr_same({{&Holder::x}}, {{&Holder::x}}); // #cwg2765-same-mptr-call
+  // both-error at -2 {{constexpr variable 'same_member_ptr' must be initialized by a constant expression}}
+  // both-note@#cwg2765-member-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note@#cwg2765-same-mptr-call {{in call to}}
+
+  // Weak member functions: pointers to the same weak decl still resolve to
+  // a single merged target at link time, so two arrays holding pointers to
+  // the same weak member are still potentially-overlapping (not provably
+  // distinct, but not provably equal either — the link-time merge is fine).
+  struct HolderWithWeak {
+    __attribute__((weak)) void f();
+    __attribute__((weak)) void g();
+  };
+  struct WithWeakMemberPtr { void (HolderWithWeak::*p)(); };
+  constexpr bool class_weak_mptr_same(
+      std::initializer_list<WithWeakMemberPtr> a,
+      std::initializer_list<WithWeakMemberPtr> b) {
+    return a.begin() == b.begin(); // #cwg2765-weak-mptr-compare
+  }
+  // Distinct weak decls may merge at link time -> Unknown.
+  constexpr bool different_weak_member_ptr =
+      class_weak_mptr_same({{&HolderWithWeak::f}}, // #cwg2765-diff-weak-mptr-call
+                           {{&HolderWithWeak::g}});
+  // both-error at -3 {{constexpr variable 'different_weak_member_ptr' must be initialized by a constant expression}}
+  // both-note@#cwg2765-weak-mptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note@#cwg2765-diff-weak-mptr-call {{in call to}}
+
+  // nullptr_t fields: the only value of nullptr_t is null, so this field
+  // never contributes inequality. The other field decides.
+  struct WithNullptrT { decltype(nullptr) np; int n; };
+  constexpr bool class_nullptr_t_same(std::initializer_list<WithNullptrT> a,
+                                      std::initializer_list<WithNullptrT> b) {
+    return a.begin() == b.begin();
+  }
+  constexpr bool different_after_nullptr_t =
+      class_nullptr_t_same({{nullptr, 1}}, {{nullptr, 2}});
+  static_assert(!different_after_nullptr_t, "");
+
+  // Aggregate carrying a std::initializer_list member: the backing array's
+  // extending declaration is the enclosing aggregate, not the
+  // initializer_list. The dedicated marker on MaterializeTemporaryExpr
+  // still recognises the backing array.
+  struct WithIL { std::initializer_list<int> il; };
+  constexpr WithIL agg_a{{1}}, agg_b{{1}};
+  constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin(); // #cwg2765-agg-compare
+  // both-error at -1 {{constexpr variable 'agg_same' must be initialized by a constant expression}}
+  // both-note@#cwg2765-agg-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+  constexpr WithIL agg_c{{1}}, agg_d{{2}};
+  constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin();
+  static_assert(!agg_different, "");
+
+  // Rvalue reference to array bound to a braced-init-list: the materialized
+  // array is NOT a std::initializer_list backing array.
+  constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) {
+    return &a[0] == &b[0];
+  }
+  constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3});
+  static_assert(!rvref_ok, "");
+
+  // Vector elements: APValue::Profile recurses element-wise, so the
+  // mergeability judgment is correctly element-sensitive (NOT "hard-code
+  // these rare kinds to NotEqual"). Confirmed by runtime observation:
+  // identical-content backing arrays are mergeable, differing ones are not.
+  typedef int v4 __attribute__((ext_vector_type(4)));
+  constexpr v4 va = {1, 2, 3, 4};
+  constexpr v4 vb = {1, 2, 3, 5};
+  struct WithVec { v4 v; };
+  constexpr bool class_vec_same(std::initializer_list<WithVec> a,
+                                std::initializer_list<WithVec> b) {
+    return a.begin() == b.begin(); // #cwg2765-vec-compare
+  }
+  constexpr bool same_vec = class_vec_same({{va}}, {{va}}); // #cwg2765-same-vec-call
+  // both-error at -1 {{constexpr variable 'same_vec' must be initialized by a constant expression}}
+  // both-note@#cwg2765-vec-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note@#cwg2765-same-vec-call {{in call to}}
+
+  constexpr bool different_vec = class_vec_same({{va}}, {{vb}});
+  static_assert(!different_vec, "");
+
+  // Complex elements: Profile bitwise-compares real / imag, so two
+  // complex values differing in only the imaginary part are correctly
+  // recognised as distinct.
+  struct WithComplex { _Complex double c; };
+  constexpr bool class_complex_same(std::initializer_list<WithComplex> a,
+                                    std::initializer_list<WithComplex> b) {
+    return a.begin() == b.begin();
+  }
+  constexpr bool different_complex = class_complex_same(
+      {{__builtin_complex(1.0, 2.0)}}, {{__builtin_complex(1.0, 3.0)}});
+  static_assert(!different_complex, "");
+}
diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
new file mode 100644
index 0000000000000..a64ce068913b5
--- /dev/null
+++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
@@ -0,0 +1,139 @@
+// RUN: %clang_cc1 %std_cxx11- -fexceptions -fcxx-exceptions -fsyntax-only --embed-dir=%S/../../../../Preprocessor/Inputs -Wno-c23-extensions -verify %s
+
+namespace std {
+using size_t = decltype(sizeof(int));
+
+template <class E> class initializer_list {
+  const E *begin_;
+  size_t size_;
+
+public:
+  constexpr initializer_list() : begin_(nullptr), size_(0) {}
+  constexpr initializer_list(const E *begin, size_t size)
+      : begin_(begin), size_(size) {}
+  constexpr const E *begin() const { return begin_; }
+  constexpr const E *end() const { return begin_ + size_; }
+  constexpr size_t size() const { return size_; }
+};
+
+template <class T> struct complex {
+  constexpr complex(double) {}
+};
+
+template <class T> struct vector {
+  vector(initializer_list<T>);
+};
+} // namespace std
+
+namespace example12 {
+void f(std::initializer_list<double> il);
+void g(float x) {
+  f({1, x, 3});
+}
+void h() {
+  f({1, 2, 3});
+}
+
+struct A {
+  mutable int i;
+};
+void q(std::initializer_list<A>);
+void r() {
+  q({A{1}, A{2}, A{3}});
+}
+} // namespace example12
+
+namespace example13 {
+typedef std::complex<double> cmplx;
+std::vector<cmplx> v1 = {1, 2, 3};
+void f() {
+  std::vector<cmplx> v2{1, 2, 3};
+  std::initializer_list<int> i3 = {1, 2, 3};
+}
+
+struct A {
+  std::initializer_list<int> i4; // expected-note {{'std::initializer_list' member declared here}}
+  A() : i4{1, 2, 3} {}
+  // expected-error at -1 {{backing array for 'std::initializer_list' member 'i4' is a temporary object whose lifetime would be shorter than the lifetime of the constructed object}}
+};
+} // namespace example13
+
+namespace embed_example {
+void bytes(std::initializer_list<unsigned char>);
+void f() {
+  bytes({
+#embed <jk.txt>
+  });
+}
+
+constexpr std::initializer_list<unsigned char> jk = {
+#embed <jk.txt>
+};
+static_assert(jk.size() == 2, "");
+static_assert(jk.begin()[0] == 'j', "");
+static_assert(jk.begin()[1] == 'k', "");
+} // namespace embed_example
+
+namespace shared_backing_arrays {
+void f2(std::initializer_list<int> ia, std::initializer_list<int> ib) {
+  (void)(ia.begin() == ib.begin());
+}
+void test() {
+  f2({1, 2, 3}, {1, 2, 3});
+}
+
+void f3() {
+  std::initializer_list<int> i1 = {1, 2, 3, 4, 5};
+  std::initializer_list<int> i2 = {2, 3, 4};
+  (void)(i1.begin() == i2.begin() + 1);
+}
+} // namespace shared_backing_arrays
+
+namespace lifetime_is_unchanged {
+const int *f4(std::initializer_list<int> i4) {
+  return i4.begin();
+}
+void test() {
+  const int *p = f4({1, 2, 3});
+  (void)*p;
+}
+} // namespace lifetime_is_unchanged
+
+namespace destructor_side_effects {
+extern "C" int printf(const char *, ...);
+
+struct C6 {
+  constexpr C6(int) {}
+  ~C6() { printf(" X"); }
+};
+
+void f6(std::initializer_list<C6>) {}
+void test() {
+  f6({1, 2, 3});
+  f6({1, 2, 3});
+}
+} // namespace destructor_side_effects
+
+namespace mutable_members {
+struct S {
+  constexpr S(int i) : i(i) {}
+  mutable int i;
+};
+
+void f(std::initializer_list<S> il) {
+  if (il.begin()->i != 1)
+    throw;
+  il.begin()->i = 4;
+}
+void test() {
+  for (int i = 0; i < 2; ++i)
+    f({1, 2, 3});
+}
+} // namespace mutable_members
+
+namespace annex_c {
+bool ne(std::initializer_list<int> a, std::initializer_list<int> b) {
+  return a.begin() != b.begin() + 1;
+}
+bool b = ne({2, 3}, {1, 2, 3});
+} // namespace annex_c
diff --git a/clang/test/CXX/drs/cwg27xx.cpp b/clang/test/CXX/drs/cwg27xx.cpp
index 16fd56e0c4a63..ea28892a633e7 100644
--- a/clang/test/CXX/drs/cwg27xx.cpp
+++ b/clang/test/CXX/drs/cwg27xx.cpp
@@ -1,10 +1,10 @@
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26 %s
 
 #if __cplusplus == 199711L
 #define static_assert(...) __extension__ _Static_assert(__VA_ARGS__)
@@ -19,22 +19,37 @@
 
 namespace std {
 #if __cplusplus >= 201103L
-using size_t = decltype(sizeof(0));
-template <typename T>
-struct initializer_list {
-  const T *p;
-  size_t n;
-
-  #if __cplusplus >= 201402L
-  constexpr
-  #endif
-  initializer_list(const T *p, size_t n);
-
-  #if __cplusplus >= 201402L
-  constexpr
-  #endif
-  const T* begin() const { return p; };
-};
+  using size_t = decltype(sizeof(int));
+
+  template <class E> class initializer_list {
+    const E *begin_;
+    size_t size_;
+
+  public:
+#if __cplusplus >= 201402L
+    constexpr
+#endif
+    initializer_list() : begin_(nullptr), size_(0) {}
+#if __cplusplus >= 201402L
+    constexpr
+#endif
+    initializer_list(const E *begin, size_t size)
+        : begin_(begin), size_(size) {}
+#if __cplusplus >= 201402L
+    constexpr
+#endif
+    const E *begin() const { return begin_; }
+#if __cplusplus >= 201402L
+    constexpr
+#endif
+    size_t size() const { return size_; }
+  };
+
+  struct string_view {
+    const char *begin_;
+    constexpr string_view(const char *begin) : begin_(begin) {}
+    constexpr const char *begin() const { return begin_; }
+  };
 #endif
 
 #if __cplusplus >= 202002L
@@ -193,7 +208,7 @@ static_assert(!__is_layout_compatible(StructWithAnonUnion, StructWithAnonUnion3)
 #endif
 } // namespace cwg2759
 
-namespace cwg2765 { // cwg2765: partial
+namespace cwg2765 { // cwg2765: 23
 static_assert(+"foo" == "foo", "");
 // expected-error at -1 {{static assertion expression is not an integral constant expression}}
 //   expected-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
@@ -211,19 +226,107 @@ static_assert((const char*)"foo" != "oo", "");
 
 #if __cplusplus >= 201103L
 constexpr const char *f() { return "foo"; }
-constexpr bool b2 = f() == f(); 
+
+constexpr bool b2 = f() == f();
 // since-cxx11-error at -1 {{constexpr variable 'b2' must be initialized by a constant expression}}
 //   since-cxx11-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
 constexpr const char *p = f();
-constexpr bool b3 = p == p; 
-#endif
+constexpr bool b3 = p == p;
+static_assert(b3, "");
+
+constexpr bool b4 = &"xfoo"[1] == &"foo\0y"[0];
+// since-cxx11-error at -1 {{constexpr variable 'b4' must be initialized by a constant expression}}
+//   since-cxx11-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
+static_assert("foo" != &"bar"[0], "");
+static_assert((const char *)"foo" != "oo", "");
+
+template <class T>
+constexpr bool f10(T s, T t) {
+  return s.begin() == t.begin(); // #cwg2765-f10-compare
+}
+constexpr bool b10a = f10<std::string_view>("abc", "abc");
+// since-cxx11-error at -1 {{constexpr variable 'b10a' must be initialized by a constant expression}}
+//   since-cxx11-note@#cwg2765-f10-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+//   since-cxx11-note at -3 {{in call to 'f10<std::string_view>({&"abc"[0]}, {&"abc"[0]})'}}
+constexpr bool b10b = f10<std::string_view>("abc", "def");
+static_assert(!b10b, "");
+
+constexpr const char *a11 = "abc";
+constexpr const char *b11 = "abc";
+constexpr bool f11() { return a11 == b11; } // #cwg2765-f11-compare
+// cxx98-20-error at -1 {{constexpr function never produces a constant expression}}
+//   cxx98-20-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+static_assert(f11() || !f11(), "");
+// since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
+//   since-cxx11-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+//   since-cxx11-note at -3 {{in call to 'f11()'}}
 
 #if __cplusplus >= 201402L
-constexpr std::initializer_list<int *> il1 = { (int *)nullptr };
-constexpr std::initializer_list<unsigned long> il2 = { 0 };
-constexpr bool b7 = il1.begin() == (void *)il2.begin();
-// FIXME-error at -1 {{constexpr variable 'b7' must be initialized by a constant expression}}
-//   FIXME-note at -2 {{address of a constexpr-unknown object cannot be used for comparison}}
+constexpr bool f(std::initializer_list<int> a, std::initializer_list<int> b) {
+  return a.begin() != b.begin(); // #cwg2765-init-list-compare
+}
+static_assert(f({1}, {1}), "");
+// since-cxx14-error at -1 {{static assertion expression is not an integral constant expression}}
+//   since-cxx14-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+//   since-cxx14-note at -3 {{in call to 'f({&{1}[0], 1}, {&{1}[0], 1})'}}
+
+constexpr bool f9(const int *p) {
+  std::initializer_list<int> il = {1, 2, 3};
+  return p ? (p == il.begin()) : f9(il.begin()); // #cwg2765-f9-compare
+}
+constexpr bool b9 = f9(nullptr);
+// since-cxx14-error at -1 {{constexpr variable 'b9' must be initialized by a constant expression}}
+//   since-cxx14-note@#cwg2765-f9-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+//   since-cxx14-note@#cwg2765-f9-compare {{in call to 'f9(&{1, 2, 3}[0])'}}
+//   since-cxx14-note at -4 {{in call to 'f9(nullptr)'}}
+
+constexpr bool b10c = f10<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+// since-cxx14-error at -1 {{constexpr variable 'b10c' must be initialized by a constant expression}}
+//   since-cxx14-note@#cwg2765-f10-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+//   since-cxx14-note at -3 {{in call to 'f10<std::initializer_list<int>>({&{1, 2, 3}[0], 3}, {&{1, 2, 3}[0], 3})'}}
+constexpr bool b10d = f10<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
+static_assert(!b10d, "");
+
+constexpr bool ne(std::initializer_list<int> a,
+                  std::initializer_list<int> b) {
+  return a.begin() != b.begin() + 1; // #cwg2765-annex-c-compare
+}
+constexpr bool annex_c = ne({2, 3}, {1, 2, 3});
+// since-cxx14-error at -1 {{constexpr variable 'annex_c' must be initialized by a constant expression}}
+//   since-cxx14-note@#cwg2765-annex-c-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+//   since-cxx14-note at -3 {{in call to 'ne({&{2, 3}[0], 2}, {&{1, 2, 3}[0], 3})'}}
+
+int a_order[10];
+constexpr int inc(int &i) { return (i += 1); }
+constexpr int twox(int &i) { return (i *= 2); }
+constexpr int f_order(int i) { return inc(i) + twox(i); }
+constexpr bool g_order() { return &a_order[f_order(1)] == &a_order[6]; }
+constexpr bool b_order = g_order();
+static_assert(b_order, "");
+
+// Aggregate carrying a std::initializer_list member: the backing array's
+// extending declaration is the enclosing aggregate, not the
+// initializer_list. The precise marker on MaterializeTemporaryExpr must
+// still recognise the backing array.
+struct WithIL { std::initializer_list<int> il; };
+constexpr WithIL agg_a{{1}}, agg_b{{1}};
+constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin();
+// since-cxx14-error at -1 {{constexpr variable 'agg_same' must be initialized by a constant expression}}
+//   since-cxx14-note at -2 {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+constexpr WithIL agg_c{{1}}, agg_d{{2}};
+constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin();
+static_assert(!agg_different, "");
+
+// Rvalue reference to array bound to a braced-init-list: the materialized
+// array is not a std::initializer_list backing array, so address
+// comparisons across two such temporaries are well-defined.
+constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) {
+  return &a[0] == &b[0];
+}
+constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3});
+static_assert(!rvref_ok, "");
+#endif
 #endif
 } // namespace cwg2765
 
diff --git a/clang/test/CodeGenCXX/Inputs/jk.txt b/clang/test/CodeGenCXX/Inputs/jk.txt
new file mode 100644
index 0000000000000..93d177a48c83a
--- /dev/null
+++ b/clang/test/CodeGenCXX/Inputs/jk.txt
@@ -0,0 +1 @@
+jk
\ No newline at end of file
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
new file mode 100644
index 0000000000000..3f5ecac648066
--- /dev/null
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+
+namespace std {
+using size_t = decltype(sizeof(int));
+
+template <class E> class initializer_list {
+  const E *begin_;
+  size_t size_;
+
+public:
+  constexpr initializer_list() : begin_(nullptr), size_(0) {}
+  constexpr initializer_list(const E *begin, size_t size)
+      : begin_(begin), size_(size) {}
+  constexpr const E *begin() const { return begin_; }
+  constexpr size_t size() const { return size_; }
+};
+} // namespace std
+
+namespace example12 {
+void f(std::initializer_list<double> il);
+
+void g(float x) {
+  // CHECK-LABEL: define{{.*}} void @_ZN9example121gEf(
+  // CHECK: alloca [3 x double],
+  // CHECK: fpext float
+  // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
+  f({1, x, 3});
+}
+
+void h() {
+  // CHECK-LABEL: define{{.*}} void @_ZN9example121hEv(
+  // CHECK: alloca [3 x double],
+  // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24,
+  // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
+  f({1, 2, 3});
+}
+} // namespace example12
+
+namespace embed_example {
+void bytes(std::initializer_list<unsigned char>);
+
+void f() {
+  // CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv(
+  // CHECK: alloca [2 x i8],
+  // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2,
+  // CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE(
+  bytes({
+#embed <jk.txt>
+  });
+}
+} // namespace embed_example
+
+namespace destructor_side_effects {
+extern "C" int printf(const char *, ...);
+
+struct C6 {
+  constexpr C6(int) {}
+  ~C6() { printf(" X"); }
+};
+
+void f6(std::initializer_list<C6>) {}
+
+void test() {
+  // CHECK-LABEL: define{{.*}} void @_ZN23destructor_side_effects4testEv(
+  // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE(
+  // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev(
+  // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE(
+  // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev(
+  f6({1, 2, 3});
+  f6({1, 2, 3});
+}
+} // namespace destructor_side_effects
+
+namespace mutable_members {
+struct S {
+  constexpr S(int i) : i(i) {}
+  mutable int i;
+};
+
+void f(std::initializer_list<S> il) {
+  if (il.begin()->i != 1)
+    throw;
+  il.begin()->i = 4;
+}
+
+void test() {
+  // CHECK-LABEL: define{{.*}} void @_ZN15mutable_members4testEv(
+  // CHECK: alloca [3 x %"struct.mutable_members::S"],
+  // CHECK: call void @_ZN15mutable_members1fESt16initializer_listINS_1SEE(
+  for (int i = 0; i < 2; ++i)
+    f({1, 2, 3});
+}
+} // namespace mutable_members
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 9988fcd333e75..264c4cee504d7 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -19160,7 +19160,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td>[<a href="https://wg21.link/intro.object">intro.object</a>]</td>
     <td>DR</td>
     <td>Address comparisons between potentially non-unique objects during constant evaluation</td>
-    <td class="partial" align="center">Partial</td>
+    <td class="full" align="center">Clang 23</td>
   </tr>
   <tr class="open" id="2766">
     <td><a href="https://cplusplus.github.io/CWG/issues/2766.html">2766</a></td>
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index f96b0cafcf2c4..427d4173cff95 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -245,7 +245,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Static storage for braced initializers</td>
   <td><a href="https://wg21.link/P2752R3">P2752R3</a> (<a href="#dr">DR</a>)</td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 23</td>
  </tr>
  <tr>
   <td>User-generated <tt>static_assert</tt> messages</td>

>From a75279617e01c52b66d447d8a30a318d48a316a6 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 23 Jun 2026 20:16:08 +0800
Subject: [PATCH 2/5] Generate the static backing array in CodeGen, avoid copy
 when construct std::vector

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/lib/AST/ByteCode/Interp.cpp             | 276 ++++++++++++---
 clang/lib/AST/ByteCode/Interp.h               |  10 +-
 clang/lib/AST/ExprConstant.cpp                | 322 +++++++++++++++---
 clang/lib/CodeGen/CGExpr.cpp                  |  38 +++
 clang/lib/CodeGen/CodeGenFunction.h           |   4 +
 clang/lib/CodeGen/CodeGenModule.cpp           |  23 ++
 clang/lib/CodeGen/CodeGenModule.h             |   4 +
 clang/test/AST/ByteCode/initializer_list.cpp  |  64 ++++
 .../cxx0x-initializer-stdinitializerlist.cpp  |   4 +-
 .../CodeGenCXX/p2752r3-initializer-list.cpp   |  30 +-
 .../ptrauth-member-function-pointer.cpp       |   6 +-
 .../SemaCXX/constant-expression-cxx11.cpp     |  14 +
 12 files changed, 700 insertions(+), 95 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 79711d2e6a21d..8a24c1a73adf8 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2874,63 +2874,261 @@ getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
   return ArraySubobjectInfo{std::move(Array), *Loc};
 }
 
-/// Returns true if \p LHS and \p RHS both designate elements of
-/// std::initializer_list backing arrays whose values agree on the overlapping
-/// region. Such backing arrays may be merged by the implementation, so
-/// comparing their addresses produces an unspecified result and is rejected
-/// as a constant expression.
-bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
-                                                    const Pointer &LHS,
-                                                    const Pointer &RHS) {
-  const ASTContext &Ctx = S.getASTContext();
-  std::optional<ArraySubobjectInfo> LHSInfo =
-      getArraySubobjectLocation(Ctx, LHS);
-  std::optional<ArraySubobjectInfo> RHSInfo =
-      getArraySubobjectLocation(Ctx, RHS);
-  if (!LHSInfo || !RHSInfo)
-    return false;
+/// Returns the enclosing array block for a pointer into an array subobject.
+static std::optional<Pointer> getContainingArray(const Pointer &Ptr) {
+  if (Ptr.isZero() || !Ptr.isBlockPointer())
+    return std::nullopt;
+
+  PtrView P = Ptr.view();
+  while (!P.isRoot()) {
+    if (P.isArrayElement()) {
+      PtrView Expanded = P.expand();
+      return Pointer(Expanded.getArray());
+    }
+    P = P.getBase();
+  }
+
+  return std::nullopt;
+}
+
+/// Returns true for array objects whose addresses are potentially non-unique
+/// and whose contents are represented by interpreter storage/APValue.
+static bool isPotentiallyNonUniqueAPValueArray(const Pointer &Array) {
+  if (isInitializerListBackingArray(Array))
+    return true;
+  if (const auto *VD = Array.getDeclDesc()->asValueDecl())
+    return isa<TemplateParamObjectDecl>(VD);
+  return false;
+}
+
+/// Returns the enclosing potentially non-unique APValue-backed array and the
+/// element-relative location designated by Ptr.
+static std::optional<ArraySubobjectInfo>
+getNonUniqueAPValueArraySubobjectLocation(const ASTContext &Ctx,
+                                          const Pointer &Ptr) {
+  if (std::optional<ArraySubobjectInfo> InitListInfo =
+          getArraySubobjectLocation(Ctx, Ptr))
+    return InitListInfo;
+
+  if (!Ptr.isZero() && Ptr.isBlockPointer() && Ptr.isArrayRoot() &&
+      isPotentiallyNonUniqueAPValueArray(Ptr)) {
+    const auto *ArrayType =
+        Ctx.getAsConstantArrayType(Ptr.getFieldDesc()->getType());
+    if (!ArrayType)
+      return std::nullopt;
+    return ArraySubobjectInfo{Ptr,
+                              ArraySubobjectLocation{0, CharUnits::Zero()}};
+  }
+
+  std::optional<Pointer> Array = getContainingArray(Ptr);
+  if (!Array || !isPotentiallyNonUniqueAPValueArray(*Array))
+    return std::nullopt;
+
+  const auto *ArrayType =
+      Ctx.getAsConstantArrayType(Array->getFieldDesc()->getType());
+  if (!ArrayType)
+    return std::nullopt;
+
+  APValue PtrValue = Ptr.toAPValue(Ctx);
+  APValue ArrayValue = Array->toAPValue(Ctx);
+  if (!PtrValue.isLValue() || !PtrValue.hasLValuePath() ||
+      !ArrayValue.isLValue() || !ArrayValue.hasLValuePath())
+    return std::nullopt;
+
+  ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+  ArrayRef<APValue::LValuePathEntry> ArrayPath = ArrayValue.getLValuePath();
+  if (Path.size() <= ArrayPath.size())
+    return std::nullopt;
+  for (unsigned I = 0, N = ArrayPath.size(); I != N; ++I) {
+    if (Path[I] != ArrayPath[I])
+      return std::nullopt;
+  }
+  Path = Path.drop_front(ArrayPath.size());
+
+  uint64_t Index = Path.front().getAsArrayIndex();
+  bool IsValidOnePastEnd = Path.size() == 1;
+  std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+      Ctx, ArrayType, Index,
+      PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
+      IsValidOnePastEnd);
+  if (!Loc)
+    return std::nullopt;
+
+  return ArraySubobjectInfo{std::move(*Array), *Loc};
+}
+
+namespace {
+/// Pairs a string literal array with the element-relative location of a pointer
+/// into it and the literal used to materialize character APValues.
+struct StringArraySubobjectInfo {
+  Pointer Array;
+  ArraySubobjectLocation Loc;
+  const StringLiteral *Literal;
+};
+} // namespace
+
+/// Returns the string literal array and element-relative location designated by
+/// Ptr.
+static std::optional<StringArraySubobjectInfo>
+getStringArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
+  if (!Ptr.pointsToStringLiteral())
+    return std::nullopt;
+
+  Pointer Array = Ptr.getDeclPtr();
+  const auto *ArrayType =
+      Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+  if (!ArrayType)
+    return std::nullopt;
 
-  if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement)
+  APValue PtrValue = Ptr.toAPValue(Ctx);
+  if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
+    return std::nullopt;
+
+  ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+  if (Path.empty())
+    return std::nullopt;
+
+  uint64_t Index = Path.front().getAsArrayIndex();
+  bool IsValidOnePastEnd = Path.size() == 1;
+  std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+      Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
+  if (!Loc)
+    return std::nullopt;
+
+  return StringArraySubobjectInfo{
+      std::move(Array), *Loc, cast<StringLiteral>(Ptr.getDeclDesc()->asExpr())};
+}
+
+/// Extracts one character from a string literal as an integer APValue payload.
+static APSInt extractStringLiteralCharacter(const ASTContext &Ctx,
+                                            const StringLiteral *Literal,
+                                            uint64_t Index) {
+  const ConstantArrayType *ArrayType =
+      Ctx.getAsConstantArrayType(Literal->getType());
+  assert(ArrayType && "string literal isn't an array");
+  QualType CharType = ArrayType->getElementType();
+  assert(CharType->isIntegerType() && "unexpected character type");
+  APSInt Value(Ctx.getTypeSize(CharType), CharType->isUnsignedIntegerType());
+  if (Index < Literal->getLength())
+    Value = Literal->getCodeUnit(Index);
+  return Value;
+}
+
+/// Compares two array views for a possible storage overlap after accounting
+/// for their pointer offsets.
+template <typename GetLHSElement, typename GetRHSElement>
+static bool arePotentiallyOverlappingArrays(
+    const ASTContext &Ctx, QualType LHSElementType,
+    ArraySubobjectLocation LHSLoc, int64_t LHSSize, GetLHSElement GetLHSElt,
+    QualType RHSElementType, ArraySubobjectLocation RHSLoc, int64_t RHSSize,
+    GetRHSElement GetRHSElt) {
+  if (LHSLoc.OffsetInElement != RHSLoc.OffsetInElement)
     return false;
 
-  const auto *LHSArrayType =
-      Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
-  const auto *RHSArrayType =
-      Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
-  if (!LHSArrayType || !RHSArrayType ||
-      !Ctx.hasSameType(LHSArrayType->getElementType(),
-                       RHSArrayType->getElementType()))
+  if (!Ctx.hasSameUnqualifiedType(LHSElementType, RHSElementType))
     return false;
 
-  QualType ElementType = LHSArrayType->getElementType();
-  int64_t LHSSize = LHSInfo->Array.getNumElems();
-  int64_t RHSSize = RHSInfo->Array.getNumElems();
-  int64_t LHSOffset = LHSInfo->Loc.Index;
-  int64_t RHSOffset = RHSInfo->Loc.Index;
+  int64_t LHSOffset = LHSLoc.Index;
+  int64_t RHSOffset = RHSLoc.Index;
   int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
   int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
   if (OverlapBegin >= OverlapEnd)
     return false;
 
   for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
-    Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow();
-    Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow();
-    std::optional<APValue> LHSEltValue =
-        LHSElt.toRValue(S.getContext(), ElementType);
-    std::optional<APValue> RHSEltValue =
-        RHSElt.toRValue(S.getContext(), ElementType);
-    // Missing element data: be conservative and assume the backing arrays
-    // may share storage.
-    if (!LHSEltValue || !RHSEltValue)
+    std::optional<APValue> LHSElt = GetLHSElt(I + LHSOffset);
+    std::optional<APValue> RHSElt = GetRHSElt(I + RHSOffset);
+    // Missing element data: be conservative and assume the arrays may share
+    // storage.
+    if (!LHSElt || !RHSElt)
       return true;
-
-    if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx))
+    if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
       return false;
   }
 
   return true;
 }
 
+/// Returns true if a string literal and a potentially non-unique constant
+/// array could share storage at the designated addresses.
+static bool mayOverlapStringLiteralAndConstantArray(InterpState &S,
+                                                    const Pointer &StringPtr,
+                                                    const Pointer &ArrayPtr) {
+  const ASTContext &Ctx = S.getASTContext();
+  std::optional<StringArraySubobjectInfo> StringInfo =
+      getStringArraySubobjectLocation(Ctx, StringPtr);
+  std::optional<ArraySubobjectInfo> ArrayInfo =
+      getNonUniqueAPValueArraySubobjectLocation(Ctx, ArrayPtr);
+  if (!StringInfo || !ArrayInfo)
+    return false;
+
+  const auto *StringArrayType =
+      Ctx.getAsConstantArrayType(StringInfo->Array.getFieldDesc()->getType());
+  const auto *ArrayType =
+      Ctx.getAsConstantArrayType(ArrayInfo->Array.getFieldDesc()->getType());
+  if (!StringArrayType || !ArrayType)
+    return false;
+
+  return arePotentiallyOverlappingArrays(
+      Ctx, StringArrayType->getElementType(), StringInfo->Loc,
+      StringInfo->Array.getNumElems(),
+      [&](int64_t Index) -> std::optional<APValue> {
+        return APValue(
+            extractStringLiteralCharacter(Ctx, StringInfo->Literal, Index));
+      },
+      ArrayType->getElementType(), ArrayInfo->Loc,
+      ArrayInfo->Array.getNumElems(),
+      [&](int64_t Index) {
+        Pointer Elt = ArrayInfo->Array.atIndex(Index).narrow();
+        return Elt.toRValue(S.getContext(), ArrayType->getElementType());
+      });
+}
+
+/// Returns true if two APValue-backed potentially non-unique arrays could share
+/// storage at the designated addresses.
+static bool arePotentiallyOverlappingAPValueArrays(InterpState &S,
+                                                   const Pointer &LHS,
+                                                   const Pointer &RHS) {
+  const ASTContext &Ctx = S.getASTContext();
+  std::optional<ArraySubobjectInfo> LHSInfo =
+      getNonUniqueAPValueArraySubobjectLocation(Ctx, LHS);
+  std::optional<ArraySubobjectInfo> RHSInfo =
+      getNonUniqueAPValueArraySubobjectLocation(Ctx, RHS);
+  if (!LHSInfo || !RHSInfo)
+    return false;
+
+  const auto *LHSArrayType =
+      Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
+  const auto *RHSArrayType =
+      Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
+  if (!LHSArrayType || !RHSArrayType)
+    return false;
+
+  return arePotentiallyOverlappingArrays(
+      Ctx, LHSArrayType->getElementType(), LHSInfo->Loc,
+      LHSInfo->Array.getNumElems(),
+      [&](int64_t Index) {
+        Pointer Elt = LHSInfo->Array.atIndex(Index).narrow();
+        return Elt.toRValue(S.getContext(), LHSArrayType->getElementType());
+      },
+      RHSArrayType->getElementType(), RHSInfo->Loc,
+      RHSInfo->Array.getNumElems(),
+      [&](int64_t Index) {
+        Pointer Elt = RHSInfo->Array.atIndex(Index).narrow();
+        return Elt.toRValue(S.getContext(), RHSArrayType->getElementType());
+      });
+}
+
+/// Returns true if two pointers designate potentially non-unique objects whose
+/// overlapping elements could be merged into the same storage.
+bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
+                                               const Pointer &LHS,
+                                               const Pointer &RHS) {
+  return arePotentiallyOverlappingAPValueArrays(S, LHS, RHS) ||
+         mayOverlapStringLiteralAndConstantArray(S, LHS, RHS) ||
+         mayOverlapStringLiteralAndConstantArray(S, RHS, LHS);
+}
+
 static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
                                 PrimType T) {
   if (T == PT_IntAPS) {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0b73ede59f7dd..417a73eff0b14 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1292,9 +1292,11 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
 
 bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
                                              const Pointer &RHS);
-bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
-                                                    const Pointer &LHS,
-                                                    const Pointer &RHS);
+/// Returns true when LHS and RHS designate potentially non-unique objects that
+/// could share storage, making their comparison non-constant.
+bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
+                                               const Pointer &LHS,
+                                               const Pointer &RHS);
 
 template <>
 inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1364,7 +1366,7 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     return true;
   }
 
-  if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) {
+  if (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
     const SourceInfo &Loc = S.Current->getSource(OpPC);
     S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
     return false;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9e77febd9fff3..8297d372ab89e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5089,57 +5089,291 @@ static bool isInitializerListBackingArray(const LValue &LV) {
   return MTE && MTE->isBackingArrayForInitializerList();
 }
 
-static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info,
-                                                           const Expr *E,
-                                                           const LValue &LHS,
-                                                           const LValue &RHS) {
-  if (!isInitializerListBackingArray(LHS) ||
-      !isInitializerListBackingArray(RHS))
-    return false;
+/// Returns the underlying string literal expression for bases that denote
+/// string literal objects in the evaluator.
+static const Expr *getStringLiteralBase(APValue::LValueBase Base) {
+  const auto *BaseExpr = Base.dyn_cast<const Expr *>();
+  if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
+    return BaseExpr;
+  return nullptr;
+}
 
-  std::optional<ArraySubobjectLocation> LHSLoc =
-      getArraySubobjectLocation(Info.Ctx, LHS);
-  std::optional<ArraySubobjectLocation> RHSLoc =
-      getArraySubobjectLocation(Info.Ctx, RHS);
-  if (!LHSLoc || !RHSLoc)
-    return false;
+/// Returns the string-literal array element and element-relative offset
+/// designated by LV, including byte-offset-only lvalues.
+static std::optional<ArraySubobjectLocation>
+getStringArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+  if (!getStringLiteralBase(LV.Base))
+    return std::nullopt;
 
-  if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement)
-    return false;
+  if (std::optional<ArraySubobjectLocation> Loc =
+          getArraySubobjectLocation(Ctx, LV))
+    return Loc;
 
-  const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base));
-  const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base));
-  if (!LHSArrayType || !RHSArrayType ||
-      !Info.Ctx.hasSameType(LHSArrayType->getElementType(),
-                            RHSArrayType->getElementType()))
-    return false;
+  const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+  if (!ArrayType || LV.Offset.isNegative())
+    return std::nullopt;
 
-  const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS);
-  const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS);
-  if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray())
-    return false;
+  uint64_t ElementSize =
+      Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+  if (!ElementSize)
+    return std::nullopt;
 
-  int64_t LHSSize = LHSArray->getArraySize();
-  int64_t RHSSize = RHSArray->getArraySize();
-  int64_t LHSOffset = LHSLoc->Index;
-  int64_t RHSOffset = RHSLoc->Index;
-  int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
-  int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
-  if (OverlapBegin >= OverlapEnd)
-    return false;
+  uint64_t Offset = LV.Offset.getQuantity();
+  uint64_t Index = Offset / ElementSize;
+  if (Index > ArrayType->getZExtSize())
+    return std::nullopt;
 
-  for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
-    const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset);
-    const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset);
-    // Missing element data: be conservative and assume the arrays may share
-    // storage.
-    if (!LHSElt || !RHSElt)
-      return true;
-    if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+  return ArraySubobjectLocation{Index,
+                                CharUnits::fromQuantity(Offset % ElementSize)};
+}
+
+namespace {
+/// Identifies an enclosing constant array subobject within an lvalue path.
+struct ArraySubobjectPathInfo {
+  const ConstantArrayType *ArrayType = nullptr;
+  unsigned ArrayPathLength = 0;
+  ArraySubobjectLocation Loc;
+};
+
+/// Describes a potentially non-unique array visible to the constant evaluator.
+struct NonUniqueArrayInfo {
+  const ConstantArrayType *ArrayType = nullptr;
+  ArraySubobjectLocation Loc;
+  /// Non-null for string literal arrays; otherwise ArrayValue or
+  /// OwnedArrayValue contains an APValue array.
+  const Expr *StringBase = nullptr;
+  const APValue *ArrayValue = nullptr;
+  std::optional<APValue> OwnedArrayValue;
+
+  /// Returns true when this view describes a string literal array.
+  bool isStringLiteral() const { return StringBase; }
+
+  /// Returns the APValue storage for non-string arrays.
+  const APValue *getArrayValue() const {
+    return OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
+  }
+
+  /// Returns the number of array elements visible through this view.
+  int64_t getSize() const {
+    if (isStringLiteral())
+      return ArrayType->getZExtSize();
+
+    const APValue *Array = getArrayValue();
+    assert(Array && Array->isArray() && "invalid APValue array");
+    return Array->getArraySize();
+  }
+
+  /// Returns the value of one element, or nullopt when the representation does
+  /// not expose enough data to prove it differs.
+  std::optional<APValue> getElement(EvalInfo &Info, int64_t Index) const {
+    if (Index < 0)
+      return std::nullopt;
+    uint64_t UIndex = static_cast<uint64_t>(Index);
+
+    if (isStringLiteral())
+      return APValue(extractStringLiteralCharacter(Info, StringBase, UIndex));
+
+    const APValue *Array = getArrayValue();
+    assert(Array && Array->isArray() && "invalid APValue array");
+    if (const APValue *Elt = GetArrayInitializedElt(*Array, UIndex))
+      return *Elt;
+    return std::nullopt;
+  }
+};
+
+/// Performs potentially-non-unique object overlap checks for two evaluator
+/// lvalues.
+class NonUniqueObjectOverlapChecker {
+public:
+  /// Creates a checker for comparisons diagnosed at expression E.
+  NonUniqueObjectOverlapChecker(EvalInfo &Info, const Expr *E)
+      : Info(Info), E(E) {}
+
+  /// Returns true when LHS and RHS could designate merged storage.
+  bool mayOverlap(const LValue &LHS, const LValue &RHS) const {
+    const ASTContext &Ctx = Info.Ctx;
+    std::optional<NonUniqueArrayInfo> LHSArray = getArrayInfo(LHS);
+    std::optional<NonUniqueArrayInfo> RHSArray = getArrayInfo(RHS);
+    if (!LHSArray || !RHSArray)
+      return false;
+
+    // Keep the existing string-literal diagnostic path authoritative for
+    // string/string comparisons.
+    if (LHSArray->isStringLiteral() && RHSArray->isStringLiteral())
+      return false;
+
+    if (LHSArray->Loc.OffsetInElement != RHSArray->Loc.OffsetInElement)
       return false;
+
+    if (!Ctx.hasSameUnqualifiedType(LHSArray->ArrayType->getElementType(),
+                                    RHSArray->ArrayType->getElementType()))
+      return false;
+
+    int64_t LHSOffset = LHSArray->Loc.Index;
+    int64_t RHSOffset = RHSArray->Loc.Index;
+    int64_t LHSSize = LHSArray->getSize();
+    int64_t RHSSize = RHSArray->getSize();
+    int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+    int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+    if (OverlapBegin >= OverlapEnd)
+      return false;
+
+    for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+      std::optional<APValue> LHSElt = LHSArray->getElement(Info, I + LHSOffset);
+      std::optional<APValue> RHSElt = RHSArray->getElement(Info, I + RHSOffset);
+      // Missing element data: be conservative and assume the arrays may share
+      // storage.
+      if (!LHSElt || !RHSElt)
+        return true;
+      if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
+        return false;
+    }
+
+    return true;
   }
 
-  return true;
+private:
+  /// Finds the innermost enclosing constant array subobject for an lvalue into
+  /// a template parameter object.
+  std::optional<ArraySubobjectPathInfo>
+  getArraySubobjectPathInfo(const LValue &LV) const {
+    const ASTContext &Ctx = Info.Ctx;
+    if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+      return std::nullopt;
+
+    QualType Type = getType(LV.Base).getNonReferenceType();
+    CharUnits PathOffset = CharUnits::Zero();
+    std::optional<ArraySubobjectPathInfo> Result;
+
+    for (unsigned I = 0, N = LV.Designator.Entries.size(); I != N; ++I) {
+      APValue::LValuePathEntry Entry = LV.Designator.Entries[I];
+
+      if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
+        uint64_t Index = Entry.getAsArrayIndex();
+        CharUnits OffsetInArray = LV.Offset - PathOffset;
+        bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
+        std::optional<ArraySubobjectLocation> Loc =
+            getArraySubobjectLocationImpl(Ctx, ArrayType, Index, OffsetInArray,
+                                          IsValidOnePastEnd);
+        if (Loc)
+          Result = ArraySubobjectPathInfo{ArrayType, I, *Loc};
+
+        QualType ElementType = ArrayType->getElementType();
+        CharUnits ElementSize = Ctx.getTypeSizeInChars(ElementType);
+        if (ElementSize.isZero())
+          return std::nullopt;
+        if (Index >
+            std::numeric_limits<uint64_t>::max() / ElementSize.getQuantity())
+          return std::nullopt;
+        PathOffset += Index * ElementSize;
+        Type = ElementType;
+        continue;
+      }
+
+      if (const FieldDecl *FD = getAsField(Entry)) {
+        if (FD->getParent()->isInvalidDecl())
+          return std::nullopt;
+        const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(FD->getParent());
+        PathOffset +=
+            Ctx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()));
+        Type = FD->getType();
+        continue;
+      }
+
+      const CXXRecordDecl *Base = getAsBaseClass(Entry);
+      const CXXRecordDecl *Derived = Type->getAsCXXRecordDecl();
+      if (!Derived || !Base)
+        return std::nullopt;
+      const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Derived);
+      PathOffset += isVirtualBaseClass(Entry) ? Layout.getVBaseClassOffset(Base)
+                                              : Layout.getBaseClassOffset(Base);
+      Type = Ctx.getCanonicalTagType(Base);
+    }
+
+    if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
+      CharUnits OffsetInArray = LV.Offset - PathOffset;
+      if (OffsetInArray.isZero())
+        Result = ArraySubobjectPathInfo{
+            ArrayType, static_cast<unsigned>(LV.Designator.Entries.size()),
+            ArraySubobjectLocation{0, CharUnits::Zero()}};
+    }
+
+    return Result;
+  }
+
+  /// Builds a comparable array view for a string literal, initializer_list
+  /// backing array, or array subobject of a template parameter object.
+  std::optional<NonUniqueArrayInfo> getArrayInfo(const LValue &LV) const {
+    ASTContext &Ctx = Info.Ctx;
+
+    if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+      std::optional<ArraySubobjectLocation> Loc =
+          getStringArraySubobjectLocation(Ctx, LV);
+      const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+      if (!Loc || !ArrayType)
+        return std::nullopt;
+
+      NonUniqueArrayInfo Result;
+      Result.ArrayType = ArrayType;
+      Result.Loc = *Loc;
+      Result.StringBase = StringBase;
+      return Result;
+    }
+
+    if (isInitializerListBackingArray(LV)) {
+      std::optional<ArraySubobjectLocation> Loc =
+          getArraySubobjectLocation(Ctx, LV);
+      const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+      const APValue *ArrayValue = GetCompleteObjectValue(Info, E, LV);
+      if (!Loc || !ArrayType || !ArrayValue || !ArrayValue->isArray())
+        return std::nullopt;
+
+      NonUniqueArrayInfo Result;
+      Result.ArrayType = ArrayType;
+      Result.Loc = *Loc;
+      Result.ArrayValue = ArrayValue;
+      return Result;
+    }
+
+    const auto *VD = LV.Base.dyn_cast<const ValueDecl *>();
+    const auto *TPO = dyn_cast_if_present<TemplateParamObjectDecl>(VD);
+    if (!TPO)
+      return std::nullopt;
+
+    std::optional<ArraySubobjectPathInfo> PathInfo =
+        getArraySubobjectPathInfo(LV);
+    if (!PathInfo)
+      return std::nullopt;
+
+    APValue ArrayValue;
+    SubobjectDesignator ArrayDesignator = LV.Designator;
+    ArrayDesignator.truncate(Ctx, LV.Base, PathInfo->ArrayPathLength);
+    CompleteObject Obj(LV.Base, const_cast<APValue *>(&TPO->getValue()),
+                       TPO->getType());
+    if (!extractSubobject(Info, E, Obj, ArrayDesignator, ArrayValue))
+      return std::nullopt;
+    if (!ArrayValue.isArray())
+      return std::nullopt;
+
+    NonUniqueArrayInfo Result;
+    Result.ArrayType = PathInfo->ArrayType;
+    Result.Loc = PathInfo->Loc;
+    Result.OwnedArrayValue = std::move(ArrayValue);
+    return Result;
+  }
+
+  EvalInfo &Info;
+  const Expr *E;
+};
+} // namespace
+
+/// Returns true when two lvalues designate potentially non-unique arrays whose
+/// overlapping elements could be merged into the same storage.
+static bool ArePotentiallyOverlappingNonUniqueObjects(EvalInfo &Info,
+                                                      const Expr *E,
+                                                      const LValue &LHS,
+                                                      const LValue &RHS) {
+  return NonUniqueObjectOverlapChecker(Info, E).mayOverlap(LHS, RHS);
 }
 
 /// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -19165,8 +19399,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       //
       if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
         return DiagComparison(diag::note_constexpr_literal_comparison);
-      if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue,
-                                                         RHSValue))
+      if (ArePotentiallyOverlappingNonUniqueObjects(Info, E, LHSValue,
+                                                    RHSValue))
         return DiagComparison(
             diag::note_constexpr_non_unique_object_comparison);
       if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 6abe4331db552..c6a5c8186c1b2 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -514,6 +514,40 @@ static RawAddress createReferenceTemporary(CodeGenFunction &CGF,
   llvm_unreachable("unknown storage duration");
 }
 
+/// Emits a constant initializer_list backing array as a private global when the
+/// temporary can be shared for the full expression without observable effects.
+std::optional<RawAddress> CodeGenFunction::tryEmitStaticInitListBackingArray(
+    const MaterializeTemporaryExpr *MTE) {
+  if (!MTE->isBackingArrayForInitializerList())
+    return std::nullopt;
+
+  switch (MTE->getStorageDuration()) {
+  case SD_FullExpression:
+  case SD_Automatic:
+    break;
+  case SD_Thread:
+  case SD_Static:
+    return std::nullopt;
+  case SD_Dynamic:
+    llvm_unreachable("temporary cannot have dynamic storage duration");
+  }
+
+  const Expr *Init = MTE->getSubExpr();
+  QualType Ty = Init->getType();
+  if (!Ty->isArrayType() ||
+      !Ty.isConstantStorage(getContext(), /*ExcludeCtor=*/true,
+                            /*ExcludeDtor=*/false))
+    return std::nullopt;
+
+  llvm::Constant *Constant =
+      ConstantEmitter(*this).tryEmitAbstractForMemory(Init, Ty);
+  if (!Constant)
+    return std::nullopt;
+
+  CharUnits Align = getContext().getTypeAlignInChars(Ty);
+  return CGM.EmitStaticInitListBackingArray(Constant, Align);
+}
+
 /// Helper method to check if the underlying ABI is AAPCS
 static bool isAAPCS(const TargetInfo &TargetInfo) {
   return TargetInfo.getABI().starts_with("aapcs");
@@ -585,6 +619,10 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
     }
   }
 
+  if (std::optional<RawAddress> StaticArray =
+          tryEmitStaticInitListBackingArray(M))
+    return MakeAddrLValue(*StaticArray, M->getType(), AlignmentSource::Decl);
+
   // Create and initialize the reference temporary.
   RawAddress Alloca = Address::invalid();
   RawAddress Object = createReferenceTemporary(*this, M, E, &Alloca);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 6d0718c243812..3ddeaa8815a4c 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4484,6 +4484,10 @@ class CodeGenFunction : public CodeGenTypeCache {
   LValue EmitConditionalOperatorLValue(const AbstractConditionalOperator *E);
   LValue EmitCastLValue(const CastExpr *E);
   LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
+  /// Attempts to emit an initializer_list backing array as a static constant
+  /// object instead of materializing a stack temporary.
+  std::optional<RawAddress>
+  tryEmitStaticInitListBackingArray(const MaterializeTemporaryExpr *E);
   LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
   LValue EmitHLSLArrayAssignLValue(const BinaryOperator *E);
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 292706d3c5088..2da1796ef2b61 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7720,6 +7720,29 @@ ConstantAddress CodeGenModule::GetAddrOfGlobalTemporary(
   return ConstantAddress(CV, Type, Align);
 }
 
+/// Creates the private constant global used for a static initializer_list
+/// backing array.
+ConstantAddress
+CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
+                                              CharUnits Align) {
+  LangAS AddrSpace = GetGlobalConstantAddressSpace();
+  auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+  auto *GV = new llvm::GlobalVariable(
+      getModule(), Init->getType(), /*isConstant=*/true,
+      llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+      /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
+  GV->setAlignment(Align.getAsAlign());
+
+  llvm::Constant *CV = GV;
+  if (AddrSpace != LangAS::Default)
+    CV = performAddrSpaceCast(
+        GV, llvm::PointerType::get(
+                getLLVMContext(),
+                getContext().getTargetAddressSpace(LangAS::Default)));
+
+  return ConstantAddress(CV, Init->getType(), Align);
+}
+
 /// EmitObjCPropertyImplementations - Emit information for synthesized
 /// properties for an implementation.
 void CodeGenModule::EmitObjCPropertyImplementations(const
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 4283b6a3dc869..3bb881aec67e5 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1047,6 +1047,10 @@ class CodeGenModule : public CodeGenTypeCache {
                                   ForDefinition_t IsForDefinition
                                     = NotForDefinition);
 
+  /// Creates a private constant global for an initializer_list backing array.
+  ConstantAddress EmitStaticInitListBackingArray(llvm::Constant *Init,
+                                                 CharUnits Align);
+
   /// Will return a global variable of the given type. If a variable with a
   /// different type already exists then a new  variable with the right type
   /// will be created and all uses of the old variable will be replaced with a
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index a6751453a7f3a..efdc407787fdc 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -92,6 +92,70 @@ namespace cwg2765 {
       begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
   static_assert(!different_three, "");
 
+  constexpr bool init_list_overlaps_string() {
+    return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+           != "hello"; // #cwg2765-init-list-string-compare \
+                        // both-warning {{result of comparison against a string literal is unspecified}}
+  }
+  static_assert(init_list_overlaps_string(), "");
+  // both-error at -1 {{static assertion expression is not an integral constant expression}}
+  // both-note@#cwg2765-init-list-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool init_list_differs_from_string() {
+    return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+           != "hello"; // both-warning {{result of comparison against a string literal is unspecified}}
+  }
+  static_assert(init_list_differs_from_string(), "");
+
+  template <int N> struct FixedString {
+    char data[N];
+    constexpr FixedString(const char (&s)[N]) {
+      for (int i = 0; i != N; ++i)
+        data[i] = s[i];
+    }
+  };
+
+  template <FixedString S>
+  constexpr bool template_array_overlaps_string = // both-error {{constexpr variable 'template_array_overlaps_string<FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+                                                  // both-note {{declared here}}
+      S.data != "hello"; // #cwg2765-template-array-string-compare
+  static_assert(template_array_overlaps_string<"hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+                                                          // both-note {{in instantiation of variable template specialization}} \
+                                                          // both-note {{initializer of 'template_array_overlaps_string<FixedString<6>{"hello"}>' is not a constant expression}}
+  // both-note@#cwg2765-template-array-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+  template <FixedString S>
+  constexpr bool template_array_differs_from_string =
+      S.data != "hello";
+  static_assert(template_array_differs_from_string<"help">);
+
+  template <FixedString S>
+  constexpr bool init_list_overlaps_template_array() {
+    return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+           != S.data; // #cwg2765-init-list-template-array-compare
+  }
+  static_assert(init_list_overlaps_template_array<"hello">());
+  // both-error at -1 {{static assertion expression is not an integral constant expression}}
+  // both-note@#cwg2765-init-list-template-array-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  template <FixedString S>
+  constexpr bool init_list_differs_from_template_array() {
+    return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+           != S.data;
+  }
+  static_assert(init_list_differs_from_template_array<"hello">());
+
+  template <FixedString A, FixedString B>
+  constexpr bool template_arrays_overlap_shifted = // both-error {{constexpr variable 'template_arrays_overlap_shifted<FixedString<5>{"ello"}, FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+                                                   // both-note {{declared here}}
+      A.data != B.data + 1; // #cwg2765-template-array-shifted-compare
+  static_assert(template_arrays_overlap_shifted<"ello", "hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+                                                                    // both-note {{in instantiation of variable template specialization}} \
+                                                                    // both-note {{initializer of 'template_arrays_overlap_shifted<FixedString<5>{"ello"}, FixedString<6>{"hello"}>' is not a constant expression}}
+  // both-note@#cwg2765-template-array-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
   constexpr bool same_object() {
     std::initializer_list<int> il = {1, 1, 1};
     return il.begin() == il.begin() && il.begin() != il.begin() + 1;
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index 62ea3c991f26c..d813acd3c71f8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -481,8 +481,8 @@ namespace B19773010 {
   void f1() {
     // CHECK-LABEL: @_ZN9B197730102f1Ev
     testcase a{{"", ENUM_CONSTANT}};
-    // X86: store ptr @.ref.tmp{{.*}}, ptr %{{.*}}, align 8
-    // AMDGCN: store ptr addrspacecast{{.*}} @.ref.tmp{{.*}}{{.*}}, ptr %{{.*}}, align 8
+    // X86: store ptr @.init.list{{.*}}, ptr %{{.*}}, align 8
+    // AMDGCN: store ptr addrspacecast{{.*}} @.init.list{{.*}}{{.*}}, ptr %{{.*}}, align 8
   }
   void f2() {
     // CHECK-LABEL: @_ZN9B197730102f2Ev
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index 3f5ecac648066..d2ff58be1ce7a 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,5 +1,9 @@
 // RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
 
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private constant [100 x i32]
+
 namespace std {
 using size_t = decltype(sizeof(int));
 
@@ -29,8 +33,9 @@ void g(float x) {
 
 void h() {
   // CHECK-LABEL: define{{.*}} void @_ZN9example121hEv(
-  // CHECK: alloca [3 x double],
-  // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24,
+  // CHECK-NOT: alloca [3 x double]
+  // CHECK-NOT: llvm.memcpy
+  // CHECK: store ptr @[[DOUBLE_INIT]], ptr %{{.*}}, align 8
   // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
   f({1, 2, 3});
 }
@@ -41,8 +46,9 @@ void bytes(std::initializer_list<unsigned char>);
 
 void f() {
   // CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv(
-  // CHECK: alloca [2 x i8],
-  // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2,
+  // CHECK-NOT: alloca [2 x i8]
+  // CHECK-NOT: llvm.memcpy
+  // CHECK: store ptr @[[EMBED_INIT]], ptr %{{.*}}, align 8
   // CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE(
   bytes({
 #embed <jk.txt>
@@ -50,6 +56,22 @@ void f() {
 }
 } // namespace embed_example
 
+namespace large_constant_list {
+#define TEN 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+#define HUNDO TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN
+
+void f(std::initializer_list<int>);
+
+void g() {
+  // CHECK-LABEL: define{{.*}} void @_ZN19large_constant_list1gEv(
+  // CHECK-NOT: alloca [100 x i32]
+  // CHECK-NOT: llvm.memcpy
+  // CHECK: store ptr @[[HUNDO_INIT]], ptr %{{.*}}, align 8
+  // CHECK: call void @_ZN19large_constant_list1fESt16initializer_listIiE(
+  f({HUNDO});
+}
+} // namespace large_constant_list
+
 namespace destructor_side_effects {
 extern "C" int printf(const char *, ...);
 
diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
index 5963b7ddea170..7ab6b945874cb 100644
--- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
+++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
@@ -21,6 +21,8 @@
 // CHECK: @__const._Z13testArrayInitv.p1 = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 }], align 8
 // CHECK: @__const._Z13testArrayInitv.c0 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 35591) to i64), i64 0 } }, align 8
 // CHECK: @__const._Z13testArrayInitv.c1 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 } }, align 8
+// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
 
 // CHECK: @_ZN22testNoexceptConversion6mfptr1E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S19nonvirtual_noexceptEv, i32 0, i64 [[TYPEDISC3:.*]]) to i64), i64 0 },
 // CHECK: @_ZN22testNoexceptConversion6mfptr2E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S16virtual_noexceptEv_vfpthunk_, i32 0, i64 [[TYPEDISC3]]) to i64), i64 0 },
@@ -427,8 +429,8 @@ MethodTy0 gmethod2 = reinterpret_cast<MethodTy0>(&Derived0::virtual1);
 // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %p1, ptr align 8 @__const._Z13testArrayInitv.p1, i64 16, i1 false)
 // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %c0, ptr align 8 @__const._Z13testArrayInitv.c0, i64 16, i1 false)
 // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %c1, ptr align 8 @__const._Z13testArrayInitv.c1, i64 16, i1 false)
-// CHECK: store { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }, ptr %{{.*}} align 8
-// CHECK: store { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }, ptr %{{.*}}, align 8
+// CHECK: store ptr @[[INITLIST0]], ptr %{{.*}}, align 8
+// CHECK: store ptr @[[INITLIST1]], ptr %{{.*}}, align 8
 
 void initList(std::initializer_list<MethodTy1>);
 
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 47a064c4026b4..3d6350504e8cb 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1939,6 +1939,20 @@ namespace InitializerList {
   static_assert(*std::initializer_list<int>{1, 2, 3}.begin() == 1, "");
   static_assert(std::initializer_list<int>{1, 2, 3}.begin()[2] == 3, "");
 
+  constexpr bool init_list_overlaps_string() {
+    return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+           != "hello"; // expected-warning {{result of comparison against a string literal is unspecified}} \
+                        // expected-note {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  }
+  static_assert(init_list_overlaps_string(), ""); // expected-error {{not an integral constant expression}} \
+                                                  // expected-note {{in call to}}
+
+  constexpr bool init_list_differs_from_string() {
+    return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+           != "hello"; // expected-warning {{result of comparison against a string literal is unspecified}}
+  }
+  static_assert(init_list_differs_from_string(), "");
+
   namespace DR2126 {
     constexpr std::initializer_list<float> il = {1.0, 2.0, 3.0};
     static_assert(il.begin()[1] == 2.0, "");

>From 8b7eedc9ad05bf3d5a7008a571c4ff0f75b60f7e Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:47:56 +0800
Subject: [PATCH 3/5] [clang] Reuse static initializer_list backing arrays

---
 clang/lib/CodeGen/CodeGenModule.cpp           | 25 ++++++++-----
 clang/lib/CodeGen/CodeGenModule.h             |  2 +
 .../CodeGenCXX/p2752r3-initializer-list.cpp   | 37 +++++++++++++++++--
 3 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 2da1796ef2b61..b8aaf553e5f3a 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7727,18 +7727,23 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
                                               CharUnits Align) {
   LangAS AddrSpace = GetGlobalConstantAddressSpace();
   auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
-  auto *GV = new llvm::GlobalVariable(
-      getModule(), Init->getType(), /*isConstant=*/true,
-      llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
-      /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
-  GV->setAlignment(Align.getAsAlign());
-
-  llvm::Constant *CV = GV;
+  llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
+  if (!Entry) {
+    Entry = new llvm::GlobalVariable(
+        getModule(), Init->getType(), /*isConstant=*/true,
+        llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+        /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
+    Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+  }
+  Entry->setAlignment(
+      std::max(Entry->getAlign().valueOrOne(), Align.getAsAlign()));
+
+  llvm::Constant *CV = Entry;
   if (AddrSpace != LangAS::Default)
     CV = performAddrSpaceCast(
-        GV, llvm::PointerType::get(
-                getLLVMContext(),
-                getContext().getTargetAddressSpace(LangAS::Default)));
+        Entry, llvm::PointerType::get(
+                   getLLVMContext(),
+                   getContext().getTargetAddressSpace(LangAS::Default)));
 
   return ConstantAddress(CV, Init->getType(), Align);
 }
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 3bb881aec67e5..f4678d512d343 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -520,6 +520,8 @@ class CodeGenModule : public CodeGenTypeCache {
   llvm::StringMap<llvm::GlobalVariable *> CFConstantStringMap;
 
   llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *> ConstantStringMap;
+  llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *>
+      StaticInitListBackingArrayMap;
   llvm::DenseMap<const UnnamedGlobalConstantDecl *, llvm::GlobalVariable *>
       UnnamedGlobalConstantDeclMap;
   llvm::DenseMap<const Decl*, llvm::Constant *> StaticLocalDeclMap;
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index d2ff58be1ce7a..f121fffa82215 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,8 +1,10 @@
 // RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
 
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private constant [2 x i8] c"jk", align 1
-// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private constant [100 x i32]
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
 
 namespace std {
 using size_t = decltype(sizeof(int));
@@ -72,6 +74,35 @@ void g() {
 }
 } // namespace large_constant_list
 
+namespace shared_static_lists {
+void f(std::initializer_list<int>);
+
+void g() {
+  // CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
+  // CHECK-NOT: alloca [3 x i32]
+  // CHECK-NOT: llvm.memcpy
+  // CHECK: store ptr @[[INT_INIT]], ptr %{{.*}}, align 8
+  // CHECK: call void @_ZN19shared_static_lists1fESt16initializer_listIiE(
+  // CHECK: store ptr @[[INT_INIT]], ptr %{{.*}}, align 8
+  // CHECK: call void @_ZN19shared_static_lists1fESt16initializer_listIiE(
+  f({1, 2, 3});
+  f({1, 2, 3});
+}
+} // namespace shared_static_lists
+
+namespace mergeable_static_list {
+void f(std::initializer_list<int>);
+
+void g() {
+  // ASM: .section .rodata.cst16,"aM", at progbits,16
+  // ASM: .long 1
+  // ASM-NEXT: .long 2
+  // ASM-NEXT: .long 3
+  // ASM-NEXT: .long 4
+  f({1, 2, 3, 4});
+}
+} // namespace mergeable_static_list
+
 namespace destructor_side_effects {
 extern "C" int printf(const char *, ...);
 

>From 99a47b9d7817fdcfd1fc49b403118a87fdfb328c Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:55:54 +0800
Subject: [PATCH 4/5] [clang] Pad static initializer_list arrays for merging

---
 clang/lib/CodeGen/CodeGenModule.cpp           | 21 +++++++++++++++++--
 .../cxx0x-initializer-stdinitializerlist.cpp  |  8 +++----
 .../CodeGenCXX/p2752r3-initializer-list.cpp   | 13 ++++++++----
 3 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index b8aaf553e5f3a..450d8e0920b82 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -71,6 +71,7 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/ConvertUTF.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/TimeProfiler.h"
 #include "llvm/TargetParser/AArch64TargetParser.h"
 #include "llvm/TargetParser/RISCVISAInfo.h"
@@ -7727,11 +7728,27 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
                                               CharUnits Align) {
   LangAS AddrSpace = GetGlobalConstantAddressSpace();
   auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+  llvm::Constant *StoredInit = Init;
+  const llvm::DataLayout &DL = getModule().getDataLayout();
+  uint64_t Size = DL.getTypeAllocSize(Init->getType());
+  uint64_t MergeableSize = llvm::PowerOf2Ceil(std::max<uint64_t>(Size, 4));
+  if (!Init->needsRelocation() && Size > 0 && Size <= 32 &&
+      MergeableSize >= 4 && MergeableSize != Size) {
+    uint64_t PaddingSize = MergeableSize - Size;
+    auto *PaddingTy =
+        llvm::ArrayType::get(llvm::Type::getInt8Ty(getLLVMContext()),
+                             PaddingSize);
+    auto *PaddedTy = llvm::StructType::get(
+        getLLVMContext(), {Init->getType(), PaddingTy}, /*isPacked=*/true);
+    StoredInit = llvm::ConstantStruct::get(
+        PaddedTy, {Init, llvm::ConstantAggregateZero::get(PaddingTy)});
+  }
+
   llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
   if (!Entry) {
     Entry = new llvm::GlobalVariable(
-        getModule(), Init->getType(), /*isConstant=*/true,
-        llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+        getModule(), StoredInit->getType(), /*isConstant=*/true,
+        llvm::GlobalValue::PrivateLinkage, StoredInit, ".init.list",
         /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
     Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
   }
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index d813acd3c71f8..cd7fa194624c8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -85,10 +85,10 @@ std::initializer_list<int> thread_local x = {1, 2, 3, 4};
 // AMDGCN: @[[PARTLY_CONSTANT_SECOND:_ZGRN15partly_constant2ilE2_]] = internal addrspace(1) global [2 x i32] zeroinitializer, align 4
 // AMDGCN: @[[PARTLY_CONSTANT_THIRD:_ZGRN15partly_constant2ilE3_]] = internal addrspace(1) constant [4 x i32] [i32 5, i32 6, i32 7, i32 8], align 4
 
-// X86: @[[REFTMP1:.*]] = private constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:.*]] = private constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
-// AMDGCN: @[[REFTMP1:.*]] = private addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:.*]] = private addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// X86: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
+// AMDGCN: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
 
 // CHECK: appending global
 
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index f121fffa82215..b872afc5b6245 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,10 +1,10 @@
 // RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
 // RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
 
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x double], [8 x i8] }> <{ [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], [8 x i8] zeroinitializer }>, align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [2 x i8], [2 x i8] }> <{ [2 x i8] c"jk", [2 x i8] zeroinitializer }>, align 1
 // CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
-// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x i32], [4 x i8] }> <{ [3 x i32] [i32 1, i32 2, i32 3], [4 x i8] zeroinitializer }>, align 4
 
 namespace std {
 using size_t = decltype(sizeof(int));
@@ -78,6 +78,11 @@ namespace shared_static_lists {
 void f(std::initializer_list<int>);
 
 void g() {
+  // ASM: .section .rodata.cst16,"aM", at progbits,16
+  // ASM: .long 1
+  // ASM-NEXT: .long 2
+  // ASM-NEXT: .long 3
+  // ASM-NEXT: .zero 4
   // CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
   // CHECK-NOT: alloca [3 x i32]
   // CHECK-NOT: llvm.memcpy
@@ -94,11 +99,11 @@ namespace mergeable_static_list {
 void f(std::initializer_list<int>);
 
 void g() {
-  // ASM: .section .rodata.cst16,"aM", at progbits,16
   // ASM: .long 1
   // ASM-NEXT: .long 2
   // ASM-NEXT: .long 3
   // ASM-NEXT: .long 4
+  // ASM-NEXT: .size {{.*}}, 16
   f({1, 2, 3, 4});
 }
 } // namespace mergeable_static_list

>From df26b56fea61fcdea0abdac16a82f0589b4ff1f9 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 30 Jun 2026 20:10:55 +0800
Subject: [PATCH 5/5] Fix test

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
index 7ab6b945874cb..0fb5ba8f6550c 100644
--- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
+++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
@@ -21,8 +21,8 @@
 // CHECK: @__const._Z13testArrayInitv.p1 = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 }], align 8
 // CHECK: @__const._Z13testArrayInitv.c0 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 35591) to i64), i64 0 } }, align 8
 // CHECK: @__const._Z13testArrayInitv.c1 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 } }, align 8
-// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
-// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
 
 // CHECK: @_ZN22testNoexceptConversion6mfptr1E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S19nonvirtual_noexceptEv, i32 0, i64 [[TYPEDISC3:.*]]) to i64), i64 0 },
 // CHECK: @_ZN22testNoexceptConversion6mfptr2E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S16virtual_noexceptEv_vfpthunk_, i32 0, i64 [[TYPEDISC3]]) to i64), i64 0 },



More information about the cfe-commits mailing list