[clang] [C++26] Implement P2752R3 'Static storage for braced initializers' (PR #197458)
Yihan Wang via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 13 11:03:21 PDT 2026
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/197458
>From 8bdc39c4519ce7f374a48d985ed276c12b4acc57 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 27 May 2026 09:39:03 -0700
Subject: [PATCH 01/12] [C++26] Implement P2752R3 'Static storage for braced
initializers'
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.md | 1 +
clang/include/clang/AST/ExprCXX.h | 17 +-
clang/include/clang/AST/Stmt.h | 14 +
clang/include/clang/AST/TypeBase.h | 9 +
.../include/clang/Basic/DiagnosticASTKinds.td | 2 +
clang/lib/AST/ASTImporter.cpp | 2 +
clang/lib/AST/ByteCode/Interp.cpp | 112 ++++++
clang/lib/AST/ByteCode/Interp.h | 14 +-
clang/lib/AST/ExprCXX.cpp | 1 +
clang/lib/AST/ExprConstShared.h | 30 +-
clang/lib/AST/ExprConstant.cpp | 331 ++++++++++++++++--
clang/lib/AST/Type.cpp | 81 +++++
clang/lib/Sema/CheckExprLifetime.cpp | 19 +-
clang/lib/Sema/SemaDeclCXX.cpp | 101 +-----
clang/lib/Sema/SemaInit.cpp | 1 +
clang/lib/Serialization/ASTReaderStmt.cpp | 1 +
clang/lib/Serialization/ASTWriterStmt.cpp | 1 +
clang/test/AST/ByteCode/initializer_list.cpp | 300 ++++++++++++++++
.../dcl.decl/dcl.init/dcl.init.list/p5.cpp | 139 ++++++++
clang/test/CXX/drs/cwg27xx.cpp | 167 +++++++--
clang/test/CodeGenCXX/Inputs/jk.txt | 1 +
.../CodeGenCXX/p2752r3-initializer-list.cpp | 93 +++++
clang/www/cxx_dr_status.html | 2 +-
clang/www/cxx_status.html | 2 +-
24 files changed, 1276 insertions(+), 165 deletions(-)
create mode 100644 clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
create mode 100644 clang/test/CodeGenCXX/Inputs/jk.txt
create mode 100644 clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bbd42848a98c2..d77975d4d69f5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -259,6 +259,7 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
#### C++2c Feature Support
- Clang now propagates `constinit` and `constexpr` in structured bindings with tuple-like initializers.
+- Clang now supports `P2752R3 <https://wg21.link/p2752r3>`_ 'Static storage for braced initializers'. (#GH104487)
- Clang now has partial support for [P1306R5](https://wg21.link/P1306R5) Expansion Statements. Iterating expansion
statements currently cannot be expanded and will result in a diagnostic, but other types of expansion statements work.
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 757f2137c90dc..d97c745b8d304 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4930,7 +4930,22 @@ class MaterializeTemporaryExpr : public Expr {
LifetimeExtendedTemporaryDecl *MTD = nullptr);
MaterializeTemporaryExpr(EmptyShell Empty)
- : Expr(MaterializeTemporaryExprClass, Empty) {}
+ : Expr(MaterializeTemporaryExprClass, Empty) {
+ MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false;
+ }
+
+ /// Whether this materialized temporary is the backing array of a
+ /// std::initializer_list.
+ ///
+ /// This used by [intro.object]/9 "potentially non-unique object" handling.
+ bool isBackingArrayForInitializerList() const {
+ return MaterializeTemporaryExprBits.IsBackingArrayForInitializerList;
+ }
+
+ /// This bitfield will set by SK_StdInitializerList step in Sema.
+ void setBackingArrayForInitializerList(bool V = true) {
+ MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = V;
+ }
/// Retrieve the temporary-generating subexpression whose value will
/// be materialized into a glvalue.
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index f07ba9205661b..e4eebe5986bd5 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -831,6 +831,19 @@ class alignas(void *) Stmt {
SourceLocation Loc;
};
+ class MaterializeTemporaryExprBitfields {
+ friend class ASTStmtReader;
+ friend class MaterializeTemporaryExpr;
+
+ LLVM_PREFERRED_TYPE(ExprBitfields)
+ unsigned : NumExprBits;
+
+ /// Whether the materialized temporary is the backing array of a
+ /// std::initializer_list.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned IsBackingArrayForInitializerList : 1;
+ };
+
class CXXThisExprBitfields {
friend class CXXThisExpr;
@@ -1381,6 +1394,7 @@ class alignas(void *) Stmt {
CXXRewrittenBinaryOperatorBitfields CXXRewrittenBinaryOperatorBits;
CXXBoolLiteralExprBitfields CXXBoolLiteralExprBits;
CXXNullPtrLiteralExprBitfields CXXNullPtrLiteralExprBits;
+ MaterializeTemporaryExprBitfields MaterializeTemporaryExprBits;
CXXThisExprBitfields CXXThisExprBits;
CXXThrowExprBitfields CXXThrowExprBits;
CXXDefaultArgExprBitfields CXXDefaultArgExprBits;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index c9658775f0470..015f196523f27 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -116,6 +116,7 @@ namespace clang {
class ASTContext;
template <typename> class CanQual;
class CXXRecordDecl;
+class Decl;
class DeclContext;
class EnumDecl;
class Expr;
@@ -2762,6 +2763,14 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
bool isNothrowT() const; // C++ std::nothrow_t
bool isAlignValT() const; // C++17 std::align_val_t
bool isStdByteType() const; // C++17 std::byte
+ bool
+ isStdClassTemplateSpecialization(const ASTContext &Ctx, StringRef ClassName,
+ QualType *TypeArg = nullptr,
+ ClassTemplateDecl **CachedDecl = nullptr,
+ const Decl **MalformedDecl = nullptr) const;
+ bool isStdInitializerListType(
+ const ASTContext &Ctx,
+ QualType *Element = nullptr) const; // C++11 std::initializer_list<T>
bool isAtomicType() const; // C11 _Atomic()
bool isUndeducedAutoType() const; // C++11 auto or
// C++14 decltype(auto)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index f86f0157b2b1f..19a5f52c11525 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -101,6 +101,8 @@ def note_constexpr_pointer_constant_comparison : Note<
"at runtime">;
def note_constexpr_literal_comparison : Note<
"comparison of addresses of potentially overlapping literals has unspecified value">;
+def note_constexpr_non_unique_object_comparison : Note<
+ "comparison of addresses of potentially non-unique objects has unspecified value">;
def note_constexpr_literal_arith : Note<
"arithmetic on addresses of potentially overlapping literals has unspecified value">;
def note_constexpr_repeated_literal_eval : Note<
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index d743717ad49de..4ea050995d148 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8697,6 +8697,8 @@ ASTNodeImporter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
auto *ToMTE = new (Importer.getToContext()) MaterializeTemporaryExpr(
ToType, ToTemporaryExpr, E->isBoundToLvalueReference(),
ToMaterializedDecl);
+ ToMTE->setBackingArrayForInitializerList(
+ E->isBackingArrayForInitializerList());
return ToMTE;
}
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index e4675978e2a18..13469758fc5c9 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2856,6 +2856,118 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
return Shorter == Longer.take_front(Shorter.size());
}
+/// Whether Ptr designates an object that is the backing array of a
+/// std::initializer_list.
+static bool isInitializerListBackingArray(const Pointer &Ptr) {
+ if (Ptr.isZero() || !Ptr.isBlockPointer() || Ptr.block()->isDynamic())
+ return false;
+
+ const auto *MTE =
+ dyn_cast_or_null<MaterializeTemporaryExpr>(Ptr.getDeclDesc()->asExpr());
+ return MTE && MTE->isBackingArrayForInitializerList();
+}
+
+namespace {
+/// Pairs an enclosing-array Pointer with the element-relative location of
+/// the original pointer within it.
+struct ArraySubobjectInfo {
+ Pointer Array;
+ ArraySubobjectLocation Loc;
+};
+} // namespace
+
+/// Returns the enclosing array and element-relative location if Ptr
+/// designates an element of an initializer_list backing array,
+/// one-past-the-end of it, or a subobject of an element. Returns
+/// std::nullopt otherwise.
+static std::optional<ArraySubobjectInfo>
+getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
+ if (Ptr.isZero() || !Ptr.isBlockPointer())
+ return std::nullopt;
+
+ Pointer Array = Ptr.getDeclPtr();
+ if (!isInitializerListBackingArray(Array))
+ return std::nullopt;
+
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
+
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
+ return std::nullopt;
+
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ if (Path.empty())
+ return std::nullopt;
+
+ uint64_t Index = Path.front().getAsArrayIndex();
+ bool IsValidOnePastEnd = Path.size() == 1;
+ std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
+ if (!Loc)
+ return std::nullopt;
+ return ArraySubobjectInfo{std::move(Array), *Loc};
+}
+
+/// Returns true if \p LHS and \p RHS both designate elements of
+/// std::initializer_list backing arrays whose values agree on the overlapping
+/// region. Such backing arrays may be merged by the implementation, so
+/// comparing their addresses produces an unspecified result and is rejected
+/// as a constant expression.
+bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS) {
+ const ASTContext &Ctx = S.getASTContext();
+ std::optional<ArraySubobjectInfo> LHSInfo =
+ getArraySubobjectLocation(Ctx, LHS);
+ std::optional<ArraySubobjectInfo> RHSInfo =
+ getArraySubobjectLocation(Ctx, RHS);
+ if (!LHSInfo || !RHSInfo)
+ return false;
+
+ if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement)
+ return false;
+
+ const auto *LHSArrayType =
+ Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
+ const auto *RHSArrayType =
+ Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
+ if (!LHSArrayType || !RHSArrayType ||
+ !Ctx.hasSameType(LHSArrayType->getElementType(),
+ RHSArrayType->getElementType()))
+ return false;
+
+ QualType ElementType = LHSArrayType->getElementType();
+ int64_t LHSSize = LHSInfo->Array.getNumElems();
+ int64_t RHSSize = RHSInfo->Array.getNumElems();
+ int64_t LHSOffset = LHSInfo->Loc.Index;
+ int64_t RHSOffset = RHSInfo->Loc.Index;
+ int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+ int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+ if (OverlapBegin >= OverlapEnd)
+ return false;
+
+ for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+ Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow();
+ Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow();
+ std::optional<APValue> LHSEltValue =
+ LHSElt.toRValue(S.getContext(), ElementType);
+ std::optional<APValue> RHSEltValue =
+ RHSElt.toRValue(S.getContext(), ElementType);
+ // Missing element data: be conservative and assume the backing arrays
+ // may share storage.
+ if (!LHSEltValue || !RHSEltValue)
+ return true;
+
+ if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx))
+ return false;
+ }
+
+ return true;
+}
+
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
PrimType T) {
if (T == PT_IntAPS) {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 201679017c98b..0bb2f9839ad9b 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1302,6 +1302,9 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
const Pointer &RHS);
+bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS);
template <>
inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1346,7 +1349,10 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- // FIXME: The source check here isn't entirely correct.
+ // C++ [intro.object]/9:
+ // An object is potentially non-unique if it is a string literal object,
+ // the backing array of an initializer list, or a subobject thereof.
+ // FIXME: The string literal source check here isn't entirely correct.
if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() &&
LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) {
if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
@@ -1368,6 +1374,12 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
+ if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
+ return false;
+ }
+
// Otherwise we need to do a bunch of extra checks before returning Unordered.
if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
RHS.getOffset() == 0) {
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index a1ea91994969c..cc0a590a26b90 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1825,6 +1825,7 @@ MaterializeTemporaryExpr::MaterializeTemporaryExpr(
LifetimeExtendedTemporaryDecl *MTD)
: Expr(MaterializeTemporaryExprClass, T,
BoundToLvalueReference ? VK_LValue : VK_XValue, OK_Ordinary) {
+ MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false;
if (MTD) {
State = MTD;
MTD->ExprWithTemporary = Temporary;
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index 7ba3071668bd1..e67ce3dabdd37 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
#define LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
+#include "clang/AST/CharUnits.h"
#include "clang/Basic/TypeTraits.h"
#include <cstdint>
#include <optional>
@@ -27,7 +28,8 @@ namespace clang {
class QualType;
class LangOptions;
class ASTContext;
-class CharUnits;
+class APValue;
+class ConstantArrayType;
class Expr;
class CallExpr;
} // namespace clang
@@ -98,6 +100,11 @@ unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
unsigned BuiltinID);
unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
const CallExpr *E);
+/// Whether two APValues could be merged into a single storage location by
+/// the implementation (the relation [intro.object]/9 cares about for
+/// initializer_list backing arrays and string literals).
+bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
+ const ASTContext &Ctx);
uint8_t GFNIMultiplicativeInverse(uint8_t Byte);
uint8_t GFNIMul(uint8_t AByte, uint8_t BByte);
@@ -110,4 +117,23 @@ std::optional<llvm::APFloat>
EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B,
std::optional<llvm::APSInt> RoundingMode, bool IsMin);
-#endif
+/// Where an lvalue into an array element lives: the element index within the
+/// array (or the array length for a one-past-the-end pointer), and the byte
+/// offset from the start of that element.
+struct ArraySubobjectLocation {
+ uint64_t Index;
+ CharUnits OffsetInElement;
+};
+
+/// Computes the array-element location designated by an lvalue whose first
+/// path entry indexes into ArrayType with the given Index and whose
+/// byte offset from the array base is LValueOffset. IsValidOnePastEnd
+/// must be true iff the lvalue is a valid one-past-the-end position of the
+/// array (which the caller determines from its own lvalue representation).
+/// Returns std::nullopt if the lvalue does not designate an element,
+/// one-past-the-end position, or subobject of an element.
+std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+ const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
+ CharUnits LValueOffset, bool IsValidOnePastEnd);
+
+#endif // LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 25a96a9c3eceb..e33d1c73b547c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -56,6 +56,7 @@
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
+#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/StringExtras.h"
@@ -2020,17 +2021,18 @@ struct LValueBaseString {
int CharWidth;
};
-// Gets the lvalue base of LVal as a string.
-static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
+// Gets the lvalue base as a string.
+static bool GetLValueBaseAsString(const ASTContext &Ctx,
+ APValue::LValueBase Base,
LValueBaseString &AsString) {
- const auto *BaseExpr = LVal.Base.dyn_cast<const Expr *>();
+ const auto *BaseExpr = Base.dyn_cast<const Expr *>();
if (!BaseExpr)
return false;
// For ObjCEncodeExpr, we need to compute and store the string.
if (const auto *EE = dyn_cast<ObjCEncodeExpr>(BaseExpr)) {
- Info.Ctx.getObjCEncodingForType(EE->getEncodedType(),
- AsString.ObjCEncodeStorage);
+ Ctx.getObjCEncodingForType(EE->getEncodedType(),
+ AsString.ObjCEncodeStorage);
AsString.Bytes = AsString.ObjCEncodeStorage;
AsString.CharWidth = 1;
return true;
@@ -2049,6 +2051,11 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
return true;
}
+static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
+ LValueBaseString &AsString) {
+ return GetLValueBaseAsString(Info.Ctx, LVal.Base, AsString);
+}
+
// Determine whether two string literals potentially overlap. This will be the
// case if they agree on the values of all the bytes on the overlapping region
// between them.
@@ -2063,31 +2070,31 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
// addresses onwards.
//
// See open core issue CWG2765 which is discussing the desired rule here.
-static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
- const LValue &LHS,
- const LValue &RHS) {
- LValueBaseString LHSString, RHSString;
- if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
- !GetLValueBaseAsString(Info, RHS, RHSString))
- return false;
+static bool ArePotentiallyOverlappingStringLiterals(
+ const LValueBaseString &LHSString, CharUnits LHSOffset,
+ const LValueBaseString &RHSString, CharUnits RHSOffset) {
+ assert(LHSString.Bytes.data() && RHSString.Bytes.data() &&
+ "passing a string with no underlying storage");
// This is the byte offset to the location of the first character of LHS
// within RHS. We don't need to look at the characters of one string that
// would appear before the start of the other string if they were merged.
- CharUnits Offset = RHS.Offset - LHS.Offset;
+ StringRef LHSBytes = LHSString.Bytes;
+ StringRef RHSBytes = RHSString.Bytes;
+ CharUnits Offset = RHSOffset - LHSOffset;
if (Offset.isNegative()) {
- if (LHSString.Bytes.size() < (size_t)-Offset.getQuantity())
+ if (LHSBytes.size() < (size_t)-Offset.getQuantity())
return false;
- LHSString.Bytes = LHSString.Bytes.drop_front(-Offset.getQuantity());
+ LHSBytes = LHSBytes.drop_front(-Offset.getQuantity());
} else {
- if (RHSString.Bytes.size() < (size_t)Offset.getQuantity())
+ if (RHSBytes.size() < (size_t)Offset.getQuantity())
return false;
- RHSString.Bytes = RHSString.Bytes.drop_front(Offset.getQuantity());
+ RHSBytes = RHSBytes.drop_front(Offset.getQuantity());
}
- bool LHSIsLonger = LHSString.Bytes.size() > RHSString.Bytes.size();
- StringRef Longer = LHSIsLonger ? LHSString.Bytes : RHSString.Bytes;
- StringRef Shorter = LHSIsLonger ? RHSString.Bytes : LHSString.Bytes;
+ bool LHSIsLonger = LHSBytes.size() > RHSBytes.size();
+ StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes;
+ StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes;
int ShorterCharWidth = (LHSIsLonger ? RHSString : LHSString).CharWidth;
// The null terminator isn't included in the string data, so check for it
@@ -2105,6 +2112,32 @@ static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
return Shorter == Longer.take_front(Shorter.size());
}
+static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
+ const LValue &LHS,
+ const LValue &RHS) {
+ LValueBaseString LHSString, RHSString;
+ if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
+ !GetLValueBaseAsString(Info, RHS, RHSString))
+ return false;
+
+ return ArePotentiallyOverlappingStringLiterals(LHSString, LHS.Offset,
+ RHSString, RHS.Offset);
+}
+
+/// APValue overload of the string-literal overlap predicate. Returns nullopt
+/// if either operand is not a string-literal (or ObjC encoding) LValue.
+static std::optional<bool> ArePotentiallyOverlappingStringLiterals(
+ const ASTContext &Ctx, const APValue &LHS, const APValue &RHS) {
+ if (!LHS.isLValue() || !RHS.isLValue())
+ return std::nullopt;
+ LValueBaseString LHSString, RHSString;
+ if (!GetLValueBaseAsString(Ctx, LHS.getLValueBase(), LHSString) ||
+ !GetLValueBaseAsString(Ctx, RHS.getLValueBase(), RHSString))
+ return std::nullopt;
+ return ArePotentiallyOverlappingStringLiterals(
+ LHSString, LHS.getLValueOffset(), RHSString, RHS.getLValueOffset());
+}
+
static bool IsWeakLValue(const LValue &Value) {
const ValueDecl *Decl = GetLValueBaseDecl(Value);
return Decl && Decl->isWeak();
@@ -4856,6 +4889,259 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType);
}
+static const APValue *GetArrayInitializedElt(const APValue &Array,
+ uint64_t Index) {
+ if (!Array.isArray() || Index >= Array.getArraySize())
+ return nullptr;
+ if (Index < Array.getArrayInitializedElts())
+ return &Array.getArrayInitializedElt(Index);
+ return Array.hasArrayFiller() ? &Array.getArrayFiller() : nullptr;
+}
+
+/// True if the LValue base names a weak entity (whose link-time identity
+/// may be merged with another).
+static bool isWeakLValueBase(const APValue &V) {
+ if (!V.isLValue())
+ return false;
+ if (const auto *VD = V.getLValueBase().dyn_cast<const ValueDecl *>())
+ return VD->isWeak();
+ return false;
+}
+
+/// True if the member-pointer target decl is weak.
+static bool isWeakMemberPointer(const APValue &V) {
+ if (!V.isMemberPointer())
+ return false;
+ const ValueDecl *D = V.getMemberPointerDecl();
+ return D && D->isWeak();
+}
+
+/// AreAPValuesPotentiallyMergeableSlow - The slow path for
+/// AreAPValuesPotentiallyMergeable. Will be invoked when the two values'
+/// APValue::Profile already differs. Returns true if the disagreement is one
+/// the runtime can collapse (overlapping string literals, weak decls that might
+/// resolve to the same target), recursing into aggregate kinds so that
+/// aggregates containing such leaves are still reported as mergeable.
+static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
+ const APValue &RHS,
+ const ASTContext &Ctx) {
+ if (!LHS.hasValue() || !RHS.hasValue())
+ return true;
+ if (LHS.getKind() != RHS.getKind())
+ return false;
+
+ switch (LHS.getKind()) {
+ case APValue::LValue: {
+ // Distinct string-literal LValues whose content could be merged.
+ if (auto Overlap = ArePotentiallyOverlappingStringLiterals(Ctx, LHS, RHS))
+ return *Overlap;
+ // Either side names a weak entity -> link-time may resolve to one.
+ return isWeakLValueBase(LHS) || isWeakLValueBase(RHS);
+ }
+
+ case APValue::MemberPointer:
+ return isWeakMemberPointer(LHS) || isWeakMemberPointer(RHS);
+
+ case APValue::Struct: {
+ if (LHS.getStructNumBases() != RHS.getStructNumBases() ||
+ LHS.getStructNumFields() != RHS.getStructNumFields())
+ return false;
+ for (unsigned I = 0, N = LHS.getStructNumBases(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getStructBase(I),
+ RHS.getStructBase(I), Ctx))
+ return false;
+ for (unsigned I = 0, N = LHS.getStructNumFields(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getStructField(I),
+ RHS.getStructField(I), Ctx))
+ return false;
+ return true;
+ }
+
+ case APValue::Union: {
+ if (LHS.getUnionField() != RHS.getUnionField())
+ return false;
+ if (!LHS.getUnionField())
+ return true;
+ return AreAPValuesPotentiallyMergeable(LHS.getUnionValue(),
+ RHS.getUnionValue(), Ctx);
+ }
+
+ case APValue::Array: {
+ if (LHS.getArraySize() != RHS.getArraySize())
+ return false;
+ for (unsigned I = 0, N = LHS.getArraySize(); I != N; ++I) {
+ const APValue *LE = GetArrayInitializedElt(LHS, I);
+ const APValue *RE = GetArrayInitializedElt(RHS, I);
+ // Missing element data: be conservative.
+ if (!LE || !RE)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
+ return false;
+ }
+ return true;
+ }
+
+ case APValue::Vector: {
+ if (LHS.getVectorLength() != RHS.getVectorLength())
+ return false;
+ for (unsigned I = 0, N = LHS.getVectorLength(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
+ RHS.getVectorElt(I), Ctx))
+ return false;
+ return true;
+ }
+
+ case APValue::Matrix: {
+ if (LHS.getMatrixNumRows() != RHS.getMatrixNumRows() ||
+ LHS.getMatrixNumColumns() != RHS.getMatrixNumColumns())
+ return false;
+ for (unsigned I = 0, N = LHS.getMatrixNumElements(); I != N; ++I)
+ if (!AreAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
+ RHS.getMatrixElt(I), Ctx))
+ return false;
+ return true;
+ }
+
+ // For every scalar kind (Int, Float, FixedPoint, ComplexInt, ComplexFloat,
+ // AddrLabelDiff, None, Indeterminate), Profile already captures the full
+ // observable content. If Profile disagreed, the values are observably
+ // distinct.
+ default:
+ return false;
+ }
+}
+
+bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
+ const ASTContext &Ctx) {
+ // Fast path: bitwise-identical APValues -> definitely mergeable.
+ llvm::FoldingSetNodeID LHSID, RHSID;
+ LHS.Profile(LHSID);
+ RHS.Profile(RHSID);
+ if (LHSID == RHSID)
+ return true;
+
+ return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
+}
+
+std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+ const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
+ CharUnits LValueOffset, bool IsValidOnePastEnd) {
+ uint64_t ArraySize = ArrayType->getZExtSize();
+ if (Index > ArraySize)
+ return std::nullopt;
+
+ if (Index == ArraySize) {
+ if (!IsValidOnePastEnd)
+ return std::nullopt;
+ return ArraySubobjectLocation{Index, CharUnits::Zero()};
+ }
+
+ if (LValueOffset.isNegative())
+ return std::nullopt;
+
+ uint64_t ElementSize =
+ Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+ if (ElementSize && Index > std::numeric_limits<uint64_t>::max() / ElementSize)
+ return std::nullopt;
+
+ uint64_t ElementOffset = ElementSize * Index;
+ uint64_t LValueOffsetQ = LValueOffset.getQuantity();
+ if (LValueOffsetQ < ElementOffset)
+ return std::nullopt;
+
+ uint64_t OffsetInElement = LValueOffsetQ - ElementOffset;
+ if (OffsetInElement > ElementSize)
+ return std::nullopt;
+
+ return ArraySubobjectLocation{Index,
+ CharUnits::fromQuantity(OffsetInElement)};
+}
+
+/// Returns the array element and element-relative location that LV
+/// designates, or std::nullopt if LV is not an element, one-past-the-end
+/// position, or subobject of an element of its base array.
+static std::optional<ArraySubobjectLocation>
+getArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+ if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+ return std::nullopt;
+
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!ArrayType)
+ return std::nullopt;
+
+ uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
+ bool IsValidOnePastEnd =
+ LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
+ return getArraySubobjectLocationImpl(Ctx, ArrayType, Index, LV.Offset,
+ IsValidOnePastEnd);
+}
+
+static const APValue *GetCompleteObjectValue(EvalInfo &Info, const Expr *E,
+ const LValue &LV) {
+ CompleteObject Obj =
+ findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
+ return Obj ? Obj.Value : nullptr;
+}
+
+static bool isInitializerListBackingArray(const LValue &LV) {
+ const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>();
+ const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr);
+ return MTE && MTE->isBackingArrayForInitializerList();
+}
+
+static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info,
+ const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS) {
+ if (!isInitializerListBackingArray(LHS) ||
+ !isInitializerListBackingArray(RHS))
+ return false;
+
+ std::optional<ArraySubobjectLocation> LHSLoc =
+ getArraySubobjectLocation(Info.Ctx, LHS);
+ std::optional<ArraySubobjectLocation> RHSLoc =
+ getArraySubobjectLocation(Info.Ctx, RHS);
+ if (!LHSLoc || !RHSLoc)
+ return false;
+
+ if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement)
+ return false;
+
+ const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base));
+ const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base));
+ if (!LHSArrayType || !RHSArrayType ||
+ !Info.Ctx.hasSameType(LHSArrayType->getElementType(),
+ RHSArrayType->getElementType()))
+ return false;
+
+ const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS);
+ const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS);
+ if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray())
+ return false;
+
+ int64_t LHSSize = LHSArray->getArraySize();
+ int64_t RHSSize = RHSArray->getArraySize();
+ int64_t LHSOffset = LHSLoc->Index;
+ int64_t RHSOffset = RHSLoc->Index;
+ int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+ int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+ if (OverlapBegin >= OverlapEnd)
+ return false;
+
+ for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+ const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset);
+ const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset);
+ // Missing element data: be conservative and assume the arrays may share
+ // storage.
+ if (!LHSElt || !RHSElt)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+ return false;
+ }
+
+ return true;
+}
+
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This
/// can also be used for 'lvalue-to-lvalue' conversions for looking up the
/// glvalue referred to by an entity of reference type.
@@ -19072,9 +19358,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
// This makes the comparison result unspecified, so it's not a constant
// expression.
//
- // TODO: Do we need to handle the initializer list case here?
if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
return DiagComparison(diag::note_constexpr_literal_comparison);
+ if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue,
+ RHSValue))
+ return DiagComparison(
+ diag::note_constexpr_non_unique_object_comparison);
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
return DiagComparison(diag::note_constexpr_opaque_call_comparison,
!IsOpaqueConstantCall(LHSValue));
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 42d148715bc40..9de276d49c538 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3327,6 +3327,87 @@ bool Type::isStdByteType() const {
return false;
}
+bool Type::isStdClassTemplateSpecialization(const ASTContext &Ctx,
+ StringRef ClassName,
+ QualType *TypeArg,
+ ClassTemplateDecl **CachedDecl,
+ const Decl **MalformedDecl) const {
+ QualType SugaredType(this, 0);
+ auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) {
+ if (!MalformedDecl)
+ return;
+ if (!D)
+ D = SugaredType->getAsTagDecl();
+ if (!D || !D->isInStdNamespace())
+ return;
+ IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo();
+ if (II && II->isStr(ClassName))
+ *MalformedDecl = D;
+ };
+
+ ClassTemplateDecl *Template = nullptr;
+ ArrayRef<TemplateArgument> Arguments;
+ if (const TemplateSpecializationType *TST =
+ SugaredType->getAsNonAliasTemplateSpecializationType()) {
+ Template = dyn_cast_or_null<ClassTemplateDecl>(
+ TST->getTemplateName().getAsTemplateDecl());
+ Arguments = TST->template_arguments();
+ } else if (const auto *TT = SugaredType->getAs<TagType>()) {
+ Template = TT->getTemplateDecl();
+ Arguments = TT->getTemplateArgs(Ctx);
+ }
+
+ if (!Template) {
+ ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl());
+ return false;
+ }
+
+ ClassTemplateDecl *Cached = CachedDecl ? *CachedDecl : nullptr;
+ if (!Cached) {
+ CXXRecordDecl *TemplateClass = Template->getTemplatedDecl();
+ IdentifierInfo *II = TemplateClass->getIdentifier();
+ if (!II || !II->isStr(ClassName) || !TemplateClass->isInStdNamespace())
+ return false;
+
+ TemplateParameterList *Params = Template->getTemplateParameters();
+ if (Params->getMinRequiredArguments() != 1 ||
+ !isa<TemplateTypeParmDecl>(Params->getParam(0)) ||
+ Params->getParam(0)->isTemplateParameterPack()) {
+ if (MalformedDecl)
+ *MalformedDecl = TemplateClass;
+ return false;
+ }
+
+ Cached = Template;
+ if (CachedDecl)
+ *CachedDecl = Template;
+ }
+
+ if (Template->getCanonicalDecl() != Cached->getCanonicalDecl())
+ return false;
+
+ if (TypeArg) {
+ if (Arguments.empty() || Arguments[0].getKind() != TemplateArgument::Type)
+ return false;
+
+ QualType ArgType = Arguments[0].getAsType();
+ if (Ctx.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() &&
+ !ArgType.getObjCLifetime()) {
+ Qualifiers Qs;
+ Qs.setObjCLifetime(Qualifiers::OCL_Strong);
+ ArgType = Ctx.getQualifiedType(ArgType, Qs);
+ }
+ *TypeArg = ArgType;
+ }
+
+ return true;
+}
+
+bool Type::isStdInitializerListType(const ASTContext &Ctx,
+ QualType *Element) const {
+ return isStdClassTemplateSpecialization(Ctx, "initializer_list", Element);
+}
+
bool Type::isSpecifierType() const {
// Note that this intentionally does not use the canonical type.
switch (getTypeClass()) {
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index fafee76ec18c3..5e9c6598b332c 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -285,17 +285,11 @@ static bool isContainerOfOwner(const RecordDecl *Container) {
isGslOwnerType(TAs[0].getAsType());
}
-// Returns true if the given Record is `std::initializer_list<pointer>`.
-static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
- if (const auto *CTSD =
- dyn_cast_if_present<ClassTemplateSpecializationDecl>(RD)) {
- const auto &TAs = CTSD->getTemplateArgs();
- return lifetimes::isInStlNamespace(RD) && RD->getIdentifier() &&
- RD->getName() == "initializer_list" && TAs.size() > 0 &&
- TAs[0].getKind() == TemplateArgument::Type &&
- lifetimes::isPointerLikeType(TAs[0].getAsType());
- }
- return false;
+// Returns true if the given type is `std::initializer_list<pointer>`.
+static bool isStdInitializerListOfPointer(QualType T, const ASTContext &Ctx) {
+ QualType ElementType;
+ return T.getNonReferenceType()->isStdInitializerListType(Ctx, &ElementType) &&
+ lifetimes::isPointerLikeType(ElementType);
}
// Returns true if the given constructor is a copy-like constructor, such as
@@ -351,7 +345,8 @@ shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
// array. We perform analysis on it to determine if there are any dangling
// temporaries in the backing array.
// E.g. std::vector<string_view> abc = {string()};
- if (isStdInitializerListOfPointer(RHSRD))
+ if (isStdInitializerListOfPointer(RHSArgType,
+ Ctor->getConstructor()->getASTContext()))
return true;
// RHS must be an owner.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 685412d681680..e947c17d06ca4 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12351,96 +12351,6 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() {
return getStdNamespace();
}
-static bool isStdClassTemplate(Sema &S, QualType SugaredType, QualType *TypeArg,
- const char *ClassName,
- ClassTemplateDecl **CachedDecl,
- const Decl **MalformedDecl) {
- // We're looking for implicit instantiations of
- // template <typename U> class std::{ClassName}.
-
- if (!S.StdNamespace) // If we haven't seen namespace std yet, this can't be
- // it.
- return false;
-
- auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) {
- if (!MalformedDecl)
- return;
- if (!D)
- D = SugaredType->getAsTagDecl();
- if (!D || !D->isInStdNamespace())
- return;
- IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo();
- if (II && II == &S.PP.getIdentifierTable().get(ClassName))
- *MalformedDecl = D;
- };
-
- ClassTemplateDecl *Template = nullptr;
- ArrayRef<TemplateArgument> Arguments;
- if (const TemplateSpecializationType *TST =
- SugaredType->getAsNonAliasTemplateSpecializationType()) {
- Template = dyn_cast_or_null<ClassTemplateDecl>(
- TST->getTemplateName().getAsTemplateDecl());
- Arguments = TST->template_arguments();
- } else if (const auto *TT = SugaredType->getAs<TagType>()) {
- Template = TT->getTemplateDecl();
- Arguments = TT->getTemplateArgs(S.Context);
- }
-
- if (!Template) {
- ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl());
- return false;
- }
-
- if (!*CachedDecl) {
- // Haven't recognized std::{ClassName} yet, maybe this is it.
- // FIXME: It seems we should just reuse LookupStdClassTemplate but the
- // semantics of this are slightly different, most notably the existing
- // "lookup" semantics explicitly diagnose an invalid definition as an
- // error.
- CXXRecordDecl *TemplateClass = Template->getTemplatedDecl();
- if (TemplateClass->getIdentifier() !=
- &S.PP.getIdentifierTable().get(ClassName) ||
- !S.getStdNamespace()->InEnclosingNamespaceSetOf(
- TemplateClass->getNonTransparentDeclContext()))
- return false;
- // This is a template called std::{ClassName}, but is it the right
- // template?
- TemplateParameterList *Params = Template->getTemplateParameters();
- if (Params->getMinRequiredArguments() != 1 ||
- !isa<TemplateTypeParmDecl>(Params->getParam(0)) ||
- Params->getParam(0)->isTemplateParameterPack()) {
- if (MalformedDecl)
- *MalformedDecl = TemplateClass;
- return false;
- }
-
- // It's the right template.
- *CachedDecl = Template;
- }
-
- if (Template->getCanonicalDecl() != (*CachedDecl)->getCanonicalDecl())
- return false;
-
- // This is an instance of std::{ClassName}. Find the argument type.
- if (TypeArg) {
- QualType ArgType = Arguments[0].getAsType();
- // FIXME: Since TST only has as-written arguments, we have to perform the
- // only kind of conversion applicable to type arguments; in Objective-C ARC:
- // - If an explicitly-specified template argument type is a lifetime type
- // with no lifetime qualifier, the __strong lifetime qualifier is
- // inferred.
- if (S.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() &&
- !ArgType.getObjCLifetime()) {
- Qualifiers Qs;
- Qs.setObjCLifetime(Qualifiers::OCL_Strong);
- ArgType = S.Context.getQualifiedType(ArgType, Qs);
- }
- *TypeArg = ArgType;
- }
-
- return true;
-}
-
bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
assert(getLangOpts().CPlusPlus &&
"Looking for std::initializer_list outside of C++.");
@@ -12448,8 +12358,10 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
// We're looking for implicit instantiations of
// template <typename E> class std::initializer_list.
- return isStdClassTemplate(*this, Ty, Element, "initializer_list",
- &StdInitializerList, /*MalformedDecl=*/nullptr);
+ return !Ty.isNull() &&
+ Ty->isStdClassTemplateSpecialization(Context, "initializer_list",
+ Element, &StdInitializerList,
+ /*MalformedDecl=*/nullptr);
}
bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element,
@@ -12460,8 +12372,9 @@ bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element,
// We're looking for implicit instantiations of
// template <typename T> struct std::type_identity.
- return isStdClassTemplate(*this, Ty, Element, "type_identity",
- &StdTypeIdentity, MalformedDecl);
+ return !Ty.isNull() &&
+ Ty->isStdClassTemplateSpecialization(Context, "type_identity", Element,
+ &StdTypeIdentity, MalformedDecl);
}
static ClassTemplateDecl *LookupStdClassTemplate(Sema &S, SourceLocation Loc,
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 09d9f1eabd058..195995d358914 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8709,6 +8709,7 @@ ExprResult InitializationSequence::Perform(Sema &S,
MaterializeTemporaryExpr *MTE = S.CreateMaterializeTemporaryExpr(
CurInit.get()->getType(), CurInit.get(),
/*BoundToLvalueReference=*/false);
+ MTE->setBackingArrayForInitializerList();
// Wrap it in a construction of a std::initializer_list<T>.
CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 87cec16a76323..fb6025601111d 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2349,6 +2349,7 @@ void ASTStmtReader::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) {
void ASTStmtReader::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
VisitExpr(E);
+ E->setBackingArrayForInitializerList(Record.readInt());
bool HasMaterialzedDecl = Record.readInt();
if (HasMaterialzedDecl)
E->State = cast<LifetimeExtendedTemporaryDecl>(Record.readDecl());
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 70477f4cf4001..53acae125892a 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2363,6 +2363,7 @@ void ASTStmtWriter::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) {
void ASTStmtWriter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
VisitExpr(E);
+ Record.push_back(E->isBackingArrayForInitializerList());
Record.push_back(static_cast<bool>(E->getLifetimeExtendedTemporaryDecl()));
if (E->getLifetimeExtendedTemporaryDecl())
Record.AddDeclRef(E->getLifetimeExtendedTemporaryDecl());
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index f882e4ff1b124..a6751453a7f3a 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -68,4 +68,304 @@ namespace rdar13395022 {
}
}
+namespace cwg2765 {
+ constexpr bool same(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.begin() != b.begin(); // #cwg2765-init-list-compare
+ }
+ static_assert(same({1}, {1}), "");
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ template <class T>
+ constexpr bool begins_equal(T a, T b) {
+ return a.begin() == b.begin(); // #cwg2765-template-compare
+ }
+ constexpr bool same_three =
+ begins_equal<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+ // both-error at -2 {{constexpr variable 'same_three' must be initialized by a constant expression}}
+ // both-note@#cwg2765-template-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_three =
+ begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
+ static_assert(!different_three, "");
+
+ constexpr bool same_object() {
+ std::initializer_list<int> il = {1, 1, 1};
+ return il.begin() == il.begin() && il.begin() != il.begin() + 1;
+ }
+ static_assert(same_object(), "");
+
+ constexpr bool same_pointer_value() {
+ std::initializer_list<int> il = {1, 1, 1};
+ const int *p = il.begin();
+ return p + 0 == p && p != p + 1;
+ }
+ static_assert(same_pointer_value(), "");
+
+ constexpr bool local_list(const int *p) {
+ std::initializer_list<int> il = {1, 2, 3};
+ return p ? (p == il.begin()) : local_list(il.begin()); // #cwg2765-local-compare
+ }
+ constexpr bool same_local = local_list(nullptr); // #cwg2765-local-call
+ // both-error at -1 {{constexpr variable 'same_local' must be initialized by a constant expression}}
+ // both-note@#cwg2765-local-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-local-compare {{in call to}}
+ // both-note@#cwg2765-local-call {{in call to}}
+
+ constexpr bool shifted(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.begin() != b.begin() + 1; // #cwg2765-shifted-compare
+ }
+ constexpr bool annex_c = shifted({2, 3}, {1, 2, 3});
+ // both-error at -1 {{constexpr variable 'annex_c' must be initialized by a constant expression}}
+ // both-note@#cwg2765-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool end_shifted(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.end() == b.begin() + 1; // #cwg2765-end-shifted-compare
+ }
+ constexpr bool same_end_shifted = end_shifted({1}, {1, 2});
+ // both-error at -1 {{constexpr variable 'same_end_shifted' must be initialized by a constant expression}}
+ // both-note@#cwg2765-end-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_end_shifted = end_shifted({1}, {2, 1});
+ static_assert(!different_end_shifted, "");
+
+ struct Box {
+ int n;
+ };
+ constexpr bool class_same(std::initializer_list<Box> a,
+ std::initializer_list<Box> b) {
+ return a.begin() == b.begin(); // #cwg2765-class-compare
+ }
+ constexpr bool same_box = class_same({{1}}, {{1}});
+ // both-error at -1 {{constexpr variable 'same_box' must be initialized by a constant expression}}
+ // both-note@#cwg2765-class-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_box = class_same({{1}}, {{2}});
+ static_assert(!different_box, "");
+
+ constexpr bool class_field_same(std::initializer_list<Box> a,
+ std::initializer_list<Box> b) {
+ return &a.begin()->n == &b.begin()->n; // #cwg2765-field-compare
+ }
+ constexpr bool same_box_field = class_field_same({{1}}, {{1}});
+ // both-error at -1 {{constexpr variable 'same_box_field' must be initialized by a constant expression}}
+ // both-note@#cwg2765-field-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool different_box_field = class_field_same({{1}}, {{2}});
+ static_assert(!different_box_field, "");
+
+ struct WithPointer {
+ const int *p;
+ int n;
+ };
+ constexpr int pointer_anchor = 0;
+ constexpr int pointer_anchor_2 = 0;
+ constexpr bool class_with_pointer_same(std::initializer_list<WithPointer> a,
+ std::initializer_list<WithPointer> b) {
+ return a.begin() == b.begin(); // #cwg2765-with-pointer-compare
+ }
+ // Both elements compare equal in the scalar field; the pointer field
+ // points to the same global, so observable equality is Unknown overall
+ // and the address comparison is unspecified.
+ constexpr bool same_after_pointer_field =
+ class_with_pointer_same({{&pointer_anchor, 1}},
+ {{&pointer_anchor, 1}});
+ // both-error at -3 {{constexpr variable 'same_after_pointer_field' must be initialized by a constant expression}}
+ // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -4 {{in call to}}
+
+ // The non-pointer field already disagrees, so we can conclude the
+ // backing arrays are distinct even though a pointer field is present.
+ constexpr bool different_after_pointer_field =
+ class_with_pointer_same({{&pointer_anchor, 1}},
+ {{&pointer_anchor, 2}});
+ static_assert(!different_after_pointer_field, "");
+
+ // Distinct strong-symbol globals have distinct addresses, so the
+ // pointer field alone is enough to conclude the backing arrays differ.
+ constexpr bool different_pointer_field =
+ class_with_pointer_same({{&pointer_anchor, 1}},
+ {{&pointer_anchor_2, 1}});
+ static_assert(!different_pointer_field, "");
+
+ // A null pointer is distinct from a pointer-to-object.
+ constexpr bool one_null_pointer =
+ class_with_pointer_same({{nullptr, 1}}, {{&pointer_anchor, 1}});
+ static_assert(!one_null_pointer, "");
+ // Both null and scalar fields agree -> potentially overlapping.
+ constexpr bool both_null_pointer =
+ class_with_pointer_same({{nullptr, 1}}, {{nullptr, 1}}); // #cwg2765-both-null-call
+ // both-error at -2 {{constexpr variable 'both_null_pointer' must be initialized by a constant expression}}
+ // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-both-null-call {{in call to}}
+
+ struct WithStringPointer { const char *p; };
+ constexpr bool class_string_pointer_same(
+ std::initializer_list<WithStringPointer> a,
+ std::initializer_list<WithStringPointer> b) {
+ return a.begin() == b.begin(); // #cwg2765-string-ptr-compare
+ }
+ constexpr bool different_string_pointer =
+ class_string_pointer_same({{"abc"}}, {{"def"}});
+ static_assert(!different_string_pointer, "");
+
+ constexpr bool same_string_pointer =
+ class_string_pointer_same({{"abc"}}, {{"abc"}}); // #cwg2765-same-string-ptr-call
+ // both-error at -2 {{constexpr variable 'same_string_pointer' must be initialized by a constant expression}}
+ // both-note@#cwg2765-string-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-same-string-ptr-call {{in call to}}
+
+ // Both pointers are at offset 0 and the literals have different sizes, so
+ // "abc" cannot be merged into the start of "abcd": that would require a
+ // null terminator at offset 3 of "abcd", which holds 'd'. The string-overlap
+ // predicate resolves this to NotEqual.
+ constexpr bool prefix_string_pointer =
+ class_string_pointer_same({{"abc"}}, {{"abcd"}});
+ static_assert(!prefix_string_pointer, "");
+
+ // A null const char * is distinct from any non-null string literal.
+ constexpr bool null_vs_string =
+ class_string_pointer_same({{nullptr}}, {{"abc"}});
+ static_assert(!null_vs_string, "");
+
+ // Floating-point uses bitwise equality: -0.0 is distinguishable from +0.0.
+ struct WithFloat { double d; };
+ constexpr bool class_float_same(std::initializer_list<WithFloat> a,
+ std::initializer_list<WithFloat> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_signed_zero = class_float_same({{-0.0}}, {{0.0}});
+ static_assert(!different_signed_zero, "");
+
+ // Anonymous union field: the active member differs between LHS and RHS, so
+ // the backing arrays must have distinct addresses.
+ struct WithAnonUnion {
+ union { int i; float f; };
+ int tag;
+ };
+ constexpr bool class_anon_union_same(std::initializer_list<WithAnonUnion> a,
+ std::initializer_list<WithAnonUnion> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_union_member =
+ class_anon_union_same({{.i = 0, .tag = 0}}, {{.f = 0.0f, .tag = 0}});
+ static_assert(!different_union_member, "");
+
+ // Member pointers compare structurally: distinct target fields imply
+ // distinct member-pointer values, so the backing arrays differ.
+ struct Holder { int x; int y; };
+ struct WithMemberPtr { int Holder::*p; };
+ constexpr bool class_member_ptr_same(
+ std::initializer_list<WithMemberPtr> a,
+ std::initializer_list<WithMemberPtr> b) {
+ return a.begin() == b.begin(); // #cwg2765-member-ptr-compare
+ }
+ constexpr bool different_member_ptr =
+ class_member_ptr_same({{&Holder::x}}, {{&Holder::y}});
+ static_assert(!different_member_ptr, "");
+
+ // Same member pointer in both arrays + only this field -> potentially
+ // overlapping, so address comparison is unspecified.
+ constexpr bool same_member_ptr =
+ class_member_ptr_same({{&Holder::x}}, {{&Holder::x}}); // #cwg2765-same-mptr-call
+ // both-error at -2 {{constexpr variable 'same_member_ptr' must be initialized by a constant expression}}
+ // both-note@#cwg2765-member-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-same-mptr-call {{in call to}}
+
+ // Weak member functions: pointers to the same weak decl still resolve to
+ // a single merged target at link time, so two arrays holding pointers to
+ // the same weak member are still potentially-overlapping (not provably
+ // distinct, but not provably equal either — the link-time merge is fine).
+ struct HolderWithWeak {
+ __attribute__((weak)) void f();
+ __attribute__((weak)) void g();
+ };
+ struct WithWeakMemberPtr { void (HolderWithWeak::*p)(); };
+ constexpr bool class_weak_mptr_same(
+ std::initializer_list<WithWeakMemberPtr> a,
+ std::initializer_list<WithWeakMemberPtr> b) {
+ return a.begin() == b.begin(); // #cwg2765-weak-mptr-compare
+ }
+ // Distinct weak decls may merge at link time -> Unknown.
+ constexpr bool different_weak_member_ptr =
+ class_weak_mptr_same({{&HolderWithWeak::f}}, // #cwg2765-diff-weak-mptr-call
+ {{&HolderWithWeak::g}});
+ // both-error at -3 {{constexpr variable 'different_weak_member_ptr' must be initialized by a constant expression}}
+ // both-note@#cwg2765-weak-mptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-diff-weak-mptr-call {{in call to}}
+
+ // nullptr_t fields: the only value of nullptr_t is null, so this field
+ // never contributes inequality. The other field decides.
+ struct WithNullptrT { decltype(nullptr) np; int n; };
+ constexpr bool class_nullptr_t_same(std::initializer_list<WithNullptrT> a,
+ std::initializer_list<WithNullptrT> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_after_nullptr_t =
+ class_nullptr_t_same({{nullptr, 1}}, {{nullptr, 2}});
+ static_assert(!different_after_nullptr_t, "");
+
+ // Aggregate carrying a std::initializer_list member: the backing array's
+ // extending declaration is the enclosing aggregate, not the
+ // initializer_list. The dedicated marker on MaterializeTemporaryExpr
+ // still recognises the backing array.
+ struct WithIL { std::initializer_list<int> il; };
+ constexpr WithIL agg_a{{1}}, agg_b{{1}};
+ constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin(); // #cwg2765-agg-compare
+ // both-error at -1 {{constexpr variable 'agg_same' must be initialized by a constant expression}}
+ // both-note@#cwg2765-agg-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+ constexpr WithIL agg_c{{1}}, agg_d{{2}};
+ constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin();
+ static_assert(!agg_different, "");
+
+ // Rvalue reference to array bound to a braced-init-list: the materialized
+ // array is NOT a std::initializer_list backing array.
+ constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) {
+ return &a[0] == &b[0];
+ }
+ constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3});
+ static_assert(!rvref_ok, "");
+
+ // Vector elements: APValue::Profile recurses element-wise, so the
+ // mergeability judgment is correctly element-sensitive (NOT "hard-code
+ // these rare kinds to NotEqual"). Confirmed by runtime observation:
+ // identical-content backing arrays are mergeable, differing ones are not.
+ typedef int v4 __attribute__((ext_vector_type(4)));
+ constexpr v4 va = {1, 2, 3, 4};
+ constexpr v4 vb = {1, 2, 3, 5};
+ struct WithVec { v4 v; };
+ constexpr bool class_vec_same(std::initializer_list<WithVec> a,
+ std::initializer_list<WithVec> b) {
+ return a.begin() == b.begin(); // #cwg2765-vec-compare
+ }
+ constexpr bool same_vec = class_vec_same({{va}}, {{va}}); // #cwg2765-same-vec-call
+ // both-error at -1 {{constexpr variable 'same_vec' must be initialized by a constant expression}}
+ // both-note@#cwg2765-vec-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note@#cwg2765-same-vec-call {{in call to}}
+
+ constexpr bool different_vec = class_vec_same({{va}}, {{vb}});
+ static_assert(!different_vec, "");
+
+ // Complex elements: Profile bitwise-compares real / imag, so two
+ // complex values differing in only the imaginary part are correctly
+ // recognised as distinct.
+ struct WithComplex { _Complex double c; };
+ constexpr bool class_complex_same(std::initializer_list<WithComplex> a,
+ std::initializer_list<WithComplex> b) {
+ return a.begin() == b.begin();
+ }
+ constexpr bool different_complex = class_complex_same(
+ {{__builtin_complex(1.0, 2.0)}}, {{__builtin_complex(1.0, 3.0)}});
+ static_assert(!different_complex, "");
+}
diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
new file mode 100644
index 0000000000000..a64ce068913b5
--- /dev/null
+++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp
@@ -0,0 +1,139 @@
+// RUN: %clang_cc1 %std_cxx11- -fexceptions -fcxx-exceptions -fsyntax-only --embed-dir=%S/../../../../Preprocessor/Inputs -Wno-c23-extensions -verify %s
+
+namespace std {
+using size_t = decltype(sizeof(int));
+
+template <class E> class initializer_list {
+ const E *begin_;
+ size_t size_;
+
+public:
+ constexpr initializer_list() : begin_(nullptr), size_(0) {}
+ constexpr initializer_list(const E *begin, size_t size)
+ : begin_(begin), size_(size) {}
+ constexpr const E *begin() const { return begin_; }
+ constexpr const E *end() const { return begin_ + size_; }
+ constexpr size_t size() const { return size_; }
+};
+
+template <class T> struct complex {
+ constexpr complex(double) {}
+};
+
+template <class T> struct vector {
+ vector(initializer_list<T>);
+};
+} // namespace std
+
+namespace example12 {
+void f(std::initializer_list<double> il);
+void g(float x) {
+ f({1, x, 3});
+}
+void h() {
+ f({1, 2, 3});
+}
+
+struct A {
+ mutable int i;
+};
+void q(std::initializer_list<A>);
+void r() {
+ q({A{1}, A{2}, A{3}});
+}
+} // namespace example12
+
+namespace example13 {
+typedef std::complex<double> cmplx;
+std::vector<cmplx> v1 = {1, 2, 3};
+void f() {
+ std::vector<cmplx> v2{1, 2, 3};
+ std::initializer_list<int> i3 = {1, 2, 3};
+}
+
+struct A {
+ std::initializer_list<int> i4; // expected-note {{'std::initializer_list' member declared here}}
+ A() : i4{1, 2, 3} {}
+ // expected-error at -1 {{backing array for 'std::initializer_list' member 'i4' is a temporary object whose lifetime would be shorter than the lifetime of the constructed object}}
+};
+} // namespace example13
+
+namespace embed_example {
+void bytes(std::initializer_list<unsigned char>);
+void f() {
+ bytes({
+#embed <jk.txt>
+ });
+}
+
+constexpr std::initializer_list<unsigned char> jk = {
+#embed <jk.txt>
+};
+static_assert(jk.size() == 2, "");
+static_assert(jk.begin()[0] == 'j', "");
+static_assert(jk.begin()[1] == 'k', "");
+} // namespace embed_example
+
+namespace shared_backing_arrays {
+void f2(std::initializer_list<int> ia, std::initializer_list<int> ib) {
+ (void)(ia.begin() == ib.begin());
+}
+void test() {
+ f2({1, 2, 3}, {1, 2, 3});
+}
+
+void f3() {
+ std::initializer_list<int> i1 = {1, 2, 3, 4, 5};
+ std::initializer_list<int> i2 = {2, 3, 4};
+ (void)(i1.begin() == i2.begin() + 1);
+}
+} // namespace shared_backing_arrays
+
+namespace lifetime_is_unchanged {
+const int *f4(std::initializer_list<int> i4) {
+ return i4.begin();
+}
+void test() {
+ const int *p = f4({1, 2, 3});
+ (void)*p;
+}
+} // namespace lifetime_is_unchanged
+
+namespace destructor_side_effects {
+extern "C" int printf(const char *, ...);
+
+struct C6 {
+ constexpr C6(int) {}
+ ~C6() { printf(" X"); }
+};
+
+void f6(std::initializer_list<C6>) {}
+void test() {
+ f6({1, 2, 3});
+ f6({1, 2, 3});
+}
+} // namespace destructor_side_effects
+
+namespace mutable_members {
+struct S {
+ constexpr S(int i) : i(i) {}
+ mutable int i;
+};
+
+void f(std::initializer_list<S> il) {
+ if (il.begin()->i != 1)
+ throw;
+ il.begin()->i = 4;
+}
+void test() {
+ for (int i = 0; i < 2; ++i)
+ f({1, 2, 3});
+}
+} // namespace mutable_members
+
+namespace annex_c {
+bool ne(std::initializer_list<int> a, std::initializer_list<int> b) {
+ return a.begin() != b.begin() + 1;
+}
+bool b = ne({2, 3}, {1, 2, 3});
+} // namespace annex_c
diff --git a/clang/test/CXX/drs/cwg27xx.cpp b/clang/test/CXX/drs/cwg27xx.cpp
index 16fd56e0c4a63..ea28892a633e7 100644
--- a/clang/test/CXX/drs/cwg27xx.cpp
+++ b/clang/test/CXX/drs/cwg27xx.cpp
@@ -1,10 +1,10 @@
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx98-20 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23 %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26 %s
#if __cplusplus == 199711L
#define static_assert(...) __extension__ _Static_assert(__VA_ARGS__)
@@ -19,22 +19,37 @@
namespace std {
#if __cplusplus >= 201103L
-using size_t = decltype(sizeof(0));
-template <typename T>
-struct initializer_list {
- const T *p;
- size_t n;
-
- #if __cplusplus >= 201402L
- constexpr
- #endif
- initializer_list(const T *p, size_t n);
-
- #if __cplusplus >= 201402L
- constexpr
- #endif
- const T* begin() const { return p; };
-};
+ using size_t = decltype(sizeof(int));
+
+ template <class E> class initializer_list {
+ const E *begin_;
+ size_t size_;
+
+ public:
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ initializer_list() : begin_(nullptr), size_(0) {}
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ initializer_list(const E *begin, size_t size)
+ : begin_(begin), size_(size) {}
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ const E *begin() const { return begin_; }
+#if __cplusplus >= 201402L
+ constexpr
+#endif
+ size_t size() const { return size_; }
+ };
+
+ struct string_view {
+ const char *begin_;
+ constexpr string_view(const char *begin) : begin_(begin) {}
+ constexpr const char *begin() const { return begin_; }
+ };
#endif
#if __cplusplus >= 202002L
@@ -193,7 +208,7 @@ static_assert(!__is_layout_compatible(StructWithAnonUnion, StructWithAnonUnion3)
#endif
} // namespace cwg2759
-namespace cwg2765 { // cwg2765: partial
+namespace cwg2765 { // cwg2765: 23
static_assert(+"foo" == "foo", "");
// expected-error at -1 {{static assertion expression is not an integral constant expression}}
// expected-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
@@ -211,19 +226,107 @@ static_assert((const char*)"foo" != "oo", "");
#if __cplusplus >= 201103L
constexpr const char *f() { return "foo"; }
-constexpr bool b2 = f() == f();
+
+constexpr bool b2 = f() == f();
// since-cxx11-error at -1 {{constexpr variable 'b2' must be initialized by a constant expression}}
// since-cxx11-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
constexpr const char *p = f();
-constexpr bool b3 = p == p;
-#endif
+constexpr bool b3 = p == p;
+static_assert(b3, "");
+
+constexpr bool b4 = &"xfoo"[1] == &"foo\0y"[0];
+// since-cxx11-error at -1 {{constexpr variable 'b4' must be initialized by a constant expression}}
+// since-cxx11-note at -2 {{comparison of addresses of potentially overlapping literals has unspecified value}}
+static_assert("foo" != &"bar"[0], "");
+static_assert((const char *)"foo" != "oo", "");
+
+template <class T>
+constexpr bool f10(T s, T t) {
+ return s.begin() == t.begin(); // #cwg2765-f10-compare
+}
+constexpr bool b10a = f10<std::string_view>("abc", "abc");
+// since-cxx11-error at -1 {{constexpr variable 'b10a' must be initialized by a constant expression}}
+// since-cxx11-note@#cwg2765-f10-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+// since-cxx11-note at -3 {{in call to 'f10<std::string_view>({&"abc"[0]}, {&"abc"[0]})'}}
+constexpr bool b10b = f10<std::string_view>("abc", "def");
+static_assert(!b10b, "");
+
+constexpr const char *a11 = "abc";
+constexpr const char *b11 = "abc";
+constexpr bool f11() { return a11 == b11; } // #cwg2765-f11-compare
+// cxx98-20-error at -1 {{constexpr function never produces a constant expression}}
+// cxx98-20-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+static_assert(f11() || !f11(), "");
+// since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
+// since-cxx11-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+// since-cxx11-note at -3 {{in call to 'f11()'}}
#if __cplusplus >= 201402L
-constexpr std::initializer_list<int *> il1 = { (int *)nullptr };
-constexpr std::initializer_list<unsigned long> il2 = { 0 };
-constexpr bool b7 = il1.begin() == (void *)il2.begin();
-// FIXME-error at -1 {{constexpr variable 'b7' must be initialized by a constant expression}}
-// FIXME-note at -2 {{address of a constexpr-unknown object cannot be used for comparison}}
+constexpr bool f(std::initializer_list<int> a, std::initializer_list<int> b) {
+ return a.begin() != b.begin(); // #cwg2765-init-list-compare
+}
+static_assert(f({1}, {1}), "");
+// since-cxx14-error at -1 {{static assertion expression is not an integral constant expression}}
+// since-cxx14-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note at -3 {{in call to 'f({&{1}[0], 1}, {&{1}[0], 1})'}}
+
+constexpr bool f9(const int *p) {
+ std::initializer_list<int> il = {1, 2, 3};
+ return p ? (p == il.begin()) : f9(il.begin()); // #cwg2765-f9-compare
+}
+constexpr bool b9 = f9(nullptr);
+// since-cxx14-error at -1 {{constexpr variable 'b9' must be initialized by a constant expression}}
+// since-cxx14-note@#cwg2765-f9-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note@#cwg2765-f9-compare {{in call to 'f9(&{1, 2, 3}[0])'}}
+// since-cxx14-note at -4 {{in call to 'f9(nullptr)'}}
+
+constexpr bool b10c = f10<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3});
+// since-cxx14-error at -1 {{constexpr variable 'b10c' must be initialized by a constant expression}}
+// since-cxx14-note@#cwg2765-f10-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note at -3 {{in call to 'f10<std::initializer_list<int>>({&{1, 2, 3}[0], 3}, {&{1, 2, 3}[0], 3})'}}
+constexpr bool b10d = f10<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
+static_assert(!b10d, "");
+
+constexpr bool ne(std::initializer_list<int> a,
+ std::initializer_list<int> b) {
+ return a.begin() != b.begin() + 1; // #cwg2765-annex-c-compare
+}
+constexpr bool annex_c = ne({2, 3}, {1, 2, 3});
+// since-cxx14-error at -1 {{constexpr variable 'annex_c' must be initialized by a constant expression}}
+// since-cxx14-note@#cwg2765-annex-c-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+// since-cxx14-note at -3 {{in call to 'ne({&{2, 3}[0], 2}, {&{1, 2, 3}[0], 3})'}}
+
+int a_order[10];
+constexpr int inc(int &i) { return (i += 1); }
+constexpr int twox(int &i) { return (i *= 2); }
+constexpr int f_order(int i) { return inc(i) + twox(i); }
+constexpr bool g_order() { return &a_order[f_order(1)] == &a_order[6]; }
+constexpr bool b_order = g_order();
+static_assert(b_order, "");
+
+// Aggregate carrying a std::initializer_list member: the backing array's
+// extending declaration is the enclosing aggregate, not the
+// initializer_list. The precise marker on MaterializeTemporaryExpr must
+// still recognise the backing array.
+struct WithIL { std::initializer_list<int> il; };
+constexpr WithIL agg_a{{1}}, agg_b{{1}};
+constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin();
+// since-cxx14-error at -1 {{constexpr variable 'agg_same' must be initialized by a constant expression}}
+// since-cxx14-note at -2 {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+constexpr WithIL agg_c{{1}}, agg_d{{2}};
+constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin();
+static_assert(!agg_different, "");
+
+// Rvalue reference to array bound to a braced-init-list: the materialized
+// array is not a std::initializer_list backing array, so address
+// comparisons across two such temporaries are well-defined.
+constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) {
+ return &a[0] == &b[0];
+}
+constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3});
+static_assert(!rvref_ok, "");
+#endif
#endif
} // namespace cwg2765
diff --git a/clang/test/CodeGenCXX/Inputs/jk.txt b/clang/test/CodeGenCXX/Inputs/jk.txt
new file mode 100644
index 0000000000000..93d177a48c83a
--- /dev/null
+++ b/clang/test/CodeGenCXX/Inputs/jk.txt
@@ -0,0 +1 @@
+jk
\ No newline at end of file
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
new file mode 100644
index 0000000000000..3f5ecac648066
--- /dev/null
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+
+namespace std {
+using size_t = decltype(sizeof(int));
+
+template <class E> class initializer_list {
+ const E *begin_;
+ size_t size_;
+
+public:
+ constexpr initializer_list() : begin_(nullptr), size_(0) {}
+ constexpr initializer_list(const E *begin, size_t size)
+ : begin_(begin), size_(size) {}
+ constexpr const E *begin() const { return begin_; }
+ constexpr size_t size() const { return size_; }
+};
+} // namespace std
+
+namespace example12 {
+void f(std::initializer_list<double> il);
+
+void g(float x) {
+ // CHECK-LABEL: define{{.*}} void @_ZN9example121gEf(
+ // CHECK: alloca [3 x double],
+ // CHECK: fpext float
+ // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
+ f({1, x, 3});
+}
+
+void h() {
+ // CHECK-LABEL: define{{.*}} void @_ZN9example121hEv(
+ // CHECK: alloca [3 x double],
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24,
+ // CHECK: call void @_ZN9example121fESt16initializer_listIdE(
+ f({1, 2, 3});
+}
+} // namespace example12
+
+namespace embed_example {
+void bytes(std::initializer_list<unsigned char>);
+
+void f() {
+ // CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv(
+ // CHECK: alloca [2 x i8],
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2,
+ // CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE(
+ bytes({
+#embed <jk.txt>
+ });
+}
+} // namespace embed_example
+
+namespace destructor_side_effects {
+extern "C" int printf(const char *, ...);
+
+struct C6 {
+ constexpr C6(int) {}
+ ~C6() { printf(" X"); }
+};
+
+void f6(std::initializer_list<C6>) {}
+
+void test() {
+ // CHECK-LABEL: define{{.*}} void @_ZN23destructor_side_effects4testEv(
+ // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE(
+ // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev(
+ // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE(
+ // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev(
+ f6({1, 2, 3});
+ f6({1, 2, 3});
+}
+} // namespace destructor_side_effects
+
+namespace mutable_members {
+struct S {
+ constexpr S(int i) : i(i) {}
+ mutable int i;
+};
+
+void f(std::initializer_list<S> il) {
+ if (il.begin()->i != 1)
+ throw;
+ il.begin()->i = 4;
+}
+
+void test() {
+ // CHECK-LABEL: define{{.*}} void @_ZN15mutable_members4testEv(
+ // CHECK: alloca [3 x %"struct.mutable_members::S"],
+ // CHECK: call void @_ZN15mutable_members1fESt16initializer_listINS_1SEE(
+ for (int i = 0; i < 2; ++i)
+ f({1, 2, 3});
+}
+} // namespace mutable_members
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index af91ac559d274..3f68691ee75bb 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -19160,7 +19160,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td>[<a href="https://wg21.link/intro.object">intro.object</a>]</td>
<td>DR</td>
<td>Address comparisons between potentially non-unique objects during constant evaluation</td>
- <td class="partial" align="center">Partial</td>
+ <td class="full" align="center">Clang 23</td>
</tr>
<tr class="open" id="2766">
<td><a href="https://cplusplus.github.io/CWG/issues/2766.html">2766</a></td>
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 0675f785bab20..308b6a4dfb63d 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -247,7 +247,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Static storage for braced initializers</td>
<td><a href="https://wg21.link/P2752R3">P2752R3</a> (<a href="#dr">DR</a>)</td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr>
<td>User-generated <tt>static_assert</tt> messages</td>
>From 7102e77741bfca09fd6bda9b5e645b4cfd3547aa Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 23 Jun 2026 20:16:08 +0800
Subject: [PATCH 02/12] Generate the static backing array in CodeGen, avoid
copy when construct std::vector
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ByteCode/Interp.cpp | 276 ++++++++++++---
clang/lib/AST/ByteCode/Interp.h | 10 +-
clang/lib/AST/ExprConstant.cpp | 322 +++++++++++++++---
clang/lib/CodeGen/CGExpr.cpp | 38 +++
clang/lib/CodeGen/CodeGenFunction.h | 4 +
clang/lib/CodeGen/CodeGenModule.cpp | 23 ++
clang/lib/CodeGen/CodeGenModule.h | 4 +
clang/test/AST/ByteCode/initializer_list.cpp | 64 ++++
.../cxx0x-initializer-stdinitializerlist.cpp | 4 +-
.../CodeGenCXX/p2752r3-initializer-list.cpp | 30 +-
.../ptrauth-member-function-pointer.cpp | 6 +-
.../SemaCXX/constant-expression-cxx11.cpp | 14 +
12 files changed, 700 insertions(+), 95 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 13469758fc5c9..0445075042fb4 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2911,63 +2911,261 @@ getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
return ArraySubobjectInfo{std::move(Array), *Loc};
}
-/// Returns true if \p LHS and \p RHS both designate elements of
-/// std::initializer_list backing arrays whose values agree on the overlapping
-/// region. Such backing arrays may be merged by the implementation, so
-/// comparing their addresses produces an unspecified result and is rejected
-/// as a constant expression.
-bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS) {
- const ASTContext &Ctx = S.getASTContext();
- std::optional<ArraySubobjectInfo> LHSInfo =
- getArraySubobjectLocation(Ctx, LHS);
- std::optional<ArraySubobjectInfo> RHSInfo =
- getArraySubobjectLocation(Ctx, RHS);
- if (!LHSInfo || !RHSInfo)
- return false;
+/// Returns the enclosing array block for a pointer into an array subobject.
+static std::optional<Pointer> getContainingArray(const Pointer &Ptr) {
+ if (Ptr.isZero() || !Ptr.isBlockPointer())
+ return std::nullopt;
+
+ PtrView P = Ptr.view();
+ while (!P.isRoot()) {
+ if (P.isArrayElement()) {
+ PtrView Expanded = P.expand();
+ return Pointer(Expanded.getArray());
+ }
+ P = P.getBase();
+ }
+
+ return std::nullopt;
+}
+
+/// Returns true for array objects whose addresses are potentially non-unique
+/// and whose contents are represented by interpreter storage/APValue.
+static bool isPotentiallyNonUniqueAPValueArray(const Pointer &Array) {
+ if (isInitializerListBackingArray(Array))
+ return true;
+ if (const auto *VD = Array.getDeclDesc()->asValueDecl())
+ return isa<TemplateParamObjectDecl>(VD);
+ return false;
+}
+
+/// Returns the enclosing potentially non-unique APValue-backed array and the
+/// element-relative location designated by Ptr.
+static std::optional<ArraySubobjectInfo>
+getNonUniqueAPValueArraySubobjectLocation(const ASTContext &Ctx,
+ const Pointer &Ptr) {
+ if (std::optional<ArraySubobjectInfo> InitListInfo =
+ getArraySubobjectLocation(Ctx, Ptr))
+ return InitListInfo;
+
+ if (!Ptr.isZero() && Ptr.isBlockPointer() && Ptr.isArrayRoot() &&
+ isPotentiallyNonUniqueAPValueArray(Ptr)) {
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Ptr.getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
+ return ArraySubobjectInfo{Ptr,
+ ArraySubobjectLocation{0, CharUnits::Zero()}};
+ }
+
+ std::optional<Pointer> Array = getContainingArray(Ptr);
+ if (!Array || !isPotentiallyNonUniqueAPValueArray(*Array))
+ return std::nullopt;
+
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array->getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
+
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ APValue ArrayValue = Array->toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath() ||
+ !ArrayValue.isLValue() || !ArrayValue.hasLValuePath())
+ return std::nullopt;
+
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ ArrayRef<APValue::LValuePathEntry> ArrayPath = ArrayValue.getLValuePath();
+ if (Path.size() <= ArrayPath.size())
+ return std::nullopt;
+ for (unsigned I = 0, N = ArrayPath.size(); I != N; ++I) {
+ if (Path[I] != ArrayPath[I])
+ return std::nullopt;
+ }
+ Path = Path.drop_front(ArrayPath.size());
+
+ uint64_t Index = Path.front().getAsArrayIndex();
+ bool IsValidOnePastEnd = Path.size() == 1;
+ std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ Ctx, ArrayType, Index,
+ PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
+ IsValidOnePastEnd);
+ if (!Loc)
+ return std::nullopt;
+
+ return ArraySubobjectInfo{std::move(*Array), *Loc};
+}
+
+namespace {
+/// Pairs a string literal array with the element-relative location of a pointer
+/// into it and the literal used to materialize character APValues.
+struct StringArraySubobjectInfo {
+ Pointer Array;
+ ArraySubobjectLocation Loc;
+ const StringLiteral *Literal;
+};
+} // namespace
+
+/// Returns the string literal array and element-relative location designated by
+/// Ptr.
+static std::optional<StringArraySubobjectInfo>
+getStringArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
+ if (!Ptr.pointsToStringLiteral())
+ return std::nullopt;
+
+ Pointer Array = Ptr.getDeclPtr();
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+ if (!ArrayType)
+ return std::nullopt;
- if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement)
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
+ return std::nullopt;
+
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ if (Path.empty())
+ return std::nullopt;
+
+ uint64_t Index = Path.front().getAsArrayIndex();
+ bool IsValidOnePastEnd = Path.size() == 1;
+ std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
+ if (!Loc)
+ return std::nullopt;
+
+ return StringArraySubobjectInfo{
+ std::move(Array), *Loc, cast<StringLiteral>(Ptr.getDeclDesc()->asExpr())};
+}
+
+/// Extracts one character from a string literal as an integer APValue payload.
+static APSInt extractStringLiteralCharacter(const ASTContext &Ctx,
+ const StringLiteral *Literal,
+ uint64_t Index) {
+ const ConstantArrayType *ArrayType =
+ Ctx.getAsConstantArrayType(Literal->getType());
+ assert(ArrayType && "string literal isn't an array");
+ QualType CharType = ArrayType->getElementType();
+ assert(CharType->isIntegerType() && "unexpected character type");
+ APSInt Value(Ctx.getTypeSize(CharType), CharType->isUnsignedIntegerType());
+ if (Index < Literal->getLength())
+ Value = Literal->getCodeUnit(Index);
+ return Value;
+}
+
+/// Compares two array views for a possible storage overlap after accounting
+/// for their pointer offsets.
+template <typename GetLHSElement, typename GetRHSElement>
+static bool arePotentiallyOverlappingArrays(
+ const ASTContext &Ctx, QualType LHSElementType,
+ ArraySubobjectLocation LHSLoc, int64_t LHSSize, GetLHSElement GetLHSElt,
+ QualType RHSElementType, ArraySubobjectLocation RHSLoc, int64_t RHSSize,
+ GetRHSElement GetRHSElt) {
+ if (LHSLoc.OffsetInElement != RHSLoc.OffsetInElement)
return false;
- const auto *LHSArrayType =
- Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
- const auto *RHSArrayType =
- Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
- if (!LHSArrayType || !RHSArrayType ||
- !Ctx.hasSameType(LHSArrayType->getElementType(),
- RHSArrayType->getElementType()))
+ if (!Ctx.hasSameUnqualifiedType(LHSElementType, RHSElementType))
return false;
- QualType ElementType = LHSArrayType->getElementType();
- int64_t LHSSize = LHSInfo->Array.getNumElems();
- int64_t RHSSize = RHSInfo->Array.getNumElems();
- int64_t LHSOffset = LHSInfo->Loc.Index;
- int64_t RHSOffset = RHSInfo->Loc.Index;
+ int64_t LHSOffset = LHSLoc.Index;
+ int64_t RHSOffset = RHSLoc.Index;
int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
if (OverlapBegin >= OverlapEnd)
return false;
for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
- Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow();
- Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow();
- std::optional<APValue> LHSEltValue =
- LHSElt.toRValue(S.getContext(), ElementType);
- std::optional<APValue> RHSEltValue =
- RHSElt.toRValue(S.getContext(), ElementType);
- // Missing element data: be conservative and assume the backing arrays
- // may share storage.
- if (!LHSEltValue || !RHSEltValue)
+ std::optional<APValue> LHSElt = GetLHSElt(I + LHSOffset);
+ std::optional<APValue> RHSElt = GetRHSElt(I + RHSOffset);
+ // Missing element data: be conservative and assume the arrays may share
+ // storage.
+ if (!LHSElt || !RHSElt)
return true;
-
- if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx))
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
return false;
}
return true;
}
+/// Returns true if a string literal and a potentially non-unique constant
+/// array could share storage at the designated addresses.
+static bool mayOverlapStringLiteralAndConstantArray(InterpState &S,
+ const Pointer &StringPtr,
+ const Pointer &ArrayPtr) {
+ const ASTContext &Ctx = S.getASTContext();
+ std::optional<StringArraySubobjectInfo> StringInfo =
+ getStringArraySubobjectLocation(Ctx, StringPtr);
+ std::optional<ArraySubobjectInfo> ArrayInfo =
+ getNonUniqueAPValueArraySubobjectLocation(Ctx, ArrayPtr);
+ if (!StringInfo || !ArrayInfo)
+ return false;
+
+ const auto *StringArrayType =
+ Ctx.getAsConstantArrayType(StringInfo->Array.getFieldDesc()->getType());
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(ArrayInfo->Array.getFieldDesc()->getType());
+ if (!StringArrayType || !ArrayType)
+ return false;
+
+ return arePotentiallyOverlappingArrays(
+ Ctx, StringArrayType->getElementType(), StringInfo->Loc,
+ StringInfo->Array.getNumElems(),
+ [&](int64_t Index) -> std::optional<APValue> {
+ return APValue(
+ extractStringLiteralCharacter(Ctx, StringInfo->Literal, Index));
+ },
+ ArrayType->getElementType(), ArrayInfo->Loc,
+ ArrayInfo->Array.getNumElems(),
+ [&](int64_t Index) {
+ Pointer Elt = ArrayInfo->Array.atIndex(Index).narrow();
+ return Elt.toRValue(S.getContext(), ArrayType->getElementType());
+ });
+}
+
+/// Returns true if two APValue-backed potentially non-unique arrays could share
+/// storage at the designated addresses.
+static bool arePotentiallyOverlappingAPValueArrays(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS) {
+ const ASTContext &Ctx = S.getASTContext();
+ std::optional<ArraySubobjectInfo> LHSInfo =
+ getNonUniqueAPValueArraySubobjectLocation(Ctx, LHS);
+ std::optional<ArraySubobjectInfo> RHSInfo =
+ getNonUniqueAPValueArraySubobjectLocation(Ctx, RHS);
+ if (!LHSInfo || !RHSInfo)
+ return false;
+
+ const auto *LHSArrayType =
+ Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
+ const auto *RHSArrayType =
+ Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
+ if (!LHSArrayType || !RHSArrayType)
+ return false;
+
+ return arePotentiallyOverlappingArrays(
+ Ctx, LHSArrayType->getElementType(), LHSInfo->Loc,
+ LHSInfo->Array.getNumElems(),
+ [&](int64_t Index) {
+ Pointer Elt = LHSInfo->Array.atIndex(Index).narrow();
+ return Elt.toRValue(S.getContext(), LHSArrayType->getElementType());
+ },
+ RHSArrayType->getElementType(), RHSInfo->Loc,
+ RHSInfo->Array.getNumElems(),
+ [&](int64_t Index) {
+ Pointer Elt = RHSInfo->Array.atIndex(Index).narrow();
+ return Elt.toRValue(S.getContext(), RHSArrayType->getElementType());
+ });
+}
+
+/// Returns true if two pointers designate potentially non-unique objects whose
+/// overlapping elements could be merged into the same storage.
+bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS) {
+ return arePotentiallyOverlappingAPValueArrays(S, LHS, RHS) ||
+ mayOverlapStringLiteralAndConstantArray(S, LHS, RHS) ||
+ mayOverlapStringLiteralAndConstantArray(S, RHS, LHS);
+}
+
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
PrimType T) {
if (T == PT_IntAPS) {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0bb2f9839ad9b..5b87a606c6d53 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1302,9 +1302,11 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
const Pointer &RHS);
-bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS);
+/// Returns true when LHS and RHS designate potentially non-unique objects that
+/// could share storage, making their comparison non-constant.
+bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
+ const Pointer &LHS,
+ const Pointer &RHS);
template <>
inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1374,7 +1376,7 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) {
+ if (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
return false;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e33d1c73b547c..f2c0b3cd02668 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5089,57 +5089,291 @@ static bool isInitializerListBackingArray(const LValue &LV) {
return MTE && MTE->isBackingArrayForInitializerList();
}
-static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info,
- const Expr *E,
- const LValue &LHS,
- const LValue &RHS) {
- if (!isInitializerListBackingArray(LHS) ||
- !isInitializerListBackingArray(RHS))
- return false;
+/// Returns the underlying string literal expression for bases that denote
+/// string literal objects in the evaluator.
+static const Expr *getStringLiteralBase(APValue::LValueBase Base) {
+ const auto *BaseExpr = Base.dyn_cast<const Expr *>();
+ if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
+ return BaseExpr;
+ return nullptr;
+}
- std::optional<ArraySubobjectLocation> LHSLoc =
- getArraySubobjectLocation(Info.Ctx, LHS);
- std::optional<ArraySubobjectLocation> RHSLoc =
- getArraySubobjectLocation(Info.Ctx, RHS);
- if (!LHSLoc || !RHSLoc)
- return false;
+/// Returns the string-literal array element and element-relative offset
+/// designated by LV, including byte-offset-only lvalues.
+static std::optional<ArraySubobjectLocation>
+getStringArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+ if (!getStringLiteralBase(LV.Base))
+ return std::nullopt;
- if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement)
- return false;
+ if (std::optional<ArraySubobjectLocation> Loc =
+ getArraySubobjectLocation(Ctx, LV))
+ return Loc;
- const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base));
- const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base));
- if (!LHSArrayType || !RHSArrayType ||
- !Info.Ctx.hasSameType(LHSArrayType->getElementType(),
- RHSArrayType->getElementType()))
- return false;
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!ArrayType || LV.Offset.isNegative())
+ return std::nullopt;
- const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS);
- const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS);
- if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray())
- return false;
+ uint64_t ElementSize =
+ Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+ if (!ElementSize)
+ return std::nullopt;
- int64_t LHSSize = LHSArray->getArraySize();
- int64_t RHSSize = RHSArray->getArraySize();
- int64_t LHSOffset = LHSLoc->Index;
- int64_t RHSOffset = RHSLoc->Index;
- int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
- int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
- if (OverlapBegin >= OverlapEnd)
- return false;
+ uint64_t Offset = LV.Offset.getQuantity();
+ uint64_t Index = Offset / ElementSize;
+ if (Index > ArrayType->getZExtSize())
+ return std::nullopt;
- for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
- const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset);
- const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset);
- // Missing element data: be conservative and assume the arrays may share
- // storage.
- if (!LHSElt || !RHSElt)
- return true;
- if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+ return ArraySubobjectLocation{Index,
+ CharUnits::fromQuantity(Offset % ElementSize)};
+}
+
+namespace {
+/// Identifies an enclosing constant array subobject within an lvalue path.
+struct ArraySubobjectPathInfo {
+ const ConstantArrayType *ArrayType = nullptr;
+ unsigned ArrayPathLength = 0;
+ ArraySubobjectLocation Loc;
+};
+
+/// Describes a potentially non-unique array visible to the constant evaluator.
+struct NonUniqueArrayInfo {
+ const ConstantArrayType *ArrayType = nullptr;
+ ArraySubobjectLocation Loc;
+ /// Non-null for string literal arrays; otherwise ArrayValue or
+ /// OwnedArrayValue contains an APValue array.
+ const Expr *StringBase = nullptr;
+ const APValue *ArrayValue = nullptr;
+ std::optional<APValue> OwnedArrayValue;
+
+ /// Returns true when this view describes a string literal array.
+ bool isStringLiteral() const { return StringBase; }
+
+ /// Returns the APValue storage for non-string arrays.
+ const APValue *getArrayValue() const {
+ return OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
+ }
+
+ /// Returns the number of array elements visible through this view.
+ int64_t getSize() const {
+ if (isStringLiteral())
+ return ArrayType->getZExtSize();
+
+ const APValue *Array = getArrayValue();
+ assert(Array && Array->isArray() && "invalid APValue array");
+ return Array->getArraySize();
+ }
+
+ /// Returns the value of one element, or nullopt when the representation does
+ /// not expose enough data to prove it differs.
+ std::optional<APValue> getElement(EvalInfo &Info, int64_t Index) const {
+ if (Index < 0)
+ return std::nullopt;
+ uint64_t UIndex = static_cast<uint64_t>(Index);
+
+ if (isStringLiteral())
+ return APValue(extractStringLiteralCharacter(Info, StringBase, UIndex));
+
+ const APValue *Array = getArrayValue();
+ assert(Array && Array->isArray() && "invalid APValue array");
+ if (const APValue *Elt = GetArrayInitializedElt(*Array, UIndex))
+ return *Elt;
+ return std::nullopt;
+ }
+};
+
+/// Performs potentially-non-unique object overlap checks for two evaluator
+/// lvalues.
+class NonUniqueObjectOverlapChecker {
+public:
+ /// Creates a checker for comparisons diagnosed at expression E.
+ NonUniqueObjectOverlapChecker(EvalInfo &Info, const Expr *E)
+ : Info(Info), E(E) {}
+
+ /// Returns true when LHS and RHS could designate merged storage.
+ bool mayOverlap(const LValue &LHS, const LValue &RHS) const {
+ const ASTContext &Ctx = Info.Ctx;
+ std::optional<NonUniqueArrayInfo> LHSArray = getArrayInfo(LHS);
+ std::optional<NonUniqueArrayInfo> RHSArray = getArrayInfo(RHS);
+ if (!LHSArray || !RHSArray)
+ return false;
+
+ // Keep the existing string-literal diagnostic path authoritative for
+ // string/string comparisons.
+ if (LHSArray->isStringLiteral() && RHSArray->isStringLiteral())
+ return false;
+
+ if (LHSArray->Loc.OffsetInElement != RHSArray->Loc.OffsetInElement)
return false;
+
+ if (!Ctx.hasSameUnqualifiedType(LHSArray->ArrayType->getElementType(),
+ RHSArray->ArrayType->getElementType()))
+ return false;
+
+ int64_t LHSOffset = LHSArray->Loc.Index;
+ int64_t RHSOffset = RHSArray->Loc.Index;
+ int64_t LHSSize = LHSArray->getSize();
+ int64_t RHSSize = RHSArray->getSize();
+ int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
+ int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
+ if (OverlapBegin >= OverlapEnd)
+ return false;
+
+ for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
+ std::optional<APValue> LHSElt = LHSArray->getElement(Info, I + LHSOffset);
+ std::optional<APValue> RHSElt = RHSArray->getElement(Info, I + RHSOffset);
+ // Missing element data: be conservative and assume the arrays may share
+ // storage.
+ if (!LHSElt || !RHSElt)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
+ return false;
+ }
+
+ return true;
}
- return true;
+private:
+ /// Finds the innermost enclosing constant array subobject for an lvalue into
+ /// a template parameter object.
+ std::optional<ArraySubobjectPathInfo>
+ getArraySubobjectPathInfo(const LValue &LV) const {
+ const ASTContext &Ctx = Info.Ctx;
+ if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+ return std::nullopt;
+
+ QualType Type = getType(LV.Base).getNonReferenceType();
+ CharUnits PathOffset = CharUnits::Zero();
+ std::optional<ArraySubobjectPathInfo> Result;
+
+ for (unsigned I = 0, N = LV.Designator.Entries.size(); I != N; ++I) {
+ APValue::LValuePathEntry Entry = LV.Designator.Entries[I];
+
+ if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
+ uint64_t Index = Entry.getAsArrayIndex();
+ CharUnits OffsetInArray = LV.Offset - PathOffset;
+ bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
+ std::optional<ArraySubobjectLocation> Loc =
+ getArraySubobjectLocationImpl(Ctx, ArrayType, Index, OffsetInArray,
+ IsValidOnePastEnd);
+ if (Loc)
+ Result = ArraySubobjectPathInfo{ArrayType, I, *Loc};
+
+ QualType ElementType = ArrayType->getElementType();
+ CharUnits ElementSize = Ctx.getTypeSizeInChars(ElementType);
+ if (ElementSize.isZero())
+ return std::nullopt;
+ if (Index >
+ std::numeric_limits<uint64_t>::max() / ElementSize.getQuantity())
+ return std::nullopt;
+ PathOffset += Index * ElementSize;
+ Type = ElementType;
+ continue;
+ }
+
+ if (const FieldDecl *FD = getAsField(Entry)) {
+ if (FD->getParent()->isInvalidDecl())
+ return std::nullopt;
+ const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(FD->getParent());
+ PathOffset +=
+ Ctx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()));
+ Type = FD->getType();
+ continue;
+ }
+
+ const CXXRecordDecl *Base = getAsBaseClass(Entry);
+ const CXXRecordDecl *Derived = Type->getAsCXXRecordDecl();
+ if (!Derived || !Base)
+ return std::nullopt;
+ const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Derived);
+ PathOffset += isVirtualBaseClass(Entry) ? Layout.getVBaseClassOffset(Base)
+ : Layout.getBaseClassOffset(Base);
+ Type = Ctx.getCanonicalTagType(Base);
+ }
+
+ if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
+ CharUnits OffsetInArray = LV.Offset - PathOffset;
+ if (OffsetInArray.isZero())
+ Result = ArraySubobjectPathInfo{
+ ArrayType, static_cast<unsigned>(LV.Designator.Entries.size()),
+ ArraySubobjectLocation{0, CharUnits::Zero()}};
+ }
+
+ return Result;
+ }
+
+ /// Builds a comparable array view for a string literal, initializer_list
+ /// backing array, or array subobject of a template parameter object.
+ std::optional<NonUniqueArrayInfo> getArrayInfo(const LValue &LV) const {
+ ASTContext &Ctx = Info.Ctx;
+
+ if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+ std::optional<ArraySubobjectLocation> Loc =
+ getStringArraySubobjectLocation(Ctx, LV);
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!Loc || !ArrayType)
+ return std::nullopt;
+
+ NonUniqueArrayInfo Result;
+ Result.ArrayType = ArrayType;
+ Result.Loc = *Loc;
+ Result.StringBase = StringBase;
+ return Result;
+ }
+
+ if (isInitializerListBackingArray(LV)) {
+ std::optional<ArraySubobjectLocation> Loc =
+ getArraySubobjectLocation(Ctx, LV);
+ const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
+ const APValue *ArrayValue = GetCompleteObjectValue(Info, E, LV);
+ if (!Loc || !ArrayType || !ArrayValue || !ArrayValue->isArray())
+ return std::nullopt;
+
+ NonUniqueArrayInfo Result;
+ Result.ArrayType = ArrayType;
+ Result.Loc = *Loc;
+ Result.ArrayValue = ArrayValue;
+ return Result;
+ }
+
+ const auto *VD = LV.Base.dyn_cast<const ValueDecl *>();
+ const auto *TPO = dyn_cast_if_present<TemplateParamObjectDecl>(VD);
+ if (!TPO)
+ return std::nullopt;
+
+ std::optional<ArraySubobjectPathInfo> PathInfo =
+ getArraySubobjectPathInfo(LV);
+ if (!PathInfo)
+ return std::nullopt;
+
+ APValue ArrayValue;
+ SubobjectDesignator ArrayDesignator = LV.Designator;
+ ArrayDesignator.truncate(Ctx, LV.Base, PathInfo->ArrayPathLength);
+ CompleteObject Obj(LV.Base, const_cast<APValue *>(&TPO->getValue()),
+ TPO->getType());
+ if (!extractSubobject(Info, E, Obj, ArrayDesignator, ArrayValue))
+ return std::nullopt;
+ if (!ArrayValue.isArray())
+ return std::nullopt;
+
+ NonUniqueArrayInfo Result;
+ Result.ArrayType = PathInfo->ArrayType;
+ Result.Loc = PathInfo->Loc;
+ Result.OwnedArrayValue = std::move(ArrayValue);
+ return Result;
+ }
+
+ EvalInfo &Info;
+ const Expr *E;
+};
+} // namespace
+
+/// Returns true when two lvalues designate potentially non-unique arrays whose
+/// overlapping elements could be merged into the same storage.
+static bool ArePotentiallyOverlappingNonUniqueObjects(EvalInfo &Info,
+ const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS) {
+ return NonUniqueObjectOverlapChecker(Info, E).mayOverlap(LHS, RHS);
}
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -19360,8 +19594,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
//
if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
return DiagComparison(diag::note_constexpr_literal_comparison);
- if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue,
- RHSValue))
+ if (ArePotentiallyOverlappingNonUniqueObjects(Info, E, LHSValue,
+ RHSValue))
return DiagComparison(
diag::note_constexpr_non_unique_object_comparison);
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 6a0d127fb451b..5b46eb9dd66e8 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -514,6 +514,40 @@ static RawAddress createReferenceTemporary(CodeGenFunction &CGF,
llvm_unreachable("unknown storage duration");
}
+/// Emits a constant initializer_list backing array as a private global when the
+/// temporary can be shared for the full expression without observable effects.
+std::optional<RawAddress> CodeGenFunction::tryEmitStaticInitListBackingArray(
+ const MaterializeTemporaryExpr *MTE) {
+ if (!MTE->isBackingArrayForInitializerList())
+ return std::nullopt;
+
+ switch (MTE->getStorageDuration()) {
+ case SD_FullExpression:
+ case SD_Automatic:
+ break;
+ case SD_Thread:
+ case SD_Static:
+ return std::nullopt;
+ case SD_Dynamic:
+ llvm_unreachable("temporary cannot have dynamic storage duration");
+ }
+
+ const Expr *Init = MTE->getSubExpr();
+ QualType Ty = Init->getType();
+ if (!Ty->isArrayType() ||
+ !Ty.isConstantStorage(getContext(), /*ExcludeCtor=*/true,
+ /*ExcludeDtor=*/false))
+ return std::nullopt;
+
+ llvm::Constant *Constant =
+ ConstantEmitter(*this).tryEmitAbstractForMemory(Init, Ty);
+ if (!Constant)
+ return std::nullopt;
+
+ CharUnits Align = getContext().getTypeAlignInChars(Ty);
+ return CGM.EmitStaticInitListBackingArray(Constant, Align);
+}
+
/// Helper method to check if the underlying ABI is AAPCS
static bool isAAPCS(const TargetInfo &TargetInfo) {
return TargetInfo.getABI().starts_with("aapcs");
@@ -585,6 +619,10 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
}
}
+ if (std::optional<RawAddress> StaticArray =
+ tryEmitStaticInitListBackingArray(M))
+ return MakeAddrLValue(*StaticArray, M->getType(), AlignmentSource::Decl);
+
// Create and initialize the reference temporary.
RawAddress Alloca = Address::invalid();
RawAddress Object = createReferenceTemporary(*this, M, E, &Alloca);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index e7c24f1f36f1e..93be0adfa4ea6 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4493,6 +4493,10 @@ class CodeGenFunction : public CodeGenTypeCache {
LValue EmitConditionalOperatorLValue(const AbstractConditionalOperator *E);
LValue EmitCastLValue(const CastExpr *E);
LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
+ /// Attempts to emit an initializer_list backing array as a static constant
+ /// object instead of materializing a stack temporary.
+ std::optional<RawAddress>
+ tryEmitStaticInitListBackingArray(const MaterializeTemporaryExpr *E);
LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
LValue EmitHLSLArrayAssignLValue(const BinaryOperator *E);
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 012a1115ca7d3..417d6b8c28479 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7766,6 +7766,29 @@ ConstantAddress CodeGenModule::GetAddrOfGlobalTemporary(
return ConstantAddress(CV, Type, Align);
}
+/// Creates the private constant global used for a static initializer_list
+/// backing array.
+ConstantAddress
+CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
+ CharUnits Align) {
+ LangAS AddrSpace = GetGlobalConstantAddressSpace();
+ auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+ auto *GV = new llvm::GlobalVariable(
+ getModule(), Init->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
+ GV->setAlignment(Align.getAsAlign());
+
+ llvm::Constant *CV = GV;
+ if (AddrSpace != LangAS::Default)
+ CV = performAddrSpaceCast(
+ GV, llvm::PointerType::get(
+ getLLVMContext(),
+ getContext().getTargetAddressSpace(LangAS::Default)));
+
+ return ConstantAddress(CV, Init->getType(), Align);
+}
+
/// EmitObjCPropertyImplementations - Emit information for synthesized
/// properties for an implementation.
void CodeGenModule::EmitObjCPropertyImplementations(const
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 0abd75ccb0551..56e5cec73ef5e 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1058,6 +1058,10 @@ class CodeGenModule : public CodeGenTypeCache {
ForDefinition_t IsForDefinition
= NotForDefinition);
+ /// Creates a private constant global for an initializer_list backing array.
+ ConstantAddress EmitStaticInitListBackingArray(llvm::Constant *Init,
+ CharUnits Align);
+
/// Will return a global variable of the given type. If a variable with a
/// different type already exists then a new variable with the right type
/// will be created and all uses of the old variable will be replaced with a
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index a6751453a7f3a..efdc407787fdc 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -92,6 +92,70 @@ namespace cwg2765 {
begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6});
static_assert(!different_three, "");
+ constexpr bool init_list_overlaps_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+ != "hello"; // #cwg2765-init-list-string-compare \
+ // both-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(init_list_overlaps_string(), "");
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool init_list_differs_from_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+ != "hello"; // both-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(init_list_differs_from_string(), "");
+
+ template <int N> struct FixedString {
+ char data[N];
+ constexpr FixedString(const char (&s)[N]) {
+ for (int i = 0; i != N; ++i)
+ data[i] = s[i];
+ }
+ };
+
+ template <FixedString S>
+ constexpr bool template_array_overlaps_string = // both-error {{constexpr variable 'template_array_overlaps_string<FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+ // both-note {{declared here}}
+ S.data != "hello"; // #cwg2765-template-array-string-compare
+ static_assert(template_array_overlaps_string<"hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in instantiation of variable template specialization}} \
+ // both-note {{initializer of 'template_array_overlaps_string<FixedString<6>{"hello"}>' is not a constant expression}}
+ // both-note@#cwg2765-template-array-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+ template <FixedString S>
+ constexpr bool template_array_differs_from_string =
+ S.data != "hello";
+ static_assert(template_array_differs_from_string<"help">);
+
+ template <FixedString S>
+ constexpr bool init_list_overlaps_template_array() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+ != S.data; // #cwg2765-init-list-template-array-compare
+ }
+ static_assert(init_list_overlaps_template_array<"hello">());
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-template-array-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ template <FixedString S>
+ constexpr bool init_list_differs_from_template_array() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+ != S.data;
+ }
+ static_assert(init_list_differs_from_template_array<"hello">());
+
+ template <FixedString A, FixedString B>
+ constexpr bool template_arrays_overlap_shifted = // both-error {{constexpr variable 'template_arrays_overlap_shifted<FixedString<5>{"ello"}, FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+ // both-note {{declared here}}
+ A.data != B.data + 1; // #cwg2765-template-array-shifted-compare
+ static_assert(template_arrays_overlap_shifted<"ello", "hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in instantiation of variable template specialization}} \
+ // both-note {{initializer of 'template_arrays_overlap_shifted<FixedString<5>{"ello"}, FixedString<6>{"hello"}>' is not a constant expression}}
+ // both-note@#cwg2765-template-array-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
constexpr bool same_object() {
std::initializer_list<int> il = {1, 1, 1};
return il.begin() == il.begin() && il.begin() != il.begin() + 1;
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index 62ea3c991f26c..d813acd3c71f8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -481,8 +481,8 @@ namespace B19773010 {
void f1() {
// CHECK-LABEL: @_ZN9B197730102f1Ev
testcase a{{"", ENUM_CONSTANT}};
- // X86: store ptr @.ref.tmp{{.*}}, ptr %{{.*}}, align 8
- // AMDGCN: store ptr addrspacecast{{.*}} @.ref.tmp{{.*}}{{.*}}, ptr %{{.*}}, align 8
+ // X86: store ptr @.init.list{{.*}}, ptr %{{.*}}, align 8
+ // AMDGCN: store ptr addrspacecast{{.*}} @.init.list{{.*}}{{.*}}, ptr %{{.*}}, align 8
}
void f2() {
// CHECK-LABEL: @_ZN9B197730102f2Ev
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index 3f5ecac648066..d2ff58be1ce7a 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,5 +1,9 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private constant [100 x i32]
+
namespace std {
using size_t = decltype(sizeof(int));
@@ -29,8 +33,9 @@ void g(float x) {
void h() {
// CHECK-LABEL: define{{.*}} void @_ZN9example121hEv(
- // CHECK: alloca [3 x double],
- // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24,
+ // CHECK-NOT: alloca [3 x double]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[DOUBLE_INIT]], ptr %{{.*}}, align 8
// CHECK: call void @_ZN9example121fESt16initializer_listIdE(
f({1, 2, 3});
}
@@ -41,8 +46,9 @@ void bytes(std::initializer_list<unsigned char>);
void f() {
// CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv(
- // CHECK: alloca [2 x i8],
- // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2,
+ // CHECK-NOT: alloca [2 x i8]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[EMBED_INIT]], ptr %{{.*}}, align 8
// CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE(
bytes({
#embed <jk.txt>
@@ -50,6 +56,22 @@ void f() {
}
} // namespace embed_example
+namespace large_constant_list {
+#define TEN 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+#define HUNDO TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN, TEN
+
+void f(std::initializer_list<int>);
+
+void g() {
+ // CHECK-LABEL: define{{.*}} void @_ZN19large_constant_list1gEv(
+ // CHECK-NOT: alloca [100 x i32]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[HUNDO_INIT]], ptr %{{.*}}, align 8
+ // CHECK: call void @_ZN19large_constant_list1fESt16initializer_listIiE(
+ f({HUNDO});
+}
+} // namespace large_constant_list
+
namespace destructor_side_effects {
extern "C" int printf(const char *, ...);
diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
index 5963b7ddea170..7ab6b945874cb 100644
--- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
+++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
@@ -21,6 +21,8 @@
// CHECK: @__const._Z13testArrayInitv.p1 = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 }], align 8
// CHECK: @__const._Z13testArrayInitv.c0 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 35591) to i64), i64 0 } }, align 8
// CHECK: @__const._Z13testArrayInitv.c1 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 } }, align 8
+// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
// CHECK: @_ZN22testNoexceptConversion6mfptr1E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S19nonvirtual_noexceptEv, i32 0, i64 [[TYPEDISC3:.*]]) to i64), i64 0 },
// CHECK: @_ZN22testNoexceptConversion6mfptr2E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S16virtual_noexceptEv_vfpthunk_, i32 0, i64 [[TYPEDISC3]]) to i64), i64 0 },
@@ -427,8 +429,8 @@ MethodTy0 gmethod2 = reinterpret_cast<MethodTy0>(&Derived0::virtual1);
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %p1, ptr align 8 @__const._Z13testArrayInitv.p1, i64 16, i1 false)
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %c0, ptr align 8 @__const._Z13testArrayInitv.c0, i64 16, i1 false)
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %c1, ptr align 8 @__const._Z13testArrayInitv.c1, i64 16, i1 false)
-// CHECK: store { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }, ptr %{{.*}} align 8
-// CHECK: store { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }, ptr %{{.*}}, align 8
+// CHECK: store ptr @[[INITLIST0]], ptr %{{.*}}, align 8
+// CHECK: store ptr @[[INITLIST1]], ptr %{{.*}}, align 8
void initList(std::initializer_list<MethodTy1>);
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 47a064c4026b4..3d6350504e8cb 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1939,6 +1939,20 @@ namespace InitializerList {
static_assert(*std::initializer_list<int>{1, 2, 3}.begin() == 1, "");
static_assert(std::initializer_list<int>{1, 2, 3}.begin()[2] == 3, "");
+ constexpr bool init_list_overlaps_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'l', 'o', '\0'}.begin()
+ != "hello"; // expected-warning {{result of comparison against a string literal is unspecified}} \
+ // expected-note {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ }
+ static_assert(init_list_overlaps_string(), ""); // expected-error {{not an integral constant expression}} \
+ // expected-note {{in call to}}
+
+ constexpr bool init_list_differs_from_string() {
+ return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
+ != "hello"; // expected-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(init_list_differs_from_string(), "");
+
namespace DR2126 {
constexpr std::initializer_list<float> il = {1.0, 2.0, 3.0};
static_assert(il.begin()[1] == 2.0, "");
>From bba1ffe19b5644755aa20f9ba18f6c9fe7e53457 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:47:56 +0800
Subject: [PATCH 03/12] [clang] Reuse static initializer_list backing arrays
---
clang/lib/CodeGen/CodeGenModule.cpp | 25 ++++++++-----
clang/lib/CodeGen/CodeGenModule.h | 2 +
.../CodeGenCXX/p2752r3-initializer-list.cpp | 37 +++++++++++++++++--
3 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 417d6b8c28479..f5a54d7e584cf 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7773,18 +7773,23 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
CharUnits Align) {
LangAS AddrSpace = GetGlobalConstantAddressSpace();
auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
- auto *GV = new llvm::GlobalVariable(
- getModule(), Init->getType(), /*isConstant=*/true,
- llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
- /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
- GV->setAlignment(Align.getAsAlign());
-
- llvm::Constant *CV = GV;
+ llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
+ if (!Entry) {
+ Entry = new llvm::GlobalVariable(
+ getModule(), Init->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
+ Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+ }
+ Entry->setAlignment(
+ std::max(Entry->getAlign().valueOrOne(), Align.getAsAlign()));
+
+ llvm::Constant *CV = Entry;
if (AddrSpace != LangAS::Default)
CV = performAddrSpaceCast(
- GV, llvm::PointerType::get(
- getLLVMContext(),
- getContext().getTargetAddressSpace(LangAS::Default)));
+ Entry, llvm::PointerType::get(
+ getLLVMContext(),
+ getContext().getTargetAddressSpace(LangAS::Default)));
return ConstantAddress(CV, Init->getType(), Align);
}
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 56e5cec73ef5e..60618adb55dfd 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -520,6 +520,8 @@ class CodeGenModule : public CodeGenTypeCache {
llvm::StringMap<llvm::GlobalVariable *> CFConstantStringMap;
llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *> ConstantStringMap;
+ llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *>
+ StaticInitListBackingArrayMap;
llvm::DenseMap<const UnnamedGlobalConstantDecl *, llvm::GlobalVariable *>
UnnamedGlobalConstantDeclMap;
llvm::DenseMap<const Decl*, llvm::Constant *> StaticLocalDeclMap;
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index d2ff58be1ce7a..f121fffa82215 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,8 +1,10 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
+// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private constant [2 x i8] c"jk", align 1
-// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private constant [100 x i32]
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
namespace std {
using size_t = decltype(sizeof(int));
@@ -72,6 +74,35 @@ void g() {
}
} // namespace large_constant_list
+namespace shared_static_lists {
+void f(std::initializer_list<int>);
+
+void g() {
+ // CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
+ // CHECK-NOT: alloca [3 x i32]
+ // CHECK-NOT: llvm.memcpy
+ // CHECK: store ptr @[[INT_INIT]], ptr %{{.*}}, align 8
+ // CHECK: call void @_ZN19shared_static_lists1fESt16initializer_listIiE(
+ // CHECK: store ptr @[[INT_INIT]], ptr %{{.*}}, align 8
+ // CHECK: call void @_ZN19shared_static_lists1fESt16initializer_listIiE(
+ f({1, 2, 3});
+ f({1, 2, 3});
+}
+} // namespace shared_static_lists
+
+namespace mergeable_static_list {
+void f(std::initializer_list<int>);
+
+void g() {
+ // ASM: .section .rodata.cst16,"aM", at progbits,16
+ // ASM: .long 1
+ // ASM-NEXT: .long 2
+ // ASM-NEXT: .long 3
+ // ASM-NEXT: .long 4
+ f({1, 2, 3, 4});
+}
+} // namespace mergeable_static_list
+
namespace destructor_side_effects {
extern "C" int printf(const char *, ...);
>From 1bc8669a993ed9d1ae6702d46467ecec8ebde9b8 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 24 Jun 2026 01:55:54 +0800
Subject: [PATCH 04/12] [clang] Pad static initializer_list arrays for merging
---
clang/lib/CodeGen/CodeGenModule.cpp | 21 +++++++++++++++++--
.../cxx0x-initializer-stdinitializerlist.cpp | 8 +++----
.../CodeGenCXX/p2752r3-initializer-list.cpp | 13 ++++++++----
3 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index f5a54d7e584cf..ff44109837266 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -71,6 +71,7 @@
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/TargetParser/AArch64TargetParser.h"
#include "llvm/TargetParser/RISCVISAInfo.h"
@@ -7773,11 +7774,27 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
CharUnits Align) {
LangAS AddrSpace = GetGlobalConstantAddressSpace();
auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+ llvm::Constant *StoredInit = Init;
+ const llvm::DataLayout &DL = getModule().getDataLayout();
+ uint64_t Size = DL.getTypeAllocSize(Init->getType());
+ uint64_t MergeableSize = llvm::PowerOf2Ceil(std::max<uint64_t>(Size, 4));
+ if (!Init->needsRelocation() && Size > 0 && Size <= 32 &&
+ MergeableSize >= 4 && MergeableSize != Size) {
+ uint64_t PaddingSize = MergeableSize - Size;
+ auto *PaddingTy =
+ llvm::ArrayType::get(llvm::Type::getInt8Ty(getLLVMContext()),
+ PaddingSize);
+ auto *PaddedTy = llvm::StructType::get(
+ getLLVMContext(), {Init->getType(), PaddingTy}, /*isPacked=*/true);
+ StoredInit = llvm::ConstantStruct::get(
+ PaddedTy, {Init, llvm::ConstantAggregateZero::get(PaddingTy)});
+ }
+
llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
if (!Entry) {
Entry = new llvm::GlobalVariable(
- getModule(), Init->getType(), /*isConstant=*/true,
- llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ getModule(), StoredInit->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, StoredInit, ".init.list",
/*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
}
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index d813acd3c71f8..cd7fa194624c8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -85,10 +85,10 @@ std::initializer_list<int> thread_local x = {1, 2, 3, 4};
// AMDGCN: @[[PARTLY_CONSTANT_SECOND:_ZGRN15partly_constant2ilE2_]] = internal addrspace(1) global [2 x i32] zeroinitializer, align 4
// AMDGCN: @[[PARTLY_CONSTANT_THIRD:_ZGRN15partly_constant2ilE3_]] = internal addrspace(1) constant [4 x i32] [i32 5, i32 6, i32 7, i32 8], align 4
-// X86: @[[REFTMP1:.*]] = private constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:.*]] = private constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
-// AMDGCN: @[[REFTMP1:.*]] = private addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:.*]] = private addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// X86: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
+// AMDGCN: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
// CHECK: appending global
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index f121fffa82215..b872afc5b6245 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,10 +1,10 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x double], [8 x i8] }> <{ [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], [8 x i8] zeroinitializer }>, align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [2 x i8], [2 x i8] }> <{ [2 x i8] c"jk", [2 x i8] zeroinitializer }>, align 1
// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
-// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x i32], [4 x i8] }> <{ [3 x i32] [i32 1, i32 2, i32 3], [4 x i8] zeroinitializer }>, align 4
namespace std {
using size_t = decltype(sizeof(int));
@@ -78,6 +78,11 @@ namespace shared_static_lists {
void f(std::initializer_list<int>);
void g() {
+ // ASM: .section .rodata.cst16,"aM", at progbits,16
+ // ASM: .long 1
+ // ASM-NEXT: .long 2
+ // ASM-NEXT: .long 3
+ // ASM-NEXT: .zero 4
// CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
// CHECK-NOT: alloca [3 x i32]
// CHECK-NOT: llvm.memcpy
@@ -94,11 +99,11 @@ namespace mergeable_static_list {
void f(std::initializer_list<int>);
void g() {
- // ASM: .section .rodata.cst16,"aM", at progbits,16
// ASM: .long 1
// ASM-NEXT: .long 2
// ASM-NEXT: .long 3
// ASM-NEXT: .long 4
+ // ASM-NEXT: .size {{.*}}, 16
f({1, 2, 3, 4});
}
} // namespace mergeable_static_list
>From 40f6b3ce361164286a44f8b4d952d21158d7c459 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 30 Jun 2026 20:10:55 +0800
Subject: [PATCH 05/12] Fix test
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
index 7ab6b945874cb..0fb5ba8f6550c 100644
--- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
+++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp
@@ -21,8 +21,8 @@
// CHECK: @__const._Z13testArrayInitv.p1 = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 }], align 8
// CHECK: @__const._Z13testArrayInitv.c0 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 35591) to i64), i64 0 } }, align 8
// CHECK: @__const._Z13testArrayInitv.c1 = private unnamed_addr constant %struct.Class0 { { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 35591) to i64), i64 0 } }, align 8
-// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
-// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST0:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base011nonvirtual0Ev, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
+// CHECK: @[[INITLIST1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [1 x { i64, i64 }] [{ i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN5Base08virtual1Ev_vfpthunk_, i32 0, i64 [[TYPEDISC1]]) to i64), i64 0 }], align 8
// CHECK: @_ZN22testNoexceptConversion6mfptr1E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S19nonvirtual_noexceptEv, i32 0, i64 [[TYPEDISC3:.*]]) to i64), i64 0 },
// CHECK: @_ZN22testNoexceptConversion6mfptr2E = global { i64, i64 } { i64 ptrtoint (ptr ptrauth (ptr @_ZN22testNoexceptConversion1S16virtual_noexceptEv_vfpthunk_, i32 0, i64 [[TYPEDISC3]]) to i64), i64 0 },
>From f40b52ab8dc4b4dfe4de8beb6e4f7e14fb9f6adc Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 30 Jun 2026 07:50:47 -0700
Subject: [PATCH 06/12] Refine NonUniqueObject logics in ExprConstant
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ExprConstant.cpp | 561 +++++++++++++++++----------------
1 file changed, 295 insertions(+), 266 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f2c0b3cd02668..244e4d7f283ef 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5003,9 +5003,10 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
}
// For every scalar kind (Int, Float, FixedPoint, ComplexInt, ComplexFloat,
- // AddrLabelDiff, None, Indeterminate), Profile already captures the full
- // observable content. If Profile disagreed, the values are observably
- // distinct.
+ // AddrLabelDiff), Profile already captures the full observable content, so a
+ // Profile disagreement means the values are observably distinct. (None and
+ // Indeterminate never reach here: APValue::hasValue() is false for both, so
+ // they are intercepted by the early return above.)
default:
return false;
}
@@ -5053,327 +5054,355 @@ std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
if (OffsetInElement > ElementSize)
return std::nullopt;
+ // Canonicalize a pointer to the byte just past element Index: it designates
+ // the start of element Index + 1 (or, when Index + 1 == ArraySize, the
+ // array's one-past-the-end position). Without this, the same address could
+ // be described as either {Index, ElementSize} or {Index + 1, 0}, and the two
+ // encodings would not compare equal in callers that match on OffsetInElement.
+ // After this step OffsetInElement is always strictly inside the element,
+ // except for the one-past-the-end location {ArraySize, 0}.
+ if (ElementSize != 0 && OffsetInElement == ElementSize) {
+ ++Index;
+ OffsetInElement = 0;
+ }
+
return ArraySubobjectLocation{Index,
CharUnits::fromQuantity(OffsetInElement)};
}
-/// Returns the array element and element-relative location that LV
-/// designates, or std::nullopt if LV is not an element, one-past-the-end
-/// position, or subobject of an element of its base array.
-static std::optional<ArraySubobjectLocation>
-getArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
- if (LV.Designator.Invalid || LV.Designator.Entries.empty())
- return std::nullopt;
+namespace {
+/// A potentially-non-unique array object: a string literal, an
+/// initializer_list backing array, or an array subobject of a
+/// template-parameter object (NTTP). [intro.object]/9 lets the implementation
+/// merge two such objects when their overlapping contents agree, which is what
+/// makes their address comparisons non-constant.
+///
+/// The kind is recorded explicitly; the InitializerList and TemplateParamObject
+/// kinds are both read through an APValue array (borrowed and owned,
+/// respectively), while the StringLiteral kind materializes characters lazily.
+/// The single public entry point is arePotentiallyOverlapping().
+class NonUniqueObject {
+public:
+ /// Returns true when two lvalues designate potentially non-unique arrays
+ /// whose overlapping elements could be merged into the same storage, making
+ /// their address comparison non-constant.
+ static bool arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
+ const LValue &LHS, const LValue &RHS);
+
+private:
+ enum class Kind { StringLiteral, InitializerList, TemplateParamObject };
+
+ /// Identifies an enclosing constant array subobject within an lvalue path.
+ struct ArraySubobjectPathInfo {
+ const ConstantArrayType *ArrayType = nullptr;
+ unsigned ArrayPathLength = 0;
+ ArraySubobjectLocation Loc;
+ };
- const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
- if (!ArrayType)
+ /// The evaluation state and diagnostic expression are stored so the member
+ /// functions need not thread them through every call.
+ NonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
+
+ /// Recognizes the non-unique array LV designates, filling in this object and
+ /// returning true, or returning false when LV is not such an object.
+ bool recognize(const LValue &LV);
+
+ /// Returns true when *this and Other could be merged into the same storage at
+ /// their designated positions: the positions must share an in-element byte
+ /// offset and element-compatible types, and every element the overlap would
+ /// alias must be pairwise mergeable. Element data the representation cannot
+ /// expose is treated conservatively as a possible match.
+ bool mayShareStorageWith(const NonUniqueObject &Other) const;
+
+ // Lvalue classification helpers used by recognize().
+
+ /// Returns the underlying string literal expression for bases that denote
+ /// string literal objects in the evaluator.
+ static const Expr *getStringLiteralBase(APValue::LValueBase Base);
+ /// Whether LV's base is an initializer_list backing array.
+ static bool isInitializerListBackingArray(const LValue &LV);
+ /// Reads the complete object value an lvalue's base denotes.
+ const APValue *getCompleteObjectValue(const LValue &LV) const;
+ /// Returns the array element and element-relative location that LV
+ /// designates, or std::nullopt if LV is not an element, one-past-the-end
+ /// position, or subobject of an element of its base array.
+ std::optional<ArraySubobjectLocation>
+ getArraySubobjectLocation(const LValue &LV) const;
+ /// Returns the string-literal array element and element-relative offset
+ /// designated by LV, including byte-offset-only lvalues.
+ std::optional<ArraySubobjectLocation>
+ getStringArraySubobjectLocation(const LValue &LV) const;
+ /// Finds the innermost enclosing constant array subobject for an lvalue into
+ /// a template parameter object.
+ std::optional<ArraySubobjectPathInfo>
+ getArraySubobjectPathInfo(const LValue &LV) const;
+
+ bool isStringLiteral() const { return TheKind == Kind::StringLiteral; }
+ const ArraySubobjectLocation &location() const { return Loc; }
+
+ /// Number of elements visible through this view.
+ int64_t size() const {
+ if (TheKind == Kind::StringLiteral)
+ return ArrayType->getZExtSize();
+ return arrayValue()->getArraySize();
+ }
+
+ /// Value of the element at Index, or std::nullopt when the representation
+ /// cannot expose enough data to prove it differs.
+ std::optional<APValue> operator[](int64_t Index) const {
+ if (Index < 0)
+ return std::nullopt;
+ uint64_t UIndex = static_cast<uint64_t>(Index);
+ if (TheKind == Kind::StringLiteral)
+ return APValue(extractStringLiteralCharacter(Info, StringExpr, UIndex));
+ if (const APValue *Elt = GetArrayInitializedElt(*arrayValue(), UIndex))
+ return *Elt;
return std::nullopt;
+ }
- uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
- bool IsValidOnePastEnd =
- LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
- return getArraySubobjectLocationImpl(Ctx, ArrayType, Index, LV.Offset,
- IsValidOnePastEnd);
-}
+ /// Returns the APValue array for the InitializerList / TemplateParamObject
+ /// kinds (borrowed or owned, respectively).
+ const APValue *arrayValue() const {
+ const APValue *Arr = OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
+ assert(Arr && Arr->isArray() && "invalid APValue array");
+ return Arr;
+ }
-static const APValue *GetCompleteObjectValue(EvalInfo &Info, const Expr *E,
- const LValue &LV) {
- CompleteObject Obj =
- findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
- return Obj ? Obj.Value : nullptr;
+ EvalInfo &Info;
+ const Expr *E;
+ Kind TheKind = Kind::StringLiteral;
+ const ConstantArrayType *ArrayType = nullptr;
+ ArraySubobjectLocation Loc;
+ const Expr *StringExpr = nullptr; // Kind::StringLiteral
+ const APValue *ArrayValue = nullptr; // Kind::InitializerList (borrowed)
+ std::optional<APValue> OwnedArrayValue; // Kind::TemplateParamObject (owned)
+};
+} // namespace
+
+const Expr *NonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
+ const auto *BaseExpr = Base.dyn_cast<const Expr *>();
+ if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
+ return BaseExpr;
+ return nullptr;
}
-static bool isInitializerListBackingArray(const LValue &LV) {
+bool NonUniqueObject::isInitializerListBackingArray(const LValue &LV) {
const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>();
const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr);
return MTE && MTE->isBackingArrayForInitializerList();
}
-/// Returns the underlying string literal expression for bases that denote
-/// string literal objects in the evaluator.
-static const Expr *getStringLiteralBase(APValue::LValueBase Base) {
- const auto *BaseExpr = Base.dyn_cast<const Expr *>();
- if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
- return BaseExpr;
- return nullptr;
+const APValue *NonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
+ CompleteObject Obj =
+ findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
+ return Obj ? Obj.Value : nullptr;
}
-/// Returns the string-literal array element and element-relative offset
-/// designated by LV, including byte-offset-only lvalues.
-static std::optional<ArraySubobjectLocation>
-getStringArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) {
+std::optional<ArraySubobjectLocation>
+NonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
+ if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+ return std::nullopt;
+
+ const auto *CAT = Info.Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!CAT)
+ return std::nullopt;
+
+ uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
+ bool IsValidOnePastEnd =
+ LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
+ return getArraySubobjectLocationImpl(Info.Ctx, CAT, Index, LV.Offset,
+ IsValidOnePastEnd);
+}
+
+std::optional<ArraySubobjectLocation>
+NonUniqueObject::getStringArraySubobjectLocation(const LValue &LV) const {
if (!getStringLiteralBase(LV.Base))
return std::nullopt;
- if (std::optional<ArraySubobjectLocation> Loc =
- getArraySubobjectLocation(Ctx, LV))
- return Loc;
+ if (std::optional<ArraySubobjectLocation> EltLoc =
+ getArraySubobjectLocation(LV))
+ return EltLoc;
- const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
- if (!ArrayType || LV.Offset.isNegative())
+ const auto *CAT = Info.Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!CAT || LV.Offset.isNegative())
return std::nullopt;
uint64_t ElementSize =
- Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+ Info.Ctx.getTypeSizeInChars(CAT->getElementType()).getQuantity();
if (!ElementSize)
return std::nullopt;
uint64_t Offset = LV.Offset.getQuantity();
uint64_t Index = Offset / ElementSize;
- if (Index > ArrayType->getZExtSize())
+ uint64_t OffsetInElement = Offset % ElementSize;
+ uint64_t ArraySize = CAT->getZExtSize();
+ // Accept element and subobject positions, plus the single one-past-the-end
+ // byte {ArraySize, 0}. Reject anything beyond that, including an offset that
+ // lands past the one-past-the-end byte with a nonzero in-element remainder.
+ if (Index > ArraySize || (Index == ArraySize && OffsetInElement != 0))
return std::nullopt;
return ArraySubobjectLocation{Index,
- CharUnits::fromQuantity(Offset % ElementSize)};
+ CharUnits::fromQuantity(OffsetInElement)};
}
-namespace {
-/// Identifies an enclosing constant array subobject within an lvalue path.
-struct ArraySubobjectPathInfo {
- const ConstantArrayType *ArrayType = nullptr;
- unsigned ArrayPathLength = 0;
- ArraySubobjectLocation Loc;
-};
-
-/// Describes a potentially non-unique array visible to the constant evaluator.
-struct NonUniqueArrayInfo {
- const ConstantArrayType *ArrayType = nullptr;
- ArraySubobjectLocation Loc;
- /// Non-null for string literal arrays; otherwise ArrayValue or
- /// OwnedArrayValue contains an APValue array.
- const Expr *StringBase = nullptr;
- const APValue *ArrayValue = nullptr;
- std::optional<APValue> OwnedArrayValue;
+std::optional<NonUniqueObject::ArraySubobjectPathInfo>
+NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
+ const ASTContext &Ctx = Info.Ctx;
+ if (LV.Designator.Invalid || LV.Designator.Entries.empty())
+ return std::nullopt;
- /// Returns true when this view describes a string literal array.
- bool isStringLiteral() const { return StringBase; }
+ QualType Type = getType(LV.Base).getNonReferenceType();
+ CharUnits PathOffset = CharUnits::Zero();
+ std::optional<ArraySubobjectPathInfo> Result;
- /// Returns the APValue storage for non-string arrays.
- const APValue *getArrayValue() const {
- return OwnedArrayValue ? &*OwnedArrayValue : ArrayValue;
- }
+ for (unsigned I = 0, N = LV.Designator.Entries.size(); I != N; ++I) {
+ APValue::LValuePathEntry Entry = LV.Designator.Entries[I];
- /// Returns the number of array elements visible through this view.
- int64_t getSize() const {
- if (isStringLiteral())
- return ArrayType->getZExtSize();
+ if (const auto *CAT = Ctx.getAsConstantArrayType(Type)) {
+ uint64_t Index = Entry.getAsArrayIndex();
+ CharUnits OffsetInArray = LV.Offset - PathOffset;
+ bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
+ std::optional<ArraySubobjectLocation> EltLoc =
+ getArraySubobjectLocationImpl(Ctx, CAT, Index, OffsetInArray,
+ IsValidOnePastEnd);
+ if (EltLoc)
+ Result = ArraySubobjectPathInfo{CAT, I, *EltLoc};
+
+ QualType ElementType = CAT->getElementType();
+ CharUnits ElementSize = Ctx.getTypeSizeInChars(ElementType);
+ if (ElementSize.isZero())
+ return std::nullopt;
+ if (Index >
+ std::numeric_limits<uint64_t>::max() / ElementSize.getQuantity())
+ return std::nullopt;
+ PathOffset += Index * ElementSize;
+ Type = ElementType;
+ continue;
+ }
- const APValue *Array = getArrayValue();
- assert(Array && Array->isArray() && "invalid APValue array");
- return Array->getArraySize();
- }
+ if (const FieldDecl *FD = getAsField(Entry)) {
+ if (FD->getParent()->isInvalidDecl())
+ return std::nullopt;
+ const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(FD->getParent());
+ PathOffset +=
+ Ctx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()));
+ Type = FD->getType();
+ continue;
+ }
- /// Returns the value of one element, or nullopt when the representation does
- /// not expose enough data to prove it differs.
- std::optional<APValue> getElement(EvalInfo &Info, int64_t Index) const {
- if (Index < 0)
+ const CXXRecordDecl *Base = getAsBaseClass(Entry);
+ const CXXRecordDecl *Derived = Type->getAsCXXRecordDecl();
+ if (!Derived || !Base)
return std::nullopt;
- uint64_t UIndex = static_cast<uint64_t>(Index);
-
- if (isStringLiteral())
- return APValue(extractStringLiteralCharacter(Info, StringBase, UIndex));
-
- const APValue *Array = getArrayValue();
- assert(Array && Array->isArray() && "invalid APValue array");
- if (const APValue *Elt = GetArrayInitializedElt(*Array, UIndex))
- return *Elt;
- return std::nullopt;
+ const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Derived);
+ PathOffset += isVirtualBaseClass(Entry) ? Layout.getVBaseClassOffset(Base)
+ : Layout.getBaseClassOffset(Base);
+ Type = Ctx.getCanonicalTagType(Base);
}
-};
-/// Performs potentially-non-unique object overlap checks for two evaluator
-/// lvalues.
-class NonUniqueObjectOverlapChecker {
-public:
- /// Creates a checker for comparisons diagnosed at expression E.
- NonUniqueObjectOverlapChecker(EvalInfo &Info, const Expr *E)
- : Info(Info), E(E) {}
-
- /// Returns true when LHS and RHS could designate merged storage.
- bool mayOverlap(const LValue &LHS, const LValue &RHS) const {
- const ASTContext &Ctx = Info.Ctx;
- std::optional<NonUniqueArrayInfo> LHSArray = getArrayInfo(LHS);
- std::optional<NonUniqueArrayInfo> RHSArray = getArrayInfo(RHS);
- if (!LHSArray || !RHSArray)
- return false;
+ if (const auto *CAT = Ctx.getAsConstantArrayType(Type)) {
+ CharUnits OffsetInArray = LV.Offset - PathOffset;
+ if (OffsetInArray.isZero())
+ Result = ArraySubobjectPathInfo{
+ CAT, static_cast<unsigned>(LV.Designator.Entries.size()),
+ ArraySubobjectLocation{0, CharUnits::Zero()}};
+ }
- // Keep the existing string-literal diagnostic path authoritative for
- // string/string comparisons.
- if (LHSArray->isStringLiteral() && RHSArray->isStringLiteral())
- return false;
+ return Result;
+}
- if (LHSArray->Loc.OffsetInElement != RHSArray->Loc.OffsetInElement)
- return false;
+bool NonUniqueObject::recognize(const LValue &LV) {
+ ASTContext &Ctx = Info.Ctx;
- if (!Ctx.hasSameUnqualifiedType(LHSArray->ArrayType->getElementType(),
- RHSArray->ArrayType->getElementType()))
+ if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+ std::optional<ArraySubobjectLocation> EltLoc =
+ getStringArraySubobjectLocation(LV);
+ const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
+ if (!EltLoc || !CAT)
return false;
+ TheKind = Kind::StringLiteral;
+ ArrayType = CAT;
+ Loc = *EltLoc;
+ StringExpr = StringBase;
+ return true;
+ }
- int64_t LHSOffset = LHSArray->Loc.Index;
- int64_t RHSOffset = RHSArray->Loc.Index;
- int64_t LHSSize = LHSArray->getSize();
- int64_t RHSSize = RHSArray->getSize();
- int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
- int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
- if (OverlapBegin >= OverlapEnd)
+ if (isInitializerListBackingArray(LV)) {
+ std::optional<ArraySubobjectLocation> EltLoc = getArraySubobjectLocation(LV);
+ const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
+ const APValue *ArrVal = getCompleteObjectValue(LV);
+ if (!EltLoc || !CAT || !ArrVal || !ArrVal->isArray())
return false;
-
- for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
- std::optional<APValue> LHSElt = LHSArray->getElement(Info, I + LHSOffset);
- std::optional<APValue> RHSElt = RHSArray->getElement(Info, I + RHSOffset);
- // Missing element data: be conservative and assume the arrays may share
- // storage.
- if (!LHSElt || !RHSElt)
- return true;
- if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
- return false;
- }
-
+ TheKind = Kind::InitializerList;
+ ArrayType = CAT;
+ Loc = *EltLoc;
+ ArrayValue = ArrVal;
return true;
}
-private:
- /// Finds the innermost enclosing constant array subobject for an lvalue into
- /// a template parameter object.
- std::optional<ArraySubobjectPathInfo>
- getArraySubobjectPathInfo(const LValue &LV) const {
- const ASTContext &Ctx = Info.Ctx;
- if (LV.Designator.Invalid || LV.Designator.Entries.empty())
- return std::nullopt;
+ const auto *VD = LV.Base.dyn_cast<const ValueDecl *>();
+ const auto *TPO = dyn_cast_if_present<TemplateParamObjectDecl>(VD);
+ if (!TPO)
+ return false;
- QualType Type = getType(LV.Base).getNonReferenceType();
- CharUnits PathOffset = CharUnits::Zero();
- std::optional<ArraySubobjectPathInfo> Result;
-
- for (unsigned I = 0, N = LV.Designator.Entries.size(); I != N; ++I) {
- APValue::LValuePathEntry Entry = LV.Designator.Entries[I];
-
- if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
- uint64_t Index = Entry.getAsArrayIndex();
- CharUnits OffsetInArray = LV.Offset - PathOffset;
- bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
- std::optional<ArraySubobjectLocation> Loc =
- getArraySubobjectLocationImpl(Ctx, ArrayType, Index, OffsetInArray,
- IsValidOnePastEnd);
- if (Loc)
- Result = ArraySubobjectPathInfo{ArrayType, I, *Loc};
-
- QualType ElementType = ArrayType->getElementType();
- CharUnits ElementSize = Ctx.getTypeSizeInChars(ElementType);
- if (ElementSize.isZero())
- return std::nullopt;
- if (Index >
- std::numeric_limits<uint64_t>::max() / ElementSize.getQuantity())
- return std::nullopt;
- PathOffset += Index * ElementSize;
- Type = ElementType;
- continue;
- }
+ std::optional<ArraySubobjectPathInfo> PathInfo = getArraySubobjectPathInfo(LV);
+ if (!PathInfo)
+ return false;
- if (const FieldDecl *FD = getAsField(Entry)) {
- if (FD->getParent()->isInvalidDecl())
- return std::nullopt;
- const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(FD->getParent());
- PathOffset +=
- Ctx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()));
- Type = FD->getType();
- continue;
- }
+ APValue ArrVal;
+ SubobjectDesignator ArrayDesignator = LV.Designator;
+ ArrayDesignator.truncate(Ctx, LV.Base, PathInfo->ArrayPathLength);
+ CompleteObject Obj(LV.Base, const_cast<APValue *>(&TPO->getValue()),
+ TPO->getType());
+ if (!extractSubobject(Info, E, Obj, ArrayDesignator, ArrVal))
+ return false;
+ if (!ArrVal.isArray())
+ return false;
+ TheKind = Kind::TemplateParamObject;
+ ArrayType = PathInfo->ArrayType;
+ Loc = PathInfo->Loc;
+ OwnedArrayValue = std::move(ArrVal);
+ return true;
+}
- const CXXRecordDecl *Base = getAsBaseClass(Entry);
- const CXXRecordDecl *Derived = Type->getAsCXXRecordDecl();
- if (!Derived || !Base)
- return std::nullopt;
- const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Derived);
- PathOffset += isVirtualBaseClass(Entry) ? Layout.getVBaseClassOffset(Base)
- : Layout.getBaseClassOffset(Base);
- Type = Ctx.getCanonicalTagType(Base);
- }
+bool NonUniqueObject::mayShareStorageWith(const NonUniqueObject &Other) const {
+ if (location().OffsetInElement != Other.location().OffsetInElement)
+ return false;
+ if (!Info.Ctx.hasSameUnqualifiedType(ArrayType->getElementType(),
+ Other.ArrayType->getElementType()))
+ return false;
- if (const auto *ArrayType = Ctx.getAsConstantArrayType(Type)) {
- CharUnits OffsetInArray = LV.Offset - PathOffset;
- if (OffsetInArray.isZero())
- Result = ArraySubobjectPathInfo{
- ArrayType, static_cast<unsigned>(LV.Designator.Entries.size()),
- ArraySubobjectLocation{0, CharUnits::Zero()}};
- }
+ int64_t LHSStart = location().Index, RHSStart = Other.location().Index;
+ int64_t Begin = std::max(-LHSStart, -RHSStart);
+ int64_t End = std::min(size() - LHSStart, Other.size() - RHSStart);
+ if (Begin >= End)
+ return false;
- return Result;
+ for (int64_t I = Begin; I != End; ++I) {
+ std::optional<APValue> LHSElt = (*this)[I + LHSStart];
+ std::optional<APValue> RHSElt = Other[I + RHSStart];
+ if (!LHSElt || !RHSElt)
+ return true;
+ if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
+ return false;
}
+ return true;
+}
- /// Builds a comparable array view for a string literal, initializer_list
- /// backing array, or array subobject of a template parameter object.
- std::optional<NonUniqueArrayInfo> getArrayInfo(const LValue &LV) const {
- ASTContext &Ctx = Info.Ctx;
-
- if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
- std::optional<ArraySubobjectLocation> Loc =
- getStringArraySubobjectLocation(Ctx, LV);
- const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
- if (!Loc || !ArrayType)
- return std::nullopt;
-
- NonUniqueArrayInfo Result;
- Result.ArrayType = ArrayType;
- Result.Loc = *Loc;
- Result.StringBase = StringBase;
- return Result;
- }
-
- if (isInitializerListBackingArray(LV)) {
- std::optional<ArraySubobjectLocation> Loc =
- getArraySubobjectLocation(Ctx, LV);
- const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base));
- const APValue *ArrayValue = GetCompleteObjectValue(Info, E, LV);
- if (!Loc || !ArrayType || !ArrayValue || !ArrayValue->isArray())
- return std::nullopt;
-
- NonUniqueArrayInfo Result;
- Result.ArrayType = ArrayType;
- Result.Loc = *Loc;
- Result.ArrayValue = ArrayValue;
- return Result;
- }
-
- const auto *VD = LV.Base.dyn_cast<const ValueDecl *>();
- const auto *TPO = dyn_cast_if_present<TemplateParamObjectDecl>(VD);
- if (!TPO)
- return std::nullopt;
-
- std::optional<ArraySubobjectPathInfo> PathInfo =
- getArraySubobjectPathInfo(LV);
- if (!PathInfo)
- return std::nullopt;
-
- APValue ArrayValue;
- SubobjectDesignator ArrayDesignator = LV.Designator;
- ArrayDesignator.truncate(Ctx, LV.Base, PathInfo->ArrayPathLength);
- CompleteObject Obj(LV.Base, const_cast<APValue *>(&TPO->getValue()),
- TPO->getType());
- if (!extractSubobject(Info, E, Obj, ArrayDesignator, ArrayValue))
- return std::nullopt;
- if (!ArrayValue.isArray())
- return std::nullopt;
-
- NonUniqueArrayInfo Result;
- Result.ArrayType = PathInfo->ArrayType;
- Result.Loc = PathInfo->Loc;
- Result.OwnedArrayValue = std::move(ArrayValue);
- return Result;
- }
+bool NonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS) {
+ NonUniqueObject LHSObj(Info, E);
+ NonUniqueObject RHSObj(Info, E);
+ if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
+ return false;
- EvalInfo &Info;
- const Expr *E;
-};
-} // namespace
+ // String/string comparisons stay with the dedicated string-literal path.
+ if (LHSObj.isStringLiteral() && RHSObj.isStringLiteral())
+ return false;
-/// Returns true when two lvalues designate potentially non-unique arrays whose
-/// overlapping elements could be merged into the same storage.
-static bool ArePotentiallyOverlappingNonUniqueObjects(EvalInfo &Info,
- const Expr *E,
- const LValue &LHS,
- const LValue &RHS) {
- return NonUniqueObjectOverlapChecker(Info, E).mayOverlap(LHS, RHS);
+ return LHSObj.mayShareStorageWith(RHSObj);
}
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -19594,8 +19623,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
//
if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
return DiagComparison(diag::note_constexpr_literal_comparison);
- if (ArePotentiallyOverlappingNonUniqueObjects(Info, E, LHSValue,
- RHSValue))
+ if (NonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
+ RHSValue))
return DiagComparison(
diag::note_constexpr_non_unique_object_comparison);
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
>From 73534cb551ae1ab95766a66fb823c5f9d20deed4 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 01:14:01 -0700
Subject: [PATCH 07/12] Revert "[clang] Pad static initializer_list arrays for
merging"
This reverts commit 99a47b9d7817fdcfd1fc49b403118a87fdfb328c.
---
clang/lib/CodeGen/CodeGenModule.cpp | 21 ++-----------------
.../cxx0x-initializer-stdinitializerlist.cpp | 8 +++----
.../CodeGenCXX/p2752r3-initializer-list.cpp | 13 ++++--------
3 files changed, 10 insertions(+), 32 deletions(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index ff44109837266..f5a54d7e584cf 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -71,7 +71,6 @@
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorHandling.h"
-#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/TargetParser/AArch64TargetParser.h"
#include "llvm/TargetParser/RISCVISAInfo.h"
@@ -7774,27 +7773,11 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
CharUnits Align) {
LangAS AddrSpace = GetGlobalConstantAddressSpace();
auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
- llvm::Constant *StoredInit = Init;
- const llvm::DataLayout &DL = getModule().getDataLayout();
- uint64_t Size = DL.getTypeAllocSize(Init->getType());
- uint64_t MergeableSize = llvm::PowerOf2Ceil(std::max<uint64_t>(Size, 4));
- if (!Init->needsRelocation() && Size > 0 && Size <= 32 &&
- MergeableSize >= 4 && MergeableSize != Size) {
- uint64_t PaddingSize = MergeableSize - Size;
- auto *PaddingTy =
- llvm::ArrayType::get(llvm::Type::getInt8Ty(getLLVMContext()),
- PaddingSize);
- auto *PaddedTy = llvm::StructType::get(
- getLLVMContext(), {Init->getType(), PaddingTy}, /*isPacked=*/true);
- StoredInit = llvm::ConstantStruct::get(
- PaddedTy, {Init, llvm::ConstantAggregateZero::get(PaddingTy)});
- }
-
llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
if (!Entry) {
Entry = new llvm::GlobalVariable(
- getModule(), StoredInit->getType(), /*isConstant=*/true,
- llvm::GlobalValue::PrivateLinkage, StoredInit, ".init.list",
+ getModule(), Init->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
/*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
}
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index cd7fa194624c8..d813acd3c71f8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -85,10 +85,10 @@ std::initializer_list<int> thread_local x = {1, 2, 3, 4};
// AMDGCN: @[[PARTLY_CONSTANT_SECOND:_ZGRN15partly_constant2ilE2_]] = internal addrspace(1) global [2 x i32] zeroinitializer, align 4
// AMDGCN: @[[PARTLY_CONSTANT_THIRD:_ZGRN15partly_constant2ilE3_]] = internal addrspace(1) constant [4 x i32] [i32 5, i32 6, i32 7, i32 8], align 4
-// X86: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
-// AMDGCN: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
+// X86: @[[REFTMP1:.*]] = private constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:.*]] = private constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// AMDGCN: @[[REFTMP1:.*]] = private addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:.*]] = private addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
// CHECK: appending global
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index b872afc5b6245..f121fffa82215 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,10 +1,10 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x double], [8 x i8] }> <{ [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], [8 x i8] zeroinitializer }>, align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [2 x i8], [2 x i8] }> <{ [2 x i8] c"jk", [2 x i8] zeroinitializer }>, align 1
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
-// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x i32], [4 x i8] }> <{ [3 x i32] [i32 1, i32 2, i32 3], [4 x i8] zeroinitializer }>, align 4
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
namespace std {
using size_t = decltype(sizeof(int));
@@ -78,11 +78,6 @@ namespace shared_static_lists {
void f(std::initializer_list<int>);
void g() {
- // ASM: .section .rodata.cst16,"aM", at progbits,16
- // ASM: .long 1
- // ASM-NEXT: .long 2
- // ASM-NEXT: .long 3
- // ASM-NEXT: .zero 4
// CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
// CHECK-NOT: alloca [3 x i32]
// CHECK-NOT: llvm.memcpy
@@ -99,11 +94,11 @@ namespace mergeable_static_list {
void f(std::initializer_list<int>);
void g() {
+ // ASM: .section .rodata.cst16,"aM", at progbits,16
// ASM: .long 1
// ASM-NEXT: .long 2
// ASM-NEXT: .long 3
// ASM-NEXT: .long 4
- // ASM-NEXT: .size {{.*}}, 16
f({1, 2, 3, 4});
}
} // namespace mergeable_static_list
>From b7c0ce23b2f6c9c4a5c717d85b3d5ddb960cb0b0 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 01:23:31 -0700
Subject: [PATCH 08/12] Revert padding and format
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ExprConstant.cpp | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 244e4d7f283ef..75752d44b281e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5173,9 +5173,9 @@ class NonUniqueObject {
Kind TheKind = Kind::StringLiteral;
const ConstantArrayType *ArrayType = nullptr;
ArraySubobjectLocation Loc;
- const Expr *StringExpr = nullptr; // Kind::StringLiteral
- const APValue *ArrayValue = nullptr; // Kind::InitializerList (borrowed)
- std::optional<APValue> OwnedArrayValue; // Kind::TemplateParamObject (owned)
+ const Expr *StringExpr = nullptr; // Kind::StringLiteral
+ const APValue *ArrayValue = nullptr; // Kind::InitializerList (borrowed)
+ std::optional<APValue> OwnedArrayValue; // Kind::TemplateParamObject (owned)
};
} // namespace
@@ -5329,7 +5329,8 @@ bool NonUniqueObject::recognize(const LValue &LV) {
}
if (isInitializerListBackingArray(LV)) {
- std::optional<ArraySubobjectLocation> EltLoc = getArraySubobjectLocation(LV);
+ std::optional<ArraySubobjectLocation> EltLoc =
+ getArraySubobjectLocation(LV);
const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
const APValue *ArrVal = getCompleteObjectValue(LV);
if (!EltLoc || !CAT || !ArrVal || !ArrVal->isArray())
@@ -5346,7 +5347,8 @@ bool NonUniqueObject::recognize(const LValue &LV) {
if (!TPO)
return false;
- std::optional<ArraySubobjectPathInfo> PathInfo = getArraySubobjectPathInfo(LV);
+ std::optional<ArraySubobjectPathInfo> PathInfo =
+ getArraySubobjectPathInfo(LV);
if (!PathInfo)
return false;
>From e7baa3fdf11e2b4c81cabd8754c4419169a79956 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 02:33:16 -0700
Subject: [PATCH 09/12] Fix test
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
.../CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index d813acd3c71f8..2ea0b5faf5a51 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -85,10 +85,10 @@ std::initializer_list<int> thread_local x = {1, 2, 3, 4};
// AMDGCN: @[[PARTLY_CONSTANT_SECOND:_ZGRN15partly_constant2ilE2_]] = internal addrspace(1) global [2 x i32] zeroinitializer, align 4
// AMDGCN: @[[PARTLY_CONSTANT_THIRD:_ZGRN15partly_constant2ilE3_]] = internal addrspace(1) constant [4 x i32] [i32 5, i32 6, i32 7, i32 8], align 4
-// X86: @[[REFTMP1:.*]] = private constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:.*]] = private constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
-// AMDGCN: @[[REFTMP1:.*]] = private addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:.*]] = private addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// X86: @[[REFTMP1:.*]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:.*]] = private unnamed_addr constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// AMDGCN: @[[REFTMP1:.*]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:.*]] = private unnamed_addr addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
// CHECK: appending global
>From 6833557cdc7e6f130b4a72455abdd978aa4e83fe Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 1 Jul 2026 23:01:03 -0700
Subject: [PATCH 10/12] Refine ExprConstant
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ByteCode/Interp.cpp | 6 ++--
clang/lib/AST/ExprConstShared.h | 2 +-
clang/lib/AST/ExprConstant.cpp | 49 +++++++++++++++++--------------
3 files changed, 31 insertions(+), 26 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 0445075042fb4..db0ef677c1d67 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2904,7 +2904,7 @@ getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
uint64_t Index = Path.front().getAsArrayIndex();
bool IsValidOnePastEnd = Path.size() == 1;
- std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
if (!Loc)
return std::nullopt;
@@ -2984,7 +2984,7 @@ getNonUniqueAPValueArraySubobjectLocation(const ASTContext &Ctx,
uint64_t Index = Path.front().getAsArrayIndex();
bool IsValidOnePastEnd = Path.size() == 1;
- std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
Ctx, ArrayType, Index,
PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
IsValidOnePastEnd);
@@ -3027,7 +3027,7 @@ getStringArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
uint64_t Index = Path.front().getAsArrayIndex();
bool IsValidOnePastEnd = Path.size() == 1;
- std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl(
+ std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
if (!Loc)
return std::nullopt;
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index e67ce3dabdd37..dd1043e83f574 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -132,7 +132,7 @@ struct ArraySubobjectLocation {
/// array (which the caller determines from its own lvalue representation).
/// Returns std::nullopt if the lvalue does not designate an element,
/// one-past-the-end position, or subobject of an element.
-std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
CharUnits LValueOffset, bool IsValidOnePastEnd);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 75752d44b281e..4fe8275e27b30 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5024,7 +5024,7 @@ bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
}
-std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl(
+std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
CharUnits LValueOffset, bool IsValidOnePastEnd) {
uint64_t ArraySize = ArrayType->getZExtSize();
@@ -5081,7 +5081,7 @@ namespace {
/// kinds are both read through an APValue array (borrowed and owned,
/// respectively), while the StringLiteral kind materializes characters lazily.
/// The single public entry point is arePotentiallyOverlapping().
-class NonUniqueObject {
+class PotentialNonUniqueObject {
public:
/// Returns true when two lvalues designate potentially non-unique arrays
/// whose overlapping elements could be merged into the same storage, making
@@ -5101,7 +5101,7 @@ class NonUniqueObject {
/// The evaluation state and diagnostic expression are stored so the member
/// functions need not thread them through every call.
- NonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
+ PotentialNonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
/// Recognizes the non-unique array LV designates, filling in this object and
/// returning true, or returning false when LV is not such an object.
@@ -5112,7 +5112,7 @@ class NonUniqueObject {
/// offset and element-compatible types, and every element the overlap would
/// alias must be pairwise mergeable. Element data the representation cannot
/// expose is treated conservatively as a possible match.
- bool mayShareStorageWith(const NonUniqueObject &Other) const;
+ bool mayShareStorageWith(const PotentialNonUniqueObject &Other) const;
// Lvalue classification helpers used by recognize().
@@ -5179,27 +5179,29 @@ class NonUniqueObject {
};
} // namespace
-const Expr *NonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
+const Expr *
+PotentialNonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
const auto *BaseExpr = Base.dyn_cast<const Expr *>();
if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
return BaseExpr;
return nullptr;
}
-bool NonUniqueObject::isInitializerListBackingArray(const LValue &LV) {
+bool PotentialNonUniqueObject::isInitializerListBackingArray(const LValue &LV) {
const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>();
const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr);
return MTE && MTE->isBackingArrayForInitializerList();
}
-const APValue *NonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
+const APValue *
+PotentialNonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
CompleteObject Obj =
findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
return Obj ? Obj.Value : nullptr;
}
std::optional<ArraySubobjectLocation>
-NonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
+PotentialNonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
if (LV.Designator.Invalid || LV.Designator.Entries.empty())
return std::nullopt;
@@ -5210,12 +5212,13 @@ NonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex();
bool IsValidOnePastEnd =
LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd();
- return getArraySubobjectLocationImpl(Info.Ctx, CAT, Index, LV.Offset,
+ return computeArraySubobjectLocation(Info.Ctx, CAT, Index, LV.Offset,
IsValidOnePastEnd);
}
std::optional<ArraySubobjectLocation>
-NonUniqueObject::getStringArraySubobjectLocation(const LValue &LV) const {
+PotentialNonUniqueObject::getStringArraySubobjectLocation(
+ const LValue &LV) const {
if (!getStringLiteralBase(LV.Base))
return std::nullopt;
@@ -5246,8 +5249,8 @@ NonUniqueObject::getStringArraySubobjectLocation(const LValue &LV) const {
CharUnits::fromQuantity(OffsetInElement)};
}
-std::optional<NonUniqueObject::ArraySubobjectPathInfo>
-NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
+std::optional<PotentialNonUniqueObject::ArraySubobjectPathInfo>
+PotentialNonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
const ASTContext &Ctx = Info.Ctx;
if (LV.Designator.Invalid || LV.Designator.Entries.empty())
return std::nullopt;
@@ -5264,7 +5267,7 @@ NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
CharUnits OffsetInArray = LV.Offset - PathOffset;
bool IsValidOnePastEnd = I + 1 == N && LV.Designator.isOnePastTheEnd();
std::optional<ArraySubobjectLocation> EltLoc =
- getArraySubobjectLocationImpl(Ctx, CAT, Index, OffsetInArray,
+ computeArraySubobjectLocation(Ctx, CAT, Index, OffsetInArray,
IsValidOnePastEnd);
if (EltLoc)
Result = ArraySubobjectPathInfo{CAT, I, *EltLoc};
@@ -5312,7 +5315,7 @@ NonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
return Result;
}
-bool NonUniqueObject::recognize(const LValue &LV) {
+bool PotentialNonUniqueObject::recognize(const LValue &LV) {
ASTContext &Ctx = Info.Ctx;
if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
@@ -5368,7 +5371,8 @@ bool NonUniqueObject::recognize(const LValue &LV) {
return true;
}
-bool NonUniqueObject::mayShareStorageWith(const NonUniqueObject &Other) const {
+bool PotentialNonUniqueObject::mayShareStorageWith(
+ const PotentialNonUniqueObject &Other) const {
if (location().OffsetInElement != Other.location().OffsetInElement)
return false;
if (!Info.Ctx.hasSameUnqualifiedType(ArrayType->getElementType(),
@@ -5392,11 +5396,12 @@ bool NonUniqueObject::mayShareStorageWith(const NonUniqueObject &Other) const {
return true;
}
-bool NonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
- const LValue &LHS,
- const LValue &RHS) {
- NonUniqueObject LHSObj(Info, E);
- NonUniqueObject RHSObj(Info, E);
+bool PotentialNonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info,
+ const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS) {
+ PotentialNonUniqueObject LHSObj(Info, E);
+ PotentialNonUniqueObject RHSObj(Info, E);
if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
return false;
@@ -19625,8 +19630,8 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
//
if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
return DiagComparison(diag::note_constexpr_literal_comparison);
- if (NonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
- RHSValue))
+ if (PotentialNonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
+ RHSValue))
return DiagComparison(
diag::note_constexpr_non_unique_object_comparison);
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
>From 152de2c5c65452ef01d894f6a838a5617e0b01be Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Sun, 12 Jul 2026 23:32:18 -0700
Subject: [PATCH 11/12] Reapply "[clang] Pad static initializer_list arrays for
merging"
This reverts commit 73534cb551ae1ab95766a66fb823c5f9d20deed4.
---
clang/lib/CodeGen/CodeGenModule.cpp | 21 +++++++++++++++++--
.../cxx0x-initializer-stdinitializerlist.cpp | 8 +++----
.../CodeGenCXX/p2752r3-initializer-list.cpp | 13 ++++++++----
3 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index f5a54d7e584cf..ff44109837266 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -71,6 +71,7 @@
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/TargetParser/AArch64TargetParser.h"
#include "llvm/TargetParser/RISCVISAInfo.h"
@@ -7773,11 +7774,27 @@ CodeGenModule::EmitStaticInitListBackingArray(llvm::Constant *Init,
CharUnits Align) {
LangAS AddrSpace = GetGlobalConstantAddressSpace();
auto TargetAS = getContext().getTargetAddressSpace(AddrSpace);
+ llvm::Constant *StoredInit = Init;
+ const llvm::DataLayout &DL = getModule().getDataLayout();
+ uint64_t Size = DL.getTypeAllocSize(Init->getType());
+ uint64_t MergeableSize = llvm::PowerOf2Ceil(std::max<uint64_t>(Size, 4));
+ if (!Init->needsRelocation() && Size > 0 && Size <= 32 &&
+ MergeableSize >= 4 && MergeableSize != Size) {
+ uint64_t PaddingSize = MergeableSize - Size;
+ auto *PaddingTy =
+ llvm::ArrayType::get(llvm::Type::getInt8Ty(getLLVMContext()),
+ PaddingSize);
+ auto *PaddedTy = llvm::StructType::get(
+ getLLVMContext(), {Init->getType(), PaddingTy}, /*isPacked=*/true);
+ StoredInit = llvm::ConstantStruct::get(
+ PaddedTy, {Init, llvm::ConstantAggregateZero::get(PaddingTy)});
+ }
+
llvm::GlobalVariable *&Entry = StaticInitListBackingArrayMap[Init];
if (!Entry) {
Entry = new llvm::GlobalVariable(
- getModule(), Init->getType(), /*isConstant=*/true,
- llvm::GlobalValue::PrivateLinkage, Init, ".init.list",
+ getModule(), StoredInit->getType(), /*isConstant=*/true,
+ llvm::GlobalValue::PrivateLinkage, StoredInit, ".init.list",
/*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal, TargetAS);
Entry->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
}
diff --git a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
index 2ea0b5faf5a51..cd7fa194624c8 100644
--- a/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
+++ b/clang/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp
@@ -85,10 +85,10 @@ std::initializer_list<int> thread_local x = {1, 2, 3, 4};
// AMDGCN: @[[PARTLY_CONSTANT_SECOND:_ZGRN15partly_constant2ilE2_]] = internal addrspace(1) global [2 x i32] zeroinitializer, align 4
// AMDGCN: @[[PARTLY_CONSTANT_THIRD:_ZGRN15partly_constant2ilE3_]] = internal addrspace(1) constant [4 x i32] [i32 5, i32 6, i32 7, i32 8], align 4
-// X86: @[[REFTMP1:.*]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
-// X86: @[[REFTMP2:.*]] = private unnamed_addr constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
-// AMDGCN: @[[REFTMP1:.*]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
-// AMDGCN: @[[REFTMP2:.*]] = private unnamed_addr addrspace(4) constant [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], align 4
+// X86: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i32] [i32 42, i32 43], align 4
+// X86: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
+// AMDGCN: @[[REFTMP1:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant [2 x i32] [i32 42, i32 43], align 4
+// AMDGCN: @[[REFTMP2:[.A-Za-z0-9_$]+]] = private unnamed_addr addrspace(4) constant <{ [3 x %{{.*}}], [4 x i8] }> <{ [3 x %{{.*}}] [%{{.*}} { i32 1 }, %{{.*}} { i32 2 }, %{{.*}} { i32 3 }], [4 x i8] zeroinitializer }>, align 4
// CHECK: appending global
diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
index f121fffa82215..b872afc5b6245 100644
--- a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
+++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp
@@ -1,10 +1,10 @@
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s
// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -S -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck --check-prefix=ASM %s
-// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], align 8
-// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [2 x i8] c"jk", align 1
+// CHECK-DAG: @[[DOUBLE_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x double], [8 x i8] }> <{ [3 x double] [double 1.000000e+00, double 2.000000e+00, double 3.000000e+00], [8 x i8] zeroinitializer }>, align 8
+// CHECK-DAG: @[[EMBED_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [2 x i8], [2 x i8] }> <{ [2 x i8] c"jk", [2 x i8] zeroinitializer }>, align 1
// CHECK-DAG: @[[HUNDO_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [100 x i32]
-// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant [3 x i32] [i32 1, i32 2, i32 3], align 4
+// CHECK-DAG: @[[INT_INIT:[.A-Za-z0-9_$]+]] = private unnamed_addr constant <{ [3 x i32], [4 x i8] }> <{ [3 x i32] [i32 1, i32 2, i32 3], [4 x i8] zeroinitializer }>, align 4
namespace std {
using size_t = decltype(sizeof(int));
@@ -78,6 +78,11 @@ namespace shared_static_lists {
void f(std::initializer_list<int>);
void g() {
+ // ASM: .section .rodata.cst16,"aM", at progbits,16
+ // ASM: .long 1
+ // ASM-NEXT: .long 2
+ // ASM-NEXT: .long 3
+ // ASM-NEXT: .zero 4
// CHECK-LABEL: define{{.*}} void @_ZN19shared_static_lists1gEv(
// CHECK-NOT: alloca [3 x i32]
// CHECK-NOT: llvm.memcpy
@@ -94,11 +99,11 @@ namespace mergeable_static_list {
void f(std::initializer_list<int>);
void g() {
- // ASM: .section .rodata.cst16,"aM", at progbits,16
// ASM: .long 1
// ASM-NEXT: .long 2
// ASM-NEXT: .long 3
// ASM-NEXT: .long 4
+ // ASM-NEXT: .size {{.*}}, 16
f({1, 2, 3, 4});
}
} // namespace mergeable_static_list
>From e959670f244ec53cfee79c950b0421a78bac76e3 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Mon, 13 Jul 2026 11:01:57 -0700
Subject: [PATCH 12/12] [clang] Refine constant evaluator implementation
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ByteCode/Interp.cpp | 444 +++++---------
clang/lib/AST/ByteCode/Interp.h | 42 +-
clang/lib/AST/ExprConstShared.h | 149 ++++-
clang/lib/AST/ExprConstant.cpp | 561 ++++++++----------
clang/test/AST/ByteCode/cxx11.cpp | 9 +
clang/test/AST/ByteCode/initializer_list.cpp | 47 ++
clang/test/AST/ByteCode/objc.mm | 7 +
.../SemaCXX/constant-expression-cxx11.cpp | 13 +
8 files changed, 647 insertions(+), 625 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index db0ef677c1d67..c7d469d35b16e 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2805,57 +2805,6 @@ bool DiagTypeid(InterpState &S, CodePtr OpPC) {
return false;
}
-bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
- const Pointer &RHS) {
- if (!LHS.pointsToStringLiteral() || !RHS.pointsToStringLiteral())
- return false;
-
- unsigned LHSOffset = LHS.isOnePastEnd() ? LHS.getNumElems() : LHS.getIndex();
- unsigned RHSOffset = RHS.isOnePastEnd() ? RHS.getNumElems() : RHS.getIndex();
- const auto *LHSLit = cast<StringLiteral>(LHS.getDeclDesc()->asExpr());
- const auto *RHSLit = cast<StringLiteral>(RHS.getDeclDesc()->asExpr());
-
- StringRef LHSStr(LHSLit->getBytes());
- unsigned LHSLength = LHSStr.size();
- StringRef RHSStr(RHSLit->getBytes());
- unsigned RHSLength = RHSStr.size();
-
- int32_t IndexDiff = RHSOffset - LHSOffset;
- if (IndexDiff < 0) {
- if (static_cast<int32_t>(LHSLength) < -IndexDiff)
- return false;
- LHSStr = LHSStr.drop_front(-IndexDiff);
- } else {
- if (static_cast<int32_t>(RHSLength) < IndexDiff)
- return false;
- RHSStr = RHSStr.drop_front(IndexDiff);
- }
-
- unsigned ShorterCharWidth;
- StringRef Shorter;
- StringRef Longer;
- if (LHSLength < RHSLength) {
- ShorterCharWidth = LHS.getFieldDesc()->getElemDataSize();
- Shorter = LHSStr;
- Longer = RHSStr;
- } else {
- ShorterCharWidth = RHS.getFieldDesc()->getElemDataSize();
- Shorter = RHSStr;
- Longer = LHSStr;
- }
-
- // The null terminator isn't included in the string data, so check for it
- // manually. If the longer string doesn't have a null terminator where the
- // shorter string ends, they aren't potentially overlapping.
- for (unsigned NullByte : llvm::seq(ShorterCharWidth)) {
- if (Shorter.size() + NullByte >= Longer.size())
- break;
- if (Longer[Shorter.size() + NullByte])
- return false;
- }
- return Shorter == Longer.take_front(Shorter.size());
-}
-
/// Whether Ptr designates an object that is the backing array of a
/// std::initializer_list.
static bool isInitializerListBackingArray(const Pointer &Ptr) {
@@ -2867,50 +2816,6 @@ static bool isInitializerListBackingArray(const Pointer &Ptr) {
return MTE && MTE->isBackingArrayForInitializerList();
}
-namespace {
-/// Pairs an enclosing-array Pointer with the element-relative location of
-/// the original pointer within it.
-struct ArraySubobjectInfo {
- Pointer Array;
- ArraySubobjectLocation Loc;
-};
-} // namespace
-
-/// Returns the enclosing array and element-relative location if Ptr
-/// designates an element of an initializer_list backing array,
-/// one-past-the-end of it, or a subobject of an element. Returns
-/// std::nullopt otherwise.
-static std::optional<ArraySubobjectInfo>
-getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
- if (Ptr.isZero() || !Ptr.isBlockPointer())
- return std::nullopt;
-
- Pointer Array = Ptr.getDeclPtr();
- if (!isInitializerListBackingArray(Array))
- return std::nullopt;
-
- const auto *ArrayType =
- Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
- if (!ArrayType)
- return std::nullopt;
-
- APValue PtrValue = Ptr.toAPValue(Ctx);
- if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
- return std::nullopt;
-
- ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
- if (Path.empty())
- return std::nullopt;
-
- uint64_t Index = Path.front().getAsArrayIndex();
- bool IsValidOnePastEnd = Path.size() == 1;
- std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
- Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
- if (!Loc)
- return std::nullopt;
- return ArraySubobjectInfo{std::move(Array), *Loc};
-}
-
/// Returns the enclosing array block for a pointer into an array subobject.
static std::optional<Pointer> getContainingArray(const Pointer &Ptr) {
if (Ptr.isZero() || !Ptr.isBlockPointer())
@@ -2930,7 +2835,7 @@ static std::optional<Pointer> getContainingArray(const Pointer &Ptr) {
/// Returns true for array objects whose addresses are potentially non-unique
/// and whose contents are represented by interpreter storage/APValue.
-static bool isPotentiallyNonUniqueAPValueArray(const Pointer &Array) {
+static bool isPotentiallyNonUniqueArray(const Pointer &Array) {
if (isInitializerListBackingArray(Array))
return true;
if (const auto *VD = Array.getDeclDesc()->asValueDecl())
@@ -2938,232 +2843,175 @@ static bool isPotentiallyNonUniqueAPValueArray(const Pointer &Array) {
return false;
}
-/// Returns the enclosing potentially non-unique APValue-backed array and the
-/// element-relative location designated by Ptr.
-static std::optional<ArraySubobjectInfo>
-getNonUniqueAPValueArraySubobjectLocation(const ASTContext &Ctx,
- const Pointer &Ptr) {
- if (std::optional<ArraySubobjectInfo> InitListInfo =
- getArraySubobjectLocation(Ctx, Ptr))
- return InitListInfo;
-
- if (!Ptr.isZero() && Ptr.isBlockPointer() && Ptr.isArrayRoot() &&
- isPotentiallyNonUniqueAPValueArray(Ptr)) {
- const auto *ArrayType =
- Ctx.getAsConstantArrayType(Ptr.getFieldDesc()->getType());
- if (!ArrayType)
- return std::nullopt;
- return ArraySubobjectInfo{Ptr,
- ArraySubobjectLocation{0, CharUnits::Zero()}};
- }
-
- std::optional<Pointer> Array = getContainingArray(Ptr);
- if (!Array || !isPotentiallyNonUniqueAPValueArray(*Array))
- return std::nullopt;
+namespace {
+/// Bytecode evaluator adapter for PotentiallyNonUniqueObject. Pointer/PtrView
+/// traversal and APValue-path interpretation remain local to this evaluator.
+class ByteCodePotentiallyNonUniqueObject final
+ : public ::PotentiallyNonUniqueObject {
+public:
+ static std::optional<ByteCodePotentiallyNonUniqueObject>
+ create(InterpState &S, const Pointer &Ptr);
+
+private:
+ ByteCodePotentiallyNonUniqueObject(InterpState &S, Kind TheKind,
+ Pointer Array,
+ const ConstantArrayType *ArrayType,
+ ArraySubobjectLocation Loc)
+ : PotentiallyNonUniqueObject(S.getASTContext(), TheKind, ArrayType, Loc,
+ Array.getNumElems()),
+ S(S), Array(std::move(Array)) {}
+
+ ByteCodePotentiallyNonUniqueObject(InterpState &S, const Expr *StringBase,
+ CharUnits RawOffset, Pointer Array,
+ const ConstantArrayType *ArrayType,
+ ArraySubobjectLocation Loc)
+ : PotentiallyNonUniqueObject(S.getASTContext()), S(S),
+ Array(std::move(Array)) {
+ bool Recognized = setRecognizedStringObject(
+ StringBase, ArrayType, Loc, this->Array.getNumElems(), RawOffset);
+ assert(Recognized && "expected a string literal object");
+ (void)Recognized;
+ }
+
+ bool getArrayElement(uint64_t Index, APValue &Result) const override;
+
+ InterpState &S;
+ Pointer Array;
+};
+} // namespace
- const auto *ArrayType =
- Ctx.getAsConstantArrayType(Array->getFieldDesc()->getType());
- if (!ArrayType)
+std::optional<ByteCodePotentiallyNonUniqueObject>
+ByteCodePotentiallyNonUniqueObject::create(InterpState &S, const Pointer &Ptr) {
+ const ASTContext &Ctx = S.getASTContext();
+ if (Ptr.isZero() || !Ptr.isBlockPointer())
return std::nullopt;
- APValue PtrValue = Ptr.toAPValue(Ctx);
- APValue ArrayValue = Array->toAPValue(Ctx);
- if (!PtrValue.isLValue() || !PtrValue.hasLValuePath() ||
- !ArrayValue.isLValue() || !ArrayValue.hasLValuePath())
- return std::nullopt;
+ const Expr *StringBase = Ptr.getDeclDesc()->asExpr();
+ if (!Ptr.block()->isDynamic() &&
+ isa_and_nonnull<StringLiteral, PredefinedExpr>(StringBase)) {
+ Pointer Array = Ptr.getDeclPtr();
+ const auto *ArrayType =
+ Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ if (!ArrayType || !PtrValue.isLValue())
+ return std::nullopt;
- ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
- ArrayRef<APValue::LValuePathEntry> ArrayPath = ArrayValue.getLValuePath();
- if (Path.size() <= ArrayPath.size())
- return std::nullopt;
- for (unsigned I = 0, N = ArrayPath.size(); I != N; ++I) {
- if (Path[I] != ArrayPath[I])
+ std::optional<ArraySubobjectLocation> Loc;
+ if (PtrValue.hasLValuePath() && !PtrValue.getLValuePath().empty()) {
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ Loc = computeArraySubobjectLocation(
+ Ctx, ArrayType, Path.front().getAsArrayIndex(),
+ PtrValue.getLValueOffset(), Path.size() == 1);
+ } else {
+ Loc = computeArraySubobjectLocationFromOffset(Ctx, ArrayType,
+ PtrValue.getLValueOffset());
+ }
+ if (!Loc)
return std::nullopt;
+ return ByteCodePotentiallyNonUniqueObject(
+ S, StringBase, PtrValue.getLValueOffset(), std::move(Array), ArrayType,
+ *Loc);
}
- Path = Path.drop_front(ArrayPath.size());
-
- uint64_t Index = Path.front().getAsArrayIndex();
- bool IsValidOnePastEnd = Path.size() == 1;
- std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
- Ctx, ArrayType, Index,
- PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
- IsValidOnePastEnd);
- if (!Loc)
- return std::nullopt;
- return ArraySubobjectInfo{std::move(*Array), *Loc};
-}
-
-namespace {
-/// Pairs a string literal array with the element-relative location of a pointer
-/// into it and the literal used to materialize character APValues.
-struct StringArraySubobjectInfo {
Pointer Array;
- ArraySubobjectLocation Loc;
- const StringLiteral *Literal;
-};
-} // namespace
-
-/// Returns the string literal array and element-relative location designated by
-/// Ptr.
-static std::optional<StringArraySubobjectInfo>
-getStringArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) {
- if (!Ptr.pointsToStringLiteral())
- return std::nullopt;
+ if (Ptr.isArrayRoot() && isPotentiallyNonUniqueArray(Ptr)) {
+ Array = Ptr;
+ } else if (std::optional<Pointer> ContainingArray = getContainingArray(Ptr);
+ ContainingArray && isPotentiallyNonUniqueArray(*ContainingArray)) {
+ Array = std::move(*ContainingArray);
+ } else {
+ Pointer DeclArray = Ptr.getDeclPtr();
+ if (!Ctx.getAsConstantArrayType(DeclArray.getFieldDesc()->getType()) ||
+ !isPotentiallyNonUniqueArray(DeclArray))
+ return std::nullopt;
+ Array = std::move(DeclArray);
+ }
- Pointer Array = Ptr.getDeclPtr();
const auto *ArrayType =
Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType());
if (!ArrayType)
return std::nullopt;
- APValue PtrValue = Ptr.toAPValue(Ctx);
- if (!PtrValue.isLValue() || !PtrValue.hasLValuePath())
- return std::nullopt;
-
- ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
- if (Path.empty())
- return std::nullopt;
-
- uint64_t Index = Path.front().getAsArrayIndex();
- bool IsValidOnePastEnd = Path.size() == 1;
- std::optional<ArraySubobjectLocation> Loc = computeArraySubobjectLocation(
- Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd);
- if (!Loc)
- return std::nullopt;
-
- return StringArraySubobjectInfo{
- std::move(Array), *Loc, cast<StringLiteral>(Ptr.getDeclDesc()->asExpr())};
-}
+ ArraySubobjectLocation Loc{0, CharUnits::Zero()};
+ if (Ptr != Array) {
+ APValue PtrValue = Ptr.toAPValue(Ctx);
+ APValue ArrayValue = Array.toAPValue(Ctx);
+ if (!PtrValue.isLValue() || !PtrValue.hasLValuePath() ||
+ !ArrayValue.isLValue() || !ArrayValue.hasLValuePath())
+ return std::nullopt;
+ ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath();
+ ArrayRef<APValue::LValuePathEntry> ArrayPath = ArrayValue.getLValuePath();
+ if (Path.size() <= ArrayPath.size())
+ return std::nullopt;
+ for (unsigned I = 0, N = ArrayPath.size(); I != N; ++I)
+ if (Path[I] != ArrayPath[I])
+ return std::nullopt;
+ Path = Path.drop_front(ArrayPath.size());
+
+ std::optional<ArraySubobjectLocation> ComputedLoc =
+ computeArraySubobjectLocation(
+ Ctx, ArrayType, Path.front().getAsArrayIndex(),
+ PtrValue.getLValueOffset() - ArrayValue.getLValueOffset(),
+ Path.size() == 1);
+ if (!ComputedLoc)
+ return std::nullopt;
+ Loc = *ComputedLoc;
+ }
-/// Extracts one character from a string literal as an integer APValue payload.
-static APSInt extractStringLiteralCharacter(const ASTContext &Ctx,
- const StringLiteral *Literal,
- uint64_t Index) {
- const ConstantArrayType *ArrayType =
- Ctx.getAsConstantArrayType(Literal->getType());
- assert(ArrayType && "string literal isn't an array");
- QualType CharType = ArrayType->getElementType();
- assert(CharType->isIntegerType() && "unexpected character type");
- APSInt Value(Ctx.getTypeSize(CharType), CharType->isUnsignedIntegerType());
- if (Index < Literal->getLength())
- Value = Literal->getCodeUnit(Index);
- return Value;
+ Kind TheKind = isInitializerListBackingArray(Array)
+ ? Kind::InitializerList
+ : Kind::TemplateParamObject;
+ return ByteCodePotentiallyNonUniqueObject(S, TheKind, std::move(Array),
+ ArrayType, Loc);
}
-/// Compares two array views for a possible storage overlap after accounting
-/// for their pointer offsets.
-template <typename GetLHSElement, typename GetRHSElement>
-static bool arePotentiallyOverlappingArrays(
- const ASTContext &Ctx, QualType LHSElementType,
- ArraySubobjectLocation LHSLoc, int64_t LHSSize, GetLHSElement GetLHSElt,
- QualType RHSElementType, ArraySubobjectLocation RHSLoc, int64_t RHSSize,
- GetRHSElement GetRHSElt) {
- if (LHSLoc.OffsetInElement != RHSLoc.OffsetInElement)
+bool ByteCodePotentiallyNonUniqueObject::getArrayElement(
+ uint64_t Index, APValue &Result) const {
+ std::optional<APValue> Value = Array.atIndex(Index).narrow().toRValue(
+ S.getContext(), arrayType()->getElementType());
+ if (!Value)
return false;
-
- if (!Ctx.hasSameUnqualifiedType(LHSElementType, RHSElementType))
- return false;
-
- int64_t LHSOffset = LHSLoc.Index;
- int64_t RHSOffset = RHSLoc.Index;
- int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset);
- int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset);
- if (OverlapBegin >= OverlapEnd)
- return false;
-
- for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) {
- std::optional<APValue> LHSElt = GetLHSElt(I + LHSOffset);
- std::optional<APValue> RHSElt = GetRHSElt(I + RHSOffset);
- // Missing element data: be conservative and assume the arrays may share
- // storage.
- if (!LHSElt || !RHSElt)
- return true;
- if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Ctx))
- return false;
- }
-
+ Result = std::move(*Value);
return true;
}
-/// Returns true if a string literal and a potentially non-unique constant
-/// array could share storage at the designated addresses.
-static bool mayOverlapStringLiteralAndConstantArray(InterpState &S,
- const Pointer &StringPtr,
- const Pointer &ArrayPtr) {
+PotentiallyNonUniqueObject::OverlapResult
+arePotentiallyOverlappingNonUniqueObjects(InterpState &S, const Pointer &LHS,
+ const Pointer &RHS) {
+ using OverlapResult = PotentiallyNonUniqueObject::OverlapResult;
const ASTContext &Ctx = S.getASTContext();
- std::optional<StringArraySubobjectInfo> StringInfo =
- getStringArraySubobjectLocation(Ctx, StringPtr);
- std::optional<ArraySubobjectInfo> ArrayInfo =
- getNonUniqueAPValueArraySubobjectLocation(Ctx, ArrayPtr);
- if (!StringInfo || !ArrayInfo)
- return false;
-
- const auto *StringArrayType =
- Ctx.getAsConstantArrayType(StringInfo->Array.getFieldDesc()->getType());
- const auto *ArrayType =
- Ctx.getAsConstantArrayType(ArrayInfo->Array.getFieldDesc()->getType());
- if (!StringArrayType || !ArrayType)
- return false;
-
- return arePotentiallyOverlappingArrays(
- Ctx, StringArrayType->getElementType(), StringInfo->Loc,
- StringInfo->Array.getNumElems(),
- [&](int64_t Index) -> std::optional<APValue> {
- return APValue(
- extractStringLiteralCharacter(Ctx, StringInfo->Literal, Index));
- },
- ArrayType->getElementType(), ArrayInfo->Loc,
- ArrayInfo->Array.getNumElems(),
- [&](int64_t Index) {
- Pointer Elt = ArrayInfo->Array.atIndex(Index).narrow();
- return Elt.toRValue(S.getContext(), ArrayType->getElementType());
- });
-}
-/// Returns true if two APValue-backed potentially non-unique arrays could share
-/// storage at the designated addresses.
-static bool arePotentiallyOverlappingAPValueArrays(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS) {
- const ASTContext &Ctx = S.getASTContext();
- std::optional<ArraySubobjectInfo> LHSInfo =
- getNonUniqueAPValueArraySubobjectLocation(Ctx, LHS);
- std::optional<ArraySubobjectInfo> RHSInfo =
- getNonUniqueAPValueArraySubobjectLocation(Ctx, RHS);
- if (!LHSInfo || !RHSInfo)
- return false;
-
- const auto *LHSArrayType =
- Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType());
- const auto *RHSArrayType =
- Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType());
- if (!LHSArrayType || !RHSArrayType)
- return false;
-
- return arePotentiallyOverlappingArrays(
- Ctx, LHSArrayType->getElementType(), LHSInfo->Loc,
- LHSInfo->Array.getNumElems(),
- [&](int64_t Index) {
- Pointer Elt = LHSInfo->Array.atIndex(Index).narrow();
- return Elt.toRValue(S.getContext(), LHSArrayType->getElementType());
- },
- RHSArrayType->getElementType(), RHSInfo->Loc,
- RHSInfo->Array.getNumElems(),
- [&](int64_t Index) {
- Pointer Elt = RHSInfo->Array.atIndex(Index).narrow();
- return Elt.toRValue(S.getContext(), RHSArrayType->getElementType());
- });
-}
+ if (!LHS.isZero() && !RHS.isZero() && LHS.isBlockPointer() &&
+ RHS.isBlockPointer() && !LHS.block()->isDynamic() &&
+ !RHS.block()->isDynamic()) {
+ const Expr *LHSBase = LHS.getDeclDesc()->asExpr();
+ const Expr *RHSBase = RHS.getDeclDesc()->asExpr();
+ if (isa_and_nonnull<StringLiteral, PredefinedExpr, ObjCEncodeExpr>(
+ LHSBase) &&
+ isa_and_nonnull<StringLiteral, PredefinedExpr, ObjCEncodeExpr>(
+ RHSBase)) {
+ APValue LHSValue = LHS.toAPValue(Ctx);
+ APValue RHSValue = RHS.toAPValue(Ctx);
+ if (LHSValue.isLValue() && RHSValue.isLValue() &&
+ PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+ Ctx, LHSBase, LHSValue.getLValueOffset(), RHSBase,
+ RHSValue.getLValueOffset())
+ .value_or(false))
+ return OverlapResult::StringLiteral;
+ }
+ }
-/// Returns true if two pointers designate potentially non-unique objects whose
-/// overlapping elements could be merged into the same storage.
-bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS) {
- return arePotentiallyOverlappingAPValueArrays(S, LHS, RHS) ||
- mayOverlapStringLiteralAndConstantArray(S, LHS, RHS) ||
- mayOverlapStringLiteralAndConstantArray(S, RHS, LHS);
+ std::optional<ByteCodePotentiallyNonUniqueObject> LHSObject =
+ ByteCodePotentiallyNonUniqueObject::create(S, LHS);
+ std::optional<ByteCodePotentiallyNonUniqueObject> RHSObject =
+ ByteCodePotentiallyNonUniqueObject::create(S, RHS);
+ if (!LHSObject || !RHSObject)
+ return OverlapResult::None;
+ if (!LHSObject->isPotentiallyOverlappingWith(*RHSObject))
+ return OverlapResult::None;
+ return LHSObject->isStringLiteral() && RHSObject->isStringLiteral()
+ ? OverlapResult::StringLiteral
+ : OverlapResult::NonUniqueObject;
}
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 5b87a606c6d53..ecf548971b178 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1300,13 +1300,10 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) {
Builtin == Builtin::BI__builtin_function_start);
}
-bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
- const Pointer &RHS);
-/// Returns true when LHS and RHS designate potentially non-unique objects that
-/// could share storage, making their comparison non-constant.
-bool arePotentiallyOverlappingNonUniqueObjects(InterpState &S,
- const Pointer &LHS,
- const Pointer &RHS);
+/// Classifies potentially overlapping string literals or non-unique objects.
+PotentiallyNonUniqueObject::OverlapResult
+arePotentiallyOverlappingNonUniqueObjects(InterpState &S, const Pointer &LHS,
+ const Pointer &RHS);
template <>
inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
@@ -1351,21 +1348,6 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- // C++ [intro.object]/9:
- // An object is potentially non-unique if it is a string literal object,
- // the backing array of an initializer list, or a subobject thereof.
- // FIXME: The string literal source check here isn't entirely correct.
- if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() &&
- LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) {
- if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
- const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_literal_comparison)
- << LHS.toDiagnosticString(S.getASTContext())
- << RHS.toDiagnosticString(S.getASTContext());
- return false;
- }
- }
-
if (Pointer::hasSameBase(LHS, RHS)) {
std::optional<size_t> A = LHS.computeOffsetForComparison(S.getASTContext());
std::optional<size_t> B = RHS.computeOffsetForComparison(S.getASTContext());
@@ -1376,11 +1358,25 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- if (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
+ // C++ [intro.object]/9:
+ // An object is potentially non-unique if it is a string literal object,
+ // the backing array of an initializer list, or a subobject thereof.
+ switch (arePotentiallyOverlappingNonUniqueObjects(S, LHS, RHS)) {
+ case PotentiallyNonUniqueObject::OverlapResult::StringLiteral: {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_literal_comparison)
+ << LHS.toDiagnosticString(S.getASTContext())
+ << RHS.toDiagnosticString(S.getASTContext());
+ return false;
+ }
+ case PotentiallyNonUniqueObject::OverlapResult::NonUniqueObject: {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison);
return false;
}
+ case PotentiallyNonUniqueObject::OverlapResult::None:
+ break;
+ }
// Otherwise we need to do a bunch of extra checks before returning Unordered.
if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index dd1043e83f574..17d3c7cf18be4 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -16,8 +16,12 @@
#include "clang/AST/CharUnits.h"
#include "clang/Basic/TypeTraits.h"
+#include <cassert>
#include <cstdint>
#include <optional>
+#include <string>
+#include <utility>
+#include <variant>
namespace llvm {
class APFloat;
@@ -32,6 +36,7 @@ class APValue;
class ConstantArrayType;
class Expr;
class CallExpr;
+class StringLiteral;
} // namespace clang
using namespace clang;
/// Values returned by __builtin_classify_type, chosen to match the values
@@ -100,12 +105,6 @@ unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
unsigned BuiltinID);
unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
const CallExpr *E);
-/// Whether two APValues could be merged into a single storage location by
-/// the implementation (the relation [intro.object]/9 cares about for
-/// initializer_list backing arrays and string literals).
-bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
- const ASTContext &Ctx);
-
uint8_t GFNIMultiplicativeInverse(uint8_t Byte);
uint8_t GFNIMul(uint8_t AByte, uint8_t BByte);
uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword,
@@ -136,4 +135,142 @@ std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index,
CharUnits LValueOffset, bool IsValidOnePastEnd);
+/// A potentially-non-unique array object: a string literal, an
+/// std::initializer_list backing array, or an array subobject of a
+/// template-parameter object (NTTP). [intro.object]/9 lets the implementation
+/// merge two such objects when their overlapping contents agree, which is what
+/// makes their address comparisons non-constant.
+///
+/// This abstract class is an evaluator-independent view of an object that
+/// [intro.object] permits an implementation to give the same address as another
+/// potentially non-unique object. Evaluators provide their own object
+/// recognition, subobject-location logic, and element materialization through
+/// derived classes.
+class PotentiallyNonUniqueObject {
+public:
+ enum class Kind { StringLiteral, InitializerList, TemplateParamObject };
+ enum class OverlapResult { None, StringLiteral, NonUniqueObject };
+
+ virtual ~PotentiallyNonUniqueObject() = default;
+
+ Kind kind() const {
+ assert(ArrayType && "object has not been recognized");
+ return TheKind;
+ }
+ bool isStringLiteral() const { return String.has_value(); }
+ const ConstantArrayType *arrayType() const { return ArrayType; }
+ const ArraySubobjectLocation &location() const { return Loc; }
+ uint64_t size() const { return Size; }
+ bool empty() const { return Size == 0; }
+
+ /// Determine whether two expressions denote string-like objects whose
+ /// storage can overlap at the given byte offsets. Returns std::nullopt if
+ /// either expression is not a string literal, predefined function-name
+ /// string, or Objective-C encoding.
+ static std::optional<bool>
+ isPotentiallyOverlappingStrings(const ASTContext &Ctx, const Expr *LHSBase,
+ CharUnits LHSOffset, const Expr *RHSBase,
+ CharUnits RHSOffset);
+
+ /// Returns true if this object and \p Other can share storage at their
+ /// designated positions. Missing element data is treated conservatively as
+ /// a possible match.
+ bool
+ isPotentiallyOverlappingWith(const PotentiallyNonUniqueObject &Other) const;
+
+protected:
+ explicit PotentiallyNonUniqueObject(const ASTContext &Ctx) : Ctx(Ctx) {}
+
+ /// Computes an array-element location from only a byte offset relative to
+ /// the array. This is used by evaluator adapters whose lvalue representation
+ /// does not carry an explicit path for the designated string subobject.
+ static std::optional<ArraySubobjectLocation>
+ computeArraySubobjectLocationFromOffset(const ASTContext &Ctx,
+ const ConstantArrayType *ArrayType,
+ CharUnits LValueOffset);
+
+ PotentiallyNonUniqueObject(const ASTContext &Ctx, Kind TheKind,
+ const ConstantArrayType *ArrayType,
+ ArraySubobjectLocation Loc, uint64_t Size)
+ : Ctx(Ctx) {
+ setRecognizedObject(TheKind, ArrayType, Loc, Size);
+ }
+
+ void setRecognizedObject(Kind NewKind, const ConstantArrayType *NewArrayType,
+ ArraySubobjectLocation NewLoc, uint64_t NewSize) {
+ assert(NewArrayType && !ArrayType && "object must be recognized once");
+ assert((NewKind == Kind::StringLiteral) == String.has_value() &&
+ "string kind and storage disagree");
+ TheKind = NewKind;
+ ArrayType = NewArrayType;
+ Loc = NewLoc;
+ Size = NewSize;
+ }
+
+ /// Recognize Base as a StringLiteral or PredefinedExpr and install its
+ /// borrowed AST storage together with the evaluator-computed array view.
+ /// Objective-C encodings are intentionally limited to
+ /// isPotentiallyOverlappingStrings; they are not exposed as generic array
+ /// objects here.
+ bool setRecognizedStringObject(const Expr *Base,
+ const ConstantArrayType *NewArrayType,
+ ArraySubobjectLocation NewLoc,
+ uint64_t NewSize, CharUnits RawOffset);
+
+ /// Materialize an array element from the evaluator's storage representation.
+ /// Returns false if the representation cannot expose enough data to prove
+ /// the element's value.
+ virtual bool getArrayElement(uint64_t Index, APValue &Result) const = 0;
+
+ const ASTContext &Ctx;
+
+private:
+ /// Owns string-like bytes synthesized during normalization (currently for
+ /// ObjCEncodeExpr), together with their code-unit width in bytes.
+ struct OwnedString {
+ std::string Bytes;
+ unsigned CharWidth;
+ };
+
+ /// Normalized storage and location for a string-like object. StringLiteral
+ /// storage is borrowed for the ASTContext lifetime, while synthesized bytes
+ /// are owned. Offset is the designated pointer's byte offset from the string
+ /// base; it remains separate from the evaluator-specific array location so
+ /// strings with different code-unit widths can be compared.
+ struct StringData {
+ StringData(const StringLiteral *Literal, CharUnits Offset)
+ : Storage(Literal), Offset(Offset) {}
+ StringData(std::string Bytes, unsigned CharWidth, CharUnits Offset)
+ : Storage(OwnedString{std::move(Bytes), CharWidth}), Offset(Offset) {}
+
+ std::variant<const StringLiteral *, OwnedString> Storage;
+ CharUnits Offset;
+ };
+
+ static std::optional<StringData>
+ getStringData(const ASTContext &Ctx, const Expr *Base, CharUnits Offset);
+ static bool isPotentiallyOverlappingStrings(const StringData &LHS,
+ const StringData &RHS);
+
+ /// Whether two APValues could be merged into a single storage location by
+ /// the implementation. The slow path recursively handles values whose
+ /// profiles differ.
+ static bool areAPValuesPotentiallyMergeable(const APValue &LHS,
+ const APValue &RHS,
+ const ASTContext &Ctx);
+ static bool areAPValuesPotentiallyMergeableSlow(const APValue &LHS,
+ const APValue &RHS,
+ const ASTContext &Ctx);
+
+ /// Materialize element Index, handling strings in the shared base before
+ /// delegating evaluator-backed objects to getArrayElement.
+ bool getElement(uint64_t Index, APValue &Result) const;
+
+ Kind TheKind = Kind::StringLiteral;
+ const ConstantArrayType *ArrayType = nullptr;
+ ArraySubobjectLocation Loc{0, CharUnits::Zero()};
+ uint64_t Size = 0;
+ std::optional<StringData> String;
+};
+
#endif // LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 4fe8275e27b30..49e5c6f5e7dd9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2014,130 +2014,6 @@ static const ValueDecl *GetLValueBaseDecl(const LValue &LVal) {
return LVal.Base.dyn_cast<const ValueDecl*>();
}
-// Information about an LValueBase that is some kind of string.
-struct LValueBaseString {
- std::string ObjCEncodeStorage;
- StringRef Bytes;
- int CharWidth;
-};
-
-// Gets the lvalue base as a string.
-static bool GetLValueBaseAsString(const ASTContext &Ctx,
- APValue::LValueBase Base,
- LValueBaseString &AsString) {
- const auto *BaseExpr = Base.dyn_cast<const Expr *>();
- if (!BaseExpr)
- return false;
-
- // For ObjCEncodeExpr, we need to compute and store the string.
- if (const auto *EE = dyn_cast<ObjCEncodeExpr>(BaseExpr)) {
- Ctx.getObjCEncodingForType(EE->getEncodedType(),
- AsString.ObjCEncodeStorage);
- AsString.Bytes = AsString.ObjCEncodeStorage;
- AsString.CharWidth = 1;
- return true;
- }
-
- // Otherwise, we have a StringLiteral.
- const auto *Lit = dyn_cast<StringLiteral>(BaseExpr);
- if (const auto *PE = dyn_cast<PredefinedExpr>(BaseExpr))
- Lit = PE->getFunctionName();
-
- if (!Lit)
- return false;
-
- AsString.Bytes = Lit->getBytes();
- AsString.CharWidth = Lit->getCharByteWidth();
- return true;
-}
-
-static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal,
- LValueBaseString &AsString) {
- return GetLValueBaseAsString(Info.Ctx, LVal.Base, AsString);
-}
-
-// Determine whether two string literals potentially overlap. This will be the
-// case if they agree on the values of all the bytes on the overlapping region
-// between them.
-//
-// The overlapping region is the portion of the two string literals that must
-// overlap in memory if the pointers actually point to the same address at
-// runtime. For example, if LHS is "abcdef" + 3 and RHS is "cdef\0gh" + 1 then
-// the overlapping region is "cdef\0", which in this case does agree, so the
-// strings are potentially overlapping. Conversely, for "foobar" + 3 versus
-// "bazbar" + 3, the overlapping region contains all of both strings, so they
-// are not potentially overlapping, even though they agree from the given
-// addresses onwards.
-//
-// See open core issue CWG2765 which is discussing the desired rule here.
-static bool ArePotentiallyOverlappingStringLiterals(
- const LValueBaseString &LHSString, CharUnits LHSOffset,
- const LValueBaseString &RHSString, CharUnits RHSOffset) {
- assert(LHSString.Bytes.data() && RHSString.Bytes.data() &&
- "passing a string with no underlying storage");
-
- // This is the byte offset to the location of the first character of LHS
- // within RHS. We don't need to look at the characters of one string that
- // would appear before the start of the other string if they were merged.
- StringRef LHSBytes = LHSString.Bytes;
- StringRef RHSBytes = RHSString.Bytes;
- CharUnits Offset = RHSOffset - LHSOffset;
- if (Offset.isNegative()) {
- if (LHSBytes.size() < (size_t)-Offset.getQuantity())
- return false;
- LHSBytes = LHSBytes.drop_front(-Offset.getQuantity());
- } else {
- if (RHSBytes.size() < (size_t)Offset.getQuantity())
- return false;
- RHSBytes = RHSBytes.drop_front(Offset.getQuantity());
- }
-
- bool LHSIsLonger = LHSBytes.size() > RHSBytes.size();
- StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes;
- StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes;
- int ShorterCharWidth = (LHSIsLonger ? RHSString : LHSString).CharWidth;
-
- // The null terminator isn't included in the string data, so check for it
- // manually. If the longer string doesn't have a null terminator where the
- // shorter string ends, they aren't potentially overlapping.
- for (int NullByte : llvm::seq(ShorterCharWidth)) {
- if (Shorter.size() + NullByte >= Longer.size())
- break;
- if (Longer[Shorter.size() + NullByte])
- return false;
- }
-
- // Otherwise, they're potentially overlapping if and only if the overlapping
- // region is the same.
- return Shorter == Longer.take_front(Shorter.size());
-}
-
-static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info,
- const LValue &LHS,
- const LValue &RHS) {
- LValueBaseString LHSString, RHSString;
- if (!GetLValueBaseAsString(Info, LHS, LHSString) ||
- !GetLValueBaseAsString(Info, RHS, RHSString))
- return false;
-
- return ArePotentiallyOverlappingStringLiterals(LHSString, LHS.Offset,
- RHSString, RHS.Offset);
-}
-
-/// APValue overload of the string-literal overlap predicate. Returns nullopt
-/// if either operand is not a string-literal (or ObjC encoding) LValue.
-static std::optional<bool> ArePotentiallyOverlappingStringLiterals(
- const ASTContext &Ctx, const APValue &LHS, const APValue &RHS) {
- if (!LHS.isLValue() || !RHS.isLValue())
- return std::nullopt;
- LValueBaseString LHSString, RHSString;
- if (!GetLValueBaseAsString(Ctx, LHS.getLValueBase(), LHSString) ||
- !GetLValueBaseAsString(Ctx, RHS.getLValueBase(), RHSString))
- return std::nullopt;
- return ArePotentiallyOverlappingStringLiterals(
- LHSString, LHS.getLValueOffset(), RHSString, RHS.getLValueOffset());
-}
-
static bool IsWeakLValue(const LValue &Value) {
const ValueDecl *Decl = GetLValueBaseDecl(Value);
return Decl && Decl->isWeak();
@@ -3559,31 +3435,30 @@ static unsigned getBaseIndex(const CXXRecordDecl *Derived,
}
/// Extract the value of a character from a string literal.
-static APSInt extractStringLiteralCharacter(EvalInfo &Info, const Expr *Lit,
- uint64_t Index) {
+static APSInt extractStringLiteralCharacter(const ASTContext &Ctx,
+ const Expr *Lit, uint64_t Index) {
assert(!isa<SourceLocExpr>(Lit) &&
"SourceLocExpr should have already been converted to a StringLiteral");
// FIXME: Support MakeStringConstant
if (const auto *ObjCEnc = dyn_cast<ObjCEncodeExpr>(Lit)) {
std::string Str;
- Info.Ctx.getObjCEncodingForType(ObjCEnc->getEncodedType(), Str);
+ Ctx.getObjCEncodingForType(ObjCEnc->getEncodedType(), Str);
assert(Index <= Str.size() && "Index too large");
return APSInt::getUnsigned(Str.c_str()[Index]);
}
if (auto PE = dyn_cast<PredefinedExpr>(Lit))
Lit = PE->getFunctionName();
- const StringLiteral *S = cast<StringLiteral>(Lit);
- const ConstantArrayType *CAT =
- Info.Ctx.getAsConstantArrayType(S->getType());
- assert(CAT && "string literal isn't an array");
- QualType CharType = CAT->getElementType();
+ const auto *Literal = cast<StringLiteral>(Lit);
+ const ConstantArrayType *ArrayType =
+ Ctx.getAsConstantArrayType(Literal->getType());
+ assert(ArrayType && "string literal isn't an array");
+ QualType CharType = ArrayType->getElementType();
assert(CharType->isIntegerType() && "unexpected character type");
- APSInt Value(Info.Ctx.getTypeSize(CharType),
- CharType->isUnsignedIntegerType());
- if (Index < S->getLength())
- Value = S->getCodeUnit(Index);
+ APSInt Value(Ctx.getTypeSize(CharType), CharType->isUnsignedIntegerType());
+ if (Index < Literal->getLength())
+ Value = Literal->getCodeUnit(Index);
return Value;
}
@@ -4916,15 +4791,13 @@ static bool isWeakMemberPointer(const APValue &V) {
return D && D->isWeak();
}
-/// AreAPValuesPotentiallyMergeableSlow - The slow path for
-/// AreAPValuesPotentiallyMergeable. Will be invoked when the two values'
-/// APValue::Profile already differs. Returns true if the disagreement is one
-/// the runtime can collapse (overlapping string literals, weak decls that might
-/// resolve to the same target), recursing into aggregate kinds so that
-/// aggregates containing such leaves are still reported as mergeable.
-static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
- const APValue &RHS,
- const ASTContext &Ctx) {
+/// areAPValuesPotentiallyMergeableSlow - Slow path for
+/// areAPValuesPotentiallyMergeable. This is invoked when the two APValue
+/// profiles differ. Returns true if the disagreement is one the runtime can
+/// collapse, recursing into aggregate kinds so aggregates containing such
+/// leaves are still reported as mergeable.
+bool PotentiallyNonUniqueObject::areAPValuesPotentiallyMergeableSlow(
+ const APValue &LHS, const APValue &RHS, const ASTContext &Ctx) {
if (!LHS.hasValue() || !RHS.hasValue())
return true;
if (LHS.getKind() != RHS.getKind())
@@ -4933,7 +4806,10 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
switch (LHS.getKind()) {
case APValue::LValue: {
// Distinct string-literal LValues whose content could be merged.
- if (auto Overlap = ArePotentiallyOverlappingStringLiterals(Ctx, LHS, RHS))
+ if (auto Overlap = isPotentiallyOverlappingStrings(
+ Ctx, LHS.getLValueBase().dyn_cast<const Expr *>(),
+ LHS.getLValueOffset(), RHS.getLValueBase().dyn_cast<const Expr *>(),
+ RHS.getLValueOffset()))
return *Overlap;
// Either side names a weak entity -> link-time may resolve to one.
return isWeakLValueBase(LHS) || isWeakLValueBase(RHS);
@@ -4947,11 +4823,11 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
LHS.getStructNumFields() != RHS.getStructNumFields())
return false;
for (unsigned I = 0, N = LHS.getStructNumBases(); I != N; ++I)
- if (!AreAPValuesPotentiallyMergeable(LHS.getStructBase(I),
+ if (!areAPValuesPotentiallyMergeable(LHS.getStructBase(I),
RHS.getStructBase(I), Ctx))
return false;
for (unsigned I = 0, N = LHS.getStructNumFields(); I != N; ++I)
- if (!AreAPValuesPotentiallyMergeable(LHS.getStructField(I),
+ if (!areAPValuesPotentiallyMergeable(LHS.getStructField(I),
RHS.getStructField(I), Ctx))
return false;
return true;
@@ -4962,7 +4838,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
return false;
if (!LHS.getUnionField())
return true;
- return AreAPValuesPotentiallyMergeable(LHS.getUnionValue(),
+ return areAPValuesPotentiallyMergeable(LHS.getUnionValue(),
RHS.getUnionValue(), Ctx);
}
@@ -4975,7 +4851,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
// Missing element data: be conservative.
if (!LE || !RE)
return true;
- if (!AreAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
+ if (!areAPValuesPotentiallyMergeable(*LE, *RE, Ctx))
return false;
}
return true;
@@ -4985,7 +4861,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
if (LHS.getVectorLength() != RHS.getVectorLength())
return false;
for (unsigned I = 0, N = LHS.getVectorLength(); I != N; ++I)
- if (!AreAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
+ if (!areAPValuesPotentiallyMergeable(LHS.getVectorElt(I),
RHS.getVectorElt(I), Ctx))
return false;
return true;
@@ -4996,7 +4872,7 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
LHS.getMatrixNumColumns() != RHS.getMatrixNumColumns())
return false;
for (unsigned I = 0, N = LHS.getMatrixNumElements(); I != N; ++I)
- if (!AreAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
+ if (!areAPValuesPotentiallyMergeable(LHS.getMatrixElt(I),
RHS.getMatrixElt(I), Ctx))
return false;
return true;
@@ -5012,8 +4888,8 @@ static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS,
}
}
-bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
- const ASTContext &Ctx) {
+bool PotentiallyNonUniqueObject::areAPValuesPotentiallyMergeable(
+ const APValue &LHS, const APValue &RHS, const ASTContext &Ctx) {
// Fast path: bitwise-identical APValues -> definitely mergeable.
llvm::FoldingSetNodeID LHSID, RHSID;
LHS.Profile(LHSID);
@@ -5021,7 +4897,7 @@ bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS,
if (LHSID == RHSID)
return true;
- return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
+ return areAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx);
}
std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
@@ -5070,28 +4946,42 @@ std::optional<ArraySubobjectLocation> computeArraySubobjectLocation(
CharUnits::fromQuantity(OffsetInElement)};
}
+std::optional<ArraySubobjectLocation>
+PotentiallyNonUniqueObject::computeArraySubobjectLocationFromOffset(
+ const ASTContext &Ctx, const ConstantArrayType *ArrayType,
+ CharUnits LValueOffset) {
+ if (LValueOffset.isNegative())
+ return std::nullopt;
+
+ uint64_t ElementSize =
+ Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity();
+ if (!ElementSize)
+ return std::nullopt;
+
+ uint64_t Offset = LValueOffset.getQuantity();
+ uint64_t Index = Offset / ElementSize;
+ uint64_t OffsetInElement = Offset % ElementSize;
+ uint64_t ArraySize = ArrayType->getZExtSize();
+ if (Index > ArraySize || (Index == ArraySize && OffsetInElement != 0))
+ return std::nullopt;
+
+ return ArraySubobjectLocation{Index,
+ CharUnits::fromQuantity(OffsetInElement)};
+}
+
namespace {
-/// A potentially-non-unique array object: a string literal, an
-/// initializer_list backing array, or an array subobject of a
-/// template-parameter object (NTTP). [intro.object]/9 lets the implementation
-/// merge two such objects when their overlapping contents agree, which is what
-/// makes their address comparisons non-constant.
-///
-/// The kind is recorded explicitly; the InitializerList and TemplateParamObject
-/// kinds are both read through an APValue array (borrowed and owned,
-/// respectively), while the StringLiteral kind materializes characters lazily.
-/// The single public entry point is arePotentiallyOverlapping().
-class PotentialNonUniqueObject {
+/// Legacy constant evaluator adapter for PotentiallyNonUniqueObject. LValue
+/// recognition and subobject location stay evaluator-specific; storage overlap
+/// and element comparison are implemented by the shared base class.
+class ExprConstantPotentiallyNonUniqueObject final
+ : public ::PotentiallyNonUniqueObject {
public:
- /// Returns true when two lvalues designate potentially non-unique arrays
- /// whose overlapping elements could be merged into the same storage, making
- /// their address comparison non-constant.
- static bool arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
- const LValue &LHS, const LValue &RHS);
+ /// Classifies potentially overlapping string literals or non-unique arrays.
+ static OverlapResult arePotentiallyOverlapping(EvalInfo &Info, const Expr *E,
+ const LValue &LHS,
+ const LValue &RHS);
private:
- enum class Kind { StringLiteral, InitializerList, TemplateParamObject };
-
/// Identifies an enclosing constant array subobject within an lvalue path.
struct ArraySubobjectPathInfo {
const ConstantArrayType *ArrayType = nullptr;
@@ -5101,64 +4991,38 @@ class PotentialNonUniqueObject {
/// The evaluation state and diagnostic expression are stored so the member
/// functions need not thread them through every call.
- PotentialNonUniqueObject(EvalInfo &Info, const Expr *E) : Info(Info), E(E) {}
+ ExprConstantPotentiallyNonUniqueObject(EvalInfo &Info, const Expr *E)
+ : PotentiallyNonUniqueObject(Info.Ctx), Info(Info), E(E) {}
/// Recognizes the non-unique array LV designates, filling in this object and
/// returning true, or returning false when LV is not such an object.
bool recognize(const LValue &LV);
- /// Returns true when *this and Other could be merged into the same storage at
- /// their designated positions: the positions must share an in-element byte
- /// offset and element-compatible types, and every element the overlap would
- /// alias must be pairwise mergeable. Element data the representation cannot
- /// expose is treated conservatively as a possible match.
- bool mayShareStorageWith(const PotentialNonUniqueObject &Other) const;
-
// Lvalue classification helpers used by recognize().
- /// Returns the underlying string literal expression for bases that denote
- /// string literal objects in the evaluator.
- static const Expr *getStringLiteralBase(APValue::LValueBase Base);
/// Whether LV's base is an initializer_list backing array.
static bool isInitializerListBackingArray(const LValue &LV);
+
/// Reads the complete object value an lvalue's base denotes.
const APValue *getCompleteObjectValue(const LValue &LV) const;
+
/// Returns the array element and element-relative location that LV
/// designates, or std::nullopt if LV is not an element, one-past-the-end
/// position, or subobject of an element of its base array.
std::optional<ArraySubobjectLocation>
getArraySubobjectLocation(const LValue &LV) const;
+
/// Returns the string-literal array element and element-relative offset
/// designated by LV, including byte-offset-only lvalues.
std::optional<ArraySubobjectLocation>
getStringArraySubobjectLocation(const LValue &LV) const;
+
/// Finds the innermost enclosing constant array subobject for an lvalue into
/// a template parameter object.
std::optional<ArraySubobjectPathInfo>
getArraySubobjectPathInfo(const LValue &LV) const;
- bool isStringLiteral() const { return TheKind == Kind::StringLiteral; }
- const ArraySubobjectLocation &location() const { return Loc; }
-
- /// Number of elements visible through this view.
- int64_t size() const {
- if (TheKind == Kind::StringLiteral)
- return ArrayType->getZExtSize();
- return arrayValue()->getArraySize();
- }
-
- /// Value of the element at Index, or std::nullopt when the representation
- /// cannot expose enough data to prove it differs.
- std::optional<APValue> operator[](int64_t Index) const {
- if (Index < 0)
- return std::nullopt;
- uint64_t UIndex = static_cast<uint64_t>(Index);
- if (TheKind == Kind::StringLiteral)
- return APValue(extractStringLiteralCharacter(Info, StringExpr, UIndex));
- if (const APValue *Elt = GetArrayInitializedElt(*arrayValue(), UIndex))
- return *Elt;
- return std::nullopt;
- }
+ bool getArrayElement(uint64_t Index, APValue &Result) const override;
/// Returns the APValue array for the InitializerList / TemplateParamObject
/// kinds (borrowed or owned, respectively).
@@ -5170,38 +5034,168 @@ class PotentialNonUniqueObject {
EvalInfo &Info;
const Expr *E;
- Kind TheKind = Kind::StringLiteral;
- const ConstantArrayType *ArrayType = nullptr;
- ArraySubobjectLocation Loc;
- const Expr *StringExpr = nullptr; // Kind::StringLiteral
const APValue *ArrayValue = nullptr; // Kind::InitializerList (borrowed)
std::optional<APValue> OwnedArrayValue; // Kind::TemplateParamObject (owned)
};
} // namespace
-const Expr *
-PotentialNonUniqueObject::getStringLiteralBase(APValue::LValueBase Base) {
- const auto *BaseExpr = Base.dyn_cast<const Expr *>();
- if (isa_and_nonnull<StringLiteral, PredefinedExpr>(BaseExpr))
- return BaseExpr;
- return nullptr;
+std::optional<PotentiallyNonUniqueObject::StringData>
+PotentiallyNonUniqueObject::getStringData(const ASTContext &Ctx,
+ const Expr *Base, CharUnits Offset) {
+ if (!Base)
+ return std::nullopt;
+ if (const auto *EE = dyn_cast<ObjCEncodeExpr>(Base)) {
+ std::string Bytes;
+ Ctx.getObjCEncodingForType(EE->getEncodedType(), Bytes);
+ return StringData(std::move(Bytes), 1, Offset);
+ }
+ if (const auto *PE = dyn_cast<PredefinedExpr>(Base))
+ Base = PE->getFunctionName();
+ if (const auto *Literal = dyn_cast<StringLiteral>(Base))
+ return StringData(Literal, Offset);
+ return std::nullopt;
}
-bool PotentialNonUniqueObject::isInitializerListBackingArray(const LValue &LV) {
+bool PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+ const StringData &LHS, const StringData &RHS) {
+ auto GetBytes = [](const StringData &String) -> StringRef {
+ if (const auto *Literal =
+ std::get_if<const StringLiteral *>(&String.Storage))
+ return (*Literal)->getBytes();
+ return std::get<OwnedString>(String.Storage).Bytes;
+ };
+ auto GetCharWidth = [](const StringData &String) {
+ if (const auto *Literal =
+ std::get_if<const StringLiteral *>(&String.Storage))
+ return (*Literal)->getCharByteWidth();
+ return std::get<OwnedString>(String.Storage).CharWidth;
+ };
+
+ StringRef LHSBytes = GetBytes(LHS);
+ StringRef RHSBytes = GetBytes(RHS);
+
+ // Bytes preceding the other string cannot be part of a merged overlapping
+ // region. Compute the distance without signed subtraction so even extreme
+ // offsets cannot overflow.
+ int64_t LHSOffset = LHS.Offset.getQuantity();
+ int64_t RHSOffset = RHS.Offset.getQuantity();
+ if (LHSOffset > RHSOffset) {
+ uint64_t Distance =
+ static_cast<uint64_t>(LHSOffset) - static_cast<uint64_t>(RHSOffset);
+ if (Distance > LHSBytes.size())
+ return false;
+ LHSBytes = LHSBytes.drop_front(static_cast<size_t>(Distance));
+ } else {
+ uint64_t Distance =
+ static_cast<uint64_t>(RHSOffset) - static_cast<uint64_t>(LHSOffset);
+ if (Distance > RHSBytes.size())
+ return false;
+ RHSBytes = RHSBytes.drop_front(static_cast<size_t>(Distance));
+ }
+
+ bool LHSIsLonger = LHSBytes.size() > RHSBytes.size();
+ StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes;
+ StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes;
+ unsigned ShorterCharWidth = GetCharWidth(LHSIsLonger ? RHS : LHS);
+
+ // StringLiteral::getBytes() omits the null terminator. Verify that the
+ // longer string has an all-zero code unit where the shorter string ends.
+ for (unsigned NullByte : llvm::seq(ShorterCharWidth)) {
+ if (Shorter.size() + NullByte >= Longer.size())
+ break;
+ if (Longer[Shorter.size() + NullByte])
+ return false;
+ }
+
+ return Shorter == Longer.take_front(Shorter.size());
+}
+
+std::optional<bool> PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+ const ASTContext &Ctx, const Expr *LHSBase, CharUnits LHSOffset,
+ const Expr *RHSBase, CharUnits RHSOffset) {
+ std::optional<StringData> LHS = getStringData(Ctx, LHSBase, LHSOffset);
+ std::optional<StringData> RHS = getStringData(Ctx, RHSBase, RHSOffset);
+ if (!LHS || !RHS)
+ return std::nullopt;
+ return isPotentiallyOverlappingStrings(*LHS, *RHS);
+}
+
+bool PotentiallyNonUniqueObject::setRecognizedStringObject(
+ const Expr *Base, const ConstantArrayType *NewArrayType,
+ ArraySubobjectLocation NewLoc, uint64_t NewSize, CharUnits RawOffset) {
+ if (const auto *PE = dyn_cast_or_null<PredefinedExpr>(Base))
+ Base = PE->getFunctionName();
+ const auto *Literal = dyn_cast_or_null<StringLiteral>(Base);
+ if (!Literal)
+ return false;
+
+ assert(!String && "string object must be recognized once");
+ String.emplace(Literal, RawOffset);
+ setRecognizedObject(Kind::StringLiteral, NewArrayType, NewLoc, NewSize);
+ return true;
+}
+
+bool PotentiallyNonUniqueObject::getElement(uint64_t Index,
+ APValue &Result) const {
+ if (!isStringLiteral())
+ return getArrayElement(Index, Result);
+
+ assert(String && "missing string storage");
+ const auto *Literal = std::get_if<const StringLiteral *>(&String->Storage);
+ assert(Literal && *Literal && "generic string object must be an AST literal");
+ Result = APValue(extractStringLiteralCharacter(Ctx, *Literal, Index));
+ return true;
+}
+
+bool PotentiallyNonUniqueObject::isPotentiallyOverlappingWith(
+ const PotentiallyNonUniqueObject &Other) const {
+ assert(arrayType() && Other.arrayType() && "unrecognized object");
+ assert(&Ctx == &Other.Ctx && "objects belong to different AST contexts");
+ if (isStringLiteral() && Other.isStringLiteral())
+ return isPotentiallyOverlappingStrings(*String, *Other.String);
+ if (location().OffsetInElement != Other.location().OffsetInElement)
+ return false;
+ if (!Ctx.hasSameUnqualifiedType(arrayType()->getElementType(),
+ Other.arrayType()->getElementType()))
+ return false;
+
+ uint64_t LHSStart = 0, RHSStart = 0;
+ if (location().Index >= Other.location().Index)
+ LHSStart = location().Index - Other.location().Index;
+ else
+ RHSStart = Other.location().Index - location().Index;
+
+ if (LHSStart >= size() || RHSStart >= Other.size())
+ return false;
+ uint64_t Count = std::min(size() - LHSStart, Other.size() - RHSStart);
+ for (uint64_t I = 0; I != Count; ++I) {
+ APValue LHSElement, RHSElement;
+ if (!getElement(LHSStart + I, LHSElement) ||
+ !Other.getElement(RHSStart + I, RHSElement))
+ return true;
+ if (!areAPValuesPotentiallyMergeable(LHSElement, RHSElement, Ctx))
+ return false;
+ }
+ return true;
+}
+
+bool ExprConstantPotentiallyNonUniqueObject::isInitializerListBackingArray(
+ const LValue &LV) {
const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>();
const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr);
return MTE && MTE->isBackingArrayForInitializerList();
}
-const APValue *
-PotentialNonUniqueObject::getCompleteObjectValue(const LValue &LV) const {
+const APValue *ExprConstantPotentiallyNonUniqueObject::getCompleteObjectValue(
+ const LValue &LV) const {
CompleteObject Obj =
findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base));
return Obj ? Obj.Value : nullptr;
}
std::optional<ArraySubobjectLocation>
-PotentialNonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
+ExprConstantPotentiallyNonUniqueObject::getArraySubobjectLocation(
+ const LValue &LV) const {
if (LV.Designator.Invalid || LV.Designator.Entries.empty())
return std::nullopt;
@@ -5217,40 +5211,21 @@ PotentialNonUniqueObject::getArraySubobjectLocation(const LValue &LV) const {
}
std::optional<ArraySubobjectLocation>
-PotentialNonUniqueObject::getStringArraySubobjectLocation(
+ExprConstantPotentiallyNonUniqueObject::getStringArraySubobjectLocation(
const LValue &LV) const {
- if (!getStringLiteralBase(LV.Base))
- return std::nullopt;
-
if (std::optional<ArraySubobjectLocation> EltLoc =
getArraySubobjectLocation(LV))
return EltLoc;
const auto *CAT = Info.Ctx.getAsConstantArrayType(getType(LV.Base));
- if (!CAT || LV.Offset.isNegative())
- return std::nullopt;
-
- uint64_t ElementSize =
- Info.Ctx.getTypeSizeInChars(CAT->getElementType()).getQuantity();
- if (!ElementSize)
- return std::nullopt;
-
- uint64_t Offset = LV.Offset.getQuantity();
- uint64_t Index = Offset / ElementSize;
- uint64_t OffsetInElement = Offset % ElementSize;
- uint64_t ArraySize = CAT->getZExtSize();
- // Accept element and subobject positions, plus the single one-past-the-end
- // byte {ArraySize, 0}. Reject anything beyond that, including an offset that
- // lands past the one-past-the-end byte with a nonzero in-element remainder.
- if (Index > ArraySize || (Index == ArraySize && OffsetInElement != 0))
+ if (!CAT)
return std::nullopt;
-
- return ArraySubobjectLocation{Index,
- CharUnits::fromQuantity(OffsetInElement)};
+ return computeArraySubobjectLocationFromOffset(Info.Ctx, CAT, LV.Offset);
}
-std::optional<PotentialNonUniqueObject::ArraySubobjectPathInfo>
-PotentialNonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
+std::optional<ExprConstantPotentiallyNonUniqueObject::ArraySubobjectPathInfo>
+ExprConstantPotentiallyNonUniqueObject::getArraySubobjectPathInfo(
+ const LValue &LV) const {
const ASTContext &Ctx = Info.Ctx;
if (LV.Designator.Invalid || LV.Designator.Entries.empty())
return std::nullopt;
@@ -5315,20 +5290,17 @@ PotentialNonUniqueObject::getArraySubobjectPathInfo(const LValue &LV) const {
return Result;
}
-bool PotentialNonUniqueObject::recognize(const LValue &LV) {
+bool ExprConstantPotentiallyNonUniqueObject::recognize(const LValue &LV) {
ASTContext &Ctx = Info.Ctx;
- if (const Expr *StringBase = getStringLiteralBase(LV.Base)) {
+ if (const Expr *StringBase = LV.Base.dyn_cast<const Expr *>()) {
std::optional<ArraySubobjectLocation> EltLoc =
getStringArraySubobjectLocation(LV);
const auto *CAT = Ctx.getAsConstantArrayType(getType(LV.Base));
- if (!EltLoc || !CAT)
- return false;
- TheKind = Kind::StringLiteral;
- ArrayType = CAT;
- Loc = *EltLoc;
- StringExpr = StringBase;
- return true;
+ if (EltLoc && CAT &&
+ setRecognizedStringObject(StringBase, CAT, *EltLoc, CAT->getZExtSize(),
+ LV.Offset))
+ return true;
}
if (isInitializerListBackingArray(LV)) {
@@ -5338,9 +5310,8 @@ bool PotentialNonUniqueObject::recognize(const LValue &LV) {
const APValue *ArrVal = getCompleteObjectValue(LV);
if (!EltLoc || !CAT || !ArrVal || !ArrVal->isArray())
return false;
- TheKind = Kind::InitializerList;
- ArrayType = CAT;
- Loc = *EltLoc;
+ setRecognizedObject(Kind::InitializerList, CAT, *EltLoc,
+ ArrVal->getArraySize());
ArrayValue = ArrVal;
return true;
}
@@ -5364,52 +5335,40 @@ bool PotentialNonUniqueObject::recognize(const LValue &LV) {
return false;
if (!ArrVal.isArray())
return false;
- TheKind = Kind::TemplateParamObject;
- ArrayType = PathInfo->ArrayType;
- Loc = PathInfo->Loc;
+ setRecognizedObject(Kind::TemplateParamObject, PathInfo->ArrayType,
+ PathInfo->Loc, ArrVal.getArraySize());
OwnedArrayValue = std::move(ArrVal);
return true;
}
-bool PotentialNonUniqueObject::mayShareStorageWith(
- const PotentialNonUniqueObject &Other) const {
- if (location().OffsetInElement != Other.location().OffsetInElement)
- return false;
- if (!Info.Ctx.hasSameUnqualifiedType(ArrayType->getElementType(),
- Other.ArrayType->getElementType()))
- return false;
-
- int64_t LHSStart = location().Index, RHSStart = Other.location().Index;
- int64_t Begin = std::max(-LHSStart, -RHSStart);
- int64_t End = std::min(size() - LHSStart, Other.size() - RHSStart);
- if (Begin >= End)
+bool ExprConstantPotentiallyNonUniqueObject::getArrayElement(
+ uint64_t Index, APValue &Result) const {
+ const APValue *Element = GetArrayInitializedElt(*arrayValue(), Index);
+ if (!Element)
return false;
-
- for (int64_t I = Begin; I != End; ++I) {
- std::optional<APValue> LHSElt = (*this)[I + LHSStart];
- std::optional<APValue> RHSElt = Other[I + RHSStart];
- if (!LHSElt || !RHSElt)
- return true;
- if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx))
- return false;
- }
+ Result = *Element;
return true;
}
-bool PotentialNonUniqueObject::arePotentiallyOverlapping(EvalInfo &Info,
- const Expr *E,
- const LValue &LHS,
- const LValue &RHS) {
- PotentialNonUniqueObject LHSObj(Info, E);
- PotentialNonUniqueObject RHSObj(Info, E);
- if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
- return false;
+PotentiallyNonUniqueObject::OverlapResult
+ExprConstantPotentiallyNonUniqueObject::arePotentiallyOverlapping(
+ EvalInfo &Info, const Expr *E, const LValue &LHS, const LValue &RHS) {
+ if (isPotentiallyOverlappingStrings(
+ Info.Ctx, LHS.Base.dyn_cast<const Expr *>(), LHS.Offset,
+ RHS.Base.dyn_cast<const Expr *>(), RHS.Offset)
+ .value_or(false))
+ return OverlapResult::StringLiteral;
- // String/string comparisons stay with the dedicated string-literal path.
- if (LHSObj.isStringLiteral() && RHSObj.isStringLiteral())
- return false;
+ ExprConstantPotentiallyNonUniqueObject LHSObj(Info, E);
+ ExprConstantPotentiallyNonUniqueObject RHSObj(Info, E);
+ if (!LHSObj.recognize(LHS) || !RHSObj.recognize(RHS))
+ return OverlapResult::None;
- return LHSObj.mayShareStorageWith(RHSObj);
+ if (!LHSObj.isPotentiallyOverlappingWith(RHSObj))
+ return OverlapResult::None;
+ return LHSObj.isStringLiteral() && RHSObj.isStringLiteral()
+ ? OverlapResult::StringLiteral
+ : OverlapResult::NonUniqueObject;
}
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -5460,7 +5419,7 @@ handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, QualType Type,
return false;
}
uint64_t CharIndex = LVal.Designator.Entries[0].getAsArrayIndex();
- RVal = APValue(extractStringLiteralCharacter(Info, Base, CharIndex));
+ RVal = APValue(extractStringLiteralCharacter(Info.Ctx, Base, CharIndex));
return true;
}
}
@@ -19628,12 +19587,16 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
// This makes the comparison result unspecified, so it's not a constant
// expression.
//
- if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
+ switch (ExprConstantPotentiallyNonUniqueObject::arePotentiallyOverlapping(
+ Info, E, LHSValue, RHSValue)) {
+ case PotentiallyNonUniqueObject::OverlapResult::StringLiteral:
return DiagComparison(diag::note_constexpr_literal_comparison);
- if (PotentialNonUniqueObject::arePotentiallyOverlapping(Info, E, LHSValue,
- RHSValue))
+ case PotentiallyNonUniqueObject::OverlapResult::NonUniqueObject:
return DiagComparison(
diag::note_constexpr_non_unique_object_comparison);
+ case PotentiallyNonUniqueObject::OverlapResult::None:
+ break;
+ }
if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
return DiagComparison(diag::note_constexpr_opaque_call_comparison,
!IsOpaqueConstantCall(LHSValue));
@@ -19941,7 +19904,9 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
if (!LHSExpr || !RHSExpr)
return DiagArith(diag::note_constexpr_pointer_arith_unspecified);
- if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
+ if (PotentiallyNonUniqueObject::isPotentiallyOverlappingStrings(
+ Info.Ctx, LHSExpr, LHSValue.Offset, RHSExpr, RHSValue.Offset)
+ .value_or(false))
return DiagArith(diag::note_constexpr_literal_arith);
const AddrLabelExpr *LHSAddrExpr = dyn_cast<AddrLabelExpr>(LHSExpr);
diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp
index 3553ab3ba68ad..b1d6765edc557 100644
--- a/clang/test/AST/ByteCode/cxx11.cpp
+++ b/clang/test/AST/ByteCode/cxx11.cpp
@@ -298,6 +298,15 @@ namespace OverlappingStrings {
constexpr bool may_overlap_2 = +"foo" == +"foo\0bar"; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
constexpr bool may_overlap_3 = +"foo" == &"bar\0foo"[4]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
constexpr bool may_overlap_4 = &"xfoo"[1] == &"xfoo"[1]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
+ constexpr bool wide_may_overlap = &u"foo"[1] == &u"xfoo"[2]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
+ constexpr bool wide_may_overlap_reverse = &u"xfoo"[2] == &u"foo"[1]; // both-error {{}} both-note {{addresses of potentially overlapping literals}}
+
+ constexpr bool predefined_may_overlap(bool Compare) {
+ return !Compare || +__func__ != "predefined_may_overlap"; // both-warning {{result of comparison against a string literal is unspecified}} #predefined-compare
+ }
+ static_assert(predefined_may_overlap(true), ""); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in call to 'predefined_may_overlap(true)'}}
+ // both-note@#predefined-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
/// Used to crash.
diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp
index efdc407787fdc..3092575d68326 100644
--- a/clang/test/AST/ByteCode/initializer_list.cpp
+++ b/clang/test/AST/ByteCode/initializer_list.cpp
@@ -102,6 +102,25 @@ namespace cwg2765 {
// both-note@#cwg2765-init-list-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
// both-note at -3 {{in call to}}
+ constexpr bool wide_init_list_overlaps_shifted_string() {
+ return std::initializer_list<char16_t>{u'x', u'a', u'b', u'\0'}.begin() + 1
+ != u"ab"; // #cwg2765-wide-init-list-string-compare \
+ // both-warning {{result of comparison against a string literal is unspecified}}
+ }
+ static_assert(wide_init_list_overlaps_shifted_string(), "");
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-wide-init-list-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
+ constexpr bool p(bool Compare) {
+ return !Compare ||
+ std::initializer_list<char>{'p', '\0'}.begin() != +__func__; // #cwg2765-init-list-predefined-compare
+ }
+ static_assert(p(true), "");
+ // both-error at -1 {{static assertion expression is not an integral constant expression}}
+ // both-note@#cwg2765-init-list-predefined-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ // both-note at -3 {{in call to}}
+
constexpr bool init_list_differs_from_string() {
return std::initializer_list<char>{'h', 'e', 'l', 'p', '\0'}.begin()
!= "hello"; // both-warning {{result of comparison against a string literal is unspecified}}
@@ -125,6 +144,20 @@ namespace cwg2765 {
// both-note {{initializer of 'template_array_overlaps_string<FixedString<6>{"hello"}>' is not a constant expression}}
// both-note@#cwg2765-template-array-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+ template <FixedString S>
+ constexpr bool template_array_object_overlaps_string = // both-error {{constexpr variable 'template_array_object_overlaps_string<FixedString<6>{"hello"}>' must be initialized by a constant expression}} \
+ // both-note {{declared here}}
+ &S.data != &"hello"; // #cwg2765-template-array-object-string-compare
+ static_assert(template_array_object_overlaps_string<"hello">); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in instantiation of variable template specialization}} \
+ // both-note {{initializer of 'template_array_object_overlaps_string<FixedString<6>{"hello"}>' is not a constant expression}}
+ // both-note@#cwg2765-template-array-object-string-compare {{comparison of addresses of potentially non-unique objects has unspecified value}}
+
+ template <FixedString S>
+ constexpr bool template_array_object_differs_from_string =
+ &S.data != &"world";
+ static_assert(template_array_object_differs_from_string<"hello">);
+
template <FixedString S>
constexpr bool template_array_differs_from_string =
S.data != "hello";
@@ -433,3 +466,17 @@ namespace cwg2765 {
{{__builtin_complex(1.0, 2.0)}}, {{__builtin_complex(1.0, 3.0)}});
static_assert(!different_complex, "");
}
+
+constexpr bool f(std::initializer_list<int> il1, std::initializer_list<double> il2) {
+ return (const void*)il1.begin() == (const void*)il2.begin();
+}
+
+static_assert(f({1,2}, {4.2439915824e-314}) == false);
+
+static_assert(f({}, {}), ""); // TODO: presumably this must give an error, since an empty list can go literally anywhere in memory, but clang accept this.
+
+int i = 42;
+constexpr bool g(std::initializer_list<int> il, int *p) { return il.begin() == p; }
+static_assert(g({42}, &i) == false, ""); // OK, right?
+constexpr bool value1 = g({}, &i); // TODO: error, right? because the empty list could go right before `i` in memory? But clang accept this.
+
diff --git a/clang/test/AST/ByteCode/objc.mm b/clang/test/AST/ByteCode/objc.mm
index f6c4ba12dc5d1..0ee0021dc0309 100644
--- a/clang/test/AST/ByteCode/objc.mm
+++ b/clang/test/AST/ByteCode/objc.mm
@@ -12,6 +12,13 @@ @interface NSString
constexpr NSString *t0 = @"abc";
constexpr NSString *t1 = @("abc");
+constexpr bool objc_encode_may_overlap(bool Compare) {
+ return !Compare || + at encode(int) != +"i"; // #objc-encode-compare
+}
+static_assert(objc_encode_may_overlap(true), ""); // both-error {{static assertion expression is not an integral constant expression}} \
+ // both-note {{in call to 'objc_encode_may_overlap(true)'}}
+// both-note@#objc-encode-compare {{comparison of addresses of potentially overlapping literals has unspecified value}}
+
#if __LP64__
typedef unsigned long NSUInteger;
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 3d6350504e8cb..61cd61a16a804 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1953,6 +1953,19 @@ namespace InitializerList {
}
static_assert(init_list_differs_from_string(), "");
+ constexpr bool f(std::initializer_list<int> il1, std::initializer_list<double> il2) {
+ return (const void*)il1.begin() == (const void*)il2.begin();
+ }
+
+ static_assert(f({1,2}, {4.2439915824e-314}) == false, "");
+
+ static_assert(f({}, {}), ""); // TODO: presumably this must give an error, since an empty list can go literally anywhere in memory, but clang accept this.
+
+ int i = 42;
+ constexpr bool g(std::initializer_list<int> il, int *p) { return il.begin() == p; }
+ static_assert(g({42}, &i) == false, ""); // OK, right?
+ constexpr bool value1 = g({}, &i); // TODO: error, right? because the empty list could go right before `i` in memory? But clang accept this.
+
namespace DR2126 {
constexpr std::initializer_list<float> il = {1.0, 2.0, 3.0};
static_assert(il.begin()[1] == 2.0, "");
More information about the cfe-commits
mailing list