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

>From c8a0f26dd7a90bfff8520f5b9db10c87bd7a16cd Mon Sep 17 00:00:00 2001
From: yronglin <[email protected]>
Date: Wed, 27 May 2026 09:39:03 -0700
Subject: [PATCH] [C++26] Implement P2752R3 'Static storage for braced
 initializers'

Signed-off-by: yronglin <[email protected]>
---
 clang/docs/ReleaseNotes.rst                   |   2 +
 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, 1278 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.rst b/clang/docs/ReleaseNotes.rst
index 603f427c40aa4..03eb3f0156b2e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -152,6 +152,8 @@ C++ Language Changes
 C++2c Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
+- 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 0287797370397..8284f0092145e 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 c64eee11fd91e..388e09fea89cf 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 7bab2d7dcddfa..7e6a90e2af31a 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8547,6 +8547,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 ed182ea899f61..95f391e8ac655 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2527,6 +2527,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 235f1c471f471..94bc9c79e3851 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1253,6 +1253,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) {
@@ -1297,7 +1300,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)) {
@@ -1317,6 +1323,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 40e129d03dcea..b485808184d8c 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1837,6 +1837,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 38aa5798cfeb9..8016e7ec0eae4 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"
@@ -2024,17 +2025,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;
@@ -2053,6 +2055,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.
@@ -2067,31 +2074,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
@@ -2109,6 +2116,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();
@@ -4860,6 +4893,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.
@@ -18695,9 +18981,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 96a398aa21dad..0df78035e5325 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3319,6 +3319,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 4e050e9bf6045..b85337a743e08 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 7c126186831eb..ee79bad64ae44 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12321,96 +12321,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++.");
@@ -12418,8 +12328,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,
@@ -12430,8 +12342,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 efc816c0d8b75..640ad8abecbc0 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8677,6 +8677,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 7e51ce8c0aca2..436179101f3b1 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 a7e815a1ef438..c425b8b47b923 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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-1 {{static assertion expression is not an integral constant 
expression}}
 //   expected-note@-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@-1 {{constexpr variable 'b2' must be initialized by a 
constant expression}}
 //   since-cxx11-note@-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@-1 {{constexpr variable 'b4' must be initialized by a 
constant expression}}
+//   since-cxx11-note@-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@-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@-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@-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@-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@-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@-1 {{constexpr variable 'b7' must be initialized by a constant 
expression}}
-//   FIXME-note@-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@-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@-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@-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@-4 {{in call to 'f9(nullptr)'}}
+
+constexpr bool b10c = f10<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+// since-cxx14-error@-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@-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@-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@-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@-1 {{constexpr variable 'agg_same' must be initialized by 
a constant expression}}
+//   since-cxx14-note@-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 e090e4afcaeca..66efea4d0999f 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 315fa54531a02..860c4b6e66cf7 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -135,7 +135,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>

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

Reply via email to