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

Yihan Wang via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 13 11:03:21 PDT 2026


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

>From 8bdc39c4519ce7f374a48d985ed276c12b4acc57 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 27 May 2026 09:39:03 -0700
Subject: [PATCH 01/12] [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               |  30 +-
 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, 1276 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 bbd42848a98c2..d77975d4d69f5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -259,6 +259,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)
 
 - Clang now has partial support for [P1306R5](https://wg21.link/P1306R5) Expansion Statements. Iterating expansion
   statements currently cannot be expanded and will result in a diagnostic, but other types of expansion statements work.
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 757f2137c90dc..d97c745b8d304 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 c9658775f0470..015f196523f27 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 f86f0157b2b1f..19a5f52c11525 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -101,6 +101,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 d743717ad49de..4ea050995d148 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8697,6 +8697,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 e4675978e2a18..13469758fc5c9 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2856,6 +2856,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 201679017c98b..0bb2f9839ad9b 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1302,6 +1302,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) {
@@ -1346,7 +1349,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)) {
@@ -1368,6 +1374,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 a1ea91994969c..cc0a590a26b90 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 7ba3071668bd1..e67ce3dabdd37 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;
 class CallExpr;
 } // namespace clang
@@ -98,6 +100,11 @@ unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
                                         unsigned BuiltinID);
 unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
                                         const CallExpr *E);
+/// 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);
@@ -110,4 +117,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 25a96a9c3eceb..e33d1c73b547c 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.
@@ -19072,9 +19358,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 42d148715bc40..9de276d49c538 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 fafee76ec18c3..5e9c6598b332c 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 685412d681680..e947c17d06ca4 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12351,96 +12351,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++.");
@@ -12448,8 +12358,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,
@@ -12460,8 +12372,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 09d9f1eabd058..195995d358914 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8709,6 +8709,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 87cec16a76323..fb6025601111d 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2349,6 +2349,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 70477f4cf4001..53acae125892a 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2363,6 +2363,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 af91ac559d274..3f68691ee75bb 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 0675f785bab20..308b6a4dfb63d 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -247,7 +247,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 7102e77741bfca09fd6bda9b5e645b4cfd3547aa Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 23 Jun 2026 20:16:08 +0800
Subject: [PATCH 02/12] 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 13469758fc5c9..0445075042fb4 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2911,63 +2911,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 0bb2f9839ad9b..5b87a606c6d53 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1302,9 +1302,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) {
@@ -1374,7 +1376,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 e33d1c73b547c..f2c0b3cd02668 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
@@ -19360,8 +19594,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 6a0d127fb451b..5b46eb9dd66e8 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 e7c24f1f36f1e..93be0adfa4ea6 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4493,6 +4493,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 012a1115ca7d3..417d6b8c28479 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7766,6 +7766,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 0abd75ccb0551..56e5cec73ef5e 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1058,6 +1058,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 bba1ffe19b5644755aa20f9ba18f6c9fe7e53457 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:47:56 +0800
Subject: [PATCH 03/12] [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 417d6b8c28479..f5a54d7e584cf 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7773,18 +7773,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 56e5cec73ef5e..60618adb55dfd 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 1bc8669a993ed9d1ae6702d46467ecec8ebde9b8 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:55:54 +0800
Subject: [PATCH 04/12] [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 f5a54d7e584cf..ff44109837266 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"
@@ -7773,11 +7774,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 40f6b3ce361164286a44f8b4d952d21158d7c459 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 30 Jun 2026 20:10:55 +0800
Subject: [PATCH 05/12] 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 },

>From f40b52ab8dc4b4dfe4de8beb6e4f7e14fb9f6adc Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 30 Jun 2026 07:50:47 -0700
Subject: [PATCH 06/12] Refine NonUniqueObject logics in ExprConstant

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/lib/AST/ExprConstant.cpp | 561 +++++++++++++++++----------------
 1 file changed, 295 insertions(+), 266 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f2c0b3cd02668..244e4d7f283ef 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5003,9 +5003,10 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
   }
 
   // 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.
+  // AddrLabelDiff), Profile already captures the full observable content, so a
+  // Profile disagreement means the values are observably distinct. (None and
+  // Indeterminate never reach here: APValue::hasValue() is false for both, so
+  // they are intercepted by the early return above.)
   default:
     return false;
   }
@@ -5053,327 +5054,355 @@ std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
   if (OffsetInElement > ElementSize)
     return std::nullopt;
 
+  // Canonicalize a pointer to the byte just past element Index: it designates
+  // the start of element Index + 1 (or, when Index + 1 == ArraySize, the
+  // array's one-past-the-end position). Without this, the same address could
+  // be described as either {Index, ElementSize} or {Index + 1, 0}, and the two
+  // encodings would not compare equal in callers that match on OffsetInElement.
+  // After this step OffsetInElement is always strictly inside the element,
+  // except for the one-past-the-end location {ArraySize, 0}.
+  if (ElementSize != 0 && OffsetInElement == ElementSize) {
+    ++Index;
+    OffsetInElement = 0;
+  }
+
   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;
+namespace {
+/// A potentially-non-unique array object: a string literal, an
+/// initializer_list backing array, or an array subobject of a
+/// template-parameter object (NTTP). [intro.object]/9 lets the implementation
+/// merge two such objects when their overlapping contents agree, which is what
+/// makes their address comparisons non-constant.
+///
+/// The kind is recorded explicitly; the InitializerList and TemplateParamObject
+/// kinds are both read through an APValue array (borrowed and owned,
+/// respectively), while the StringLiteral kind materializes characters lazily.
+/// The single public entry point is arePotentiallyOverlapping().
+class NonUniqueObject {
+public:
+  /// Returns true when two lvalues designate potentially non-unique arrays
+  /// whose overlapping elements could be merged into the same storage, making
+  /// their address comparison non-constant.
+  static bool arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
+                                        const LValue &LHS, const LValue &RHS);
+
+private:
+  enum class Kind { StringLiteral, InitializerList, TemplateParamObject };
+
+  /// Identifies an enclosing constant array subobject within an lvalue path.
+  struct ArraySubobjectPathInfo {
+    const ConstantArrayType *ArrayType = nullptr;
+    unsigned ArrayPathLength = 0;
+    ArraySubobjectLocation Loc;
+  };
 
-  const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
-  if (!ArrayType)
+  /// The evaluation state and diagnostic expression are stored so the member
+  /// functions need not thread them through every call.
+  NonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
+
+  /// Recognizes the non-unique array LV designates, filling in this object and
+  /// returning true, or returning false when LV is not such an object.
+  bool recognize(const LValue &LV);
+
+  /// Returns true when *this and Other could be merged into the same storage at
+  /// their designated positions: the positions must share an in-element byte
+  /// offset and element-compatible types, and every element the overlap would
+  /// alias must be pairwise mergeable. Element data the representation cannot
+  /// expose is treated conservatively as a possible match.
+  bool mayShareStorageWith(const NonUniqueObject &Other) const;
+
+  // Lvalue classification helpers used by recognize().
+
+  /// Returns the underlying string literal expression for bases that denote
+  /// string literal objects in the evaluator.
+  static const Expr *getStringLiteralBase(APValue::LValueBase Base);
+  /// Whether LV's base is an initializer_list backing array.
+  static bool isInitializerListBackingArray(const LValue &LV);
+  /// Reads the complete object value an lvalue's base denotes.
+  const APValue *getCompleteObjectValue(const LValue &LV) const;
+  /// 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.
+  std::optional<ArraySubobjectLocation>
+  getArraySubobjectLocation(const LValue &LV) const;
+  /// Returns the string-literal array element and element-relative offset
+  /// designated by LV, including byte-offset-only lvalues.
+  std::optional<ArraySubobjectLocation>
+  getStringArraySubobjectLocation(const LValue &LV) const;
+  /// Finds the innermost enclosing constant array subobject for an lvalue into
+  /// a template parameter object.
+  std::optional<ArraySubobjectPathInfo>
+  getArraySubobjectPathInfo(const LValue &LV) const;
+
+  bool isStringLiteral() const { return TheKind == Kind::StringLiteral; }
+  const ArraySubobjectLocation &location() const { return Loc; }
+
+  /// Number of elements visible through this view.
+  int64_t size() const {
+    if (TheKind == Kind::StringLiteral)
+      return ArrayType->getZExtSize();
+    return arrayValue()->getArraySize();
+  }
+
+  /// Value of the element at Index, or std::nullopt when the representation
+  /// cannot expose enough data to prove it differs.
+  std::optional<APValue> operator[](int64_t Index) const {
+    if (Index < 0)
+      return std::nullopt;
+    uint64_t UIndex = static_cast<uint64_t>(Index);
+    if (TheKind == Kind::StringLiteral)
+      return APValue(extractStringLiteralCharacter(Info, StringExpr, UIndex));
+    if (const APValue *Elt = GetArrayInitializedElt(*arrayValue(), UIndex))
+      return *Elt;
     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);
-}
+  /// Returns the APValue array for the InitializerList / TemplateParamObject
+  /// kinds (borrowed or owned, respectively).
+  const APValue *arrayValue() const {
+    const APValue *Arr = OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
+    assert(Arr && Arr->isArray() && "invalid APValue array");
+    return Arr;
+  }
 
-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;
+  EvalInfo &Info;
+  const Expr *E;
+  Kind TheKind = Kind::StringLiteral;
+  const ConstantArrayType *ArrayType = nullptr;
+  ArraySubobjectLocation Loc;
+  const Expr *StringExpr = nullptr;        // Kind::StringLiteral
+  const APValue *ArrayValue = nullptr;     // Kind::InitializerList (borrowed)
+  std::optional<APValue> OwnedArrayValue;  // Kind::TemplateParamObject (owned)
+};
+} // namespace
+
+const Expr *NonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
+  const auto *BaseExpr = Base.dyn_cast<const Expr *>();
+  if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
+    return BaseExpr;
+  return nullptr;
 }
 
-static bool isInitializerListBackingArray(const LValue &LV) {
+bool NonUniqueObject::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();
 }
 
-/// 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;
+const APValue *NonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
+  CompleteObject Obj =
+      findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
+  return Obj ? Obj.Value : nullptr;
 }
 
-/// 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) {
+std::optional<ArraySubobjectLocation>
+NonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
+  if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+    return std::nullopt;
+
+  const auto *CAT = Info.Ctx.getAsConstantArrayType(getType(LV.Base));
+  if (!CAT)
+    return std::nullopt;
+
+  uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
+  bool IsValidOnePastEnd =
+      LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
+  return getArraySubobjectLocationImpl(Info.Ctx, CAT, Index, LV.Offset,
+                                       IsValidOnePastEnd);
+}
+
+std::optional<ArraySubobjectLocation>
+NonUniqueObject::getStringArraySubobjectLocation(const LValue &LV) const {
   if (!getStringLiteralBase(LV.Base))
     return std::nullopt;
 
-  if (std::optional<ArraySubobjectLocation> Loc =
-          getArraySubobjectLocation(Ctx, LV))
-    return Loc;
+  if (std::optional<ArraySubobjectLocation> EltLoc =
+          getArraySubobjectLocation(LV))
+    return EltLoc;
 
-  const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
-  if (!ArrayType || LV.Offset.isNegative())
+  const auto *CAT = Info.Ctx.getAsConstantArrayType(getType(LV.Base));
+  if (!CAT || LV.Offset.isNegative())
     return std::nullopt;
 
   uint64_t ElementSize =
-      Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+      Info.Ctx.getTypeSizeInChars(CAT->getElementType()).getQuantity();
   if (!ElementSize)
     return std::nullopt;
 
   uint64_t Offset = LV.Offset.getQuantity();
   uint64_t Index = Offset / ElementSize;
-  if (Index > ArrayType->getZExtSize())
+  uint64_t OffsetInElement = Offset % ElementSize;
+  uint64_t ArraySize = CAT->getZExtSize();
+  // Accept element and subobject positions, plus the single one-past-the-end
+  // byte {ArraySize, 0}. Reject anything beyond that, including an offset that
+  // lands past the one-past-the-end byte with a nonzero in-element remainder.
+  if (Index > ArraySize || (Index == ArraySize && OffsetInElement != 0))
     return std::nullopt;
 
   return ArraySubobjectLocation{Index,
-                                CharUnits::fromQuantity(Offset % ElementSize)};
+                                CharUnits::fromQuantity(OffsetInElement)};
 }
 
-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;
+std::optional<NonUniqueObject::ArraySubobjectPathInfo>
+NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
+  const ASTContext &Ctx = Info.Ctx;
+  if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+    return std::nullopt;
 
-  /// Returns true when this view describes a string literal array.
-  bool isStringLiteral() const { return StringBase; }
+  QualType Type = getType(LV.Base).getNonReferenceType();
+  CharUnits PathOffset = CharUnits::Zero();
+  std::optional<ArraySubobjectPathInfo> Result;
 
-  /// Returns the APValue storage for non-string arrays.
-  const APValue *getArrayValue() const {
-    return OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
-  }
+  for (unsigned I = 0, N = LV.Designator.Entries.size(); I != N; ++I) {
+    APValue::LValuePathEntry Entry = LV.Designator.Entries[I];
 
-  /// Returns the number of array elements visible through this view.
-  int64_t getSize() const {
-    if (isStringLiteral())
-      return ArrayType->getZExtSize();
+    if (const auto *CAT = Ctx.getAsConstantArrayType(Type)) {
+      uint64_t Index = Entry.getAsArrayIndex();
+      CharUnits OffsetInArray = LV.Offset - PathOffset;
+      bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
+      std::optional<ArraySubobjectLocation> EltLoc =
+          getArraySubobjectLocationImpl(Ctx, CAT, Index, OffsetInArray,
+                                        IsValidOnePastEnd);
+      if (EltLoc)
+        Result = ArraySubobjectPathInfo{CAT, I, *EltLoc};
+
+      QualType ElementType = CAT->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;
+    }
 
-    const APValue *Array = getArrayValue();
-    assert(Array && Array->isArray() && "invalid APValue array");
-    return Array->getArraySize();
-  }
+    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;
+    }
 
-  /// 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)
+    const CXXRecordDecl *Base = getAsBaseClass(Entry);
+    const CXXRecordDecl *Derived = Type->getAsCXXRecordDecl();
+    if (!Derived || !Base)
       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;
+    const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Derived);
+    PathOffset += isVirtualBaseClass(Entry) ? Layout.getVBaseClassOffset(Base)
+                                            : Layout.getBaseClassOffset(Base);
+    Type = Ctx.getCanonicalTagType(Base);
   }
-};
 
-/// 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;
+  if (const auto *CAT = Ctx.getAsConstantArrayType(Type)) {
+    CharUnits OffsetInArray = LV.Offset - PathOffset;
+    if (OffsetInArray.isZero())
+      Result = ArraySubobjectPathInfo{
+          CAT, static_cast<unsigned>(LV.Designator.Entries.size()),
+          ArraySubobjectLocation{0, CharUnits::Zero()}};
+  }
 
-    // Keep the existing string-literal diagnostic path authoritative for
-    // string/string comparisons.
-    if (LHSArray->isStringLiteral() && RHSArray->isStringLiteral())
-      return false;
+  return Result;
+}
 
-    if (LHSArray->Loc.OffsetInElement != RHSArray->Loc.OffsetInElement)
-      return false;
+bool NonUniqueObject::recognize(const LValue &LV) {
+  ASTContext &Ctx = Info.Ctx;
 
-    if (!Ctx.hasSameUnqualifiedType(LHSArray->ArrayType->getElementType(),
-                                    RHSArray->ArrayType->getElementType()))
+  if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+    std::optional<ArraySubobjectLocation> EltLoc =
+        getStringArraySubobjectLocation(LV);
+    const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
+    if (!EltLoc || !CAT)
       return false;
+    TheKind = Kind::StringLiteral;
+    ArrayType = CAT;
+    Loc = *EltLoc;
+    StringExpr = StringBase;
+    return true;
+  }
 
-    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)
+  if (isInitializerListBackingArray(LV)) {
+    std::optional<ArraySubobjectLocation> EltLoc = getArraySubobjectLocation(LV);
+    const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
+    const APValue *ArrVal = getCompleteObjectValue(LV);
+    if (!EltLoc || !CAT || !ArrVal || !ArrVal->isArray())
       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;
-    }
-
+    TheKind = Kind::InitializerList;
+    ArrayType = CAT;
+    Loc = *EltLoc;
+    ArrayValue = ArrVal;
     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;
+  const auto *VD = LV.Base.dyn_cast<const ValueDecl *>();
+  const auto *TPO = dyn_cast_if_present<TemplateParamObjectDecl>(VD);
+  if (!TPO)
+    return false;
 
-    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;
-      }
+  std::optional<ArraySubobjectPathInfo> PathInfo = getArraySubobjectPathInfo(LV);
+  if (!PathInfo)
+    return false;
 
-      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;
-      }
+  APValue ArrVal;
+  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, ArrVal))
+    return false;
+  if (!ArrVal.isArray())
+    return false;
+  TheKind = Kind::TemplateParamObject;
+  ArrayType = PathInfo->ArrayType;
+  Loc = PathInfo->Loc;
+  OwnedArrayValue = std::move(ArrVal);
+  return true;
+}
 
-      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);
-    }
+bool NonUniqueObject::mayShareStorageWith(const NonUniqueObject &Other) const {
+  if (location().OffsetInElement != Other.location().OffsetInElement)
+    return false;
+  if (!Info.Ctx.hasSameUnqualifiedType(ArrayType->getElementType(),
+                                       Other.ArrayType->getElementType()))
+    return false;
 
-    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()}};
-    }
+  int64_t LHSStart = location().Index, RHSStart = Other.location().Index;
+  int64_t Begin = std::max(-LHSStart, -RHSStart);
+  int64_t End = std::min(size() - LHSStart, Other.size() - RHSStart);
+  if (Begin >= End)
+    return false;
 
-    return Result;
+  for (int64_t I = Begin; I != End; ++I) {
+    std::optional<APValue> LHSElt = (*this)[I + LHSStart];
+    std::optional<APValue> RHSElt = Other[I + RHSStart];
+    if (!LHSElt || !RHSElt)
+      return true;
+    if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+      return false;
   }
+  return true;
+}
 
-  /// 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;
-  }
+bool NonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
+                                                const LValue &LHS,
+                                                const LValue &RHS) {
+  NonUniqueObject LHSObj(Info, E);
+  NonUniqueObject RHSObj(Info, E);
+  if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
+    return false;
 
-  EvalInfo &Info;
-  const Expr *E;
-};
-} // namespace
+  // String/string comparisons stay with the dedicated string-literal path.
+  if (LHSObj.isStringLiteral() && RHSObj.isStringLiteral())
+    return false;
 
-/// 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);
+  return LHSObj.mayShareStorageWith(RHSObj);
 }
 
 /// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -19594,8 +19623,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       //
       if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
         return DiagComparison(diag::note_constexpr_literal_comparison);
-      if (ArePotentiallyOverlappingNonUniqueObjects(Info, E, LHSValue,
-                                                    RHSValue))
+      if (NonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
+                                                     RHSValue))
         return DiagComparison(
             diag::note_constexpr_non_unique_object_comparison);
       if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))

>From 73534cb551ae1ab95766a66fb823c5f9d20deed4 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 01:14:01 -0700
Subject: [PATCH 07/12] Revert "[clang] Pad static initializer_list arrays for
 merging"

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

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index ff44109837266..f5a54d7e584cf 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -71,7 +71,6 @@
 #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"
@@ -7774,27 +7773,11 @@ 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(), StoredInit->getType(), /*isConstant=*/true,
-        llvm::GlobalValue::PrivateLinkage, StoredInit, ".init.list",
+        getModule(), Init->getType(), /*isConstant=*/true,
+        llvm::GlobalValue::PrivateLinkage, Init, ".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 cd7fa194624c8..d813acd3c71f8 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:[.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
+// 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
 
 // CHECK: appending global
 
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index b872afc5b6245..f121fffa82215 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], [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: @[[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], [4 x i8] }> <{ [3 x i32] [i32 1, i32 2, i32 3], [4 x i8] zeroinitializer }>, align 4
+// 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));
@@ -78,11 +78,6 @@ 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
@@ -99,11 +94,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 b7c0ce23b2f6c9c4a5c717d85b3d5ddb960cb0b0 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 01:23:31 -0700
Subject: [PATCH 08/12] Revert padding and format

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/lib/AST/ExprConstant.cpp | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 244e4d7f283ef..75752d44b281e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5173,9 +5173,9 @@ class NonUniqueObject {
   Kind TheKind = Kind::StringLiteral;
   const ConstantArrayType *ArrayType = nullptr;
   ArraySubobjectLocation Loc;
-  const Expr *StringExpr = nullptr;        // Kind::StringLiteral
-  const APValue *ArrayValue = nullptr;     // Kind::InitializerList (borrowed)
-  std::optional<APValue> OwnedArrayValue;  // Kind::TemplateParamObject (owned)
+  const Expr *StringExpr = nullptr;       // Kind::StringLiteral
+  const APValue *ArrayValue = nullptr;    // Kind::InitializerList (borrowed)
+  std::optional<APValue> OwnedArrayValue; // Kind::TemplateParamObject (owned)
 };
 } // namespace
 
@@ -5329,7 +5329,8 @@ bool NonUniqueObject::recognize(const LValue &LV) {
   }
 
   if (isInitializerListBackingArray(LV)) {
-    std::optional<ArraySubobjectLocation> EltLoc = getArraySubobjectLocation(LV);
+    std::optional<ArraySubobjectLocation> EltLoc =
+        getArraySubobjectLocation(LV);
     const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
     const APValue *ArrVal = getCompleteObjectValue(LV);
     if (!EltLoc || !CAT || !ArrVal || !ArrVal->isArray())
@@ -5346,7 +5347,8 @@ bool NonUniqueObject::recognize(const LValue &LV) {
   if (!TPO)
     return false;
 
-  std::optional<ArraySubobjectPathInfo> PathInfo = getArraySubobjectPathInfo(LV);
+  std::optional<ArraySubobjectPathInfo> PathInfo =
+      getArraySubobjectPathInfo(LV);
   if (!PathInfo)
     return false;
 

>From e7baa3fdf11e2b4c81cabd8754c4419169a79956 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 02:33:16 -0700
Subject: [PATCH 09/12] Fix test

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 .../CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp   | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index d813acd3c71f8..2ea0b5faf5a51 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:.*]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:.*]] = private unnamed_addr constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// AMDGCN: @[[REFTMP1:.*]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:.*]] = private unnamed_addr addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
 
 // CHECK: appending global
 

>From 6833557cdc7e6f130b4a72455abdd978aa4e83fe Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 23:01:03 -0700
Subject: [PATCH 10/12] Refine ExprConstant

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/lib/AST/ByteCode/Interp.cpp |  6 ++--
 clang/lib/AST/ExprConstShared.h   |  2 +-
 clang/lib/AST/ExprConstant.cpp    | 49 +++++++++++++++++--------------
 3 files changed, 31 insertions(+), 26 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 0445075042fb4..db0ef677c1d67 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2904,7 +2904,7 @@ getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
 
   uint64_t Index = Path.front().getAsArrayIndex();
   bool IsValidOnePastEnd = Path.size() == 1;
-  std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+  std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
       Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
   if (!Loc)
     return std::nullopt;
@@ -2984,7 +2984,7 @@ getNonUniqueAPValueArraySubobjectLocation(const ASTContext &Ctx,
 
   uint64_t Index = Path.front().getAsArrayIndex();
   bool IsValidOnePastEnd = Path.size() == 1;
-  std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+  std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
       Ctx, ArrayType, Index,
       PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
       IsValidOnePastEnd);
@@ -3027,7 +3027,7 @@ getStringArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
 
   uint64_t Index = Path.front().getAsArrayIndex();
   bool IsValidOnePastEnd = Path.size() == 1;
-  std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+  std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
       Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
   if (!Loc)
     return std::nullopt;
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index e67ce3dabdd37..dd1043e83f574 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -132,7 +132,7 @@ struct ArraySubobjectLocation {
 /// 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(
+std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
     const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
     CharUnits LValueOffset, bool IsValidOnePastEnd);
 
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 75752d44b281e..4fe8275e27b30 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5024,7 +5024,7 @@ bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
   return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
 }
 
-std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
     const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
     CharUnits LValueOffset, bool IsValidOnePastEnd) {
   uint64_t ArraySize = ArrayType->getZExtSize();
@@ -5081,7 +5081,7 @@ namespace {
 /// kinds are both read through an APValue array (borrowed and owned,
 /// respectively), while the StringLiteral kind materializes characters lazily.
 /// The single public entry point is arePotentiallyOverlapping().
-class NonUniqueObject {
+class PotentialNonUniqueObject {
 public:
   /// Returns true when two lvalues designate potentially non-unique arrays
   /// whose overlapping elements could be merged into the same storage, making
@@ -5101,7 +5101,7 @@ class NonUniqueObject {
 
   /// The evaluation state and diagnostic expression are stored so the member
   /// functions need not thread them through every call.
-  NonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
+  PotentialNonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
 
   /// Recognizes the non-unique array LV designates, filling in this object and
   /// returning true, or returning false when LV is not such an object.
@@ -5112,7 +5112,7 @@ class NonUniqueObject {
   /// offset and element-compatible types, and every element the overlap would
   /// alias must be pairwise mergeable. Element data the representation cannot
   /// expose is treated conservatively as a possible match.
-  bool mayShareStorageWith(const NonUniqueObject &Other) const;
+  bool mayShareStorageWith(const PotentialNonUniqueObject &Other) const;
 
   // Lvalue classification helpers used by recognize().
 
@@ -5179,27 +5179,29 @@ class NonUniqueObject {
 };
 } // namespace
 
-const Expr *NonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
+const Expr *
+PotentialNonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
   const auto *BaseExpr = Base.dyn_cast<const Expr *>();
   if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
     return BaseExpr;
   return nullptr;
 }
 
-bool NonUniqueObject::isInitializerListBackingArray(const LValue &LV) {
+bool PotentialNonUniqueObject::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();
 }
 
-const APValue *NonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
+const APValue *
+PotentialNonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
   CompleteObject Obj =
       findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
   return Obj ? Obj.Value : nullptr;
 }
 
 std::optional<ArraySubobjectLocation>
-NonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
+PotentialNonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
   if (LV.Designator.Invalid || LV.Designator.Entries.empty())
     return std::nullopt;
 
@@ -5210,12 +5212,13 @@ NonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
   uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
   bool IsValidOnePastEnd =
       LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
-  return getArraySubobjectLocationImpl(Info.Ctx, CAT, Index, LV.Offset,
+  return computeArraySubobjectLocation(Info.Ctx, CAT, Index, LV.Offset,
                                        IsValidOnePastEnd);
 }
 
 std::optional<ArraySubobjectLocation>
-NonUniqueObject::getStringArraySubobjectLocation(const LValue &LV) const {
+PotentialNonUniqueObject::getStringArraySubobjectLocation(
+    const LValue &LV) const {
   if (!getStringLiteralBase(LV.Base))
     return std::nullopt;
 
@@ -5246,8 +5249,8 @@ NonUniqueObject::getStringArraySubobjectLocation(const LValue &LV) const {
                                 CharUnits::fromQuantity(OffsetInElement)};
 }
 
-std::optional<NonUniqueObject::ArraySubobjectPathInfo>
-NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
+std::optional<PotentialNonUniqueObject::ArraySubobjectPathInfo>
+PotentialNonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
   const ASTContext &Ctx = Info.Ctx;
   if (LV.Designator.Invalid || LV.Designator.Entries.empty())
     return std::nullopt;
@@ -5264,7 +5267,7 @@ NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
       CharUnits OffsetInArray = LV.Offset - PathOffset;
       bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
       std::optional<ArraySubobjectLocation> EltLoc =
-          getArraySubobjectLocationImpl(Ctx, CAT, Index, OffsetInArray,
+          computeArraySubobjectLocation(Ctx, CAT, Index, OffsetInArray,
                                         IsValidOnePastEnd);
       if (EltLoc)
         Result = ArraySubobjectPathInfo{CAT, I, *EltLoc};
@@ -5312,7 +5315,7 @@ NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
   return Result;
 }
 
-bool NonUniqueObject::recognize(const LValue &LV) {
+bool PotentialNonUniqueObject::recognize(const LValue &LV) {
   ASTContext &Ctx = Info.Ctx;
 
   if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
@@ -5368,7 +5371,8 @@ bool NonUniqueObject::recognize(const LValue &LV) {
   return true;
 }
 
-bool NonUniqueObject::mayShareStorageWith(const NonUniqueObject &Other) const {
+bool PotentialNonUniqueObject::mayShareStorageWith(
+    const PotentialNonUniqueObject &Other) const {
   if (location().OffsetInElement != Other.location().OffsetInElement)
     return false;
   if (!Info.Ctx.hasSameUnqualifiedType(ArrayType->getElementType(),
@@ -5392,11 +5396,12 @@ bool NonUniqueObject::mayShareStorageWith(const NonUniqueObject &Other) const {
   return true;
 }
 
-bool NonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
-                                                const LValue &LHS,
-                                                const LValue &RHS) {
-  NonUniqueObject LHSObj(Info, E);
-  NonUniqueObject RHSObj(Info, E);
+bool PotentialNonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info,
+                                                         const Expr *E,
+                                                         const LValue &LHS,
+                                                         const LValue &RHS) {
+  PotentialNonUniqueObject LHSObj(Info, E);
+  PotentialNonUniqueObject RHSObj(Info, E);
   if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
     return false;
 
@@ -19625,8 +19630,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       //
       if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
         return DiagComparison(diag::note_constexpr_literal_comparison);
-      if (NonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
-                                                     RHSValue))
+      if (PotentialNonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
+                                                              RHSValue))
         return DiagComparison(
             diag::note_constexpr_non_unique_object_comparison);
       if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))

>From 152de2c5c65452ef01d894f6a838a5617e0b01be Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Sun, 12 Jul 2026 23:32:18 -0700
Subject: [PATCH 11/12] Reapply "[clang] Pad static initializer_list arrays for
 merging"

This reverts commit 73534cb551ae1ab95766a66fb823c5f9d20deed4.
---
 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 f5a54d7e584cf..ff44109837266 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"
@@ -7773,11 +7774,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 2ea0b5faf5a51..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 unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:.*]] = private unnamed_addr constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
-// AMDGCN: @[[REFTMP1:.*]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:.*]] = private unnamed_addr 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 e959670f244ec53cfee79c950b0421a78bac76e3 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Mon, 13 Jul 2026 11:01:57 -0700
Subject: [PATCH 12/12] [clang] Refine constant evaluator implementation

Signed-off-by: yronglin <yronglin777 at gmail.com>
---
 clang/lib/AST/ByteCode/Interp.cpp             | 444 +++++---------
 clang/lib/AST/ByteCode/Interp.h               |  42 +-
 clang/lib/AST/ExprConstShared.h               | 149 ++++-
 clang/lib/AST/ExprConstant.cpp                | 561 ++++++++----------
 clang/test/AST/ByteCode/cxx11.cpp             |   9 +
 clang/test/AST/ByteCode/initializer_list.cpp  |  47 ++
 clang/test/AST/ByteCode/objc.mm               |   7 +
 .../SemaCXX/constant-expression-cxx11.cpp     |  13 +
 8 files changed, 647 insertions(+), 625 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index db0ef677c1d67..c7d469d35b16e 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2805,57 +2805,6 @@ bool DiagTypeid(InterpState &S, CodePtr OpPC) {
   return false;
 }
 
-bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
-                                             const Pointer &RHS) {
-  if (!LHS.pointsToStringLiteral() || !RHS.pointsToStringLiteral())
-    return false;
-
-  unsigned LHSOffset = LHS.isOnePastEnd() ? LHS.getNumElems() : LHS.getIndex();
-  unsigned RHSOffset = RHS.isOnePastEnd() ? RHS.getNumElems() : RHS.getIndex();
-  const auto *LHSLit = cast<StringLiteral>(LHS.getDeclDesc()->asExpr());
-  const auto *RHSLit = cast<StringLiteral>(RHS.getDeclDesc()->asExpr());
-
-  StringRef LHSStr(LHSLit->getBytes());
-  unsigned LHSLength = LHSStr.size();
-  StringRef RHSStr(RHSLit->getBytes());
-  unsigned RHSLength = RHSStr.size();
-
-  int32_t IndexDiff = RHSOffset - LHSOffset;
-  if (IndexDiff < 0) {
-    if (static_cast<int32_t>(LHSLength) < -IndexDiff)
-      return false;
-    LHSStr = LHSStr.drop_front(-IndexDiff);
-  } else {
-    if (static_cast<int32_t>(RHSLength) < IndexDiff)
-      return false;
-    RHSStr = RHSStr.drop_front(IndexDiff);
-  }
-
-  unsigned ShorterCharWidth;
-  StringRef Shorter;
-  StringRef Longer;
-  if (LHSLength < RHSLength) {
-    ShorterCharWidth = LHS.getFieldDesc()->getElemDataSize();
-    Shorter = LHSStr;
-    Longer = RHSStr;
-  } else {
-    ShorterCharWidth = RHS.getFieldDesc()->getElemDataSize();
-    Shorter = RHSStr;
-    Longer = LHSStr;
-  }
-
-  // The null terminator isn't included in the string data, so check for it
-  // manually. If the longer string doesn't have a null terminator where the
-  // shorter string ends, they aren't potentially overlapping.
-  for (unsigned NullByte : llvm::seq(ShorterCharWidth)) {
-    if (Shorter.size() + NullByte >= Longer.size())
-      break;
-    if (Longer[Shorter.size() + NullByte])
-      return false;
-  }
-  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) {
@@ -2867,50 +2816,6 @@ static bool isInitializerListBackingArray(const Pointer &Ptr) {
   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 = computeArraySubobjectLocation(
-      Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
-  if (!Loc)
-    return std::nullopt;
-  return ArraySubobjectInfo{std::move(Array), *Loc};
-}
-
 /// 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())
@@ -2930,7 +2835,7 @@ static std::optional<Pointer> getContainingArray(const Pointer &Ptr) {
 
 /// 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) {
+static bool isPotentiallyNonUniqueArray(const Pointer &Array) {
   if (isInitializerListBackingArray(Array))
     return true;
   if (const auto *VD = Array.getDeclDesc()->asValueDecl())
@@ -2938,232 +2843,175 @@ static bool isPotentiallyNonUniqueAPValueArray(const Pointer &Array) {
   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;
+namespace {
+/// Bytecode evaluator adapter for PotentiallyNonUniqueObject. Pointer/PtrView
+/// traversal and APValue-path interpretation remain local to this evaluator.
+class ByteCodePotentiallyNonUniqueObject final
+    : public ::PotentiallyNonUniqueObject {
+public:
+  static std::optional<ByteCodePotentiallyNonUniqueObject>
+  create(InterpState &S, const Pointer &Ptr);
+
+private:
+  ByteCodePotentiallyNonUniqueObject(InterpState &S, Kind TheKind,
+                                     Pointer Array,
+                                     const ConstantArrayType *ArrayType,
+                                     ArraySubobjectLocation Loc)
+      : PotentiallyNonUniqueObject(S.getASTContext(), TheKind, ArrayType, Loc,
+                                   Array.getNumElems()),
+        S(S), Array(std::move(Array)) {}
+
+  ByteCodePotentiallyNonUniqueObject(InterpState &S, const Expr *StringBase,
+                                     CharUnits RawOffset, Pointer Array,
+                                     const ConstantArrayType *ArrayType,
+                                     ArraySubobjectLocation Loc)
+      : PotentiallyNonUniqueObject(S.getASTContext()), S(S),
+        Array(std::move(Array)) {
+    bool Recognized = setRecognizedStringObject(
+        StringBase, ArrayType, Loc, this->Array.getNumElems(), RawOffset);
+    assert(Recognized && "expected a string literal object");
+    (void)Recognized;
+  }
+
+  bool getArrayElement(uint64_t Index, APValue &Result) const override;
+
+  InterpState &S;
+  Pointer Array;
+};
+} // namespace
 
-  const auto *ArrayType =
-      Ctx.getAsConstantArrayType(Array->getFieldDesc()->getType());
-  if (!ArrayType)
+std::optional<ByteCodePotentiallyNonUniqueObject>
+ByteCodePotentiallyNonUniqueObject::create(InterpState &S, const Pointer &Ptr) {
+  const ASTContext &Ctx = S.getASTContext();
+  if (Ptr.isZero() || !Ptr.isBlockPointer())
     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;
+  const Expr *StringBase = Ptr.getDeclDesc()->asExpr();
+  if (!Ptr.block()->isDynamic() &&
+      isa_and_nonnull<StringLiteral, PredefinedExpr>(StringBase)) {
+    Pointer Array = Ptr.getDeclPtr();
+    const auto *ArrayType =
+        Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+    APValue PtrValue = Ptr.toAPValue(Ctx);
+    if (!ArrayType || !PtrValue.isLValue())
+      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])
+    std::optional<ArraySubobjectLocation> Loc;
+    if (PtrValue.hasLValuePath() && !PtrValue.getLValuePath().empty()) {
+      ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+      Loc = computeArraySubobjectLocation(
+          Ctx, ArrayType, Path.front().getAsArrayIndex(),
+          PtrValue.getLValueOffset(), Path.size() == 1);
+    } else {
+      Loc = computeArraySubobjectLocationFromOffset(Ctx, ArrayType,
+                                                    PtrValue.getLValueOffset());
+    }
+    if (!Loc)
       return std::nullopt;
+    return ByteCodePotentiallyNonUniqueObject(
+        S, StringBase, PtrValue.getLValueOffset(), std::move(Array), ArrayType,
+        *Loc);
   }
-  Path = Path.drop_front(ArrayPath.size());
-
-  uint64_t Index = Path.front().getAsArrayIndex();
-  bool IsValidOnePastEnd = Path.size() == 1;
-  std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
-      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;
+  if (Ptr.isArrayRoot() && isPotentiallyNonUniqueArray(Ptr)) {
+    Array = Ptr;
+  } else if (std::optional<Pointer> ContainingArray = getContainingArray(Ptr);
+             ContainingArray && isPotentiallyNonUniqueArray(*ContainingArray)) {
+    Array = std::move(*ContainingArray);
+  } else {
+    Pointer DeclArray = Ptr.getDeclPtr();
+    if (!Ctx.getAsConstantArrayType(DeclArray.getFieldDesc()->getType()) ||
+        !isPotentiallyNonUniqueArray(DeclArray))
+      return std::nullopt;
+    Array = std::move(DeclArray);
+  }
 
-  Pointer Array = Ptr.getDeclPtr();
   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 = computeArraySubobjectLocation(
-      Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
-  if (!Loc)
-    return std::nullopt;
-
-  return StringArraySubobjectInfo{
-      std::move(Array), *Loc, cast<StringLiteral>(Ptr.getDeclDesc()->asExpr())};
-}
+  ArraySubobjectLocation Loc{0, CharUnits::Zero()};
+  if (Ptr != Array) {
+    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());
+
+    std::optional<ArraySubobjectLocation> ComputedLoc =
+        computeArraySubobjectLocation(
+            Ctx, ArrayType, Path.front().getAsArrayIndex(),
+            PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
+            Path.size() == 1);
+    if (!ComputedLoc)
+      return std::nullopt;
+    Loc = *ComputedLoc;
+  }
 
-/// 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;
+  Kind TheKind = isInitializerListBackingArray(Array)
+                     ? Kind::InitializerList
+                     : Kind::TemplateParamObject;
+  return ByteCodePotentiallyNonUniqueObject(S, TheKind, std::move(Array),
+                                            ArrayType, Loc);
 }
 
-/// 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)
+bool ByteCodePotentiallyNonUniqueObject::getArrayElement(
+    uint64_t Index, APValue &Result) const {
+  std::optional<APValue> Value = Array.atIndex(Index).narrow().toRValue(
+      S.getContext(), arrayType()->getElementType());
+  if (!Value)
     return false;
-
-  if (!Ctx.hasSameUnqualifiedType(LHSElementType, RHSElementType))
-    return false;
-
-  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) {
-    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(*LHSElt, *RHSElt, Ctx))
-      return false;
-  }
-
+  Result = std::move(*Value);
   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) {
+PotentiallyNonUniqueObject::OverlapResult
+arePotentiallyOverlappingNonUniqueObjects(InterpState &S, const Pointer &LHS,
+                                          const Pointer &RHS) {
+  using OverlapResult = PotentiallyNonUniqueObject::OverlapResult;
   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());
-      });
-}
+  if (!LHS.isZero() && !RHS.isZero() && LHS.isBlockPointer() &&
+      RHS.isBlockPointer() && !LHS.block()->isDynamic() &&
+      !RHS.block()->isDynamic()) {
+    const Expr *LHSBase = LHS.getDeclDesc()->asExpr();
+    const Expr *RHSBase = RHS.getDeclDesc()->asExpr();
+    if (isa_and_nonnull<StringLiteral, PredefinedExpr, ObjCEncodeExpr>(
+            LHSBase) &&
+        isa_and_nonnull<StringLiteral, PredefinedExpr, ObjCEncodeExpr>(
+            RHSBase)) {
+      APValue LHSValue = LHS.toAPValue(Ctx);
+      APValue RHSValue = RHS.toAPValue(Ctx);
+      if (LHSValue.isLValue() && RHSValue.isLValue() &&
+          PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+              Ctx, LHSBase, LHSValue.getLValueOffset(), RHSBase,
+              RHSValue.getLValueOffset())
+              .value_or(false))
+        return OverlapResult::StringLiteral;
+    }
+  }
 
-/// 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);
+  std::optional<ByteCodePotentiallyNonUniqueObject> LHSObject =
+      ByteCodePotentiallyNonUniqueObject::create(S, LHS);
+  std::optional<ByteCodePotentiallyNonUniqueObject> RHSObject =
+      ByteCodePotentiallyNonUniqueObject::create(S, RHS);
+  if (!LHSObject || !RHSObject)
+    return OverlapResult::None;
+  if (!LHSObject->isPotentiallyOverlappingWith(*RHSObject))
+    return OverlapResult::None;
+  return LHSObject->isStringLiteral() && RHSObject->isStringLiteral()
+             ? OverlapResult::StringLiteral
+             : OverlapResult::NonUniqueObject;
 }
 
 static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 5b87a606c6d53..ecf548971b178 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1300,13 +1300,10 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
           Builtin == Builtin::BI__builtin_function_start);
 }
 
-bool arePotentiallyOverlappingStringLiterals(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);
+/// Classifies potentially overlapping string literals or non-unique objects.
+PotentiallyNonUniqueObject::OverlapResult
+arePotentiallyOverlappingNonUniqueObjects(InterpState &S, const Pointer &LHS,
+                                          const Pointer &RHS);
 
 template <>
 inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1351,21 +1348,6 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     return true;
   }
 
-  // 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)) {
-      const SourceInfo &Loc = S.Current->getSource(OpPC);
-      S.FFDiag(Loc, diag::note_constexpr_literal_comparison)
-          << LHS.toDiagnosticString(S.getASTContext())
-          << RHS.toDiagnosticString(S.getASTContext());
-      return false;
-    }
-  }
-
   if (Pointer::hasSameBase(LHS, RHS)) {
     std::optional<size_t> A = LHS.computeOffsetForComparison(S.getASTContext());
     std::optional<size_t> B = RHS.computeOffsetForComparison(S.getASTContext());
@@ -1376,11 +1358,25 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     return true;
   }
 
-  if (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
+  // 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.
+  switch (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
+  case PotentiallyNonUniqueObject::OverlapResult::StringLiteral: {
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_literal_comparison)
+        << LHS.toDiagnosticString(S.getASTContext())
+        << RHS.toDiagnosticString(S.getASTContext());
+    return false;
+  }
+  case PotentiallyNonUniqueObject::OverlapResult::NonUniqueObject: {
     const SourceInfo &Loc = S.Current->getSource(OpPC);
     S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
     return false;
   }
+  case PotentiallyNonUniqueObject::OverlapResult::None:
+    break;
+  }
 
   // Otherwise we need to do a bunch of extra checks before returning Unordered.
   if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index dd1043e83f574..17d3c7cf18be4 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -16,8 +16,12 @@
 
 #include "clang/AST/CharUnits.h"
 #include "clang/Basic/TypeTraits.h"
+#include <cassert>
 #include <cstdint>
 #include <optional>
+#include <string>
+#include <utility>
+#include <variant>
 
 namespace llvm {
 class APFloat;
@@ -32,6 +36,7 @@ class APValue;
 class ConstantArrayType;
 class Expr;
 class CallExpr;
+class StringLiteral;
 } // namespace clang
 using namespace clang;
 /// Values returned by __builtin_classify_type, chosen to match the values
@@ -100,12 +105,6 @@ unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
                                         unsigned BuiltinID);
 unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
                                         const CallExpr *E);
-/// 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,
@@ -136,4 +135,142 @@ std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
     const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
     CharUnits LValueOffset, bool IsValidOnePastEnd);
 
+/// A potentially-non-unique array object: a string literal, an
+/// std::initializer_list backing array, or an array subobject of a
+/// template-parameter object (NTTP). [intro.object]/9 lets the implementation
+/// merge two such objects when their overlapping contents agree, which is what
+/// makes their address comparisons non-constant.
+///
+/// This abstract class is an evaluator-independent view of an object that
+/// [intro.object] permits an implementation to give the same address as another
+/// potentially non-unique object. Evaluators provide their own object
+/// recognition, subobject-location logic, and element materialization through
+/// derived classes.
+class PotentiallyNonUniqueObject {
+public:
+  enum class Kind { StringLiteral, InitializerList, TemplateParamObject };
+  enum class OverlapResult { None, StringLiteral, NonUniqueObject };
+
+  virtual ~PotentiallyNonUniqueObject() = default;
+
+  Kind kind() const {
+    assert(ArrayType && "object has not been recognized");
+    return TheKind;
+  }
+  bool isStringLiteral() const { return String.has_value(); }
+  const ConstantArrayType *arrayType() const { return ArrayType; }
+  const ArraySubobjectLocation &location() const { return Loc; }
+  uint64_t size() const { return Size; }
+  bool empty() const { return Size == 0; }
+
+  /// Determine whether two expressions denote string-like objects whose
+  /// storage can overlap at the given byte offsets. Returns std::nullopt if
+  /// either expression is not a string literal, predefined function-name
+  /// string, or Objective-C encoding.
+  static std::optional<bool>
+  isPotentiallyOverlappingStrings(const ASTContext &Ctx, const Expr *LHSBase,
+                                  CharUnits LHSOffset, const Expr *RHSBase,
+                                  CharUnits RHSOffset);
+
+  /// Returns true if this object and \p Other can share storage at their
+  /// designated positions. Missing element data is treated conservatively as
+  /// a possible match.
+  bool
+  isPotentiallyOverlappingWith(const PotentiallyNonUniqueObject &Other) const;
+
+protected:
+  explicit PotentiallyNonUniqueObject(const ASTContext &Ctx) : Ctx(Ctx) {}
+
+  /// Computes an array-element location from only a byte offset relative to
+  /// the array. This is used by evaluator adapters whose lvalue representation
+  /// does not carry an explicit path for the designated string subobject.
+  static std::optional<ArraySubobjectLocation>
+  computeArraySubobjectLocationFromOffset(const ASTContext &Ctx,
+                                          const ConstantArrayType *ArrayType,
+                                          CharUnits LValueOffset);
+
+  PotentiallyNonUniqueObject(const ASTContext &Ctx, Kind TheKind,
+                             const ConstantArrayType *ArrayType,
+                             ArraySubobjectLocation Loc, uint64_t Size)
+      : Ctx(Ctx) {
+    setRecognizedObject(TheKind, ArrayType, Loc, Size);
+  }
+
+  void setRecognizedObject(Kind NewKind, const ConstantArrayType *NewArrayType,
+                           ArraySubobjectLocation NewLoc, uint64_t NewSize) {
+    assert(NewArrayType && !ArrayType && "object must be recognized once");
+    assert((NewKind == Kind::StringLiteral) == String.has_value() &&
+           "string kind and storage disagree");
+    TheKind = NewKind;
+    ArrayType = NewArrayType;
+    Loc = NewLoc;
+    Size = NewSize;
+  }
+
+  /// Recognize Base as a StringLiteral or PredefinedExpr and install its
+  /// borrowed AST storage together with the evaluator-computed array view.
+  /// Objective-C encodings are intentionally limited to
+  /// isPotentiallyOverlappingStrings; they are not exposed as generic array
+  /// objects here.
+  bool setRecognizedStringObject(const Expr *Base,
+                                 const ConstantArrayType *NewArrayType,
+                                 ArraySubobjectLocation NewLoc,
+                                 uint64_t NewSize, CharUnits RawOffset);
+
+  /// Materialize an array element from the evaluator's storage representation.
+  /// Returns false if the representation cannot expose enough data to prove
+  /// the element's value.
+  virtual bool getArrayElement(uint64_t Index, APValue &Result) const = 0;
+
+  const ASTContext &Ctx;
+
+private:
+  /// Owns string-like bytes synthesized during normalization (currently for
+  /// ObjCEncodeExpr), together with their code-unit width in bytes.
+  struct OwnedString {
+    std::string Bytes;
+    unsigned CharWidth;
+  };
+
+  /// Normalized storage and location for a string-like object. StringLiteral
+  /// storage is borrowed for the ASTContext lifetime, while synthesized bytes
+  /// are owned. Offset is the designated pointer's byte offset from the string
+  /// base; it remains separate from the evaluator-specific array location so
+  /// strings with different code-unit widths can be compared.
+  struct StringData {
+    StringData(const StringLiteral *Literal, CharUnits Offset)
+        : Storage(Literal), Offset(Offset) {}
+    StringData(std::string Bytes, unsigned CharWidth, CharUnits Offset)
+        : Storage(OwnedString{std::move(Bytes), CharWidth}), Offset(Offset) {}
+
+    std::variant<const StringLiteral *, OwnedString> Storage;
+    CharUnits Offset;
+  };
+
+  static std::optional<StringData>
+  getStringData(const ASTContext &Ctx, const Expr *Base, CharUnits Offset);
+  static bool isPotentiallyOverlappingStrings(const StringData &LHS,
+                                              const StringData &RHS);
+
+  /// Whether two APValues could be merged into a single storage location by
+  /// the implementation. The slow path recursively handles values whose
+  /// profiles differ.
+  static bool areAPValuesPotentiallyMergeable(const APValue &LHS,
+                                              const APValue &RHS,
+                                              const ASTContext &Ctx);
+  static bool areAPValuesPotentiallyMergeableSlow(const APValue &LHS,
+                                                  const APValue &RHS,
+                                                  const ASTContext &Ctx);
+
+  /// Materialize element Index, handling strings in the shared base before
+  /// delegating evaluator-backed objects to getArrayElement.
+  bool getElement(uint64_t Index, APValue &Result) const;
+
+  Kind TheKind = Kind::StringLiteral;
+  const ConstantArrayType *ArrayType = nullptr;
+  ArraySubobjectLocation Loc{0, CharUnits::Zero()};
+  uint64_t Size = 0;
+  std::optional<StringData> String;
+};
+
 #endif // LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 4fe8275e27b30..49e5c6f5e7dd9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2014,130 +2014,6 @@ static const ValueDecl *GetLValueBaseDecl(const LValue &LVal) {
   return LVal.Base.dyn_cast<const ValueDecl*>();
 }
 
-// Information about an LValueBase that is some kind of string.
-struct LValueBaseString {
-  std::string ObjCEncodeStorage;
-  StringRef Bytes;
-  int CharWidth;
-};
-
-// Gets the lvalue base as a string.
-static bool GetLValueBaseAsString(const ASTContext &Ctx,
-                                  APValue::LValueBase Base,
-                                  LValueBaseString &AsString) {
-  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)) {
-    Ctx.getObjCEncodingForType(EE->getEncodedType(),
-                               AsString.ObjCEncodeStorage);
-    AsString.Bytes = AsString.ObjCEncodeStorage;
-    AsString.CharWidth = 1;
-    return true;
-  }
-
-  // Otherwise, we have a StringLiteral.
-  const auto *Lit = dyn_cast<StringLiteral>(BaseExpr);
-  if (const auto *PE = dyn_cast<PredefinedExpr>(BaseExpr))
-    Lit = PE->getFunctionName();
-
-  if (!Lit)
-    return false;
-
-  AsString.Bytes = Lit->getBytes();
-  AsString.CharWidth = Lit->getCharByteWidth();
-  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.
-//
-// The overlapping region is the portion of the two string literals that must
-// overlap in memory if the pointers actually point to the same address at
-// runtime. For example, if LHS is "abcdef" + 3 and RHS is "cdef\0gh" + 1 then
-// the overlapping region is "cdef\0", which in this case does agree, so the
-// strings are potentially overlapping. Conversely, for "foobar" + 3 versus
-// "bazbar" + 3, the overlapping region contains all of both strings, so they
-// are not potentially overlapping, even though they agree from the given
-// addresses onwards.
-//
-// See open core issue CWG2765 which is discussing the desired rule here.
-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.
-  StringRef LHSBytes = LHSString.Bytes;
-  StringRef RHSBytes = RHSString.Bytes;
-  CharUnits Offset = RHSOffset - LHSOffset;
-  if (Offset.isNegative()) {
-    if (LHSBytes.size() < (size_t)-Offset.getQuantity())
-      return false;
-    LHSBytes = LHSBytes.drop_front(-Offset.getQuantity());
-  } else {
-    if (RHSBytes.size() < (size_t)Offset.getQuantity())
-      return false;
-    RHSBytes = RHSBytes.drop_front(Offset.getQuantity());
-  }
-
-  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
-  // manually. If the longer string doesn't have a null terminator where the
-  // shorter string ends, they aren't potentially overlapping.
-  for (int NullByte : llvm::seq(ShorterCharWidth)) {
-    if (Shorter.size() + NullByte >= Longer.size())
-      break;
-    if (Longer[Shorter.size() + NullByte])
-      return false;
-  }
-
-  // Otherwise, they're potentially overlapping if and only if the overlapping
-  // region is the same.
-  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();
@@ -3559,31 +3435,30 @@ static unsigned getBaseIndex(const CXXRecordDecl *Derived,
 }
 
 /// Extract the value of a character from a string literal.
-static APSInt extractStringLiteralCharacter(EvalInfo &Info, const Expr *Lit,
-                                            uint64_t Index) {
+static APSInt extractStringLiteralCharacter(const ASTContext &Ctx,
+                                            const Expr *Lit, uint64_t Index) {
   assert(!isa<SourceLocExpr>(Lit) &&
          "SourceLocExpr should have already been converted to a StringLiteral");
 
   // FIXME: Support MakeStringConstant
   if (const auto *ObjCEnc = dyn_cast<ObjCEncodeExpr>(Lit)) {
     std::string Str;
-    Info.Ctx.getObjCEncodingForType(ObjCEnc->getEncodedType(), Str);
+    Ctx.getObjCEncodingForType(ObjCEnc->getEncodedType(), Str);
     assert(Index <= Str.size() && "Index too large");
     return APSInt::getUnsigned(Str.c_str()[Index]);
   }
 
   if (auto PE = dyn_cast<PredefinedExpr>(Lit))
     Lit = PE->getFunctionName();
-  const StringLiteral *S = cast<StringLiteral>(Lit);
-  const ConstantArrayType *CAT =
-      Info.Ctx.getAsConstantArrayType(S->getType());
-  assert(CAT && "string literal isn't an array");
-  QualType CharType = CAT->getElementType();
+  const auto *Literal = cast<StringLiteral>(Lit);
+  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(Info.Ctx.getTypeSize(CharType),
-               CharType->isUnsignedIntegerType());
-  if (Index < S->getLength())
-    Value = S->getCodeUnit(Index);
+  APSInt Value(Ctx.getTypeSize(CharType), CharType->isUnsignedIntegerType());
+  if (Index < Literal->getLength())
+    Value = Literal->getCodeUnit(Index);
   return Value;
 }
 
@@ -4916,15 +4791,13 @@ static bool isWeakMemberPointer(const APValue &V) {
   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) {
+/// areAPValuesPotentiallyMergeableSlow - Slow path for
+/// areAPValuesPotentiallyMergeable. This is invoked when the two APValue
+/// profiles differ. Returns true if the disagreement is one the runtime can
+/// collapse, recursing into aggregate kinds so aggregates containing such
+/// leaves are still reported as mergeable.
+bool PotentiallyNonUniqueObject::areAPValuesPotentiallyMergeableSlow(
+    const APValue &LHS, const APValue &RHS, const ASTContext &Ctx) {
   if (!LHS.hasValue() || !RHS.hasValue())
     return true;
   if (LHS.getKind() != RHS.getKind())
@@ -4933,7 +4806,10 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
   switch (LHS.getKind()) {
   case APValue::LValue: {
     // Distinct string-literal LValues whose content could be merged.
-    if (auto Overlap = ArePotentiallyOverlappingStringLiterals(Ctx, LHS, RHS))
+    if (auto Overlap = isPotentiallyOverlappingStrings(
+            Ctx, LHS.getLValueBase().dyn_cast<const Expr *>(),
+            LHS.getLValueOffset(), RHS.getLValueBase().dyn_cast<const Expr *>(),
+            RHS.getLValueOffset()))
       return *Overlap;
     // Either side names a weak entity -> link-time may resolve to one.
     return isWeakLValueBase(LHS) || isWeakLValueBase(RHS);
@@ -4947,11 +4823,11 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
         LHS.getStructNumFields() != RHS.getStructNumFields())
       return false;
     for (unsigned I = 0, N = LHS.getStructNumBases(); I != N; ++I)
-      if (!AreAPValuesPotentiallyMergeable(LHS.getStructBase(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),
+      if (!areAPValuesPotentiallyMergeable(LHS.getStructField(I),
                                            RHS.getStructField(I), Ctx))
         return false;
     return true;
@@ -4962,7 +4838,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
       return false;
     if (!LHS.getUnionField())
       return true;
-    return AreAPValuesPotentiallyMergeable(LHS.getUnionValue(),
+    return areAPValuesPotentiallyMergeable(LHS.getUnionValue(),
                                            RHS.getUnionValue(), Ctx);
   }
 
@@ -4975,7 +4851,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
       // Missing element data: be conservative.
       if (!LE || !RE)
         return true;
-      if (!AreAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
+      if (!areAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
         return false;
     }
     return true;
@@ -4985,7 +4861,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
     if (LHS.getVectorLength() != RHS.getVectorLength())
       return false;
     for (unsigned I = 0, N = LHS.getVectorLength(); I != N; ++I)
-      if (!AreAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
+      if (!areAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
                                            RHS.getVectorElt(I), Ctx))
         return false;
     return true;
@@ -4996,7 +4872,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
         LHS.getMatrixNumColumns() != RHS.getMatrixNumColumns())
       return false;
     for (unsigned I = 0, N = LHS.getMatrixNumElements(); I != N; ++I)
-      if (!AreAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
+      if (!areAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
                                            RHS.getMatrixElt(I), Ctx))
         return false;
     return true;
@@ -5012,8 +4888,8 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
   }
 }
 
-bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
-                                     const ASTContext &Ctx) {
+bool PotentiallyNonUniqueObject::areAPValuesPotentiallyMergeable(
+    const APValue &LHS, const APValue &RHS, const ASTContext &Ctx) {
   // Fast path: bitwise-identical APValues -> definitely mergeable.
   llvm::FoldingSetNodeID LHSID, RHSID;
   LHS.Profile(LHSID);
@@ -5021,7 +4897,7 @@ bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
   if (LHSID == RHSID)
     return true;
 
-  return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
+  return areAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
 }
 
 std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
@@ -5070,28 +4946,42 @@ std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
                                 CharUnits::fromQuantity(OffsetInElement)};
 }
 
+std::optional<ArraySubobjectLocation>
+PotentiallyNonUniqueObject::computeArraySubobjectLocationFromOffset(
+    const ASTContext &Ctx, const ConstantArrayType *ArrayType,
+    CharUnits LValueOffset) {
+  if (LValueOffset.isNegative())
+    return std::nullopt;
+
+  uint64_t ElementSize =
+      Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+  if (!ElementSize)
+    return std::nullopt;
+
+  uint64_t Offset = LValueOffset.getQuantity();
+  uint64_t Index = Offset / ElementSize;
+  uint64_t OffsetInElement = Offset % ElementSize;
+  uint64_t ArraySize = ArrayType->getZExtSize();
+  if (Index > ArraySize || (Index == ArraySize && OffsetInElement != 0))
+    return std::nullopt;
+
+  return ArraySubobjectLocation{Index,
+                                CharUnits::fromQuantity(OffsetInElement)};
+}
+
 namespace {
-/// A potentially-non-unique array object: a string literal, an
-/// initializer_list backing array, or an array subobject of a
-/// template-parameter object (NTTP). [intro.object]/9 lets the implementation
-/// merge two such objects when their overlapping contents agree, which is what
-/// makes their address comparisons non-constant.
-///
-/// The kind is recorded explicitly; the InitializerList and TemplateParamObject
-/// kinds are both read through an APValue array (borrowed and owned,
-/// respectively), while the StringLiteral kind materializes characters lazily.
-/// The single public entry point is arePotentiallyOverlapping().
-class PotentialNonUniqueObject {
+/// Legacy constant evaluator adapter for PotentiallyNonUniqueObject. LValue
+/// recognition and subobject location stay evaluator-specific; storage overlap
+/// and element comparison are implemented by the shared base class.
+class ExprConstantPotentiallyNonUniqueObject final
+    : public ::PotentiallyNonUniqueObject {
 public:
-  /// Returns true when two lvalues designate potentially non-unique arrays
-  /// whose overlapping elements could be merged into the same storage, making
-  /// their address comparison non-constant.
-  static bool arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
-                                        const LValue &LHS, const LValue &RHS);
+  /// Classifies potentially overlapping string literals or non-unique arrays.
+  static OverlapResult arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
+                                                 const LValue &LHS,
+                                                 const LValue &RHS);
 
 private:
-  enum class Kind { StringLiteral, InitializerList, TemplateParamObject };
-
   /// Identifies an enclosing constant array subobject within an lvalue path.
   struct ArraySubobjectPathInfo {
     const ConstantArrayType *ArrayType = nullptr;
@@ -5101,64 +4991,38 @@ class PotentialNonUniqueObject {
 
   /// The evaluation state and diagnostic expression are stored so the member
   /// functions need not thread them through every call.
-  PotentialNonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
+  ExprConstantPotentiallyNonUniqueObject(EvalInfo &Info, const Expr *E)
+      : PotentiallyNonUniqueObject(Info.Ctx), Info(Info), E(E) {}
 
   /// Recognizes the non-unique array LV designates, filling in this object and
   /// returning true, or returning false when LV is not such an object.
   bool recognize(const LValue &LV);
 
-  /// Returns true when *this and Other could be merged into the same storage at
-  /// their designated positions: the positions must share an in-element byte
-  /// offset and element-compatible types, and every element the overlap would
-  /// alias must be pairwise mergeable. Element data the representation cannot
-  /// expose is treated conservatively as a possible match.
-  bool mayShareStorageWith(const PotentialNonUniqueObject &Other) const;
-
   // Lvalue classification helpers used by recognize().
 
-  /// Returns the underlying string literal expression for bases that denote
-  /// string literal objects in the evaluator.
-  static const Expr *getStringLiteralBase(APValue::LValueBase Base);
   /// Whether LV's base is an initializer_list backing array.
   static bool isInitializerListBackingArray(const LValue &LV);
+
   /// Reads the complete object value an lvalue's base denotes.
   const APValue *getCompleteObjectValue(const LValue &LV) const;
+
   /// 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.
   std::optional<ArraySubobjectLocation>
   getArraySubobjectLocation(const LValue &LV) const;
+
   /// Returns the string-literal array element and element-relative offset
   /// designated by LV, including byte-offset-only lvalues.
   std::optional<ArraySubobjectLocation>
   getStringArraySubobjectLocation(const LValue &LV) const;
+
   /// Finds the innermost enclosing constant array subobject for an lvalue into
   /// a template parameter object.
   std::optional<ArraySubobjectPathInfo>
   getArraySubobjectPathInfo(const LValue &LV) const;
 
-  bool isStringLiteral() const { return TheKind == Kind::StringLiteral; }
-  const ArraySubobjectLocation &location() const { return Loc; }
-
-  /// Number of elements visible through this view.
-  int64_t size() const {
-    if (TheKind == Kind::StringLiteral)
-      return ArrayType->getZExtSize();
-    return arrayValue()->getArraySize();
-  }
-
-  /// Value of the element at Index, or std::nullopt when the representation
-  /// cannot expose enough data to prove it differs.
-  std::optional<APValue> operator[](int64_t Index) const {
-    if (Index < 0)
-      return std::nullopt;
-    uint64_t UIndex = static_cast<uint64_t>(Index);
-    if (TheKind == Kind::StringLiteral)
-      return APValue(extractStringLiteralCharacter(Info, StringExpr, UIndex));
-    if (const APValue *Elt = GetArrayInitializedElt(*arrayValue(), UIndex))
-      return *Elt;
-    return std::nullopt;
-  }
+  bool getArrayElement(uint64_t Index, APValue &Result) const override;
 
   /// Returns the APValue array for the InitializerList / TemplateParamObject
   /// kinds (borrowed or owned, respectively).
@@ -5170,38 +5034,168 @@ class PotentialNonUniqueObject {
 
   EvalInfo &Info;
   const Expr *E;
-  Kind TheKind = Kind::StringLiteral;
-  const ConstantArrayType *ArrayType = nullptr;
-  ArraySubobjectLocation Loc;
-  const Expr *StringExpr = nullptr;       // Kind::StringLiteral
   const APValue *ArrayValue = nullptr;    // Kind::InitializerList (borrowed)
   std::optional<APValue> OwnedArrayValue; // Kind::TemplateParamObject (owned)
 };
 } // namespace
 
-const Expr *
-PotentialNonUniqueObject::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<PotentiallyNonUniqueObject::StringData>
+PotentiallyNonUniqueObject::getStringData(const ASTContext &Ctx,
+                                          const Expr *Base, CharUnits Offset) {
+  if (!Base)
+    return std::nullopt;
+  if (const auto *EE = dyn_cast<ObjCEncodeExpr>(Base)) {
+    std::string Bytes;
+    Ctx.getObjCEncodingForType(EE->getEncodedType(), Bytes);
+    return StringData(std::move(Bytes), 1, Offset);
+  }
+  if (const auto *PE = dyn_cast<PredefinedExpr>(Base))
+    Base = PE->getFunctionName();
+  if (const auto *Literal = dyn_cast<StringLiteral>(Base))
+    return StringData(Literal, Offset);
+  return std::nullopt;
 }
 
-bool PotentialNonUniqueObject::isInitializerListBackingArray(const LValue &LV) {
+bool PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+    const StringData &LHS, const StringData &RHS) {
+  auto GetBytes = [](const StringData &String) -> StringRef {
+    if (const auto *Literal =
+            std::get_if<const StringLiteral *>(&String.Storage))
+      return (*Literal)->getBytes();
+    return std::get<OwnedString>(String.Storage).Bytes;
+  };
+  auto GetCharWidth = [](const StringData &String) {
+    if (const auto *Literal =
+            std::get_if<const StringLiteral *>(&String.Storage))
+      return (*Literal)->getCharByteWidth();
+    return std::get<OwnedString>(String.Storage).CharWidth;
+  };
+
+  StringRef LHSBytes = GetBytes(LHS);
+  StringRef RHSBytes = GetBytes(RHS);
+
+  // Bytes preceding the other string cannot be part of a merged overlapping
+  // region. Compute the distance without signed subtraction so even extreme
+  // offsets cannot overflow.
+  int64_t LHSOffset = LHS.Offset.getQuantity();
+  int64_t RHSOffset = RHS.Offset.getQuantity();
+  if (LHSOffset > RHSOffset) {
+    uint64_t Distance =
+        static_cast<uint64_t>(LHSOffset) - static_cast<uint64_t>(RHSOffset);
+    if (Distance > LHSBytes.size())
+      return false;
+    LHSBytes = LHSBytes.drop_front(static_cast<size_t>(Distance));
+  } else {
+    uint64_t Distance =
+        static_cast<uint64_t>(RHSOffset) - static_cast<uint64_t>(LHSOffset);
+    if (Distance > RHSBytes.size())
+      return false;
+    RHSBytes = RHSBytes.drop_front(static_cast<size_t>(Distance));
+  }
+
+  bool LHSIsLonger = LHSBytes.size() > RHSBytes.size();
+  StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes;
+  StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes;
+  unsigned ShorterCharWidth = GetCharWidth(LHSIsLonger ? RHS : LHS);
+
+  // StringLiteral::getBytes() omits the null terminator. Verify that the
+  // longer string has an all-zero code unit where the shorter string ends.
+  for (unsigned NullByte : llvm::seq(ShorterCharWidth)) {
+    if (Shorter.size() + NullByte >= Longer.size())
+      break;
+    if (Longer[Shorter.size() + NullByte])
+      return false;
+  }
+
+  return Shorter == Longer.take_front(Shorter.size());
+}
+
+std::optional<bool> PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+    const ASTContext &Ctx, const Expr *LHSBase, CharUnits LHSOffset,
+    const Expr *RHSBase, CharUnits RHSOffset) {
+  std::optional<StringData> LHS = getStringData(Ctx, LHSBase, LHSOffset);
+  std::optional<StringData> RHS = getStringData(Ctx, RHSBase, RHSOffset);
+  if (!LHS || !RHS)
+    return std::nullopt;
+  return isPotentiallyOverlappingStrings(*LHS, *RHS);
+}
+
+bool PotentiallyNonUniqueObject::setRecognizedStringObject(
+    const Expr *Base, const ConstantArrayType *NewArrayType,
+    ArraySubobjectLocation NewLoc, uint64_t NewSize, CharUnits RawOffset) {
+  if (const auto *PE = dyn_cast_or_null<PredefinedExpr>(Base))
+    Base = PE->getFunctionName();
+  const auto *Literal = dyn_cast_or_null<StringLiteral>(Base);
+  if (!Literal)
+    return false;
+
+  assert(!String && "string object must be recognized once");
+  String.emplace(Literal, RawOffset);
+  setRecognizedObject(Kind::StringLiteral, NewArrayType, NewLoc, NewSize);
+  return true;
+}
+
+bool PotentiallyNonUniqueObject::getElement(uint64_t Index,
+                                            APValue &Result) const {
+  if (!isStringLiteral())
+    return getArrayElement(Index, Result);
+
+  assert(String && "missing string storage");
+  const auto *Literal = std::get_if<const StringLiteral *>(&String->Storage);
+  assert(Literal && *Literal && "generic string object must be an AST literal");
+  Result = APValue(extractStringLiteralCharacter(Ctx, *Literal, Index));
+  return true;
+}
+
+bool PotentiallyNonUniqueObject::isPotentiallyOverlappingWith(
+    const PotentiallyNonUniqueObject &Other) const {
+  assert(arrayType() && Other.arrayType() && "unrecognized object");
+  assert(&Ctx == &Other.Ctx && "objects belong to different AST contexts");
+  if (isStringLiteral() && Other.isStringLiteral())
+    return isPotentiallyOverlappingStrings(*String, *Other.String);
+  if (location().OffsetInElement != Other.location().OffsetInElement)
+    return false;
+  if (!Ctx.hasSameUnqualifiedType(arrayType()->getElementType(),
+                                  Other.arrayType()->getElementType()))
+    return false;
+
+  uint64_t LHSStart = 0, RHSStart = 0;
+  if (location().Index >= Other.location().Index)
+    LHSStart = location().Index - Other.location().Index;
+  else
+    RHSStart = Other.location().Index - location().Index;
+
+  if (LHSStart >= size() || RHSStart >= Other.size())
+    return false;
+  uint64_t Count = std::min(size() - LHSStart, Other.size() - RHSStart);
+  for (uint64_t I = 0; I != Count; ++I) {
+    APValue LHSElement, RHSElement;
+    if (!getElement(LHSStart + I, LHSElement) ||
+        !Other.getElement(RHSStart + I, RHSElement))
+      return true;
+    if (!areAPValuesPotentiallyMergeable(LHSElement, RHSElement, Ctx))
+      return false;
+  }
+  return true;
+}
+
+bool ExprConstantPotentiallyNonUniqueObject::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();
 }
 
-const APValue *
-PotentialNonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
+const APValue *ExprConstantPotentiallyNonUniqueObject::getCompleteObjectValue(
+    const LValue &LV) const {
   CompleteObject Obj =
       findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
   return Obj ? Obj.Value : nullptr;
 }
 
 std::optional<ArraySubobjectLocation>
-PotentialNonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
+ExprConstantPotentiallyNonUniqueObject::getArraySubobjectLocation(
+    const LValue &LV) const {
   if (LV.Designator.Invalid || LV.Designator.Entries.empty())
     return std::nullopt;
 
@@ -5217,40 +5211,21 @@ PotentialNonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
 }
 
 std::optional<ArraySubobjectLocation>
-PotentialNonUniqueObject::getStringArraySubobjectLocation(
+ExprConstantPotentiallyNonUniqueObject::getStringArraySubobjectLocation(
     const LValue &LV) const {
-  if (!getStringLiteralBase(LV.Base))
-    return std::nullopt;
-
   if (std::optional<ArraySubobjectLocation> EltLoc =
           getArraySubobjectLocation(LV))
     return EltLoc;
 
   const auto *CAT = Info.Ctx.getAsConstantArrayType(getType(LV.Base));
-  if (!CAT || LV.Offset.isNegative())
-    return std::nullopt;
-
-  uint64_t ElementSize =
-      Info.Ctx.getTypeSizeInChars(CAT->getElementType()).getQuantity();
-  if (!ElementSize)
-    return std::nullopt;
-
-  uint64_t Offset = LV.Offset.getQuantity();
-  uint64_t Index = Offset / ElementSize;
-  uint64_t OffsetInElement = Offset % ElementSize;
-  uint64_t ArraySize = CAT->getZExtSize();
-  // Accept element and subobject positions, plus the single one-past-the-end
-  // byte {ArraySize, 0}. Reject anything beyond that, including an offset that
-  // lands past the one-past-the-end byte with a nonzero in-element remainder.
-  if (Index > ArraySize || (Index == ArraySize && OffsetInElement != 0))
+  if (!CAT)
     return std::nullopt;
-
-  return ArraySubobjectLocation{Index,
-                                CharUnits::fromQuantity(OffsetInElement)};
+  return computeArraySubobjectLocationFromOffset(Info.Ctx, CAT, LV.Offset);
 }
 
-std::optional<PotentialNonUniqueObject::ArraySubobjectPathInfo>
-PotentialNonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
+std::optional<ExprConstantPotentiallyNonUniqueObject::ArraySubobjectPathInfo>
+ExprConstantPotentiallyNonUniqueObject::getArraySubobjectPathInfo(
+    const LValue &LV) const {
   const ASTContext &Ctx = Info.Ctx;
   if (LV.Designator.Invalid || LV.Designator.Entries.empty())
     return std::nullopt;
@@ -5315,20 +5290,17 @@ PotentialNonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
   return Result;
 }
 
-bool PotentialNonUniqueObject::recognize(const LValue &LV) {
+bool ExprConstantPotentiallyNonUniqueObject::recognize(const LValue &LV) {
   ASTContext &Ctx = Info.Ctx;
 
-  if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+  if (const Expr *StringBase = LV.Base.dyn_cast<const Expr *>()) {
     std::optional<ArraySubobjectLocation> EltLoc =
         getStringArraySubobjectLocation(LV);
     const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
-    if (!EltLoc || !CAT)
-      return false;
-    TheKind = Kind::StringLiteral;
-    ArrayType = CAT;
-    Loc = *EltLoc;
-    StringExpr = StringBase;
-    return true;
+    if (EltLoc && CAT &&
+        setRecognizedStringObject(StringBase, CAT, *EltLoc, CAT->getZExtSize(),
+                                  LV.Offset))
+      return true;
   }
 
   if (isInitializerListBackingArray(LV)) {
@@ -5338,9 +5310,8 @@ bool PotentialNonUniqueObject::recognize(const LValue &LV) {
     const APValue *ArrVal = getCompleteObjectValue(LV);
     if (!EltLoc || !CAT || !ArrVal || !ArrVal->isArray())
       return false;
-    TheKind = Kind::InitializerList;
-    ArrayType = CAT;
-    Loc = *EltLoc;
+    setRecognizedObject(Kind::InitializerList, CAT, *EltLoc,
+                        ArrVal->getArraySize());
     ArrayValue = ArrVal;
     return true;
   }
@@ -5364,52 +5335,40 @@ bool PotentialNonUniqueObject::recognize(const LValue &LV) {
     return false;
   if (!ArrVal.isArray())
     return false;
-  TheKind = Kind::TemplateParamObject;
-  ArrayType = PathInfo->ArrayType;
-  Loc = PathInfo->Loc;
+  setRecognizedObject(Kind::TemplateParamObject, PathInfo->ArrayType,
+                      PathInfo->Loc, ArrVal.getArraySize());
   OwnedArrayValue = std::move(ArrVal);
   return true;
 }
 
-bool PotentialNonUniqueObject::mayShareStorageWith(
-    const PotentialNonUniqueObject &Other) const {
-  if (location().OffsetInElement != Other.location().OffsetInElement)
-    return false;
-  if (!Info.Ctx.hasSameUnqualifiedType(ArrayType->getElementType(),
-                                       Other.ArrayType->getElementType()))
-    return false;
-
-  int64_t LHSStart = location().Index, RHSStart = Other.location().Index;
-  int64_t Begin = std::max(-LHSStart, -RHSStart);
-  int64_t End = std::min(size() - LHSStart, Other.size() - RHSStart);
-  if (Begin >= End)
+bool ExprConstantPotentiallyNonUniqueObject::getArrayElement(
+    uint64_t Index, APValue &Result) const {
+  const APValue *Element = GetArrayInitializedElt(*arrayValue(), Index);
+  if (!Element)
     return false;
-
-  for (int64_t I = Begin; I != End; ++I) {
-    std::optional<APValue> LHSElt = (*this)[I + LHSStart];
-    std::optional<APValue> RHSElt = Other[I + RHSStart];
-    if (!LHSElt || !RHSElt)
-      return true;
-    if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
-      return false;
-  }
+  Result = *Element;
   return true;
 }
 
-bool PotentialNonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info,
-                                                         const Expr *E,
-                                                         const LValue &LHS,
-                                                         const LValue &RHS) {
-  PotentialNonUniqueObject LHSObj(Info, E);
-  PotentialNonUniqueObject RHSObj(Info, E);
-  if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
-    return false;
+PotentiallyNonUniqueObject::OverlapResult
+ExprConstantPotentiallyNonUniqueObject::arePotentiallyOverlapping(
+    EvalInfo &Info, const Expr *E, const LValue &LHS, const LValue &RHS) {
+  if (isPotentiallyOverlappingStrings(
+          Info.Ctx, LHS.Base.dyn_cast<const Expr *>(), LHS.Offset,
+          RHS.Base.dyn_cast<const Expr *>(), RHS.Offset)
+          .value_or(false))
+    return OverlapResult::StringLiteral;
 
-  // String/string comparisons stay with the dedicated string-literal path.
-  if (LHSObj.isStringLiteral() && RHSObj.isStringLiteral())
-    return false;
+  ExprConstantPotentiallyNonUniqueObject LHSObj(Info, E);
+  ExprConstantPotentiallyNonUniqueObject RHSObj(Info, E);
+  if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
+    return OverlapResult::None;
 
-  return LHSObj.mayShareStorageWith(RHSObj);
+  if (!LHSObj.isPotentiallyOverlappingWith(RHSObj))
+    return OverlapResult::None;
+  return LHSObj.isStringLiteral() && RHSObj.isStringLiteral()
+             ? OverlapResult::StringLiteral
+             : OverlapResult::NonUniqueObject;
 }
 
 /// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -5460,7 +5419,7 @@ handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, QualType Type,
         return false;
       }
       uint64_t CharIndex = LVal.Designator.Entries[0].getAsArrayIndex();
-      RVal = APValue(extractStringLiteralCharacter(Info, Base, CharIndex));
+      RVal = APValue(extractStringLiteralCharacter(Info.Ctx, Base, CharIndex));
       return true;
     }
   }
@@ -19628,12 +19587,16 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       // This makes the comparison result unspecified, so it's not a constant
       // expression.
       //
-      if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
+      switch (ExprConstantPotentiallyNonUniqueObject::arePotentiallyOverlapping(
+          Info, E, LHSValue, RHSValue)) {
+      case PotentiallyNonUniqueObject::OverlapResult::StringLiteral:
         return DiagComparison(diag::note_constexpr_literal_comparison);
-      if (PotentialNonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
-                                                              RHSValue))
+      case PotentiallyNonUniqueObject::OverlapResult::NonUniqueObject:
         return DiagComparison(
             diag::note_constexpr_non_unique_object_comparison);
+      case PotentiallyNonUniqueObject::OverlapResult::None:
+        break;
+      }
       if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
         return DiagComparison(diag::note_constexpr_opaque_call_comparison,
                               !IsOpaqueConstantCall(LHSValue));
@@ -19941,7 +19904,9 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
       if (!LHSExpr || !RHSExpr)
         return DiagArith(diag::note_constexpr_pointer_arith_unspecified);
 
-      if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
+      if (PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+              Info.Ctx, LHSExpr, LHSValue.Offset, RHSExpr, RHSValue.Offset)
+              .value_or(false))
         return DiagArith(diag::note_constexpr_literal_arith);
 
       const AddrLabelExpr *LHSAddrExpr = dyn_cast<AddrLabelExpr>(LHSExpr);
diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp
index 3553ab3ba68ad..b1d6765edc557 100644
--- a/clang/test/AST/ByteCode/cxx11.cpp
+++ b/clang/test/AST/ByteCode/cxx11.cpp
@@ -298,6 +298,15 @@ namespace OverlappingStrings {
   constexpr bool may_overlap_2 = +"foo" == +"foo\0bar"; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
   constexpr bool may_overlap_3 = +"foo" == &"bar\0foo"[4]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
   constexpr bool may_overlap_4 = &"xfoo"[1] == &"xfoo"[1]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
+  constexpr bool wide_may_overlap = &u"foo"[1] == &u"xfoo"[2]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
+  constexpr bool wide_may_overlap_reverse = &u"xfoo"[2] == &u"foo"[1]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
+
+  constexpr bool predefined_may_overlap(bool Compare) {
+    return !Compare || +__func__ != "predefined_may_overlap"; // both-warning {{result of comparison against a string literal is unspecified}} #predefined-compare
+  }
+  static_assert(predefined_may_overlap(true), ""); // both-error {{static assertion expression is not an integral constant expression}} \
+                                                    // both-note {{in call to 'predefined_may_overlap(true)'}}
+  // both-note@#predefined-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
 
 
   /// Used to crash.
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index efdc407787fdc..3092575d68326 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -102,6 +102,25 @@ namespace cwg2765 {
   // 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 wide_init_list_overlaps_shifted_string() {
+    return std::initializer_list<char16_t>{u'x', u'a', u'b', u'\0'}.begin() + 1
+           != u"ab"; // #cwg2765-wide-init-list-string-compare \
+                      // both-warning {{result of comparison against a string literal is unspecified}}
+  }
+  static_assert(wide_init_list_overlaps_shifted_string(), "");
+  // both-error at -1 {{static assertion expression is not an integral constant expression}}
+  // both-note@#cwg2765-wide-init-list-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+  // both-note at -3 {{in call to}}
+
+  constexpr bool p(bool Compare) {
+    return !Compare ||
+           std::initializer_list<char>{'p', '\0'}.begin() != +__func__; // #cwg2765-init-list-predefined-compare
+  }
+  static_assert(p(true), "");
+  // both-error at -1 {{static assertion expression is not an integral constant expression}}
+  // both-note@#cwg2765-init-list-predefined-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}}
@@ -125,6 +144,20 @@ namespace cwg2765 {
                                                           // 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_object_overlaps_string = // both-error {{constexpr variable 'template_array_object_overlaps_string<FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+                                                         // both-note {{declared here}}
+      &S.data != &"hello"; // #cwg2765-template-array-object-string-compare
+  static_assert(template_array_object_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_object_overlaps_string<FixedString<6>{"hello"}>' is not a constant expression}}
+  // both-note@#cwg2765-template-array-object-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+  template <FixedString S>
+  constexpr bool template_array_object_differs_from_string =
+      &S.data != &"world";
+  static_assert(template_array_object_differs_from_string<"hello">);
+
   template <FixedString S>
   constexpr bool template_array_differs_from_string =
       S.data != "hello";
@@ -433,3 +466,17 @@ namespace cwg2765 {
       {{__builtin_complex(1.0, 2.0)}}, {{__builtin_complex(1.0, 3.0)}});
   static_assert(!different_complex, "");
 }
+
+constexpr bool f(std::initializer_list<int> il1, std::initializer_list<double> il2) {
+  return (const void*)il1.begin() == (const void*)il2.begin();
+}
+
+static_assert(f({1,2}, {4.2439915824e-314}) == false);
+
+static_assert(f({}, {}), ""); // TODO: presumably this must give an error, since an empty list can go literally anywhere in memory, but clang accept this.
+
+int i = 42;
+constexpr bool g(std::initializer_list<int> il, int *p) { return il.begin() == p; }
+static_assert(g({42}, &i) == false, "");  // OK, right?
+constexpr bool value1 = g({}, &i);  // TODO: error, right? because the empty list could go right before `i` in memory? But clang accept this.
+
diff --git a/clang/test/AST/ByteCode/objc.mm b/clang/test/AST/ByteCode/objc.mm
index f6c4ba12dc5d1..0ee0021dc0309 100644
--- a/clang/test/AST/ByteCode/objc.mm
+++ b/clang/test/AST/ByteCode/objc.mm
@@ -12,6 +12,13 @@ @interface NSString
 constexpr NSString *t0 = @"abc";
 constexpr NSString *t1 = @("abc");
 
+constexpr bool objc_encode_may_overlap(bool Compare) {
+  return !Compare || + at encode(int) != +"i"; // #objc-encode-compare
+}
+static_assert(objc_encode_may_overlap(true), ""); // both-error {{static assertion expression is not an integral constant expression}} \
+                                                  // both-note {{in call to 'objc_encode_may_overlap(true)'}}
+// both-note@#objc-encode-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+
 
 #if __LP64__
 typedef unsigned long NSUInteger;
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 3d6350504e8cb..61cd61a16a804 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1953,6 +1953,19 @@ namespace InitializerList {
   }
   static_assert(init_list_differs_from_string(), "");
 
+  constexpr bool f(std::initializer_list<int> il1, std::initializer_list<double> il2) {
+    return (const void*)il1.begin() == (const void*)il2.begin();
+  }
+
+  static_assert(f({1,2}, {4.2439915824e-314}) == false, "");
+
+  static_assert(f({}, {}), ""); // TODO: presumably this must give an error, since an empty list can go literally anywhere in memory, but clang accept this.
+
+  int i = 42;
+  constexpr bool g(std::initializer_list<int> il, int *p) { return il.begin() == p; }
+  static_assert(g({42}, &i) == false, "");  // OK, right?
+  constexpr bool value1 = g({}, &i);  // TODO: error, right? because the empty list could go right before `i` in memory? But clang accept this.
+
   namespace DR2126 {
     constexpr std::initializer_list<float> il = {1.0, 2.0, 3.0};
     static_assert(il.begin()[1] == 2.0, "");



More information about the cfe-commits mailing list