[clang] [C++26] Implement P2752R3 'Static storage for braced initializers' (PR #197458)
Yihan Wang via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 30 05:20:48 PDT 2026
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/197458
>From 76fcb0a8d54b10d4e9204cf06cd3ef1d67b1215a Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 27 May 2026 09:39:03 -0700
Subject: [PATCH 1/5] [C++26] Implement P2752R3 'Static storage for braced
initializers'
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.md | 1 +
clang/include/clang/AST/ExprCXX.h | 17 +-
clang/include/clang/AST/Stmt.h | 14 +
clang/include/clang/AST/TypeBase.h | 9 +
.../include/clang/Basic/DiagnosticASTKinds.td | 2 +
clang/lib/AST/ASTImporter.cpp | 2 +
clang/lib/AST/ByteCode/Interp.cpp | 112 ++++++
clang/lib/AST/ByteCode/Interp.h | 14 +-
clang/lib/AST/ExprCXX.cpp | 1 +
clang/lib/AST/ExprConstShared.h | 31 +-
clang/lib/AST/ExprConstant.cpp | 331 ++++++++++++++++--
clang/lib/AST/Type.cpp | 81 +++++
clang/lib/Sema/CheckExprLifetime.cpp | 19 +-
clang/lib/Sema/SemaDeclCXX.cpp | 101 +-----
clang/lib/Sema/SemaInit.cpp | 1 +
clang/lib/Serialization/ASTReaderStmt.cpp | 1 +
clang/lib/Serialization/ASTWriterStmt.cpp | 1 +
clang/test/AST/ByteCode/initializer_list.cpp | 300 ++++++++++++++++
.../dcl.decl/dcl.init/dcl.init.list/p5.cpp | 139 ++++++++
clang/test/CXX/drs/cwg27xx.cpp | 167 +++++++--
clang/test/CodeGenCXX/Inputs/jk.txt | 1 +
.../CodeGenCXX/p2752r3-initializer-list.cpp | 93 +++++
clang/www/cxx_dr_status.html | 2 +-
clang/www/cxx_status.html | 2 +-
24 files changed, 1277 insertions(+), 165 deletions(-)
create mode 100644 clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
create mode 100644 clang/test/CodeGenCXX/Inputs/jk.txt
create mode 100644 clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index b372e5b58068b..c990afd43ba59 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -214,6 +214,7 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
#### C++2c Feature Support
- Clang now propagates `constinit` and `constexpr` in structured bindings with tuple-like initializers.
+- Clang now supports `P2752R3 <https://wg21.link/p2752r3>`_ 'Static storage for braced initializers'. (#GH104487)
#### C++23 Feature Support
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 36e5f8940228e..09c7423a2eb36 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4930,7 +4930,22 @@ class MaterializeTemporaryExpr : public Expr {
LifetimeExtendedTemporaryDecl *MTD = nullptr);
MaterializeTemporaryExpr(EmptyShell Empty)
- : Expr(MaterializeTemporaryExprClass, Empty) {}
+ : Expr(MaterializeTemporaryExprClass, Empty) {
+ MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false;
+ }
+
+ /// Whether this materialized temporary is the backing array of a
+ /// std::initializer_list.
+ ///
+ /// This used by [intro.object]/9 "potentially non-unique object" handling.
+ bool isBackingArrayForInitializerList() const {
+ return MaterializeTemporaryExprBits.IsBackingArrayForInitializerList;
+ }
+
+ /// This bitfield will set by SK_StdInitializerList step in Sema.
+ void setBackingArrayForInitializerList(bool V = true) {
+ MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = V;
+ }
/// Retrieve the temporary-generating subexpression whose value will
/// be materialized into a glvalue.
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index f07ba9205661b..e4eebe5986bd5 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -831,6 +831,19 @@ class alignas(void *) Stmt {
SourceLocation Loc;
};
+ class MaterializeTemporaryExprBitfields {
+ friend class ASTStmtReader;
+ friend class MaterializeTemporaryExpr;
+
+ LLVM_PREFERRED_TYPE(ExprBitfields)
+ unsigned : NumExprBits;
+
+ /// Whether the materialized temporary is the backing array of a
+ /// std::initializer_list.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned IsBackingArrayForInitializerList : 1;
+ };
+
class CXXThisExprBitfields {
friend class CXXThisExpr;
@@ -1381,6 +1394,7 @@ class alignas(void *) Stmt {
CXXRewrittenBinaryOperatorBitfields CXXRewrittenBinaryOperatorBits;
CXXBoolLiteralExprBitfields CXXBoolLiteralExprBits;
CXXNullPtrLiteralExprBitfields CXXNullPtrLiteralExprBits;
+ MaterializeTemporaryExprBitfields MaterializeTemporaryExprBits;
CXXThisExprBitfields CXXThisExprBits;
CXXThrowExprBitfields CXXThrowExprBits;
CXXDefaultArgExprBitfields CXXDefaultArgExprBits;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 3a801e2857b13..ac3dc8e58a515 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -116,6 +116,7 @@ namespace clang {
class ASTContext;
template <typename> class CanQual;
class CXXRecordDecl;
+class Decl;
class DeclContext;
class EnumDecl;
class Expr;
@@ -2762,6 +2763,14 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
bool isNothrowT() const; // C++ std::nothrow_t
bool isAlignValT() const; // C++17 std::align_val_t
bool isStdByteType() const; // C++17 std::byte
+ bool
+ isStdClassTemplateSpecialization(const ASTContext &Ctx, StringRef ClassName,
+ QualType *TypeArg = nullptr,
+ ClassTemplateDecl **CachedDecl = nullptr,
+ const Decl **MalformedDecl = nullptr) const;
+ bool isStdInitializerListType(
+ const ASTContext &Ctx,
+ QualType *Element = nullptr) const; // C++11 std::initializer_list<T>
bool isAtomicType() const; // C11 _Atomic()
bool isUndeducedAutoType() const; // C++11 auto or
// C++14 decltype(auto)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index bde418695f647..f4f239a16a38f 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -99,6 +99,8 @@ def note_constexpr_pointer_constant_comparison : Note<
"at runtime">;
def note_constexpr_literal_comparison : Note<
"comparison of addresses of potentially overlapping literals has unspecified value">;
+def note_constexpr_non_unique_object_comparison : Note<
+ "comparison of addresses of potentially non-unique objects has unspecified value">;
def note_constexpr_literal_arith : Note<
"arithmetic on addresses of potentially overlapping literals has unspecified value">;
def note_constexpr_repeated_literal_eval : Note<
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 567d2d07298a3..0abb902bd1aba 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8577,6 +8577,8 @@ ASTNodeImporter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
auto *ToMTE = new (Importer.getToContext()) MaterializeTemporaryExpr(
ToType, ToTemporaryExpr, E->isBoundToLvalueReference(),
ToMaterializedDecl);
+ ToMTE->setBackingArrayForInitializerList(
+ E->isBackingArrayForInitializerList());
return ToMTE;
}
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 4815828adb613..79711d2e6a21d 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2819,6 +2819,118 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
return Shorter == Longer.take_front(Shorter.size());
}
+/// Whether Ptr designates an object that is the backing array of a
+/// std::initializer_list.
+static bool isInitializerListBackingArray(const Pointer &Ptr) {
+ if (Ptr.isZero() || !Ptr.isBlockPointer() || Ptr.block()->isDynamic())
+ return false;
+
+ const auto *MTE =
+ dyn_cast_or_null<MaterializeTemporaryExpr>(Ptr.getDeclDesc()->asExpr());
+ return MTE && MTE->isBackingArrayForInitializerList();
+}
+
+namespace {
+/// Pairs an enclosing-array Pointer with the element-relative location of
+/// the original pointer within it.
+struct ArraySubobjectInfo {
+ Pointer Array;
+ ArraySubobjectLocation Loc;
+};
+} // namespace
+
+/// Returns the enclosing array and element-relative location if Ptr
+/// designates an element of an initializer_list backing array,
+/// one-past-the-end of it, or a subobject of an element. Returns
+/// std::nullopt otherwise.
+static std::optional<ArraySubobjectInfo>
+getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
+ if (Ptr.isZero() || !Ptr.isBlockPointer())
+ return std::nullopt;
+
+ Pointer Array = Ptr.getDeclPtr();
+ if (!isInitializerListBackingArray(Array))
+ return std::nullopt;
+
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
+
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
+ return std::nullopt;
+
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ if (Path.empty())
+ return std::nullopt;
+
+ uint64_t Index = Path.front().getAsArrayIndex();
+ bool IsValidOnePastEnd = Path.size() == 1;
+ std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
+ if (!Loc)
+ return std::nullopt;
+ return ArraySubobjectInfo{std::move(Array), *Loc};
+}
+
+/// Returns true if \p LHS and \p RHS both designate elements of
+/// std::initializer_list backing arrays whose values agree on the overlapping
+/// region. Such backing arrays may be merged by the implementation, so
+/// comparing their addresses produces an unspecified result and is rejected
+/// as a constant expression.
+bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS) {
+ const ASTContext &Ctx = S.getASTContext();
+ std::optional<ArraySubobjectInfo> LHSInfo =
+ getArraySubobjectLocation(Ctx, LHS);
+ std::optional<ArraySubobjectInfo> RHSInfo =
+ getArraySubobjectLocation(Ctx, RHS);
+ if (!LHSInfo || !RHSInfo)
+ return false;
+
+ if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement)
+ return false;
+
+ const auto *LHSArrayType =
+ Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
+ const auto *RHSArrayType =
+ Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
+ if (!LHSArrayType || !RHSArrayType ||
+ !Ctx.hasSameType(LHSArrayType->getElementType(),
+ RHSArrayType->getElementType()))
+ return false;
+
+ QualType ElementType = LHSArrayType->getElementType();
+ int64_t LHSSize = LHSInfo->Array.getNumElems();
+ int64_t RHSSize = RHSInfo->Array.getNumElems();
+ int64_t LHSOffset = LHSInfo->Loc.Index;
+ int64_t RHSOffset = RHSInfo->Loc.Index;
+ int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+ int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+ if (OverlapBegin >= OverlapEnd)
+ return false;
+
+ for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+ Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow();
+ Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow();
+ std::optional<APValue> LHSEltValue =
+ LHSElt.toRValue(S.getContext(), ElementType);
+ std::optional<APValue> RHSEltValue =
+ RHSElt.toRValue(S.getContext(), ElementType);
+ // Missing element data: be conservative and assume the backing arrays
+ // may share storage.
+ if (!LHSEltValue || !RHSEltValue)
+ return true;
+
+ if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx))
+ return false;
+ }
+
+ return true;
+}
+
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
PrimType T) {
if (T == PT_IntAPS) {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 97d9e7cc14e32..0b73ede59f7dd 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1292,6 +1292,9 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
const Pointer &RHS);
+bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS);
template <>
inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1336,7 +1339,10 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- // FIXME: The source check here isn't entirely correct.
+ // C++ [intro.object]/9:
+ // An object is potentially non-unique if it is a string literal object,
+ // the backing array of an initializer list, or a subobject thereof.
+ // FIXME: The string literal source check here isn't entirely correct.
if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() &&
LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) {
if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
@@ -1358,6 +1364,12 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
+ if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
+ return false;
+ }
+
// Otherwise we need to do a bunch of extra checks before returning Unordered.
if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
RHS.getOffset() == 0) {
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 960691219be6d..bbe237f0ad205 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1825,6 +1825,7 @@ MaterializeTemporaryExpr::MaterializeTemporaryExpr(
LifetimeExtendedTemporaryDecl *MTD)
: Expr(MaterializeTemporaryExprClass, T,
BoundToLvalueReference ? VK_LValue : VK_XValue, OK_Ordinary) {
+ MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false;
if (MTD) {
State = MTD;
MTD->ExprWithTemporary = Temporary;
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index 619c79a1408f3..73c4ab76ade19 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
#define LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
+#include "clang/AST/CharUnits.h"
#include "clang/Basic/TypeTraits.h"
#include <cstdint>
#include <optional>
@@ -27,7 +28,8 @@ namespace clang {
class QualType;
class LangOptions;
class ASTContext;
-class CharUnits;
+class APValue;
+class ConstantArrayType;
class Expr;
} // namespace clang
using namespace clang;
@@ -78,6 +80,12 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C,
CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
UnaryExprOrTypeTrait ExprKind);
+/// Whether two APValues could be merged into a single storage location by
+/// the implementation (the relation [intro.object]/9 cares about for
+/// initializer_list backing arrays and string literals).
+bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
+ const ASTContext &Ctx);
+
uint8_t GFNIMultiplicativeInverse(uint8_t Byte);
uint8_t GFNIMul(uint8_t AByte, uint8_t BByte);
uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword,
@@ -89,4 +97,23 @@ std::optional<llvm::APFloat>
EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B,
std::optional<llvm::APSInt> RoundingMode, bool IsMin);
-#endif
+/// Where an lvalue into an array element lives: the element index within the
+/// array (or the array length for a one-past-the-end pointer), and the byte
+/// offset from the start of that element.
+struct ArraySubobjectLocation {
+ uint64_t Index;
+ CharUnits OffsetInElement;
+};
+
+/// Computes the array-element location designated by an lvalue whose first
+/// path entry indexes into ArrayType with the given Index and whose
+/// byte offset from the array base is LValueOffset. IsValidOnePastEnd
+/// must be true iff the lvalue is a valid one-past-the-end position of the
+/// array (which the caller determines from its own lvalue representation).
+/// Returns std::nullopt if the lvalue does not designate an element,
+/// one-past-the-end position, or subobject of an element.
+std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+ const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
+ CharUnits LValueOffset, bool IsValidOnePastEnd);
+
+#endif // LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 563d6b3bb0cf9..9e77febd9fff3 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -56,6 +56,7 @@
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
+#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/StringExtras.h"
@@ -2020,17 +2021,18 @@ struct LValueBaseString {
int CharWidth;
};
-// Gets the lvalue base of LVal as a string.
-static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
+// Gets the lvalue base as a string.
+static bool GetLValueBaseAsString(const ASTContext &Ctx,
+ APValue::LValueBase Base,
LValueBaseString &AsString) {
- const auto *BaseExpr = LVal.Base.dyn_cast<const Expr *>();
+ const auto *BaseExpr = Base.dyn_cast<const Expr *>();
if (!BaseExpr)
return false;
// For ObjCEncodeExpr, we need to compute and store the string.
if (const auto *EE = dyn_cast<ObjCEncodeExpr>(BaseExpr)) {
- Info.Ctx.getObjCEncodingForType(EE->getEncodedType(),
- AsString.ObjCEncodeStorage);
+ Ctx.getObjCEncodingForType(EE->getEncodedType(),
+ AsString.ObjCEncodeStorage);
AsString.Bytes = AsString.ObjCEncodeStorage;
AsString.CharWidth = 1;
return true;
@@ -2049,6 +2051,11 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
return true;
}
+static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
+ LValueBaseString &AsString) {
+ return GetLValueBaseAsString(Info.Ctx, LVal.Base, AsString);
+}
+
// Determine whether two string literals potentially overlap. This will be the
// case if they agree on the values of all the bytes on the overlapping region
// between them.
@@ -2063,31 +2070,31 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
// addresses onwards.
//
// See open core issue CWG2765 which is discussing the desired rule here.
-static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
- const LValue &LHS,
- const LValue &RHS) {
- LValueBaseString LHSString, RHSString;
- if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
- !GetLValueBaseAsString(Info, RHS, RHSString))
- return false;
+static bool ArePotentiallyOverlappingStringLiterals(
+ const LValueBaseString &LHSString, CharUnits LHSOffset,
+ const LValueBaseString &RHSString, CharUnits RHSOffset) {
+ assert(LHSString.Bytes.data() && RHSString.Bytes.data() &&
+ "passing a string with no underlying storage");
// This is the byte offset to the location of the first character of LHS
// within RHS. We don't need to look at the characters of one string that
// would appear before the start of the other string if they were merged.
- CharUnits Offset = RHS.Offset - LHS.Offset;
+ StringRef LHSBytes = LHSString.Bytes;
+ StringRef RHSBytes = RHSString.Bytes;
+ CharUnits Offset = RHSOffset - LHSOffset;
if (Offset.isNegative()) {
- if (LHSString.Bytes.size() < (size_t)-Offset.getQuantity())
+ if (LHSBytes.size() < (size_t)-Offset.getQuantity())
return false;
- LHSString.Bytes = LHSString.Bytes.drop_front(-Offset.getQuantity());
+ LHSBytes = LHSBytes.drop_front(-Offset.getQuantity());
} else {
- if (RHSString.Bytes.size() < (size_t)Offset.getQuantity())
+ if (RHSBytes.size() < (size_t)Offset.getQuantity())
return false;
- RHSString.Bytes = RHSString.Bytes.drop_front(Offset.getQuantity());
+ RHSBytes = RHSBytes.drop_front(Offset.getQuantity());
}
- bool LHSIsLonger = LHSString.Bytes.size() > RHSString.Bytes.size();
- StringRef Longer = LHSIsLonger ? LHSString.Bytes : RHSString.Bytes;
- StringRef Shorter = LHSIsLonger ? RHSString.Bytes : LHSString.Bytes;
+ bool LHSIsLonger = LHSBytes.size() > RHSBytes.size();
+ StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes;
+ StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes;
int ShorterCharWidth = (LHSIsLonger ? RHSString : LHSString).CharWidth;
// The null terminator isn't included in the string data, so check for it
@@ -2105,6 +2112,32 @@ static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
return Shorter == Longer.take_front(Shorter.size());
}
+static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
+ const LValue &LHS,
+ const LValue &RHS) {
+ LValueBaseString LHSString, RHSString;
+ if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
+ !GetLValueBaseAsString(Info, RHS, RHSString))
+ return false;
+
+ return ArePotentiallyOverlappingStringLiterals(LHSString, LHS.Offset,
+ RHSString, RHS.Offset);
+}
+
+/// APValue overload of the string-literal overlap predicate. Returns nullopt
+/// if either operand is not a string-literal (or ObjC encoding) LValue.
+static std::optional<bool> ArePotentiallyOverlappingStringLiterals(
+ const ASTContext &Ctx, const APValue &LHS, const APValue &RHS) {
+ if (!LHS.isLValue() || !RHS.isLValue())
+ return std::nullopt;
+ LValueBaseString LHSString, RHSString;
+ if (!GetLValueBaseAsString(Ctx, LHS.getLValueBase(), LHSString) ||
+ !GetLValueBaseAsString(Ctx, RHS.getLValueBase(), RHSString))
+ return std::nullopt;
+ return ArePotentiallyOverlappingStringLiterals(
+ LHSString, LHS.getLValueOffset(), RHSString, RHS.getLValueOffset());
+}
+
static bool IsWeakLValue(const LValue &Value) {
const ValueDecl *Decl = GetLValueBaseDecl(Value);
return Decl && Decl->isWeak();
@@ -4856,6 +4889,259 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType);
}
+static const APValue *GetArrayInitializedElt(const APValue &Array,
+ uint64_t Index) {
+ if (!Array.isArray() || Index >= Array.getArraySize())
+ return nullptr;
+ if (Index < Array.getArrayInitializedElts())
+ return &Array.getArrayInitializedElt(Index);
+ return Array.hasArrayFiller() ? &Array.getArrayFiller() : nullptr;
+}
+
+/// True if the LValue base names a weak entity (whose link-time identity
+/// may be merged with another).
+static bool isWeakLValueBase(const APValue &V) {
+ if (!V.isLValue())
+ return false;
+ if (const auto *VD = V.getLValueBase().dyn_cast<const ValueDecl *>())
+ return VD->isWeak();
+ return false;
+}
+
+/// True if the member-pointer target decl is weak.
+static bool isWeakMemberPointer(const APValue &V) {
+ if (!V.isMemberPointer())
+ return false;
+ const ValueDecl *D = V.getMemberPointerDecl();
+ return D && D->isWeak();
+}
+
+/// AreAPValuesPotentiallyMergeableSlow - The slow path for
+/// AreAPValuesPotentiallyMergeable. Will be invoked when the two values'
+/// APValue::Profile already differs. Returns true if the disagreement is one
+/// the runtime can collapse (overlapping string literals, weak decls that might
+/// resolve to the same target), recursing into aggregate kinds so that
+/// aggregates containing such leaves are still reported as mergeable.
+static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
+ const APValue &RHS,
+ const ASTContext &Ctx) {
+ if (!LHS.hasValue() || !RHS.hasValue())
+ return true;
+ if (LHS.getKind() != RHS.getKind())
+ return false;
+
+ switch (LHS.getKind()) {
+ case APValue::LValue: {
+ // Distinct string-literal LValues whose content could be merged.
+ if (auto Overlap = ArePotentiallyOverlappingStringLiterals(Ctx, LHS, RHS))
+ return *Overlap;
+ // Either side names a weak entity -> link-time may resolve to one.
+ return isWeakLValueBase(LHS) || isWeakLValueBase(RHS);
+ }
+
+ case APValue::MemberPointer:
+ return isWeakMemberPointer(LHS) || isWeakMemberPointer(RHS);
+
+ case APValue::Struct: {
+ if (LHS.getStructNumBases() != RHS.getStructNumBases() ||
+ LHS.getStructNumFields() != RHS.getStructNumFields())
+ return false;
+ for (unsigned I = 0, N = LHS.getStructNumBases(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getStructBase(I),
+ RHS.getStructBase(I), Ctx))
+ return false;
+ for (unsigned I = 0, N = LHS.getStructNumFields(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getStructField(I),
+ RHS.getStructField(I), Ctx))
+ return false;
+ return true;
+ }
+
+ case APValue::Union: {
+ if (LHS.getUnionField() != RHS.getUnionField())
+ return false;
+ if (!LHS.getUnionField())
+ return true;
+ return AreAPValuesPotentiallyMergeable(LHS.getUnionValue(),
+ RHS.getUnionValue(), Ctx);
+ }
+
+ case APValue::Array: {
+ if (LHS.getArraySize() != RHS.getArraySize())
+ return false;
+ for (unsigned I = 0, N = LHS.getArraySize(); I != N; ++I) {
+ const APValue *LE = GetArrayInitializedElt(LHS, I);
+ const APValue *RE = GetArrayInitializedElt(RHS, I);
+ // Missing element data: be conservative.
+ if (!LE || !RE)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
+ return false;
+ }
+ return true;
+ }
+
+ case APValue::Vector: {
+ if (LHS.getVectorLength() != RHS.getVectorLength())
+ return false;
+ for (unsigned I = 0, N = LHS.getVectorLength(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
+ RHS.getVectorElt(I), Ctx))
+ return false;
+ return true;
+ }
+
+ case APValue::Matrix: {
+ if (LHS.getMatrixNumRows() != RHS.getMatrixNumRows() ||
+ LHS.getMatrixNumColumns() != RHS.getMatrixNumColumns())
+ return false;
+ for (unsigned I = 0, N = LHS.getMatrixNumElements(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
+ RHS.getMatrixElt(I), Ctx))
+ return false;
+ return true;
+ }
+
+ // For every scalar kind (Int, Float, FixedPoint, ComplexInt, ComplexFloat,
+ // AddrLabelDiff, None, Indeterminate), Profile already captures the full
+ // observable content. If Profile disagreed, the values are observably
+ // distinct.
+ default:
+ return false;
+ }
+}
+
+bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
+ const ASTContext &Ctx) {
+ // Fast path: bitwise-identical APValues -> definitely mergeable.
+ llvm::FoldingSetNodeID LHSID, RHSID;
+ LHS.Profile(LHSID);
+ RHS.Profile(RHSID);
+ if (LHSID == RHSID)
+ return true;
+
+ return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
+}
+
+std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+ const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
+ CharUnits LValueOffset, bool IsValidOnePastEnd) {
+ uint64_t ArraySize = ArrayType->getZExtSize();
+ if (Index > ArraySize)
+ return std::nullopt;
+
+ if (Index == ArraySize) {
+ if (!IsValidOnePastEnd)
+ return std::nullopt;
+ return ArraySubobjectLocation{Index, CharUnits::Zero()};
+ }
+
+ if (LValueOffset.isNegative())
+ return std::nullopt;
+
+ uint64_t ElementSize =
+ Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+ if (ElementSize && Index > std::numeric_limits<uint64_t>::max() / ElementSize)
+ return std::nullopt;
+
+ uint64_t ElementOffset = ElementSize * Index;
+ uint64_t LValueOffsetQ = LValueOffset.getQuantity();
+ if (LValueOffsetQ < ElementOffset)
+ return std::nullopt;
+
+ uint64_t OffsetInElement = LValueOffsetQ - ElementOffset;
+ if (OffsetInElement > ElementSize)
+ return std::nullopt;
+
+ return ArraySubobjectLocation{Index,
+ CharUnits::fromQuantity(OffsetInElement)};
+}
+
+/// Returns the array element and element-relative location that LV
+/// designates, or std::nullopt if LV is not an element, one-past-the-end
+/// position, or subobject of an element of its base array.
+static std::optional<ArraySubobjectLocation>
+getArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+ if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+ return std::nullopt;
+
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!ArrayType)
+ return std::nullopt;
+
+ uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
+ bool IsValidOnePastEnd =
+ LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
+ return getArraySubobjectLocationImpl(Ctx, ArrayType, Index, LV.Offset,
+ IsValidOnePastEnd);
+}
+
+static const APValue *GetCompleteObjectValue(EvalInfo &Info, const Expr *E,
+ const LValue &LV) {
+ CompleteObject Obj =
+ findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
+ return Obj ? Obj.Value : nullptr;
+}
+
+static bool isInitializerListBackingArray(const LValue &LV) {
+ const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>();
+ const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr);
+ return MTE && MTE->isBackingArrayForInitializerList();
+}
+
+static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info,
+ const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS) {
+ if (!isInitializerListBackingArray(LHS) ||
+ !isInitializerListBackingArray(RHS))
+ return false;
+
+ std::optional<ArraySubobjectLocation> LHSLoc =
+ getArraySubobjectLocation(Info.Ctx, LHS);
+ std::optional<ArraySubobjectLocation> RHSLoc =
+ getArraySubobjectLocation(Info.Ctx, RHS);
+ if (!LHSLoc || !RHSLoc)
+ return false;
+
+ if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement)
+ return false;
+
+ const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base));
+ const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base));
+ if (!LHSArrayType || !RHSArrayType ||
+ !Info.Ctx.hasSameType(LHSArrayType->getElementType(),
+ RHSArrayType->getElementType()))
+ return false;
+
+ const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS);
+ const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS);
+ if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray())
+ return false;
+
+ int64_t LHSSize = LHSArray->getArraySize();
+ int64_t RHSSize = RHSArray->getArraySize();
+ int64_t LHSOffset = LHSLoc->Index;
+ int64_t RHSOffset = RHSLoc->Index;
+ int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+ int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+ if (OverlapBegin >= OverlapEnd)
+ return false;
+
+ for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+ const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset);
+ const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset);
+ // Missing element data: be conservative and assume the arrays may share
+ // storage.
+ if (!LHSElt || !RHSElt)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+ return false;
+ }
+
+ return true;
+}
+
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This
/// can also be used for 'lvalue-to-lvalue' conversions for looking up the
/// glvalue referred to by an entity of reference type.
@@ -18877,9 +19163,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
// This makes the comparison result unspecified, so it's not a constant
// expression.
//
- // TODO: Do we need to handle the initializer list case here?
if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
return DiagComparison(diag::note_constexpr_literal_comparison);
+ if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue,
+ RHSValue))
+ return DiagComparison(
+ diag::note_constexpr_non_unique_object_comparison);
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
return DiagComparison(diag::note_constexpr_opaque_call_comparison,
!IsOpaqueConstantCall(LHSValue));
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 19b85d0e0af69..13897198aae7e 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3327,6 +3327,87 @@ bool Type::isStdByteType() const {
return false;
}
+bool Type::isStdClassTemplateSpecialization(const ASTContext &Ctx,
+ StringRef ClassName,
+ QualType *TypeArg,
+ ClassTemplateDecl **CachedDecl,
+ const Decl **MalformedDecl) const {
+ QualType SugaredType(this, 0);
+ auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) {
+ if (!MalformedDecl)
+ return;
+ if (!D)
+ D = SugaredType->getAsTagDecl();
+ if (!D || !D->isInStdNamespace())
+ return;
+ IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo();
+ if (II && II->isStr(ClassName))
+ *MalformedDecl = D;
+ };
+
+ ClassTemplateDecl *Template = nullptr;
+ ArrayRef<TemplateArgument> Arguments;
+ if (const TemplateSpecializationType *TST =
+ SugaredType->getAsNonAliasTemplateSpecializationType()) {
+ Template = dyn_cast_or_null<ClassTemplateDecl>(
+ TST->getTemplateName().getAsTemplateDecl());
+ Arguments = TST->template_arguments();
+ } else if (const auto *TT = SugaredType->getAs<TagType>()) {
+ Template = TT->getTemplateDecl();
+ Arguments = TT->getTemplateArgs(Ctx);
+ }
+
+ if (!Template) {
+ ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl());
+ return false;
+ }
+
+ ClassTemplateDecl *Cached = CachedDecl ? *CachedDecl : nullptr;
+ if (!Cached) {
+ CXXRecordDecl *TemplateClass = Template->getTemplatedDecl();
+ IdentifierInfo *II = TemplateClass->getIdentifier();
+ if (!II || !II->isStr(ClassName) || !TemplateClass->isInStdNamespace())
+ return false;
+
+ TemplateParameterList *Params = Template->getTemplateParameters();
+ if (Params->getMinRequiredArguments() != 1 ||
+ !isa<TemplateTypeParmDecl>(Params->getParam(0)) ||
+ Params->getParam(0)->isTemplateParameterPack()) {
+ if (MalformedDecl)
+ *MalformedDecl = TemplateClass;
+ return false;
+ }
+
+ Cached = Template;
+ if (CachedDecl)
+ *CachedDecl = Template;
+ }
+
+ if (Template->getCanonicalDecl() != Cached->getCanonicalDecl())
+ return false;
+
+ if (TypeArg) {
+ if (Arguments.empty() || Arguments[0].getKind() != TemplateArgument::Type)
+ return false;
+
+ QualType ArgType = Arguments[0].getAsType();
+ if (Ctx.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() &&
+ !ArgType.getObjCLifetime()) {
+ Qualifiers Qs;
+ Qs.setObjCLifetime(Qualifiers::OCL_Strong);
+ ArgType = Ctx.getQualifiedType(ArgType, Qs);
+ }
+ *TypeArg = ArgType;
+ }
+
+ return true;
+}
+
+bool Type::isStdInitializerListType(const ASTContext &Ctx,
+ QualType *Element) const {
+ return isStdClassTemplateSpecialization(Ctx, "initializer_list", Element);
+}
+
bool Type::isSpecifierType() const {
// Note that this intentionally does not use the canonical type.
switch (getTypeClass()) {
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index d15b2c6518cec..7b466ec94b70c 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -285,17 +285,11 @@ static bool isContainerOfOwner(const RecordDecl *Container) {
isGslOwnerType(TAs[0].getAsType());
}
-// Returns true if the given Record is `std::initializer_list<pointer>`.
-static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
- if (const auto *CTSD =
- dyn_cast_if_present<ClassTemplateSpecializationDecl>(RD)) {
- const auto &TAs = CTSD->getTemplateArgs();
- return lifetimes::isInStlNamespace(RD) && RD->getIdentifier() &&
- RD->getName() == "initializer_list" && TAs.size() > 0 &&
- TAs[0].getKind() == TemplateArgument::Type &&
- lifetimes::isPointerLikeType(TAs[0].getAsType());
- }
- return false;
+// Returns true if the given type is `std::initializer_list<pointer>`.
+static bool isStdInitializerListOfPointer(QualType T, const ASTContext &Ctx) {
+ QualType ElementType;
+ return T.getNonReferenceType()->isStdInitializerListType(Ctx, &ElementType) &&
+ lifetimes::isPointerLikeType(ElementType);
}
// Returns true if the given constructor is a copy-like constructor, such as
@@ -351,7 +345,8 @@ shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
// array. We perform analysis on it to determine if there are any dangling
// temporaries in the backing array.
// E.g. std::vector<string_view> abc = {string()};
- if (isStdInitializerListOfPointer(RHSRD))
+ if (isStdInitializerListOfPointer(RHSArgType,
+ Ctor->getConstructor()->getASTContext()))
return true;
// RHS must be an owner.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c645d96da5c00..9f43916c4fecc 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12327,96 +12327,6 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() {
return getStdNamespace();
}
-static bool isStdClassTemplate(Sema &S, QualType SugaredType, QualType *TypeArg,
- const char *ClassName,
- ClassTemplateDecl **CachedDecl,
- const Decl **MalformedDecl) {
- // We're looking for implicit instantiations of
- // template <typename U> class std::{ClassName}.
-
- if (!S.StdNamespace) // If we haven't seen namespace std yet, this can't be
- // it.
- return false;
-
- auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) {
- if (!MalformedDecl)
- return;
- if (!D)
- D = SugaredType->getAsTagDecl();
- if (!D || !D->isInStdNamespace())
- return;
- IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo();
- if (II && II == &S.PP.getIdentifierTable().get(ClassName))
- *MalformedDecl = D;
- };
-
- ClassTemplateDecl *Template = nullptr;
- ArrayRef<TemplateArgument> Arguments;
- if (const TemplateSpecializationType *TST =
- SugaredType->getAsNonAliasTemplateSpecializationType()) {
- Template = dyn_cast_or_null<ClassTemplateDecl>(
- TST->getTemplateName().getAsTemplateDecl());
- Arguments = TST->template_arguments();
- } else if (const auto *TT = SugaredType->getAs<TagType>()) {
- Template = TT->getTemplateDecl();
- Arguments = TT->getTemplateArgs(S.Context);
- }
-
- if (!Template) {
- ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl());
- return false;
- }
-
- if (!*CachedDecl) {
- // Haven't recognized std::{ClassName} yet, maybe this is it.
- // FIXME: It seems we should just reuse LookupStdClassTemplate but the
- // semantics of this are slightly different, most notably the existing
- // "lookup" semantics explicitly diagnose an invalid definition as an
- // error.
- CXXRecordDecl *TemplateClass = Template->getTemplatedDecl();
- if (TemplateClass->getIdentifier() !=
- &S.PP.getIdentifierTable().get(ClassName) ||
- !S.getStdNamespace()->InEnclosingNamespaceSetOf(
- TemplateClass->getNonTransparentDeclContext()))
- return false;
- // This is a template called std::{ClassName}, but is it the right
- // template?
- TemplateParameterList *Params = Template->getTemplateParameters();
- if (Params->getMinRequiredArguments() != 1 ||
- !isa<TemplateTypeParmDecl>(Params->getParam(0)) ||
- Params->getParam(0)->isTemplateParameterPack()) {
- if (MalformedDecl)
- *MalformedDecl = TemplateClass;
- return false;
- }
-
- // It's the right template.
- *CachedDecl = Template;
- }
-
- if (Template->getCanonicalDecl() != (*CachedDecl)->getCanonicalDecl())
- return false;
-
- // This is an instance of std::{ClassName}. Find the argument type.
- if (TypeArg) {
- QualType ArgType = Arguments[0].getAsType();
- // FIXME: Since TST only has as-written arguments, we have to perform the
- // only kind of conversion applicable to type arguments; in Objective-C ARC:
- // - If an explicitly-specified template argument type is a lifetime type
- // with no lifetime qualifier, the __strong lifetime qualifier is
- // inferred.
- if (S.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() &&
- !ArgType.getObjCLifetime()) {
- Qualifiers Qs;
- Qs.setObjCLifetime(Qualifiers::OCL_Strong);
- ArgType = S.Context.getQualifiedType(ArgType, Qs);
- }
- *TypeArg = ArgType;
- }
-
- return true;
-}
-
bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
assert(getLangOpts().CPlusPlus &&
"Looking for std::initializer_list outside of C++.");
@@ -12424,8 +12334,10 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
// We're looking for implicit instantiations of
// template <typename E> class std::initializer_list.
- return isStdClassTemplate(*this, Ty, Element, "initializer_list",
- &StdInitializerList, /*MalformedDecl=*/nullptr);
+ return !Ty.isNull() &&
+ Ty->isStdClassTemplateSpecialization(Context, "initializer_list",
+ Element, &StdInitializerList,
+ /*MalformedDecl=*/nullptr);
}
bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element,
@@ -12436,8 +12348,9 @@ bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element,
// We're looking for implicit instantiations of
// template <typename T> struct std::type_identity.
- return isStdClassTemplate(*this, Ty, Element, "type_identity",
- &StdTypeIdentity, MalformedDecl);
+ return !Ty.isNull() &&
+ Ty->isStdClassTemplateSpecialization(Context, "type_identity", Element,
+ &StdTypeIdentity, MalformedDecl);
}
static ClassTemplateDecl *LookupStdClassTemplate(Sema &S, SourceLocation Loc,
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index dad9c8c972dd9..e9849e286af16 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8690,6 +8690,7 @@ ExprResult InitializationSequence::Perform(Sema &S,
MaterializeTemporaryExpr *MTE = S.CreateMaterializeTemporaryExpr(
CurInit.get()->getType(), CurInit.get(),
/*BoundToLvalueReference=*/false);
+ MTE->setBackingArrayForInitializerList();
// Wrap it in a construction of a std::initializer_list<T>.
CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 02ccf8d4d41c2..6e0c06e602a95 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2322,6 +2322,7 @@ void ASTStmtReader::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) {
void ASTStmtReader::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
VisitExpr(E);
+ E->setBackingArrayForInitializerList(Record.readInt());
bool HasMaterialzedDecl = Record.readInt();
if (HasMaterialzedDecl)
E->State = cast<LifetimeExtendedTemporaryDecl>(Record.readDecl());
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 4e9eadd730a56..09fd79fd30010 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2332,6 +2332,7 @@ void ASTStmtWriter::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) {
void ASTStmtWriter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
VisitExpr(E);
+ Record.push_back(E->isBackingArrayForInitializerList());
Record.push_back(static_cast<bool>(E->getLifetimeExtendedTemporaryDecl()));
if (E->getLifetimeExtendedTemporaryDecl())
Record.AddDeclRef(E->getLifetimeExtendedTemporaryDecl());
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index f882e4ff1b124..a6751453a7f3a 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -68,4 +68,304 @@ namespace rdar13395022 {
}
}
+namespace cwg2765 {
+ constexpr bool same(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.begin() != b.begin(); // #cwg2765-init-list-compare
+ }
+ static_assert(same({1}, {1}), "");
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ template <class T>
+ constexpr bool begins_equal(T a, T b) {
+ return a.begin() == b.begin(); // #cwg2765-template-compare
+ }
+ constexpr bool same_three =
+ begins_equal<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+ // both-error at -2 {{constexpr variable 'same_three' must be initialized by a constant expression}}
+ // both-note@#cwg2765-template-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_three =
+ begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
+ static_assert(!different_three, "");
+
+ constexpr bool same_object() {
+ std::initializer_list<int> il = {1, 1, 1};
+ return il.begin() == il.begin() && il.begin() != il.begin() + 1;
+ }
+ static_assert(same_object(), "");
+
+ constexpr bool same_pointer_value() {
+ std::initializer_list<int> il = {1, 1, 1};
+ const int *p = il.begin();
+ return p + 0 == p && p != p + 1;
+ }
+ static_assert(same_pointer_value(), "");
+
+ constexpr bool local_list(const int *p) {
+ std::initializer_list<int> il = {1, 2, 3};
+ return p ? (p == il.begin()) : local_list(il.begin()); // #cwg2765-local-compare
+ }
+ constexpr bool same_local = local_list(nullptr); // #cwg2765-local-call
+ // both-error at -1 {{constexpr variable 'same_local' must be initialized by a constant expression}}
+ // both-note@#cwg2765-local-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-local-compare {{in call to}}
+ // both-note@#cwg2765-local-call {{in call to}}
+
+ constexpr bool shifted(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.begin() != b.begin() + 1; // #cwg2765-shifted-compare
+ }
+ constexpr bool annex_c = shifted({2, 3}, {1, 2, 3});
+ // both-error at -1 {{constexpr variable 'annex_c' must be initialized by a constant expression}}
+ // both-note@#cwg2765-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool end_shifted(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.end() == b.begin() + 1; // #cwg2765-end-shifted-compare
+ }
+ constexpr bool same_end_shifted = end_shifted({1}, {1, 2});
+ // both-error at -1 {{constexpr variable 'same_end_shifted' must be initialized by a constant expression}}
+ // both-note@#cwg2765-end-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_end_shifted = end_shifted({1}, {2, 1});
+ static_assert(!different_end_shifted, "");
+
+ struct Box {
+ int n;
+ };
+ constexpr bool class_same(std::initializer_list<Box> a,
+ std::initializer_list<Box> b) {
+ return a.begin() == b.begin(); // #cwg2765-class-compare
+ }
+ constexpr bool same_box = class_same({{1}}, {{1}});
+ // both-error at -1 {{constexpr variable 'same_box' must be initialized by a constant expression}}
+ // both-note@#cwg2765-class-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_box = class_same({{1}}, {{2}});
+ static_assert(!different_box, "");
+
+ constexpr bool class_field_same(std::initializer_list<Box> a,
+ std::initializer_list<Box> b) {
+ return &a.begin()->n == &b.begin()->n; // #cwg2765-field-compare
+ }
+ constexpr bool same_box_field = class_field_same({{1}}, {{1}});
+ // both-error at -1 {{constexpr variable 'same_box_field' must be initialized by a constant expression}}
+ // both-note@#cwg2765-field-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_box_field = class_field_same({{1}}, {{2}});
+ static_assert(!different_box_field, "");
+
+ struct WithPointer {
+ const int *p;
+ int n;
+ };
+ constexpr int pointer_anchor = 0;
+ constexpr int pointer_anchor_2 = 0;
+ constexpr bool class_with_pointer_same(std::initializer_list<WithPointer> a,
+ std::initializer_list<WithPointer> b) {
+ return a.begin() == b.begin(); // #cwg2765-with-pointer-compare
+ }
+ // Both elements compare equal in the scalar field; the pointer field
+ // points to the same global, so observable equality is Unknown overall
+ // and the address comparison is unspecified.
+ constexpr bool same_after_pointer_field =
+ class_with_pointer_same({{&pointer_anchor, 1}},
+ {{&pointer_anchor, 1}});
+ // both-error at -3 {{constexpr variable 'same_after_pointer_field' must be initialized by a constant expression}}
+ // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -4 {{in call to}}
+
+ // The non-pointer field already disagrees, so we can conclude the
+ // backing arrays are distinct even though a pointer field is present.
+ constexpr bool different_after_pointer_field =
+ class_with_pointer_same({{&pointer_anchor, 1}},
+ {{&pointer_anchor, 2}});
+ static_assert(!different_after_pointer_field, "");
+
+ // Distinct strong-symbol globals have distinct addresses, so the
+ // pointer field alone is enough to conclude the backing arrays differ.
+ constexpr bool different_pointer_field =
+ class_with_pointer_same({{&pointer_anchor, 1}},
+ {{&pointer_anchor_2, 1}});
+ static_assert(!different_pointer_field, "");
+
+ // A null pointer is distinct from a pointer-to-object.
+ constexpr bool one_null_pointer =
+ class_with_pointer_same({{nullptr, 1}}, {{&pointer_anchor, 1}});
+ static_assert(!one_null_pointer, "");
+ // Both null and scalar fields agree -> potentially overlapping.
+ constexpr bool both_null_pointer =
+ class_with_pointer_same({{nullptr, 1}}, {{nullptr, 1}}); // #cwg2765-both-null-call
+ // both-error at -2 {{constexpr variable 'both_null_pointer' must be initialized by a constant expression}}
+ // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-both-null-call {{in call to}}
+
+ struct WithStringPointer { const char *p; };
+ constexpr bool class_string_pointer_same(
+ std::initializer_list<WithStringPointer> a,
+ std::initializer_list<WithStringPointer> b) {
+ return a.begin() == b.begin(); // #cwg2765-string-ptr-compare
+ }
+ constexpr bool different_string_pointer =
+ class_string_pointer_same({{"abc"}}, {{"def"}});
+ static_assert(!different_string_pointer, "");
+
+ constexpr bool same_string_pointer =
+ class_string_pointer_same({{"abc"}}, {{"abc"}}); // #cwg2765-same-string-ptr-call
+ // both-error at -2 {{constexpr variable 'same_string_pointer' must be initialized by a constant expression}}
+ // both-note@#cwg2765-string-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-same-string-ptr-call {{in call to}}
+
+ // Both pointers are at offset 0 and the literals have different sizes, so
+ // "abc" cannot be merged into the start of "abcd": that would require a
+ // null terminator at offset 3 of "abcd", which holds 'd'. The string-overlap
+ // predicate resolves this to NotEqual.
+ constexpr bool prefix_string_pointer =
+ class_string_pointer_same({{"abc"}}, {{"abcd"}});
+ static_assert(!prefix_string_pointer, "");
+
+ // A null const char * is distinct from any non-null string literal.
+ constexpr bool null_vs_string =
+ class_string_pointer_same({{nullptr}}, {{"abc"}});
+ static_assert(!null_vs_string, "");
+
+ // Floating-point uses bitwise equality: -0.0 is distinguishable from +0.0.
+ struct WithFloat { double d; };
+ constexpr bool class_float_same(std::initializer_list<WithFloat> a,
+ std::initializer_list<WithFloat> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_signed_zero = class_float_same({{-0.0}}, {{0.0}});
+ static_assert(!different_signed_zero, "");
+
+ // Anonymous union field: the active member differs between LHS and RHS, so
+ // the backing arrays must have distinct addresses.
+ struct WithAnonUnion {
+ union { int i; float f; };
+ int tag;
+ };
+ constexpr bool class_anon_union_same(std::initializer_list<WithAnonUnion> a,
+ std::initializer_list<WithAnonUnion> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_union_member =
+ class_anon_union_same({{.i = 0, .tag = 0}}, {{.f = 0.0f, .tag = 0}});
+ static_assert(!different_union_member, "");
+
+ // Member pointers compare structurally: distinct target fields imply
+ // distinct member-pointer values, so the backing arrays differ.
+ struct Holder { int x; int y; };
+ struct WithMemberPtr { int Holder::*p; };
+ constexpr bool class_member_ptr_same(
+ std::initializer_list<WithMemberPtr> a,
+ std::initializer_list<WithMemberPtr> b) {
+ return a.begin() == b.begin(); // #cwg2765-member-ptr-compare
+ }
+ constexpr bool different_member_ptr =
+ class_member_ptr_same({{&Holder::x}}, {{&Holder::y}});
+ static_assert(!different_member_ptr, "");
+
+ // Same member pointer in both arrays + only this field -> potentially
+ // overlapping, so address comparison is unspecified.
+ constexpr bool same_member_ptr =
+ class_member_ptr_same({{&Holder::x}}, {{&Holder::x}}); // #cwg2765-same-mptr-call
+ // both-error at -2 {{constexpr variable 'same_member_ptr' must be initialized by a constant expression}}
+ // both-note@#cwg2765-member-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-same-mptr-call {{in call to}}
+
+ // Weak member functions: pointers to the same weak decl still resolve to
+ // a single merged target at link time, so two arrays holding pointers to
+ // the same weak member are still potentially-overlapping (not provably
+ // distinct, but not provably equal either — the link-time merge is fine).
+ struct HolderWithWeak {
+ __attribute__((weak)) void f();
+ __attribute__((weak)) void g();
+ };
+ struct WithWeakMemberPtr { void (HolderWithWeak::*p)(); };
+ constexpr bool class_weak_mptr_same(
+ std::initializer_list<WithWeakMemberPtr> a,
+ std::initializer_list<WithWeakMemberPtr> b) {
+ return a.begin() == b.begin(); // #cwg2765-weak-mptr-compare
+ }
+ // Distinct weak decls may merge at link time -> Unknown.
+ constexpr bool different_weak_member_ptr =
+ class_weak_mptr_same({{&HolderWithWeak::f}}, // #cwg2765-diff-weak-mptr-call
+ {{&HolderWithWeak::g}});
+ // both-error at -3 {{constexpr variable 'different_weak_member_ptr' must be initialized by a constant expression}}
+ // both-note@#cwg2765-weak-mptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-diff-weak-mptr-call {{in call to}}
+
+ // nullptr_t fields: the only value of nullptr_t is null, so this field
+ // never contributes inequality. The other field decides.
+ struct WithNullptrT { decltype(nullptr) np; int n; };
+ constexpr bool class_nullptr_t_same(std::initializer_list<WithNullptrT> a,
+ std::initializer_list<WithNullptrT> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_after_nullptr_t =
+ class_nullptr_t_same({{nullptr, 1}}, {{nullptr, 2}});
+ static_assert(!different_after_nullptr_t, "");
+
+ // Aggregate carrying a std::initializer_list member: the backing array's
+ // extending declaration is the enclosing aggregate, not the
+ // initializer_list. The dedicated marker on MaterializeTemporaryExpr
+ // still recognises the backing array.
+ struct WithIL { std::initializer_list<int> il; };
+ constexpr WithIL agg_a{{1}}, agg_b{{1}};
+ constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin(); // #cwg2765-agg-compare
+ // both-error at -1 {{constexpr variable 'agg_same' must be initialized by a constant expression}}
+ // both-note@#cwg2765-agg-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+ constexpr WithIL agg_c{{1}}, agg_d{{2}};
+ constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin();
+ static_assert(!agg_different, "");
+
+ // Rvalue reference to array bound to a braced-init-list: the materialized
+ // array is NOT a std::initializer_list backing array.
+ constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) {
+ return &a[0] == &b[0];
+ }
+ constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3});
+ static_assert(!rvref_ok, "");
+
+ // Vector elements: APValue::Profile recurses element-wise, so the
+ // mergeability judgment is correctly element-sensitive (NOT "hard-code
+ // these rare kinds to NotEqual"). Confirmed by runtime observation:
+ // identical-content backing arrays are mergeable, differing ones are not.
+ typedef int v4 __attribute__((ext_vector_type(4)));
+ constexpr v4 va = {1, 2, 3, 4};
+ constexpr v4 vb = {1, 2, 3, 5};
+ struct WithVec { v4 v; };
+ constexpr bool class_vec_same(std::initializer_list<WithVec> a,
+ std::initializer_list<WithVec> b) {
+ return a.begin() == b.begin(); // #cwg2765-vec-compare
+ }
+ constexpr bool same_vec = class_vec_same({{va}}, {{va}}); // #cwg2765-same-vec-call
+ // both-error at -1 {{constexpr variable 'same_vec' must be initialized by a constant expression}}
+ // both-note@#cwg2765-vec-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-same-vec-call {{in call to}}
+
+ constexpr bool different_vec = class_vec_same({{va}}, {{vb}});
+ static_assert(!different_vec, "");
+
+ // Complex elements: Profile bitwise-compares real / imag, so two
+ // complex values differing in only the imaginary part are correctly
+ // recognised as distinct.
+ struct WithComplex { _Complex double c; };
+ constexpr bool class_complex_same(std::initializer_list<WithComplex> a,
+ std::initializer_list<WithComplex> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_complex = class_complex_same(
+ {{__builtin_complex(1.0, 2.0)}}, {{__builtin_complex(1.0, 3.0)}});
+ static_assert(!different_complex, "");
+}
diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
new file mode 100644
index 0000000000000..a64ce068913b5
--- /dev/null
+++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
@@ -0,0 +1,139 @@
+// RUN: %clang_cc1 %std_cxx11- -fexceptions -fcxx-exceptions -fsyntax-only --embed-dir=%S/../../../../Preprocessor/Inputs -Wno-c23-extensions -verify %s
+
+namespace std {
+using size_t = decltype(sizeof(int));
+
+template <class E> class initializer_list {
+ const E *begin_;
+ size_t size_;
+
+public:
+ constexpr initializer_list() : begin_(nullptr), size_(0) {}
+ constexpr initializer_list(const E *begin, size_t size)
+ : begin_(begin), size_(size) {}
+ constexpr const E *begin() const { return begin_; }
+ constexpr const E *end() const { return begin_ + size_; }
+ constexpr size_t size() const { return size_; }
+};
+
+template <class T> struct complex {
+ constexpr complex(double) {}
+};
+
+template <class T> struct vector {
+ vector(initializer_list<T>);
+};
+} // namespace std
+
+namespace example12 {
+void f(std::initializer_list<double> il);
+void g(float x) {
+ f({1, x, 3});
+}
+void h() {
+ f({1, 2, 3});
+}
+
+struct A {
+ mutable int i;
+};
+void q(std::initializer_list<A>);
+void r() {
+ q({A{1}, A{2}, A{3}});
+}
+} // namespace example12
+
+namespace example13 {
+typedef std::complex<double> cmplx;
+std::vector<cmplx> v1 = {1, 2, 3};
+void f() {
+ std::vector<cmplx> v2{1, 2, 3};
+ std::initializer_list<int> i3 = {1, 2, 3};
+}
+
+struct A {
+ std::initializer_list<int> i4; // expected-note {{'std::initializer_list' member declared here}}
+ A() : i4{1, 2, 3} {}
+ // expected-error at -1 {{backing array for 'std::initializer_list' member 'i4' is a temporary object whose lifetime would be shorter than the lifetime of the constructed object}}
+};
+} // namespace example13
+
+namespace embed_example {
+void bytes(std::initializer_list<unsigned char>);
+void f() {
+ bytes({
+#embed <jk.txt>
+ });
+}
+
+constexpr std::initializer_list<unsigned char> jk = {
+#embed <jk.txt>
+};
+static_assert(jk.size() == 2, "");
+static_assert(jk.begin()[0] == 'j', "");
+static_assert(jk.begin()[1] == 'k', "");
+} // namespace embed_example
+
+namespace shared_backing_arrays {
+void f2(std::initializer_list<int> ia, std::initializer_list<int> ib) {
+ (void)(ia.begin() == ib.begin());
+}
+void test() {
+ f2({1, 2, 3}, {1, 2, 3});
+}
+
+void f3() {
+ std::initializer_list<int> i1 = {1, 2, 3, 4, 5};
+ std::initializer_list<int> i2 = {2, 3, 4};
+ (void)(i1.begin() == i2.begin() + 1);
+}
+} // namespace shared_backing_arrays
+
+namespace lifetime_is_unchanged {
+const int *f4(std::initializer_list<int> i4) {
+ return i4.begin();
+}
+void test() {
+ const int *p = f4({1, 2, 3});
+ (void)*p;
+}
+} // namespace lifetime_is_unchanged
+
+namespace destructor_side_effects {
+extern "C" int printf(const char *, ...);
+
+struct C6 {
+ constexpr C6(int) {}
+ ~C6() { printf(" X"); }
+};
+
+void f6(std::initializer_list<C6>) {}
+void test() {
+ f6({1, 2, 3});
+ f6({1, 2, 3});
+}
+} // namespace destructor_side_effects
+
+namespace mutable_members {
+struct S {
+ constexpr S(int i) : i(i) {}
+ mutable int i;
+};
+
+void f(std::initializer_list<S> il) {
+ if (il.begin()->i != 1)
+ throw;
+ il.begin()->i = 4;
+}
+void test() {
+ for (int i = 0; i < 2; ++i)
+ f({1, 2, 3});
+}
+} // namespace mutable_members
+
+namespace annex_c {
+bool ne(std::initializer_list<int> a, std::initializer_list<int> b) {
+ return a.begin() != b.begin() + 1;
+}
+bool b = ne({2, 3}, {1, 2, 3});
+} // namespace annex_c
diff --git a/clang/test/CXX/drs/cwg27xx.cpp b/clang/test/CXX/drs/cwg27xx.cpp
index 16fd56e0c4a63..ea28892a633e7 100644
--- a/clang/test/CXX/drs/cwg27xx.cpp
+++ b/clang/test/CXX/drs/cwg27xx.cpp
@@ -1,10 +1,10 @@
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26 %s
#if __cplusplus == 199711L
#define static_assert(...) __extension__ _Static_assert(__VA_ARGS__)
@@ -19,22 +19,37 @@
namespace std {
#if __cplusplus >= 201103L
-using size_t = decltype(sizeof(0));
-template <typename T>
-struct initializer_list {
- const T *p;
- size_t n;
-
- #if __cplusplus >= 201402L
- constexpr
- #endif
- initializer_list(const T *p, size_t n);
-
- #if __cplusplus >= 201402L
- constexpr
- #endif
- const T* begin() const { return p; };
-};
+ using size_t = decltype(sizeof(int));
+
+ template <class E> class initializer_list {
+ const E *begin_;
+ size_t size_;
+
+ public:
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ initializer_list() : begin_(nullptr), size_(0) {}
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ initializer_list(const E *begin, size_t size)
+ : begin_(begin), size_(size) {}
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ const E *begin() const { return begin_; }
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ size_t size() const { return size_; }
+ };
+
+ struct string_view {
+ const char *begin_;
+ constexpr string_view(const char *begin) : begin_(begin) {}
+ constexpr const char *begin() const { return begin_; }
+ };
#endif
#if __cplusplus >= 202002L
@@ -193,7 +208,7 @@ static_assert(!__is_layout_compatible(StructWithAnonUnion, StructWithAnonUnion3)
#endif
} // namespace cwg2759
-namespace cwg2765 { // cwg2765: partial
+namespace cwg2765 { // cwg2765: 23
static_assert(+"foo" == "foo", "");
// expected-error at -1 {{static assertion expression is not an integral constant expression}}
// expected-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
@@ -211,19 +226,107 @@ static_assert((const char*)"foo" != "oo", "");
#if __cplusplus >= 201103L
constexpr const char *f() { return "foo"; }
-constexpr bool b2 = f() == f();
+
+constexpr bool b2 = f() == f();
// since-cxx11-error at -1 {{constexpr variable 'b2' must be initialized by a constant expression}}
// since-cxx11-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
constexpr const char *p = f();
-constexpr bool b3 = p == p;
-#endif
+constexpr bool b3 = p == p;
+static_assert(b3, "");
+
+constexpr bool b4 = &"xfoo"[1] == &"foo\0y"[0];
+// since-cxx11-error at -1 {{constexpr variable 'b4' must be initialized by a constant expression}}
+// since-cxx11-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
+static_assert("foo" != &"bar"[0], "");
+static_assert((const char *)"foo" != "oo", "");
+
+template <class T>
+constexpr bool f10(T s, T t) {
+ return s.begin() == t.begin(); // #cwg2765-f10-compare
+}
+constexpr bool b10a = f10<std::string_view>("abc", "abc");
+// since-cxx11-error at -1 {{constexpr variable 'b10a' must be initialized by a constant expression}}
+// since-cxx11-note@#cwg2765-f10-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+// since-cxx11-note at -3 {{in call to 'f10<std::string_view>({&"abc"[0]}, {&"abc"[0]})'}}
+constexpr bool b10b = f10<std::string_view>("abc", "def");
+static_assert(!b10b, "");
+
+constexpr const char *a11 = "abc";
+constexpr const char *b11 = "abc";
+constexpr bool f11() { return a11 == b11; } // #cwg2765-f11-compare
+// cxx98-20-error at -1 {{constexpr function never produces a constant expression}}
+// cxx98-20-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+static_assert(f11() || !f11(), "");
+// since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
+// since-cxx11-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+// since-cxx11-note at -3 {{in call to 'f11()'}}
#if __cplusplus >= 201402L
-constexpr std::initializer_list<int *> il1 = { (int *)nullptr };
-constexpr std::initializer_list<unsigned long> il2 = { 0 };
-constexpr bool b7 = il1.begin() == (void *)il2.begin();
-// FIXME-error at -1 {{constexpr variable 'b7' must be initialized by a constant expression}}
-// FIXME-note at -2 {{address of a constexpr-unknown object cannot be used for comparison}}
+constexpr bool f(std::initializer_list<int> a, std::initializer_list<int> b) {
+ return a.begin() != b.begin(); // #cwg2765-init-list-compare
+}
+static_assert(f({1}, {1}), "");
+// since-cxx14-error at -1 {{static assertion expression is not an integral constant expression}}
+// since-cxx14-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note at -3 {{in call to 'f({&{1}[0], 1}, {&{1}[0], 1})'}}
+
+constexpr bool f9(const int *p) {
+ std::initializer_list<int> il = {1, 2, 3};
+ return p ? (p == il.begin()) : f9(il.begin()); // #cwg2765-f9-compare
+}
+constexpr bool b9 = f9(nullptr);
+// since-cxx14-error at -1 {{constexpr variable 'b9' must be initialized by a constant expression}}
+// since-cxx14-note@#cwg2765-f9-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note@#cwg2765-f9-compare {{in call to 'f9(&{1, 2, 3}[0])'}}
+// since-cxx14-note at -4 {{in call to 'f9(nullptr)'}}
+
+constexpr bool b10c = f10<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+// since-cxx14-error at -1 {{constexpr variable 'b10c' must be initialized by a constant expression}}
+// since-cxx14-note@#cwg2765-f10-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note at -3 {{in call to 'f10<std::initializer_list<int>>({&{1, 2, 3}[0], 3}, {&{1, 2, 3}[0], 3})'}}
+constexpr bool b10d = f10<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
+static_assert(!b10d, "");
+
+constexpr bool ne(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.begin() != b.begin() + 1; // #cwg2765-annex-c-compare
+}
+constexpr bool annex_c = ne({2, 3}, {1, 2, 3});
+// since-cxx14-error at -1 {{constexpr variable 'annex_c' must be initialized by a constant expression}}
+// since-cxx14-note@#cwg2765-annex-c-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note at -3 {{in call to 'ne({&{2, 3}[0], 2}, {&{1, 2, 3}[0], 3})'}}
+
+int a_order[10];
+constexpr int inc(int &i) { return (i += 1); }
+constexpr int twox(int &i) { return (i *= 2); }
+constexpr int f_order(int i) { return inc(i) + twox(i); }
+constexpr bool g_order() { return &a_order[f_order(1)] == &a_order[6]; }
+constexpr bool b_order = g_order();
+static_assert(b_order, "");
+
+// Aggregate carrying a std::initializer_list member: the backing array's
+// extending declaration is the enclosing aggregate, not the
+// initializer_list. The precise marker on MaterializeTemporaryExpr must
+// still recognise the backing array.
+struct WithIL { std::initializer_list<int> il; };
+constexpr WithIL agg_a{{1}}, agg_b{{1}};
+constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin();
+// since-cxx14-error at -1 {{constexpr variable 'agg_same' must be initialized by a constant expression}}
+// since-cxx14-note at -2 {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+constexpr WithIL agg_c{{1}}, agg_d{{2}};
+constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin();
+static_assert(!agg_different, "");
+
+// Rvalue reference to array bound to a braced-init-list: the materialized
+// array is not a std::initializer_list backing array, so address
+// comparisons across two such temporaries are well-defined.
+constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) {
+ return &a[0] == &b[0];
+}
+constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3});
+static_assert(!rvref_ok, "");
+#endif
#endif
} // namespace cwg2765
diff --git a/clang/test/CodeGenCXX/Inputs/jk.txt b/clang/test/CodeGenCXX/Inputs/jk.txt
new file mode 100644
index 0000000000000..93d177a48c83a
--- /dev/null
+++ b/clang/test/CodeGenCXX/Inputs/jk.txt
@@ -0,0 +1 @@
+jk
\ No newline at end of file
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
new file mode 100644
index 0000000000000..3f5ecac648066
--- /dev/null
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+
+namespace std {
+using size_t = decltype(sizeof(int));
+
+template <class E> class initializer_list {
+ const E *begin_;
+ size_t size_;
+
+public:
+ constexpr initializer_list() : begin_(nullptr), size_(0) {}
+ constexpr initializer_list(const E *begin, size_t size)
+ : begin_(begin), size_(size) {}
+ constexpr const E *begin() const { return begin_; }
+ constexpr size_t size() const { return size_; }
+};
+} // namespace std
+
+namespace example12 {
+void f(std::initializer_list<double> il);
+
+void g(float x) {
+ // CHECK-LABEL: define{{.*}} void @_ZN9example121gEf(
+ // CHECK: alloca [3 x double],
+ // CHECK: fpext float
+ // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
+ f({1, x, 3});
+}
+
+void h() {
+ // CHECK-LABEL: define{{.*}} void @_ZN9example121hEv(
+ // CHECK: alloca [3 x double],
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24,
+ // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
+ f({1, 2, 3});
+}
+} // namespace example12
+
+namespace embed_example {
+void bytes(std::initializer_list<unsigned char>);
+
+void f() {
+ // CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv(
+ // CHECK: alloca [2 x i8],
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2,
+ // CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE(
+ bytes({
+#embed <jk.txt>
+ });
+}
+} // namespace embed_example
+
+namespace destructor_side_effects {
+extern "C" int printf(const char *, ...);
+
+struct C6 {
+ constexpr C6(int) {}
+ ~C6() { printf(" X"); }
+};
+
+void f6(std::initializer_list<C6>) {}
+
+void test() {
+ // CHECK-LABEL: define{{.*}} void @_ZN23destructor_side_effects4testEv(
+ // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE(
+ // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev(
+ // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE(
+ // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev(
+ f6({1, 2, 3});
+ f6({1, 2, 3});
+}
+} // namespace destructor_side_effects
+
+namespace mutable_members {
+struct S {
+ constexpr S(int i) : i(i) {}
+ mutable int i;
+};
+
+void f(std::initializer_list<S> il) {
+ if (il.begin()->i != 1)
+ throw;
+ il.begin()->i = 4;
+}
+
+void test() {
+ // CHECK-LABEL: define{{.*}} void @_ZN15mutable_members4testEv(
+ // CHECK: alloca [3 x %"struct.mutable_members::S"],
+ // CHECK: call void @_ZN15mutable_members1fESt16initializer_listINS_1SEE(
+ for (int i = 0; i < 2; ++i)
+ f({1, 2, 3});
+}
+} // namespace mutable_members
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 9988fcd333e75..264c4cee504d7 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -19160,7 +19160,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td>[<a href="https://wg21.link/intro.object">intro.object</a>]</td>
<td>DR</td>
<td>Address comparisons between potentially non-unique objects during constant evaluation</td>
- <td class="partial" align="center">Partial</td>
+ <td class="full" align="center">Clang 23</td>
</tr>
<tr class="open" id="2766">
<td><a href="https://cplusplus.github.io/CWG/issues/2766.html">2766</a></td>
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index f96b0cafcf2c4..427d4173cff95 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -245,7 +245,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Static storage for braced initializers</td>
<td><a href="https://wg21.link/P2752R3">P2752R3</a> (<a href="#dr">DR</a>)</td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr>
<td>User-generated <tt>static_assert</tt> messages</td>
>From a75279617e01c52b66d447d8a30a318d48a316a6 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 23 Jun 2026 20:16:08 +0800
Subject: [PATCH 2/5] Generate the static backing array in CodeGen, avoid copy
when construct std::vector
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ByteCode/Interp.cpp | 276 ++++++++++++---
clang/lib/AST/ByteCode/Interp.h | 10 +-
clang/lib/AST/ExprConstant.cpp | 322 +++++++++++++++---
clang/lib/CodeGen/CGExpr.cpp | 38 +++
clang/lib/CodeGen/CodeGenFunction.h | 4 +
clang/lib/CodeGen/CodeGenModule.cpp | 23 ++
clang/lib/CodeGen/CodeGenModule.h | 4 +
clang/test/AST/ByteCode/initializer_list.cpp | 64 ++++
.../cxx0x-initializer-stdinitializerlist.cpp | 4 +-
.../CodeGenCXX/p2752r3-initializer-list.cpp | 30 +-
.../ptrauth-member-function-pointer.cpp | 6 +-
.../SemaCXX/constant-expression-cxx11.cpp | 14 +
12 files changed, 700 insertions(+), 95 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 79711d2e6a21d..8a24c1a73adf8 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2874,63 +2874,261 @@ getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
return ArraySubobjectInfo{std::move(Array), *Loc};
}
-/// Returns true if \p LHS and \p RHS both designate elements of
-/// std::initializer_list backing arrays whose values agree on the overlapping
-/// region. Such backing arrays may be merged by the implementation, so
-/// comparing their addresses produces an unspecified result and is rejected
-/// as a constant expression.
-bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS) {
- const ASTContext &Ctx = S.getASTContext();
- std::optional<ArraySubobjectInfo> LHSInfo =
- getArraySubobjectLocation(Ctx, LHS);
- std::optional<ArraySubobjectInfo> RHSInfo =
- getArraySubobjectLocation(Ctx, RHS);
- if (!LHSInfo || !RHSInfo)
- return false;
+/// Returns the enclosing array block for a pointer into an array subobject.
+static std::optional<Pointer> getContainingArray(const Pointer &Ptr) {
+ if (Ptr.isZero() || !Ptr.isBlockPointer())
+ return std::nullopt;
+
+ PtrView P = Ptr.view();
+ while (!P.isRoot()) {
+ if (P.isArrayElement()) {
+ PtrView Expanded = P.expand();
+ return Pointer(Expanded.getArray());
+ }
+ P = P.getBase();
+ }
+
+ return std::nullopt;
+}
+
+/// Returns true for array objects whose addresses are potentially non-unique
+/// and whose contents are represented by interpreter storage/APValue.
+static bool isPotentiallyNonUniqueAPValueArray(const Pointer &Array) {
+ if (isInitializerListBackingArray(Array))
+ return true;
+ if (const auto *VD = Array.getDeclDesc()->asValueDecl())
+ return isa<TemplateParamObjectDecl>(VD);
+ return false;
+}
+
+/// Returns the enclosing potentially non-unique APValue-backed array and the
+/// element-relative location designated by Ptr.
+static std::optional<ArraySubobjectInfo>
+getNonUniqueAPValueArraySubobjectLocation(const ASTContext &Ctx,
+ const Pointer &Ptr) {
+ if (std::optional<ArraySubobjectInfo> InitListInfo =
+ getArraySubobjectLocation(Ctx, Ptr))
+ return InitListInfo;
+
+ if (!Ptr.isZero() && Ptr.isBlockPointer() && Ptr.isArrayRoot() &&
+ isPotentiallyNonUniqueAPValueArray(Ptr)) {
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Ptr.getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
+ return ArraySubobjectInfo{Ptr,
+ ArraySubobjectLocation{0, CharUnits::Zero()}};
+ }
+
+ std::optional<Pointer> Array = getContainingArray(Ptr);
+ if (!Array || !isPotentiallyNonUniqueAPValueArray(*Array))
+ return std::nullopt;
+
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array->getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
+
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ APValue ArrayValue = Array->toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath() ||
+ !ArrayValue.isLValue() || !ArrayValue.hasLValuePath())
+ return std::nullopt;
+
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ ArrayRef<APValue::LValuePathEntry> ArrayPath = ArrayValue.getLValuePath();
+ if (Path.size() <= ArrayPath.size())
+ return std::nullopt;
+ for (unsigned I = 0, N = ArrayPath.size(); I != N; ++I) {
+ if (Path[I] != ArrayPath[I])
+ return std::nullopt;
+ }
+ Path = Path.drop_front(ArrayPath.size());
+
+ uint64_t Index = Path.front().getAsArrayIndex();
+ bool IsValidOnePastEnd = Path.size() == 1;
+ std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ Ctx, ArrayType, Index,
+ PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
+ IsValidOnePastEnd);
+ if (!Loc)
+ return std::nullopt;
+
+ return ArraySubobjectInfo{std::move(*Array), *Loc};
+}
+
+namespace {
+/// Pairs a string literal array with the element-relative location of a pointer
+/// into it and the literal used to materialize character APValues.
+struct StringArraySubobjectInfo {
+ Pointer Array;
+ ArraySubobjectLocation Loc;
+ const StringLiteral *Literal;
+};
+} // namespace
+
+/// Returns the string literal array and element-relative location designated by
+/// Ptr.
+static std::optional<StringArraySubobjectInfo>
+getStringArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
+ if (!Ptr.pointsToStringLiteral())
+ return std::nullopt;
+
+ Pointer Array = Ptr.getDeclPtr();
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
- if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement)
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
+ return std::nullopt;
+
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ if (Path.empty())
+ return std::nullopt;
+
+ uint64_t Index = Path.front().getAsArrayIndex();
+ bool IsValidOnePastEnd = Path.size() == 1;
+ std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
+ if (!Loc)
+ return std::nullopt;
+
+ return StringArraySubobjectInfo{
+ std::move(Array), *Loc, cast<StringLiteral>(Ptr.getDeclDesc()->asExpr())};
+}
+
+/// Extracts one character from a string literal as an integer APValue payload.
+static APSInt extractStringLiteralCharacter(const ASTContext &Ctx,
+ const StringLiteral *Literal,
+ uint64_t Index) {
+ const ConstantArrayType *ArrayType =
+ Ctx.getAsConstantArrayType(Literal->getType());
+ assert(ArrayType && "string literal isn't an array");
+ QualType CharType = ArrayType->getElementType();
+ assert(CharType->isIntegerType() && "unexpected character type");
+ APSInt Value(Ctx.getTypeSize(CharType), CharType->isUnsignedIntegerType());
+ if (Index < Literal->getLength())
+ Value = Literal->getCodeUnit(Index);
+ return Value;
+}
+
+/// Compares two array views for a possible storage overlap after accounting
+/// for their pointer offsets.
+template <typename GetLHSElement, typename GetRHSElement>
+static bool arePotentiallyOverlappingArrays(
+ const ASTContext &Ctx, QualType LHSElementType,
+ ArraySubobjectLocation LHSLoc, int64_t LHSSize, GetLHSElement GetLHSElt,
+ QualType RHSElementType, ArraySubobjectLocation RHSLoc, int64_t RHSSize,
+ GetRHSElement GetRHSElt) {
+ if (LHSLoc.OffsetInElement != RHSLoc.OffsetInElement)
return false;
- const auto *LHSArrayType =
- Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
- const auto *RHSArrayType =
- Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
- if (!LHSArrayType || !RHSArrayType ||
- !Ctx.hasSameType(LHSArrayType->getElementType(),
- RHSArrayType->getElementType()))
+ if (!Ctx.hasSameUnqualifiedType(LHSElementType, RHSElementType))
return false;
- QualType ElementType = LHSArrayType->getElementType();
- int64_t LHSSize = LHSInfo->Array.getNumElems();
- int64_t RHSSize = RHSInfo->Array.getNumElems();
- int64_t LHSOffset = LHSInfo->Loc.Index;
- int64_t RHSOffset = RHSInfo->Loc.Index;
+ int64_t LHSOffset = LHSLoc.Index;
+ int64_t RHSOffset = RHSLoc.Index;
int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
if (OverlapBegin >= OverlapEnd)
return false;
for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
- Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow();
- Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow();
- std::optional<APValue> LHSEltValue =
- LHSElt.toRValue(S.getContext(), ElementType);
- std::optional<APValue> RHSEltValue =
- RHSElt.toRValue(S.getContext(), ElementType);
- // Missing element data: be conservative and assume the backing arrays
- // may share storage.
- if (!LHSEltValue || !RHSEltValue)
+ std::optional<APValue> LHSElt = GetLHSElt(I + LHSOffset);
+ std::optional<APValue> RHSElt = GetRHSElt(I + RHSOffset);
+ // Missing element data: be conservative and assume the arrays may share
+ // storage.
+ if (!LHSElt || !RHSElt)
return true;
-
- if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx))
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
return false;
}
return true;
}
+/// Returns true if a string literal and a potentially non-unique constant
+/// array could share storage at the designated addresses.
+static bool mayOverlapStringLiteralAndConstantArray(InterpState &S,
+ const Pointer &StringPtr,
+ const Pointer &ArrayPtr) {
+ const ASTContext &Ctx = S.getASTContext();
+ std::optional<StringArraySubobjectInfo> StringInfo =
+ getStringArraySubobjectLocation(Ctx, StringPtr);
+ std::optional<ArraySubobjectInfo> ArrayInfo =
+ getNonUniqueAPValueArraySubobjectLocation(Ctx, ArrayPtr);
+ if (!StringInfo || !ArrayInfo)
+ return false;
+
+ const auto *StringArrayType =
+ Ctx.getAsConstantArrayType(StringInfo->Array.getFieldDesc()->getType());
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(ArrayInfo->Array.getFieldDesc()->getType());
+ if (!StringArrayType || !ArrayType)
+ return false;
+
+ return arePotentiallyOverlappingArrays(
+ Ctx, StringArrayType->getElementType(), StringInfo->Loc,
+ StringInfo->Array.getNumElems(),
+ [&](int64_t Index) -> std::optional<APValue> {
+ return APValue(
+ extractStringLiteralCharacter(Ctx, StringInfo->Literal, Index));
+ },
+ ArrayType->getElementType(), ArrayInfo->Loc,
+ ArrayInfo->Array.getNumElems(),
+ [&](int64_t Index) {
+ Pointer Elt = ArrayInfo->Array.atIndex(Index).narrow();
+ return Elt.toRValue(S.getContext(), ArrayType->getElementType());
+ });
+}
+
+/// Returns true if two APValue-backed potentially non-unique arrays could share
+/// storage at the designated addresses.
+static bool arePotentiallyOverlappingAPValueArrays(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS) {
+ const ASTContext &Ctx = S.getASTContext();
+ std::optional<ArraySubobjectInfo> LHSInfo =
+ getNonUniqueAPValueArraySubobjectLocation(Ctx, LHS);
+ std::optional<ArraySubobjectInfo> RHSInfo =
+ getNonUniqueAPValueArraySubobjectLocation(Ctx, RHS);
+ if (!LHSInfo || !RHSInfo)
+ return false;
+
+ const auto *LHSArrayType =
+ Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
+ const auto *RHSArrayType =
+ Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
+ if (!LHSArrayType || !RHSArrayType)
+ return false;
+
+ return arePotentiallyOverlappingArrays(
+ Ctx, LHSArrayType->getElementType(), LHSInfo->Loc,
+ LHSInfo->Array.getNumElems(),
+ [&](int64_t Index) {
+ Pointer Elt = LHSInfo->Array.atIndex(Index).narrow();
+ return Elt.toRValue(S.getContext(), LHSArrayType->getElementType());
+ },
+ RHSArrayType->getElementType(), RHSInfo->Loc,
+ RHSInfo->Array.getNumElems(),
+ [&](int64_t Index) {
+ Pointer Elt = RHSInfo->Array.atIndex(Index).narrow();
+ return Elt.toRValue(S.getContext(), RHSArrayType->getElementType());
+ });
+}
+
+/// Returns true if two pointers designate potentially non-unique objects whose
+/// overlapping elements could be merged into the same storage.
+bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS) {
+ return arePotentiallyOverlappingAPValueArrays(S, LHS, RHS) ||
+ mayOverlapStringLiteralAndConstantArray(S, LHS, RHS) ||
+ mayOverlapStringLiteralAndConstantArray(S, RHS, LHS);
+}
+
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
PrimType T) {
if (T == PT_IntAPS) {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0b73ede59f7dd..417a73eff0b14 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1292,9 +1292,11 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
const Pointer &RHS);
-bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS);
+/// Returns true when LHS and RHS designate potentially non-unique objects that
+/// could share storage, making their comparison non-constant.
+bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS);
template <>
inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1364,7 +1366,7 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) {
+ if (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
return false;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9e77febd9fff3..8297d372ab89e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5089,57 +5089,291 @@ static bool isInitializerListBackingArray(const LValue &LV) {
return MTE && MTE->isBackingArrayForInitializerList();
}
-static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info,
- const Expr *E,
- const LValue &LHS,
- const LValue &RHS) {
- if (!isInitializerListBackingArray(LHS) ||
- !isInitializerListBackingArray(RHS))
- return false;
+/// Returns the underlying string literal expression for bases that denote
+/// string literal objects in the evaluator.
+static const Expr *getStringLiteralBase(APValue::LValueBase Base) {
+ const auto *BaseExpr = Base.dyn_cast<const Expr *>();
+ if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
+ return BaseExpr;
+ return nullptr;
+}
- std::optional<ArraySubobjectLocation> LHSLoc =
- getArraySubobjectLocation(Info.Ctx, LHS);
- std::optional<ArraySubobjectLocation> RHSLoc =
- getArraySubobjectLocation(Info.Ctx, RHS);
- if (!LHSLoc || !RHSLoc)
- return false;
+/// Returns the string-literal array element and element-relative offset
+/// designated by LV, including byte-offset-only lvalues.
+static std::optional<ArraySubobjectLocation>
+getStringArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+ if (!getStringLiteralBase(LV.Base))
+ return std::nullopt;
- if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement)
- return false;
+ if (std::optional<ArraySubobjectLocation> Loc =
+ getArraySubobjectLocation(Ctx, LV))
+ return Loc;
- const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base));
- const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base));
- if (!LHSArrayType || !RHSArrayType ||
- !Info.Ctx.hasSameType(LHSArrayType->getElementType(),
- RHSArrayType->getElementType()))
- return false;
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!ArrayType || LV.Offset.isNegative())
+ return std::nullopt;
- const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS);
- const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS);
- if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray())
- return false;
+ uint64_t ElementSize =
+ Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+ if (!ElementSize)
+ return std::nullopt;
- int64_t LHSSize = LHSArray->getArraySize();
- int64_t RHSSize = RHSArray->getArraySize();
- int64_t LHSOffset = LHSLoc->Index;
- int64_t RHSOffset = RHSLoc->Index;
- int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
- int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
- if (OverlapBegin >= OverlapEnd)
- return false;
+ uint64_t Offset = LV.Offset.getQuantity();
+ uint64_t Index = Offset / ElementSize;
+ if (Index > ArrayType->getZExtSize())
+ return std::nullopt;
- for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
- const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset);
- const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset);
- // Missing element data: be conservative and assume the arrays may share
- // storage.
- if (!LHSElt || !RHSElt)
- return true;
- if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+ return ArraySubobjectLocation{Index,
+ CharUnits::fromQuantity(Offset % ElementSize)};
+}
+
+namespace {
+/// Identifies an enclosing constant array subobject within an lvalue path.
+struct ArraySubobjectPathInfo {
+ const ConstantArrayType *ArrayType = nullptr;
+ unsigned ArrayPathLength = 0;
+ ArraySubobjectLocation Loc;
+};
+
+/// Describes a potentially non-unique array visible to the constant evaluator.
+struct NonUniqueArrayInfo {
+ const ConstantArrayType *ArrayType = nullptr;
+ ArraySubobjectLocation Loc;
+ /// Non-null for string literal arrays; otherwise ArrayValue or
+ /// OwnedArrayValue contains an APValue array.
+ const Expr *StringBase = nullptr;
+ const APValue *ArrayValue = nullptr;
+ std::optional<APValue> OwnedArrayValue;
+
+ /// Returns true when this view describes a string literal array.
+ bool isStringLiteral() const { return StringBase; }
+
+ /// Returns the APValue storage for non-string arrays.
+ const APValue *getArrayValue() const {
+ return OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
+ }
+
+ /// Returns the number of array elements visible through this view.
+ int64_t getSize() const {
+ if (isStringLiteral())
+ return ArrayType->getZExtSize();
+
+ const APValue *Array = getArrayValue();
+ assert(Array && Array->isArray() && "invalid APValue array");
+ return Array->getArraySize();
+ }
+
+ /// Returns the value of one element, or nullopt when the representation does
+ /// not expose enough data to prove it differs.
+ std::optional<APValue> getElement(EvalInfo &Info, int64_t Index) const {
+ if (Index < 0)
+ return std::nullopt;
+ uint64_t UIndex = static_cast<uint64_t>(Index);
+
+ if (isStringLiteral())
+ return APValue(extractStringLiteralCharacter(Info, StringBase, UIndex));
+
+ const APValue *Array = getArrayValue();
+ assert(Array && Array->isArray() && "invalid APValue array");
+ if (const APValue *Elt = GetArrayInitializedElt(*Array, UIndex))
+ return *Elt;
+ return std::nullopt;
+ }
+};
+
+/// Performs potentially-non-unique object overlap checks for two evaluator
+/// lvalues.
+class NonUniqueObjectOverlapChecker {
+public:
+ /// Creates a checker for comparisons diagnosed at expression E.
+ NonUniqueObjectOverlapChecker(EvalInfo &Info, const Expr *E)
+ : Info(Info), E(E) {}
+
+ /// Returns true when LHS and RHS could designate merged storage.
+ bool mayOverlap(const LValue &LHS, const LValue &RHS) const {
+ const ASTContext &Ctx = Info.Ctx;
+ std::optional<NonUniqueArrayInfo> LHSArray = getArrayInfo(LHS);
+ std::optional<NonUniqueArrayInfo> RHSArray = getArrayInfo(RHS);
+ if (!LHSArray || !RHSArray)
+ return false;
+
+ // Keep the existing string-literal diagnostic path authoritative for
+ // string/string comparisons.
+ if (LHSArray->isStringLiteral() && RHSArray->isStringLiteral())
+ return false;
+
+ if (LHSArray->Loc.OffsetInElement != RHSArray->Loc.OffsetInElement)
return false;
+
+ if (!Ctx.hasSameUnqualifiedType(LHSArray->ArrayType->getElementType(),
+ RHSArray->ArrayType->getElementType()))
+ return false;
+
+ int64_t LHSOffset = LHSArray->Loc.Index;
+ int64_t RHSOffset = RHSArray->Loc.Index;
+ int64_t LHSSize = LHSArray->getSize();
+ int64_t RHSSize = RHSArray->getSize();
+ int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+ int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+ if (OverlapBegin >= OverlapEnd)
+ return false;
+
+ for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+ std::optional<APValue> LHSElt = LHSArray->getElement(Info, I + LHSOffset);
+ std::optional<APValue> RHSElt = RHSArray->getElement(Info, I + RHSOffset);
+ // Missing element data: be conservative and assume the arrays may share
+ // storage.
+ if (!LHSElt || !RHSElt)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
+ return false;
+ }
+
+ return true;
}
- return true;
+private:
+ /// Finds the innermost enclosing constant array subobject for an lvalue into
+ /// a template parameter object.
+ std::optional<ArraySubobjectPathInfo>
+ getArraySubobjectPathInfo(const LValue &LV) const {
+ const ASTContext &Ctx = Info.Ctx;
+ if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+ return std::nullopt;
+
+ QualType Type = getType(LV.Base).getNonReferenceType();
+ CharUnits PathOffset = CharUnits::Zero();
+ std::optional<ArraySubobjectPathInfo> Result;
+
+ for (unsigned I = 0, N = LV.Designator.Entries.size(); I != N; ++I) {
+ APValue::LValuePathEntry Entry = LV.Designator.Entries[I];
+
+ if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
+ uint64_t Index = Entry.getAsArrayIndex();
+ CharUnits OffsetInArray = LV.Offset - PathOffset;
+ bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
+ std::optional<ArraySubobjectLocation> Loc =
+ getArraySubobjectLocationImpl(Ctx, ArrayType, Index, OffsetInArray,
+ IsValidOnePastEnd);
+ if (Loc)
+ Result = ArraySubobjectPathInfo{ArrayType, I, *Loc};
+
+ QualType ElementType = ArrayType->getElementType();
+ CharUnits ElementSize = Ctx.getTypeSizeInChars(ElementType);
+ if (ElementSize.isZero())
+ return std::nullopt;
+ if (Index >
+ std::numeric_limits<uint64_t>::max() / ElementSize.getQuantity())
+ return std::nullopt;
+ PathOffset += Index * ElementSize;
+ Type = ElementType;
+ continue;
+ }
+
+ if (const FieldDecl *FD = getAsField(Entry)) {
+ if (FD->getParent()->isInvalidDecl())
+ return std::nullopt;
+ const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(FD->getParent());
+ PathOffset +=
+ Ctx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()));
+ Type = FD->getType();
+ continue;
+ }
+
+ const CXXRecordDecl *Base = getAsBaseClass(Entry);
+ const CXXRecordDecl *Derived = Type->getAsCXXRecordDecl();
+ if (!Derived || !Base)
+ return std::nullopt;
+ const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Derived);
+ PathOffset += isVirtualBaseClass(Entry) ? Layout.getVBaseClassOffset(Base)
+ : Layout.getBaseClassOffset(Base);
+ Type = Ctx.getCanonicalTagType(Base);
+ }
+
+ if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
+ CharUnits OffsetInArray = LV.Offset - PathOffset;
+ if (OffsetInArray.isZero())
+ Result = ArraySubobjectPathInfo{
+ ArrayType, static_cast<unsigned>(LV.Designator.Entries.size()),
+ ArraySubobjectLocation{0, CharUnits::Zero()}};
+ }
+
+ return Result;
+ }
+
+ /// Builds a comparable array view for a string literal, initializer_list
+ /// backing array, or array subobject of a template parameter object.
+ std::optional<NonUniqueArrayInfo> getArrayInfo(const LValue &LV) const {
+ ASTContext &Ctx = Info.Ctx;
+
+ if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+ std::optional<ArraySubobjectLocation> Loc =
+ getStringArraySubobjectLocation(Ctx, LV);
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!Loc || !ArrayType)
+ return std::nullopt;
+
+ NonUniqueArrayInfo Result;
+ Result.ArrayType = ArrayType;
+ Result.Loc = *Loc;
+ Result.StringBase = StringBase;
+ return Result;
+ }
+
+ if (isInitializerListBackingArray(LV)) {
+ std::optional<ArraySubobjectLocation> Loc =
+ getArraySubobjectLocation(Ctx, LV);
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ const APValue *ArrayValue = GetCompleteObjectValue(Info, E, LV);
+ if (!Loc || !ArrayType || !ArrayValue || !ArrayValue->isArray())
+ return std::nullopt;
+
+ NonUniqueArrayInfo Result;
+ Result.ArrayType = ArrayType;
+ Result.Loc = *Loc;
+ Result.ArrayValue = ArrayValue;
+ return Result;
+ }
+
+ const auto *VD = LV.Base.dyn_cast<const ValueDecl *>();
+ const auto *TPO = dyn_cast_if_present<TemplateParamObjectDecl>(VD);
+ if (!TPO)
+ return std::nullopt;
+
+ std::optional<ArraySubobjectPathInfo> PathInfo =
+ getArraySubobjectPathInfo(LV);
+ if (!PathInfo)
+ return std::nullopt;
+
+ APValue ArrayValue;
+ SubobjectDesignator ArrayDesignator = LV.Designator;
+ ArrayDesignator.truncate(Ctx, LV.Base, PathInfo->ArrayPathLength);
+ CompleteObject Obj(LV.Base, const_cast<APValue *>(&TPO->getValue()),
+ TPO->getType());
+ if (!extractSubobject(Info, E, Obj, ArrayDesignator, ArrayValue))
+ return std::nullopt;
+ if (!ArrayValue.isArray())
+ return std::nullopt;
+
+ NonUniqueArrayInfo Result;
+ Result.ArrayType = PathInfo->ArrayType;
+ Result.Loc = PathInfo->Loc;
+ Result.OwnedArrayValue = std::move(ArrayValue);
+ return Result;
+ }
+
+ EvalInfo &Info;
+ const Expr *E;
+};
+} // namespace
+
+/// Returns true when two lvalues designate potentially non-unique arrays whose
+/// overlapping elements could be merged into the same storage.
+static bool ArePotentiallyOverlappingNonUniqueObjects(EvalInfo &Info,
+ const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS) {
+ return NonUniqueObjectOverlapChecker(Info, E).mayOverlap(LHS, RHS);
}
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -19165,8 +19399,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
//
if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
return DiagComparison(diag::note_constexpr_literal_comparison);
- if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue,
- RHSValue))
+ if (ArePotentiallyOverlappingNonUniqueObjects(Info, E, LHSValue,
+ RHSValue))
return DiagComparison(
diag::note_constexpr_non_unique_object_comparison);
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 6abe4331db552..c6a5c8186c1b2 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -514,6 +514,40 @@ static RawAddress createReferenceTemporary(CodeGenFunction &CGF,
llvm_unreachable("unknown storage duration");
}
+/// Emits a constant initializer_list backing array as a private global when the
+/// temporary can be shared for the full expression without observable effects.
+std::optional<RawAddress> CodeGenFunction::tryEmitStaticInitListBackingArray(
+ const MaterializeTemporaryExpr *MTE) {
+ if (!MTE->isBackingArrayForInitializerList())
+ return std::nullopt;
+
+ switch (MTE->getStorageDuration()) {
+ case SD_FullExpression:
+ case SD_Automatic:
+ break;
+ case SD_Thread:
+ case SD_Static:
+ return std::nullopt;
+ case SD_Dynamic:
+ llvm_unreachable("temporary cannot have dynamic storage duration");
+ }
+
+ const Expr *Init = MTE->getSubExpr();
+ QualType Ty = Init->getType();
+ if (!Ty->isArrayType() ||
+ !Ty.isConstantStorage(getContext(), /*ExcludeCtor=*/true,
+ /*ExcludeDtor=*/false))
+ return std::nullopt;
+
+ llvm::Constant *Constant =
+ ConstantEmitter(*this).tryEmitAbstractForMemory(Init, Ty);
+ if (!Constant)
+ return std::nullopt;
+
+ CharUnits Align = getContext().getTypeAlignInChars(Ty);
+ return CGM.EmitStaticInitListBackingArray(Constant, Align);
+}
+
/// Helper method to check if the underlying ABI is AAPCS
static bool isAAPCS(const TargetInfo &TargetInfo) {
return TargetInfo.getABI().starts_with("aapcs");
@@ -585,6 +619,10 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
}
}
+ if (std::optional<RawAddress> StaticArray =
+ tryEmitStaticInitListBackingArray(M))
+ return MakeAddrLValue(*StaticArray, M->getType(), AlignmentSource::Decl);
+
// Create and initialize the reference temporary.
RawAddress Alloca = Address::invalid();
RawAddress Object = createReferenceTemporary(*this, M, E, &Alloca);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 6d0718c243812..3ddeaa8815a4c 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4484,6 +4484,10 @@ class CodeGenFunction : public CodeGenTypeCache {
LValue EmitConditionalOperatorLValue(const AbstractConditionalOperator *E);
LValue EmitCastLValue(const CastExpr *E);
LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
+ /// Attempts to emit an initializer_list backing array as a static constant
+ /// object instead of materializing a stack temporary.
+ std::optional<RawAddress>
+ tryEmitStaticInitListBackingArray(const MaterializeTemporaryExpr *E);
LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
LValue EmitHLSLArrayAssignLValue(const BinaryOperator *E);
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 292706d3c5088..2da1796ef2b61 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7720,6 +7720,29 @@ ConstantAddress CodeGenModule::GetAddrOfGlobalTemporary(
return ConstantAddress(CV, Type, Align);
}
+/// Creates the private constant global used for a static initializer_list
+/// backing array.
+ConstantAddress
+CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
+ CharUnits Align) {
+ LangAS AddrSpace = GetGlobalConstantAddressSpace();
+ auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+ auto *GV = new llvm::GlobalVariable(
+ getModule(), Init->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
+ GV->setAlignment(Align.getAsAlign());
+
+ llvm::Constant *CV = GV;
+ if (AddrSpace != LangAS::Default)
+ CV = performAddrSpaceCast(
+ GV, llvm::PointerType::get(
+ getLLVMContext(),
+ getContext().getTargetAddressSpace(LangAS::Default)));
+
+ return ConstantAddress(CV, Init->getType(), Align);
+}
+
/// EmitObjCPropertyImplementations - Emit information for synthesized
/// properties for an implementation.
void CodeGenModule::EmitObjCPropertyImplementations(const
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 4283b6a3dc869..3bb881aec67e5 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1047,6 +1047,10 @@ class CodeGenModule : public CodeGenTypeCache {
ForDefinition_t IsForDefinition
= NotForDefinition);
+ /// Creates a private constant global for an initializer_list backing array.
+ ConstantAddress EmitStaticInitListBackingArray(llvm::Constant *Init,
+ CharUnits Align);
+
/// Will return a global variable of the given type. If a variable with a
/// different type already exists then a new variable with the right type
/// will be created and all uses of the old variable will be replaced with a
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index a6751453a7f3a..efdc407787fdc 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -92,6 +92,70 @@ namespace cwg2765 {
begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
static_assert(!different_three, "");
+ constexpr bool init_list_overlaps_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+ != "hello"; // #cwg2765-init-list-string-compare \
+ // both-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(init_list_overlaps_string(), "");
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool init_list_differs_from_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+ != "hello"; // both-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(init_list_differs_from_string(), "");
+
+ template <int N> struct FixedString {
+ char data[N];
+ constexpr FixedString(const char (&s)[N]) {
+ for (int i = 0; i != N; ++i)
+ data[i] = s[i];
+ }
+ };
+
+ template <FixedString S>
+ constexpr bool template_array_overlaps_string = // both-error {{constexpr variable 'template_array_overlaps_string<FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+ // both-note {{declared here}}
+ S.data != "hello"; // #cwg2765-template-array-string-compare
+ static_assert(template_array_overlaps_string<"hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in instantiation of variable template specialization}} \
+ // both-note {{initializer of 'template_array_overlaps_string<FixedString<6>{"hello"}>' is not a constant expression}}
+ // both-note@#cwg2765-template-array-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+ template <FixedString S>
+ constexpr bool template_array_differs_from_string =
+ S.data != "hello";
+ static_assert(template_array_differs_from_string<"help">);
+
+ template <FixedString S>
+ constexpr bool init_list_overlaps_template_array() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+ != S.data; // #cwg2765-init-list-template-array-compare
+ }
+ static_assert(init_list_overlaps_template_array<"hello">());
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-template-array-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ template <FixedString S>
+ constexpr bool init_list_differs_from_template_array() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+ != S.data;
+ }
+ static_assert(init_list_differs_from_template_array<"hello">());
+
+ template <FixedString A, FixedString B>
+ constexpr bool template_arrays_overlap_shifted = // both-error {{constexpr variable 'template_arrays_overlap_shifted<FixedString<5>{"ello"}, FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+ // both-note {{declared here}}
+ A.data != B.data + 1; // #cwg2765-template-array-shifted-compare
+ static_assert(template_arrays_overlap_shifted<"ello", "hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in instantiation of variable template specialization}} \
+ // both-note {{initializer of 'template_arrays_overlap_shifted<FixedString<5>{"ello"}, FixedString<6>{"hello"}>' is not a constant expression}}
+ // both-note@#cwg2765-template-array-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
constexpr bool same_object() {
std::initializer_list<int> il = {1, 1, 1};
return il.begin() == il.begin() && il.begin() != il.begin() + 1;
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index 62ea3c991f26c..d813acd3c71f8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -481,8 +481,8 @@ namespace B19773010 {
void f1() {
// CHECK-LABEL: @_ZN9B197730102f1Ev
testcase a{{"", ENUM_CONSTANT}};
- // X86: store ptr @.ref.tmp{{.*}}, ptr %{{.*}}, align 8
- // AMDGCN: store ptr addrspacecast{{.*}} @.ref.tmp{{.*}}{{.*}}, ptr %{{.*}}, align 8
+ // X86: store ptr @.init.list{{.*}}, ptr %{{.*}}, align 8
+ // AMDGCN: store ptr addrspacecast{{.*}} @.init.list{{.*}}{{.*}}, ptr %{{.*}}, align 8
}
void f2() {
// CHECK-LABEL: @_ZN9B197730102f2Ev
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index 3f5ecac648066..d2ff58be1ce7a 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,5 +1,9 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private constant [100 x i32]
+
namespace std {
using size_t = decltype(sizeof(int));
@@ -29,8 +33,9 @@ void g(float x) {
void h() {
// CHECK-LABEL: define{{.*}} void @_ZN9example121hEv(
- // CHECK: alloca [3 x double],
- // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24,
+ // CHECK-NOT: alloca [3 x double]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[DOUBLE_INIT]], ptr %{{.*}}, align 8
// CHECK: call void @_ZN9example121fESt16initializer_listIdE(
f({1, 2, 3});
}
@@ -41,8 +46,9 @@ void bytes(std::initializer_list<unsigned char>);
void f() {
// CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv(
- // CHECK: alloca [2 x i8],
- // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2,
+ // CHECK-NOT: alloca [2 x i8]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[EMBED_INIT]], ptr %{{.*}}, align 8
// CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE(
bytes({
#embed <jk.txt>
@@ -50,6 +56,22 @@ void f() {
}
} // namespace embed_example
+namespace large_constant_list {
+#define TEN 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+#define HUNDO TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN
+
+void f(std::initializer_list<int>);
+
+void g() {
+ // CHECK-LABEL: define{{.*}} void @_ZN19large_constant_list1gEv(
+ // CHECK-NOT: alloca [100 x i32]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[HUNDO_INIT]], ptr %{{.*}}, align 8
+ // CHECK: call void @_ZN19large_constant_list1fESt16initializer_listIiE(
+ f({HUNDO});
+}
+} // namespace large_constant_list
+
namespace destructor_side_effects {
extern "C" int printf(const char *, ...);
diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
index 5963b7ddea170..7ab6b945874cb 100644
--- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
+++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
@@ -21,6 +21,8 @@
// CHECK: @__const._Z13testArrayInitv.p1 = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 }], align 8
// CHECK: @__const._Z13testArrayInitv.c0 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 35591) to i64), i64 0 } }, align 8
// CHECK: @__const._Z13testArrayInitv.c1 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 } }, align 8
+// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
// CHECK: @_ZN22testNoexceptConversion6mfptr1E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S19nonvirtual_noexceptEv, i32 0, i64 [[TYPEDISC3:.*]]) to i64), i64 0 },
// CHECK: @_ZN22testNoexceptConversion6mfptr2E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S16virtual_noexceptEv_vfpthunk_, i32 0, i64 [[TYPEDISC3]]) to i64), i64 0 },
@@ -427,8 +429,8 @@ MethodTy0 gmethod2 = reinterpret_cast<MethodTy0>(&Derived0::virtual1);
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %p1, ptr align 8 @__const._Z13testArrayInitv.p1, i64 16, i1 false)
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %c0, ptr align 8 @__const._Z13testArrayInitv.c0, i64 16, i1 false)
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %c1, ptr align 8 @__const._Z13testArrayInitv.c1, i64 16, i1 false)
-// CHECK: store { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }, ptr %{{.*}} align 8
-// CHECK: store { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }, ptr %{{.*}}, align 8
+// CHECK: store ptr @[[INITLIST0]], ptr %{{.*}}, align 8
+// CHECK: store ptr @[[INITLIST1]], ptr %{{.*}}, align 8
void initList(std::initializer_list<MethodTy1>);
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 47a064c4026b4..3d6350504e8cb 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1939,6 +1939,20 @@ namespace InitializerList {
static_assert(*std::initializer_list<int>{1, 2, 3}.begin() == 1, "");
static_assert(std::initializer_list<int>{1, 2, 3}.begin()[2] == 3, "");
+ constexpr bool init_list_overlaps_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+ != "hello"; // expected-warning {{result of comparison against a string literal is unspecified}} \
+ // expected-note {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ }
+ static_assert(init_list_overlaps_string(), ""); // expected-error {{not an integral constant expression}} \
+ // expected-note {{in call to}}
+
+ constexpr bool init_list_differs_from_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+ != "hello"; // expected-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(init_list_differs_from_string(), "");
+
namespace DR2126 {
constexpr std::initializer_list<float> il = {1.0, 2.0, 3.0};
static_assert(il.begin()[1] == 2.0, "");
>From 8b7eedc9ad05bf3d5a7008a571c4ff0f75b60f7e Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:47:56 +0800
Subject: [PATCH 3/5] [clang] Reuse static initializer_list backing arrays
---
clang/lib/CodeGen/CodeGenModule.cpp | 25 ++++++++-----
clang/lib/CodeGen/CodeGenModule.h | 2 +
.../CodeGenCXX/p2752r3-initializer-list.cpp | 37 +++++++++++++++++--
3 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 2da1796ef2b61..b8aaf553e5f3a 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7727,18 +7727,23 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
CharUnits Align) {
LangAS AddrSpace = GetGlobalConstantAddressSpace();
auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
- auto *GV = new llvm::GlobalVariable(
- getModule(), Init->getType(), /*isConstant=*/true,
- llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
- /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
- GV->setAlignment(Align.getAsAlign());
-
- llvm::Constant *CV = GV;
+ llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
+ if (!Entry) {
+ Entry = new llvm::GlobalVariable(
+ getModule(), Init->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
+ Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+ }
+ Entry->setAlignment(
+ std::max(Entry->getAlign().valueOrOne(), Align.getAsAlign()));
+
+ llvm::Constant *CV = Entry;
if (AddrSpace != LangAS::Default)
CV = performAddrSpaceCast(
- GV, llvm::PointerType::get(
- getLLVMContext(),
- getContext().getTargetAddressSpace(LangAS::Default)));
+ Entry, llvm::PointerType::get(
+ getLLVMContext(),
+ getContext().getTargetAddressSpace(LangAS::Default)));
return ConstantAddress(CV, Init->getType(), Align);
}
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 3bb881aec67e5..f4678d512d343 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -520,6 +520,8 @@ class CodeGenModule : public CodeGenTypeCache {
llvm::StringMap<llvm::GlobalVariable *> CFConstantStringMap;
llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *> ConstantStringMap;
+ llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *>
+ StaticInitListBackingArrayMap;
llvm::DenseMap<const UnnamedGlobalConstantDecl *, llvm::GlobalVariable *>
UnnamedGlobalConstantDeclMap;
llvm::DenseMap<const Decl*, llvm::Constant *> StaticLocalDeclMap;
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index d2ff58be1ce7a..f121fffa82215 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,8 +1,10 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private constant [2 x i8] c"jk", align 1
-// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private constant [100 x i32]
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
namespace std {
using size_t = decltype(sizeof(int));
@@ -72,6 +74,35 @@ void g() {
}
} // namespace large_constant_list
+namespace shared_static_lists {
+void f(std::initializer_list<int>);
+
+void g() {
+ // CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
+ // CHECK-NOT: alloca [3 x i32]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[INT_INIT]], ptr %{{.*}}, align 8
+ // CHECK: call void @_ZN19shared_static_lists1fESt16initializer_listIiE(
+ // CHECK: store ptr @[[INT_INIT]], ptr %{{.*}}, align 8
+ // CHECK: call void @_ZN19shared_static_lists1fESt16initializer_listIiE(
+ f({1, 2, 3});
+ f({1, 2, 3});
+}
+} // namespace shared_static_lists
+
+namespace mergeable_static_list {
+void f(std::initializer_list<int>);
+
+void g() {
+ // ASM: .section .rodata.cst16,"aM", at progbits,16
+ // ASM: .long 1
+ // ASM-NEXT: .long 2
+ // ASM-NEXT: .long 3
+ // ASM-NEXT: .long 4
+ f({1, 2, 3, 4});
+}
+} // namespace mergeable_static_list
+
namespace destructor_side_effects {
extern "C" int printf(const char *, ...);
>From 99a47b9d7817fdcfd1fc49b403118a87fdfb328c Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:55:54 +0800
Subject: [PATCH 4/5] [clang] Pad static initializer_list arrays for merging
---
clang/lib/CodeGen/CodeGenModule.cpp | 21 +++++++++++++++++--
.../cxx0x-initializer-stdinitializerlist.cpp | 8 +++----
.../CodeGenCXX/p2752r3-initializer-list.cpp | 13 ++++++++----
3 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index b8aaf553e5f3a..450d8e0920b82 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -71,6 +71,7 @@
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/TargetParser/AArch64TargetParser.h"
#include "llvm/TargetParser/RISCVISAInfo.h"
@@ -7727,11 +7728,27 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
CharUnits Align) {
LangAS AddrSpace = GetGlobalConstantAddressSpace();
auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+ llvm::Constant *StoredInit = Init;
+ const llvm::DataLayout &DL = getModule().getDataLayout();
+ uint64_t Size = DL.getTypeAllocSize(Init->getType());
+ uint64_t MergeableSize = llvm::PowerOf2Ceil(std::max<uint64_t>(Size, 4));
+ if (!Init->needsRelocation() && Size > 0 && Size <= 32 &&
+ MergeableSize >= 4 && MergeableSize != Size) {
+ uint64_t PaddingSize = MergeableSize - Size;
+ auto *PaddingTy =
+ llvm::ArrayType::get(llvm::Type::getInt8Ty(getLLVMContext()),
+ PaddingSize);
+ auto *PaddedTy = llvm::StructType::get(
+ getLLVMContext(), {Init->getType(), PaddingTy}, /*isPacked=*/true);
+ StoredInit = llvm::ConstantStruct::get(
+ PaddedTy, {Init, llvm::ConstantAggregateZero::get(PaddingTy)});
+ }
+
llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
if (!Entry) {
Entry = new llvm::GlobalVariable(
- getModule(), Init->getType(), /*isConstant=*/true,
- llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ getModule(), StoredInit->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, StoredInit, ".init.list",
/*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
}
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index d813acd3c71f8..cd7fa194624c8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -85,10 +85,10 @@ std::initializer_list<int> thread_local x = {1, 2, 3, 4};
// AMDGCN: @[[PARTLY_CONSTANT_SECOND:_ZGRN15partly_constant2ilE2_]] = internal addrspace(1) global [2 x i32] zeroinitializer, align 4
// AMDGCN: @[[PARTLY_CONSTANT_THIRD:_ZGRN15partly_constant2ilE3_]] = internal addrspace(1) constant [4 x i32] [i32 5, i32 6, i32 7, i32 8], align 4
-// X86: @[[REFTMP1:.*]] = private constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:.*]] = private constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
-// AMDGCN: @[[REFTMP1:.*]] = private addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:.*]] = private addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// X86: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
+// AMDGCN: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
// CHECK: appending global
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index f121fffa82215..b872afc5b6245 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,10 +1,10 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x double], [8 x i8] }> <{ [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], [8 x i8] zeroinitializer }>, align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [2 x i8], [2 x i8] }> <{ [2 x i8] c"jk", [2 x i8] zeroinitializer }>, align 1
// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
-// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x i32], [4 x i8] }> <{ [3 x i32] [i32 1, i32 2, i32 3], [4 x i8] zeroinitializer }>, align 4
namespace std {
using size_t = decltype(sizeof(int));
@@ -78,6 +78,11 @@ namespace shared_static_lists {
void f(std::initializer_list<int>);
void g() {
+ // ASM: .section .rodata.cst16,"aM", at progbits,16
+ // ASM: .long 1
+ // ASM-NEXT: .long 2
+ // ASM-NEXT: .long 3
+ // ASM-NEXT: .zero 4
// CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
// CHECK-NOT: alloca [3 x i32]
// CHECK-NOT: llvm.memcpy
@@ -94,11 +99,11 @@ namespace mergeable_static_list {
void f(std::initializer_list<int>);
void g() {
- // ASM: .section .rodata.cst16,"aM", at progbits,16
// ASM: .long 1
// ASM-NEXT: .long 2
// ASM-NEXT: .long 3
// ASM-NEXT: .long 4
+ // ASM-NEXT: .size {{.*}}, 16
f({1, 2, 3, 4});
}
} // namespace mergeable_static_list
>From df26b56fea61fcdea0abdac16a82f0589b4ff1f9 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 30 Jun 2026 20:10:55 +0800
Subject: [PATCH 5/5] Fix test
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
index 7ab6b945874cb..0fb5ba8f6550c 100644
--- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
+++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
@@ -21,8 +21,8 @@
// CHECK: @__const._Z13testArrayInitv.p1 = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 }], align 8
// CHECK: @__const._Z13testArrayInitv.c0 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 35591) to i64), i64 0 } }, align 8
// CHECK: @__const._Z13testArrayInitv.c1 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 } }, align 8
-// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
-// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
// CHECK: @_ZN22testNoexceptConversion6mfptr1E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S19nonvirtual_noexceptEv, i32 0, i64 [[TYPEDISC3:.*]]) to i64), i64 0 },
// CHECK: @_ZN22testNoexceptConversion6mfptr2E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S16virtual_noexceptEv_vfpthunk_, i32 0, i64 [[TYPEDISC3]]) to i64), i64 0 },
More information about the cfe-commits
mailing list