[clang] [clang][bytecode] Use opaque pointers for decl-based dummy pointer (PR #222897)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 11 02:35:57 PDT 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID: <llvm.org/llvm/llvm-project/pull/222897 at github.com>
In-Reply-To:
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/222897
This uses the recently introduced opaque pointers for what we used to
use dummy pointers for, if the base of the pointer is a declaration.
This of course means we see opaque pointers in a lot more places.
However, we don't need to actually allocate anything for their data
anymore, resulting in a lot fewer allocations for such pointers.
>From 4fe904d715c18eafdd22b789dd92803bcbba9f72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 11 Sep 2026 10:21:27 +0200
Subject: [PATCH 1/2] Reapply "[clang][bytecode] Use opaque pointers for
decl-based dummy pointers" (#222599)
This reverts commit 73f0077ff60c58cae6c1f753b046ce93ebc4db3f.
---
clang/lib/AST/ByteCode/Compiler.cpp | 19 +-
clang/lib/AST/ByteCode/Interp.cpp | 218 +++++++++++++++---
clang/lib/AST/ByteCode/Interp.h | 107 ++++++---
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 57 +++--
clang/lib/AST/ByteCode/InterpHelpers.h | 5 +
clang/lib/AST/ByteCode/MemberPointer.h | 2 +
clang/lib/AST/ByteCode/Opcodes.td | 9 +-
clang/lib/AST/ByteCode/Pointer.cpp | 82 +++++--
clang/lib/AST/ByteCode/Pointer.h | 37 ++-
clang/test/AST/ByteCode/records.cpp | 11 +
clang/test/CodeGen/pr4349.c | 3 +-
clang/test/SemaCXX/new-delete.cpp | 14 +-
.../SemaTemplate/temp_arg_nontype_cxx1z.cpp | 1 +
clang/unittests/AST/ByteCode/toAPValue.cpp | 2 -
14 files changed, 444 insertions(+), 123 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index daa5307c92298..473494c694a98 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7935,12 +7935,18 @@ bool Compiler<Emitter>::VisitUnaryOperator(const UnaryOperator *E) {
// check), so that e.g. '&*(int *)0' is not rejected.
if (!Ctx.getLangOpts().CPlusPlus) {
const Expr *Sub = SubExpr->IgnoreParens();
+
if (const auto *Deref = dyn_cast<UnaryOperator>(Sub);
- Deref && Deref->getOpcode() == UO_Deref)
- return this->delegate(Deref->getSubExpr());
+ Deref && Deref->getOpcode() == UO_Deref) {
+ if (DiscardResult)
+ return this->discard(Deref->getSubExpr());
+ return this->visit(Deref->getSubExpr()) && this->emitAddrOf(E);
+ }
}
// We should already have a pointer when we get here.
- return this->delegate(SubExpr);
+ if (DiscardResult)
+ return this->discard(SubExpr);
+ return this->delegate(SubExpr) && this->emitAddrOf(E);
case UO_Deref: // *x
if (DiscardResult)
return this->discard(SubExpr);
@@ -8729,11 +8735,10 @@ template <class Emitter>
bool Compiler<Emitter>::emitDummyPtr(DeclOrExpr D, const Expr *E, bool CU) {
assert(!DiscardResult && "Should've been checked before");
- if (ToLValue) {
- if (const auto *VD = D.asValueDecl())
- return this->emitGetOpaquePtr(VD, CU, E);
- }
+ if (const auto *VD = D.asValueDecl())
+ return this->emitGetOpaquePtr(VD, CU, E);
+ assert(D.asExpr());
unsigned DummyID = P.getOrCreateDummy(D, CU);
if (!this->emitGetPtrGlobal(DummyID, E))
return false;
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index e205b025bbe01..160cc745c89b6 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -97,6 +97,23 @@ static void noteValueLocation(InterpState &S, const Block *B) {
S.Note(Desc->getLocation(), diag::note_declared_at);
}
+static void noteValueLocation(InterpState &S, const Pointer &Ptr) {
+ if (Ptr.isBlockPointer()) {
+ const Block *B = Ptr.block();
+ const Descriptor *Desc = B->getDescriptor();
+ if (B->isDynamic())
+ S.Note(Desc->getLocation(), diag::note_constexpr_dynamic_alloc_here);
+ else if (B->isTemporary())
+ S.Note(Desc->getLocation(), diag::note_constexpr_temporary_here);
+ else
+ S.Note(Desc->getLocation(), diag::note_declared_at);
+ return;
+ }
+
+ if (Ptr.isOpaquePointer())
+ S.Note(Ptr.asOpaquePointer().Base->getLocation(), diag::note_declared_at);
+}
+
static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
const ValueDecl *VD,
AccessKinds AK = AK_Read);
@@ -200,6 +217,38 @@ static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
S.Note(VD->getLocation(), diag::note_declared_at);
}
+static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ AccessKinds AK) {
+
+ if (!Ptr.isBlockPointer())
+ return true;
+
+ const Block *B = Ptr.block();
+ if (B->getDeclID()) {
+ if (!(B->isStatic() && B->isTemporary()))
+ return true;
+
+ const auto *MTE = dyn_cast_if_present<MaterializeTemporaryExpr>(
+ B->getDescriptor()->asExpr());
+ if (!MTE)
+ return true;
+
+ // FIXME(perf): Since we do this check on every Load from a static
+ // temporary, it might make sense to cache the value of the
+ // isUsableInConstantExpressions call.
+ if (S.checkingConstantDestruction() ||
+ (B->getEvalID() != S.EvalID &&
+ !MTE->isUsableInConstantExpressions(S.getASTContext()))) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
+ S.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
+ noteValueLocation(S, B);
+ return false;
+ }
+ }
+
+ return true;
+}
+
static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Block *B,
AccessKinds AK) {
if (B->getDeclID()) {
@@ -460,7 +509,7 @@ bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
} else if (!S.checkingPotentialConstantExpression()) {
S.FFDiag(Src, diag::note_constexpr_access_uninit)
<< AK << /*uninitialized=*/false << S.Current->getRange(OpPC);
- noteValueLocation(S, Ptr.block());
+ noteValueLocation(S, Ptr);
}
return false;
@@ -897,7 +946,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
}
// Block and string pointers are the only ones we can actually read from.
if (!Ptr.isReadablePointerType())
- return false;
+ return CheckDummy(S, OpPC, Ptr, AK);
if (Ptr.isBlockPointer() && !Ptr.block()->isAccessible()) {
if (!CheckLive(S, OpPC, Ptr, AK))
@@ -964,7 +1013,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
assert(!Ptr.isZero());
if (!Ptr.isReadablePointerType())
- return false;
+ return CheckDummy(S, OpPC, Ptr, AK_Read);
if (Ptr.isBlockPointer() && !Ptr.block()->isAccessible()) {
if (!CheckLive(S, OpPC, Ptr, AK_Read))
@@ -996,7 +1045,13 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
bool WillBeActivated) {
- if (!Ptr.isBlockPointer() || Ptr.isZero())
+ if (Ptr.isZero())
+ return false;
+
+ if (Ptr.isOpaquePointer())
+ return CheckDummy(S, OpPC, Ptr, AK_Assign);
+
+ if (!Ptr.isBlockPointer())
return false;
if (!Ptr.block()->isAccessible()) {
@@ -1043,6 +1098,8 @@ bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Assign))
return false;
+ if (!Ptr.isBlockPointer())
+ return false;
return true;
}
@@ -1263,6 +1320,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
const Pointer &Ptr) {
+ if (!Ptr.isBlockPointer() && !Ptr.isOpaquePointer())
+ return false;
// Regular new type(...) call.
if (isa_and_nonnull<CXXNewExpr>(Source))
return true;
@@ -1279,7 +1338,7 @@ bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc)
<< Ptr.toDiagnosticString(S.getASTContext());
- noteValueLocation(S, Ptr.block());
+ noteValueLocation(S, Ptr);
return false;
}
@@ -1305,6 +1364,24 @@ bool InvalidDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR,
return CheckDeclRef(S, OpPC, DR);
}
+bool CheckDummy(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ AccessKinds AK) {
+ if (!Ptr.isDummy())
+ return true;
+
+ const VarDecl *D = Ptr.getRootVarDecl();
+ if (!D)
+ return false;
+
+ if (AK == AK_Read || AK == AK_Increment || AK == AK_Decrement)
+ return diagnoseUnknownDecl(S, OpPC, D, AK);
+
+ if (AK == AK_Destroy || S.getLangOpts().CPlusPlus14)
+ S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_modify_global);
+ return false;
+}
+
+// FIXME: Remove this once all dummy pointers are opaque pointers.
bool CheckDummy(InterpState &S, CodePtr OpPC, const Block *B, AccessKinds AK) {
if (!B->isDummy())
return true;
@@ -1429,7 +1506,7 @@ bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
return true;
if (!Ptr.isBlockPointer())
- return false;
+ return CheckDeleteSource(S, OpPC, nullptr, Ptr);
// Remove base casts.
QualType InitialType = Ptr.getType();
@@ -1811,7 +1888,7 @@ static bool diagnoseOutOfLifetimeDestroy(InterpState &S, CodePtr OpPC,
bool checkDestructor(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!CheckLive(S, OpPC, Ptr, AK_Destroy))
return false;
- if (!CheckTemporary(S, OpPC, Ptr.block(), AK_Destroy))
+ if (!CheckTemporary(S, OpPC, Ptr, AK_Destroy))
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Destroy))
return false;
@@ -1827,7 +1904,7 @@ bool checkDestructor(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return true;
// Can't call a dtor on a global variable.
- if (Ptr.block()->isStatic()) {
+ if (Ptr.isOpaquePointer() || Ptr.block()->isStatic()) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_modify_global);
return false;
@@ -2056,9 +2133,22 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
return true;
}
-static bool getDynamicDecl(InterpState &S, CodePtr OpPC, PtrView TypePtr,
+static bool getDynamicDecl(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
const CXXRecordDecl *&DynamicDecl) {
+ auto diagUnknownDynamicType = [&](const Pointer &P) -> bool {
+ APValue V = P.toAPValue(S.getASTContext());
+ QualType TT = S.getASTContext().getLValueReferenceType(P.getType());
+ S.FFDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_polymorphic_unknown_dynamic_type)
+ << AK_MemberCall << V.getAsString(S.getASTContext(), TT);
+ return false;
+ };
+
+ if (!Ptr.isBlockPointer())
+ return diagUnknownDynamicType(Ptr);
+
+ PtrView TypePtr = Ptr.view();
if (S.InitializingPtrs.empty()) {
TypePtr = TypePtr.stripBaseCasts();
} else {
@@ -2090,14 +2180,8 @@ static bool getDynamicDecl(InterpState &S, CodePtr OpPC, PtrView TypePtr,
QualType DynamicType = TypePtr.getType();
if (TypePtr.Pointee->isStatic() || TypePtr.isConst()) {
if (const VarDecl *VD = Pointer(TypePtr).getRootVarDecl();
- VD && !VD->isConstexpr()) {
- const Expr *E = S.Current->getExpr(OpPC);
- APValue V = Pointer(TypePtr).toAPValue(S.getASTContext());
- QualType TT = S.getASTContext().getLValueReferenceType(DynamicType);
- S.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
- << AK_MemberCall << V.getAsString(S.getASTContext(), TT);
- return false;
- }
+ VD && !VD->isConstexpr())
+ return diagUnknownDynamicType(Pointer(TypePtr));
}
if (DynamicType->isPointerType() || DynamicType->isReferenceType()) {
@@ -2162,7 +2246,7 @@ bool DynamicCast(InterpState &S, CodePtr OpPC, const Type *DestTypePtr,
const auto &Ptr = S.Stk.pop<Pointer>();
QualType TargetType = QualType(DestTypePtr, 0);
- if (Ptr.isConstexprUnknown()) {
+ if (Ptr.isConstexprUnknown() || Ptr.isOpaquePointer()) {
QualType T = Ptr.getType();
const Expr *E = S.Current->getExpr(OpPC);
APValue V = Ptr.toAPValue(S.getASTContext());
@@ -2331,13 +2415,13 @@ bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func,
size_t ThisOffset = ArgSize - (Func->hasRVO() ? primSize(PT_Ptr) : 0);
Pointer &ThisPtr = S.Stk.peek<Pointer>(ThisOffset);
- if (!ThisPtr.isBlockPointer())
+ if (!ThisPtr.isBlockPointer() && !ThisPtr.isOpaquePointer())
return false;
const FunctionDecl *Callee = Func->getDecl();
const CXXRecordDecl *DynamicDecl = nullptr;
- if (!getDynamicDecl(S, OpPC, ThisPtr.view(), DynamicDecl))
+ if (!getDynamicDecl(S, OpPC, ThisPtr, DynamicDecl))
return false;
assert(DynamicDecl);
@@ -2611,7 +2695,7 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
}
if (!Ptr.isBlockPointer())
- return false;
+ return CheckDummy(S, OpPC, Ptr, AK_Construct);
if (!CheckRange(S, OpPC, Ptr, AK_Construct))
return false;
@@ -2625,9 +2709,9 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
return false;
if (!CheckLive(S, OpPC, Ptr, AK_Construct))
return false;
- return CheckDummy(S, OpPC, Ptr.block(), AK_Construct);
+ return CheckDummy(S, OpPC, Ptr, AK_Construct);
}
- if (!CheckTemporary(S, OpPC, Ptr.block(), AK_Construct))
+ if (!CheckTemporary(S, OpPC, Ptr, AK_Construct))
return false;
// CheckLifetime for this and all base pointers.
@@ -2773,6 +2857,12 @@ bool CheckPointerToIntegralCast(InterpState &S, CodePtr OpPC,
if (Ptr.isIntegralPointer())
return true;
+ if (Ptr.isOpaquePointer()) {
+ if (!CheckIntegralAddressCast(S, OpPC, BitWidth))
+ return false;
+ return Ptr.isRoot();
+ }
+
if (Ptr.isDummy()) {
if (!CheckIntegralAddressCast(S, OpPC, BitWidth))
return false;
@@ -2862,7 +2952,7 @@ bool GetTypeid(InterpState &S, const Type *TypePtr, const Type *TypeInfoType) {
bool GetTypeidPtr(InterpState &S, CodePtr OpPC, const Type *TypeInfoType) {
const auto &P = S.Stk.pop<Pointer>();
- if (!P.isBlockPointer())
+ if (!P.isBlockPointer() && !P.isOpaquePointer())
return false;
if (P.isConstexprUnknown()) {
@@ -2876,7 +2966,12 @@ bool GetTypeidPtr(InterpState &S, CodePtr OpPC, const Type *TypeInfoType) {
}
// Pick the most-derived type.
- CanQualType T = P.stripBaseCasts().getType()->getCanonicalTypeUnqualified();
+ CanQualType T;
+ if (P.isBlockPointer())
+ T = P.stripBaseCasts().getType()->getCanonicalTypeUnqualified();
+ else
+ T = P.getType()->getCanonicalTypeUnqualified();
+
// ... unless we're currently constructing this object.
// FIXME: We have a similar check to this in more places.
if (S.Current->getFunction()) {
@@ -2979,6 +3074,17 @@ static void copyPrimitiveMemory(InterpState &S, PtrView Ptr, PrimType T) {
auto *NewPath = new (S.P) const CXXRecordDecl *[PathLength];
std::copy_n(Val.path(), PathLength, NewPath);
Val.takePath(NewPath);
+ } else if (T == PT_Ptr) {
+ auto &Val = Ptr.deref<Pointer>();
+ if (Val.isOpaquePointer() && Val.asOpaquePointer().PathLength != 0) {
+ const OpaquePointer &OP = Val.asOpaquePointer();
+ auto *NewPath = new (S.P) PointerPathEntry[OP.PathLength];
+ std::memcpy(NewPath, OP.Path, OP.PathLength * sizeof(PointerPathEntry));
+ Val = Pointer(OP.withPath(NewPath, OP.PathLength,
+ OP.getFieldType().getTypePtr(),
+ OP.isOnePastEnd()),
+ Val.getByteOffset());
+ }
}
}
@@ -2991,6 +3097,17 @@ static void copyPrimitiveMemory(InterpState &S, PtrView Ptr) {
auto *NewPath = new (S.P) const CXXRecordDecl *[PathLength];
std::copy_n(Val.path(), PathLength, NewPath);
Val.takePath(NewPath);
+ } else if constexpr (std::is_same_v<T, Pointer>) {
+ auto &Val = Ptr.deref<Pointer>();
+ if (Val.isOpaquePointer() && Val.asOpaquePointer().PathLength != 0) {
+ const OpaquePointer &OP = Val.asOpaquePointer();
+ auto *NewPath = new (S.P) PointerPathEntry[OP.PathLength];
+ std::memcpy(NewPath, OP.Path, OP.PathLength * sizeof(PointerPathEntry));
+ Val = Pointer(OP.withPath(NewPath, OP.PathLength,
+ OP.getFieldType().getTypePtr(),
+ OP.isOnePastEnd()),
+ Val.getByteOffset());
+ }
} else {
auto &Val = Ptr.deref<T>();
if (!Val.singleWord()) {
@@ -3050,6 +3167,8 @@ static void finishGlobalRecurse(InterpState &S, PtrView Ptr) {
bool FinishInitGlobal(InterpState &S) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
+ if (!Ptr.isBlockPointer())
+ return true;
finishGlobalRecurse(S, Ptr.view());
if (Ptr.canBeInitialized()) {
@@ -3355,13 +3474,16 @@ std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
return Ptr;
const OpaquePointer &OP = Ptr.asOpaquePointer();
- QualType ArrTy = OP.getSurroundingArray();
- QualType ElemTy = ArrTy;
+ QualType ArrTy = OP.getSurroundingArray().getCanonicalType();
+ QualType ElemTy = OP.getFieldType();
unsigned NumElems = 1;
- if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe()) {
- ElemTy = AT->getElementType();
- if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+
+ if (OP.isArrayElement()) {
+ if (const ConstantArrayType *CAT =
+ S.getASTContext().getAsConstantArrayType(ArrTy))
NumElems = CAT->getZExtSize();
+ } else {
+ ArrTy = ElemTy;
}
if (isa<IncompleteArrayType>(ArrTy)) {
@@ -3373,10 +3495,10 @@ std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
if (Offset > NumElems) {
if (Op == ArithOp::Add)
S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
- << Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems;
+ << Offset << /*non-array*/ !OP.isArrayElement() << NumElems;
else
S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
- << -Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems;
+ << -Offset << /*non-array*/ !OP.isArrayElement() << NumElems;
}
if (!validType(ElemTy) || !validType(ArrTy)) {
@@ -3412,6 +3534,38 @@ std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
return Pointer(OP.withPastEnd(true), NewOffset);
}
+bool virtBaseHelper(InterpState &S, const CXXRecordDecl *Decl,
+ const Pointer &Ptr) {
+ if (Ptr.isOpaquePointer()) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ if (!OP.getFieldType()->isRecordType()) {
+ S.Stk.push<Pointer>(Ptr);
+ return true;
+ }
+
+ PointerPathEntry *NewPath =
+ S.extendPointerPath(OP.PathLength + 1, OP.Path,
+ PointerPathEntry::base(Decl, /*IsVirtual=*/true));
+
+ S.Stk.push<Pointer>(
+ OP.withPath(NewPath, OP.PathLength + 1,
+ S.getASTContext().getCanonicalTagType(Decl).getTypePtr()),
+ Ptr.getByteOffset());
+ return true;
+ }
+
+ if (!Ptr.isBlockPointer())
+ return false;
+ if (!Ptr.getFieldDesc()->isRecord())
+ return false;
+ Pointer Base = Ptr.stripBaseCasts();
+ const Record::Base *VirtBase = Base.getRecord()->findVirtualBase(Decl);
+ if (!VirtBase)
+ return false;
+ S.Stk.push<Pointer>(Base.atField(VirtBase->Offset));
+ return true;
+}
+
// FIXME: Would be nice to generate this instead of hardcoding it here.
[[maybe_unused]] static constexpr bool OpReturns(Opcode Op) {
return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet ||
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 61118d77b7ac2..343883872728b 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1494,15 +1494,30 @@ bool CMP3(InterpState &S, CodePtr OpPC, const ComparisonCategoryInfo *CmpInfo) {
const T &LHS = S.Stk.pop<T>();
const Pointer &P = S.Stk.peek<Pointer>();
- ComparisonCategoryResult CmpResult = LHS.compare(RHS);
+ ComparisonCategoryResult CmpResult;
if constexpr (std::is_same_v<T, Pointer>) {
- if (CmpResult == ComparisonCategoryResult::Unordered) {
- const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_unspecified)
+ if (!Pointer::hasSameBase(LHS, RHS)) {
+ S.FFDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_pointer_comparison_unspecified)
<< LHS.toDiagnosticString(S.getASTContext())
<< RHS.toDiagnosticString(S.getASTContext());
return false;
}
+ std::optional<size_t> LHSOffset =
+ LHS.computeLayoutOffset(S.getASTContext());
+ std::optional<size_t> RHSOffset =
+ RHS.computeLayoutOffset(S.getASTContext());
+ if (!LHSOffset || !RHSOffset)
+ return false;
+
+ if (LHSOffset < RHSOffset)
+ CmpResult = ComparisonCategoryResult::Less;
+ else if (LHSOffset > RHSOffset)
+ CmpResult = ComparisonCategoryResult::Greater;
+ else
+ CmpResult = ComparisonCategoryResult::Equal;
+ } else {
+ CmpResult = LHS.compare(RHS);
}
assert(CmpInfo);
@@ -1673,6 +1688,9 @@ bool GetField(InterpState &S, CodePtr OpPC, uint32_t I) {
if (!CheckRange(S, OpPC, Obj, CSK_Field))
return false;
+ if (!Obj.isBlockPointer())
+ return false;
+
// FIXME(postswitch): The isUnknownSizeArray() check here is only needed
// to keep an invalid sample producing the same diagnostics as the current
// interpreter.
@@ -1696,6 +1714,9 @@ bool GetFieldPop(InterpState &S, CodePtr OpPC, uint32_t I) {
if (!CheckRange(S, OpPC, Obj, CSK_Field))
return false;
+ if (!Obj.isBlockPointer())
+ return false;
+
// FIXME(postswitch): The isUnknownSizeArray() check here is only needed
// to keep an invalid sample producing the same diagnostics as the current
// interpreter.
@@ -1716,6 +1737,10 @@ bool GetThisField(InterpState &S, CodePtr OpPC, uint32_t I) {
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
+
+ if (!This.isBlockPointer())
+ return false;
+
const Pointer &Field = This.atField(I);
if (!CheckLoad(S, OpPC, Field))
return false;
@@ -1773,6 +1798,18 @@ bool InitGlobal(InterpState &S, uint32_t I) {
NewPath[I] = Val.getPathEntry(I);
}
Val.takePath(NewPath);
+ } else if constexpr (std::is_same_v<T, Pointer>) {
+ auto &Val = P.deref<Pointer>();
+ if (Val.isOpaquePointer() && Val.asOpaquePointer().PathLength != 0) {
+ const OpaquePointer &OP = Val.asOpaquePointer();
+ auto *NewPath = new (S.P) PointerPathEntry[OP.PathLength];
+ std::memcpy(NewPath, OP.Path, OP.PathLength * sizeof(PointerPathEntry));
+ Val = Pointer(OP.withPath(NewPath, OP.PathLength,
+ OP.getFieldType().getTypePtr(),
+ OP.isOnePastEnd()),
+ Val.getByteOffset());
+ }
+
} else if constexpr (needsAlloc<T>()) {
auto &Val = P.deref<T>();
if (!Val.singleWord()) {
@@ -2090,6 +2127,8 @@ inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) {
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
+ if (!This.isBlockPointer())
+ return false;
S.Stk.push<Pointer>(This.atField(Off));
return true;
}
@@ -2153,46 +2192,36 @@ inline bool CheckNull(InterpState &S, CodePtr OpPC) {
return true;
}
-inline bool VirtBaseHelper(InterpState &S, const RecordDecl *Decl,
- const Pointer &Ptr) {
- if (!Ptr.isBlockPointer())
- return false;
- if (!Ptr.getFieldDesc()->isRecord())
- return false;
- Pointer Base = Ptr.stripBaseCasts();
- const Record::Base *VirtBase = Base.getRecord()->findVirtualBase(Decl);
- if (!VirtBase)
- return false;
- S.Stk.push<Pointer>(Base.atField(VirtBase->Offset));
- return true;
-}
+bool virtBaseHelper(InterpState &S, const CXXRecordDecl *Decl,
+ const Pointer &Ptr);
inline bool GetPtrVirtBasePop(InterpState &S, CodePtr OpPC,
- const RecordDecl *D) {
+ const CXXRecordDecl *D) {
assert(D);
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (!CheckNull(S, OpPC, Ptr, CSK_Base))
return false;
- return VirtBaseHelper(S, D, Ptr);
+ return virtBaseHelper(S, D, Ptr);
}
-inline bool GetPtrVirtBase(InterpState &S, CodePtr OpPC, const RecordDecl *D) {
+inline bool GetPtrVirtBase(InterpState &S, CodePtr OpPC,
+ const CXXRecordDecl *D) {
assert(D);
const Pointer &Ptr = S.Stk.peek<Pointer>();
if (!CheckNull(S, OpPC, Ptr, CSK_Base))
return false;
- return VirtBaseHelper(S, D, Ptr);
+ return virtBaseHelper(S, D, Ptr);
}
inline bool GetPtrThisVirtBase(InterpState &S, CodePtr OpPC,
- const RecordDecl *D) {
+ const CXXRecordDecl *D) {
assert(D);
if (S.checkingPotentialConstantExpression())
return false;
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
- return VirtBaseHelper(S, D, This);
+ return virtBaseHelper(S, D, This);
}
//===----------------------------------------------------------------------===//
@@ -3037,6 +3066,22 @@ bool CastFloatingIntegral(InterpState &S, CodePtr OpPC, uint32_t FPOI) {
}
}
+inline bool AddrOf(InterpState &S, CodePtr OpPC) {
+ const Pointer Ptr = S.Stk.pop<Pointer>();
+
+ if (Ptr.isOpaquePointer()) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ QualType T = QualType(OP.FieldType.getPointer(), 0);
+ T = S.getASTContext().getPointerType(T);
+
+ S.Stk.push<Pointer>(OP.withFieldType(T.getTypePtr(), OP.isOnePastEnd()));
+ } else {
+ S.Stk.push<Pointer>(Ptr);
+ }
+
+ return true;
+}
+
bool CheckPointerToIntegralCast(InterpState &S, CodePtr OpPC,
const Pointer &Ptr, unsigned BitWidth);
bool CheckIntegralAddressCast(InterpState &S, CodePtr OpPC, unsigned BitWidth);
@@ -3068,6 +3113,8 @@ bool CastPointerIntegral(InterpState &S, CodePtr OpPC) {
Kind = IntegralKind::BlockAddress;
}
S.Stk.push<T>(Kind, PtrVal, /*Offset=*/0);
+ } else if (Ptr.isOpaquePointer()) {
+ S.Stk.push<T>(IntegralKind::Address, Ptr.asOpaquePointer().Base, 0);
} else if (Ptr.isFunctionPointer()) {
const void *FuncDecl = Ptr.asFunctionPointer().Func->getDecl();
S.Stk.push<T>(IntegralKind::FunctionAddress, FuncDecl, /*Offset=*/0);
@@ -3496,15 +3543,17 @@ inline bool ExpandPtr(InterpState &S) {
return true;
}
-bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
- APSInt &&Index, bool AllowReplace = true);
-
// Implementation for ArrayElemPtr and ArrayElemPtrPop ops.
template <typename T>
inline bool arrayElemPtr(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
const T &Offset) {
- if (Ptr.isOpaquePointer())
+ if (Ptr.isOpaquePointer()) {
+ if (S.inConstantContext() && !Offset.isZero() &&
+ !CheckArray(S, OpPC, Ptr)) {
+ return false;
+ }
return arrayElemPtrOpaque(S, OpPC, Ptr, Offset.toAPSInt());
+ }
if (Offset.isZero()) {
if (const Descriptor *Desc = Ptr.getFieldDesc();
@@ -4141,6 +4190,10 @@ inline bool BitCast(InterpState &S, CodePtr OpPC) {
Pointer FromPtr = S.Stk.pop<Pointer>();
Pointer &ToPtr = S.Stk.peek<Pointer>();
+ // FIXME: Could allow reading from string pointers?
+ if (!FromPtr.isBlockPointer() || !ToPtr.isBlockPointer())
+ return false;
+
const Descriptor *D = FromPtr.getFieldDesc();
if (D->isPrimitiveArray() && FromPtr.isArrayRoot())
FromPtr = FromPtr.atIndex(0);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 0432308ef55f6..42ee33541b41b 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -165,6 +165,9 @@ static QualType getElemType(const Pointer &P) {
->getElementType();
}
+ if (P.isOpaquePointer() || P.isIntegralPointer())
+ return P.getType();
+
const Descriptor *Desc = P.getFieldDesc();
QualType T = Desc->getType();
if (Desc->isPrimitive())
@@ -409,7 +412,7 @@ static bool interp__builtin_strlen(InterpState &S, CodePtr OpPC,
if (!StrPtr.isBlockPointer())
return false;
- if (!CheckDummy(S, OpPC, StrPtr.block(), AK_Read))
+ if (!CheckDummy(S, OpPC, StrPtr, AK_Read))
return false;
if (!StrPtr.getFieldDesc()->isPrimitiveArray())
@@ -1318,13 +1321,13 @@ static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
}
assert(FirstArgT == PT_Ptr);
const Pointer &Ptr = S.Stk.pop<Pointer>();
- if (!Ptr.isBlockPointer()) {
+ if (!Ptr.isBlockPointer() && !Ptr.isOpaquePointer()) {
S.FFDiag(Call->getArg(0), diag::note_constexpr_alignment_compute)
<< Alignment;
return false;
}
- const ValueDecl *PtrDecl = Ptr.getDeclDesc()->asValueDecl();
+ const VarDecl *PtrDecl = Ptr.getRootVarDecl();
// We need a pointer for a declaration here.
if (!PtrDecl) {
if (BuiltinOp == Builtin::BI__builtin_is_aligned)
@@ -1336,10 +1339,19 @@ static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
return false;
}
- // For one-past-end pointers, we can't call getIndex() since it asserts.
- // Use getNumElems() instead which gives the correct index for past-end.
- unsigned PtrOffset =
- Ptr.isElementPastEnd() ? Ptr.getNumElems() : Ptr.getIndex();
+ unsigned PtrOffset;
+ if (Ptr.isBlockPointer()) {
+ // For one-past-end pointers, we can't call getIndex() since it asserts.
+ // Use getNumElems() instead which gives the correct index for past-end.
+ PtrOffset = Ptr.isElementPastEnd() ? Ptr.getNumElems() : Ptr.getIndex();
+ } else {
+ if (std::optional<size_t> PtrOff =
+ Ptr.computeLayoutOffset(S.getASTContext()))
+ PtrOffset = *PtrOff;
+ else
+ return false;
+ }
+
CharUnits BaseAlignment = S.getASTContext().getDeclAlign(PtrDecl);
CharUnits PtrAlign =
BaseAlignment.alignmentAtOffset(CharUnits::fromQuantity(PtrOffset));
@@ -1388,8 +1400,18 @@ static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
? llvm::alignDown(PtrOffset, Alignment64)
: llvm::alignTo(PtrOffset, Alignment64));
- S.Stk.push<Pointer>(Ptr.atIndex(NewOffset.getQuantity()));
- return true;
+ if (Ptr.isBlockPointer()) {
+ S.Stk.push<Pointer>(Ptr.atIndex(NewOffset.getQuantity()));
+ return true;
+ }
+
+ assert(Ptr.isOpaquePointer());
+
+ APSInt APOffset =
+ APSInt(APInt(64, NewOffset.getQuantity(), /*IsSigned=*/true),
+ /*IsUnsigned=*/false);
+ return arrayElemPtrOpaque(S, OpPC, Ptr, std::move(APOffset),
+ /*AllocReplace=*/true);
}
// Otherwise, we cannot constant-evaluate the result.
@@ -1420,9 +1442,9 @@ static bool interp__builtin_assume_aligned(InterpState &S, CodePtr OpPC,
CharUnits Align = CharUnits::fromQuantity(Alignment.getZExtValue());
// If there is a base object, then it must have the correct alignment.
- if (Ptr.isBlockPointer()) {
+ if (Ptr.isBlockPointer() || Ptr.isOpaquePointer()) {
CharUnits BaseAlignment;
- if (const auto *VD = Ptr.getDeclDesc()->asValueDecl())
+ if (const auto *VD = Ptr.getRootVarDecl())
BaseAlignment = ASTCtx.getDeclAlign(VD);
else if (const auto *E = Ptr.getRootExpr())
BaseAlignment = GetAlignOfExpr(ASTCtx, E, UETT_AlignOf);
@@ -1443,7 +1465,7 @@ static bool interp__builtin_assume_aligned(InterpState &S, CodePtr OpPC,
if (ExtraOffset)
AVOffset -= CharUnits::fromQuantity(ExtraOffset->getZExtValue());
if (AVOffset.alignTo(Align) != AVOffset) {
- if (Ptr.isBlockPointer())
+ if (Ptr.isBlockPointer() || Ptr.isOpaquePointer())
S.CCEDiag(Call->getArg(0),
diag::note_constexpr_baa_insufficient_alignment)
<< 1 << AVOffset.getQuantity() << Align.getQuantity();
@@ -2123,10 +2145,6 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
pushInteger(S, 0, Call->getType());
return true;
}
-
- if (!PtrA.isReadablePointerType() || !PtrB.isReadablePointerType())
- return false;
-
bool IsWide =
(ID == Builtin::BIwmemcmp || ID == Builtin::BI__builtin_wmemcmp);
@@ -2144,6 +2162,9 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
return false;
}
+ if (!PtrA.isReadablePointerType() || !PtrB.isReadablePointerType())
+ return false;
+
if (!CheckLoad(S, OpPC, PtrA, AK_Read) || !CheckLoad(S, OpPC, PtrB, AK_Read))
return false;
@@ -2423,14 +2444,14 @@ static bool interp__builtin_is_within_lifetime(InterpState &S, CodePtr OpPC,
return false;
if (!CheckMutable(S, OpPC, Ptr))
return false;
- if (!CheckDummy(S, OpPC, Ptr.block(), AK_Read))
+ if (!CheckDummy(S, OpPC, Ptr, AK_Read))
return false;
}
// Check if we're currently running an initializer.
if (S.initializingBlock(Ptr.block()))
return Error(2);
- if (S.EvaluatingDecl && Ptr.getDeclDesc()->asVarDecl() == S.EvaluatingDecl)
+ if (S.EvaluatingDecl && Ptr.getRootVarDecl() == S.EvaluatingDecl)
return Error(2);
pushInteger(S, Result, Call->getType());
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 4c60670ac5a0c..f183efb5b19d1 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -41,6 +41,11 @@ bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
/// Checks if a pointer is a dummy pointer.
bool CheckDummy(InterpState &S, CodePtr OpPC, const Block *B, AccessKinds AK);
+bool CheckDummy(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ AccessKinds AK);
+
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ APSInt &&Index, bool AllowReplace = true);
/// Checks if a pointer is in range.
template <typename T>
diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h
index b23acf7befc67..39c7c24e1a304 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.h
+++ b/clang/lib/AST/ByteCode/MemberPointer.h
@@ -101,6 +101,8 @@ class MemberPointer final {
std::optional<Pointer> toPointer(const Context &Ctx) const;
bool isBaseCastPossible() const {
+ if (!Base.isBlockPointer())
+ return false;
if (PtrOffset < 0)
return true;
return static_cast<uint64_t>(PtrOffset) <= Base.getByteOffset();
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 4be5495a7ed25..1831dc161f0a5 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -55,6 +55,7 @@ def ArgFixedPoint : ArgType { let Name = "FixedPoint"; let AsRef = true; }
def ArgFunction : ArgType { let Name = "const Function *"; }
def ArgFunctionDecl : ArgType { let Name = "const FunctionDecl *"; }
def ArgRecordDecl : ArgType { let Name = "const RecordDecl *"; }
+def ArgCXXRecordDecl : ArgType { let Name = "const CXXRecordDecl *"; }
def ArgRecordField : ArgType { let Name = "const Record::Field *"; }
def ArgFltSemantics : ArgType { let Name = "const llvm::fltSemantics *"; }
def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; }
@@ -328,6 +329,8 @@ class OffsetOpcode : Opcode {
let Args = [ArgUint32];
}
+def AddrOf : Opcode;
+
// [] -> [Pointer]
def GetPtrLocal : OffsetOpcode {
bit HasCustomEval = 1;
@@ -379,11 +382,11 @@ def GetPtrDerivedPop : Opcode { let Args = [ArgUint32, ArgBool, ArgTypePtr]; }
// [Pointer] -> [Pointer]
def GetPtrVirtBasePop : Opcode {
// RecordDecl of base class.
- let Args = [ArgRecordDecl];
+ let Args = [ArgCXXRecordDecl];
}
def GetPtrVirtBase : Opcode {
// RecordDecl of base class.
- let Args = [ArgRecordDecl];
+ let Args = [ArgCXXRecordDecl];
}
def IsBaseClass : SuccessOpcode;
@@ -397,7 +400,7 @@ def GetPtrThisBase : Opcode {
// [] -> [Pointer]
def GetPtrThisVirtBase : Opcode {
// RecordDecl of base class.
- let Args = [ArgRecordDecl];
+ let Args = [ArgCXXRecordDecl];
}
// [] -> [Pointer]
def This : Opcode;
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 81fa2beaedb24..8016680ef9c0a 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -242,9 +242,34 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
return APValue(APValue::LValueBase(Str.Base),
CharUnits::fromQuantity(Offset * elemSize()), Path,
/*OnePastTheEnd=*/false, /*IsNull=*/false);
- case Storage::Opaque:
- return APValue(APValue::LValueBase(Opaque.Base), CharUnits::Zero(), Path,
- /*IsOnePastEnd=*/Opaque.isOnePastEnd(), /*IsNullPtr=*/false);
+ case Storage::Opaque: {
+ if (!Opaque.Base->getType()->isPointerType()) {
+ for (const PointerPathEntry &Entry : Opaque.path()) {
+ switch (Entry.Kind) {
+ case PointerPathEntry::Field:
+ Path.push_back(APValue::LValuePathEntry({Entry.FD, false}));
+ break;
+ case PointerPathEntry::Base:
+ Path.push_back(APValue::LValuePathEntry(
+ {Entry.RD.getPointer(), Entry.RD.getInt()}));
+ break;
+ case PointerPathEntry::Array:
+ Path.push_back(APValue::LValuePathEntry::ArrayIndex(Entry.Index));
+ break;
+ case PointerPathEntry::NegativeArray:
+ Path.push_back(APValue::LValuePathEntry::ArrayIndex(-Entry.Index));
+ break;
+ }
+ }
+ }
+ size_t LayoutOffset = Opaque.computeLayoutOffset(ASTCtx).value_or(0);
+ auto Offset = CharUnits::fromQuantity(LayoutOffset + getByteOffset());
+ auto Result =
+ APValue(Opaque.Base, Offset, Path,
+ /*IsOnePastEnd=*/Opaque.isOnePastEnd(), /*IsNullPtr=*/false);
+ Result.setConstexprUnknown(Opaque.isConstexprUnknown());
+ return Result;
+ }
}
assert(isBlockPointer());
@@ -446,7 +471,9 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const {
case Storage::String:
return reinterpret_cast<uintptr_t>(Str.getLiteral()) + Offset;
case Storage::Opaque:
- return reinterpret_cast<uintptr_t>(asOpaquePointer().Base) + Offset;
+ if (auto O = Opaque.computeLayoutOffset(ASTCtx))
+ return *O + Offset;
+ return std::nullopt;
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
@@ -527,7 +554,9 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
case Storage::String:
return Offset * Str.getLiteral()->getCharByteWidth();
case Storage::Opaque:
- return Opaque.computeLayoutOffset(ASTCtx);
+ if (auto O = Opaque.computeLayoutOffset(ASTCtx))
+ return *O + Offset;
+ return std::nullopt;
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
@@ -876,17 +905,39 @@ bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
if (A.isZero() && B.isZero())
return true;
- if (A.isIntegralPointer() && B.isIntegralPointer())
+ // We allow comparisons between opaque pointers and block pointers, provided
+ // they have the same declaration as base.
+ if (A.StorageKind != B.StorageKind) {
+ if (A.isOpaquePointer() && B.isBlockPointer()) {
+ if (const VarDecl *BDecl = B.block()->getDescriptor()->asVarDecl())
+ return BDecl == A.Opaque.Base->getMostRecentDecl();
+
+ return false;
+ }
+ if (B.isOpaquePointer() && A.isBlockPointer()) {
+ if (const VarDecl *ADecl = A.block()->getDescriptor()->asVarDecl())
+ return ADecl == B.Opaque.Base->getMostRecentDecl();
+ return false;
+ }
+ return false;
+ }
+
+ switch (A.StorageKind) {
+ case Storage::Int:
return true;
- if (A.isFunctionPointer() && B.isFunctionPointer())
+ case Storage::Block:
+ // See below.
+ break;
+ case Storage::Fn:
return true;
- if (A.isTypeidPointer() && B.isTypeidPointer())
+ case Storage::Typeid:
return A.asTypeidPointer().TypePtr == B.asTypeidPointer().TypePtr;
- if (A.isStringPointer() && B.isStringPointer())
+ case Storage::String:
return A.Str.ID == B.Str.ID && A.Str.getLiteral() == B.Str.getLiteral();
-
- if (A.StorageKind != B.StorageKind)
- return false;
+ case Storage::Opaque:
+ return A.asOpaquePointer().Base->getMostRecentDecl() ==
+ B.asOpaquePointer().Base->getMostRecentDecl();
+ }
return A.asBlockPointer().Pointee == B.asBlockPointer().Pointee;
}
@@ -1292,7 +1343,12 @@ OpaquePointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
- Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+ if (Entry.RD.getInt())
+ Offset +=
+ Layout.getVBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+ else
+ Offset +=
+ Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
} break;
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index fd09b4908f0b5..3bf6a363d6afd 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -494,7 +494,6 @@ struct OpaquePointer {
bool isUnknownSizeArray() const;
bool isRoot() const;
};
-struct OpaqueTag {};
enum class Storage { Int, Block, Fn, Typeid, String, Opaque };
@@ -570,18 +569,33 @@ class Pointer {
/// Equality operators are just for tests.
bool operator==(const Pointer &P) const {
- if (P.StorageKind != StorageKind)
+ if (StorageKind != P.StorageKind)
return false;
- if (isIntegralPointer())
+
+ switch (StorageKind) {
+ case Storage::Int:
return P.Int.Value == Int.Value && P.Int.Ty == Int.Ty &&
P.Offset == Offset;
-
- if (isFunctionPointer())
+ case Storage::Block:
+ return P.view() == view();
+ case Storage::Fn:
return P.Fn.Func == Fn.Func && P.Offset == Offset;
- if (isStringPointer())
+ case Storage::Typeid:
+ llvm_unreachable("typeid in operator==?");
+ case Storage::String:
return Str.Base == P.Str.Base && Offset == P.Offset;
-
- return P.view() == view();
+ case Storage::Opaque:
+ if (!(P.Opaque.Base == Opaque.Base &&
+ P.Opaque.PathLength == Opaque.PathLength))
+ return false;
+ if (P.Offset != Offset)
+ return false;
+ if (Opaque.PathLength == 0)
+ return true;
+ return std::memcmp(P.Opaque.Path, Opaque.Path,
+ sizeof(PointerPathEntry) * Opaque.PathLength) == 0;
+ }
+ llvm_unreachable("Unhandled storage kind");
}
bool operator!=(const Pointer &P) const { return !(P == *this); }
@@ -922,6 +936,9 @@ class Pointer {
return Fn.Func->getDecl()->isWeak();
}
+
+ if (isOpaquePointer())
+ return Opaque.Base->isWeak();
if (!isBlockPointer())
return false;
@@ -940,6 +957,8 @@ class Pointer {
/// Checks if the pointer points to a dummy value.
bool isDummy() const {
+ if (isOpaquePointer())
+ return true;
if (!isBlockPointer())
return false;
return view().isDummy();
@@ -951,6 +970,8 @@ class Pointer {
return true;
if (isStringPointer())
return true;
+ if (!isBlockPointer())
+ return false;
return view().isConst();
}
bool isConstInMutable() const {
diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp
index 36b5cb62fe95f..90e7c1eb6ec64 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -2054,3 +2054,14 @@ namespace BaseInitViaDIE {
constexpr SS ss {};
static_assert(ss.b == 42, "");
}
+
+namespace OPEOpaque {
+ struct S {char c[14];};
+ extern S s;
+ static_assert((&s + 1) - &s == 1, "");
+
+ extern int a[12];
+ static_assert ((&a + 12 - &a) == 12, ""); // both-error {{not an integral constant expression}} \
+ // both-note {{cannot refer to element 12 of non-array object in a constant expression}}
+
+}
diff --git a/clang/test/CodeGen/pr4349.c b/clang/test/CodeGen/pr4349.c
index 3bec499e0b3f5..025a9b3903775 100644
--- a/clang/test/CodeGen/pr4349.c
+++ b/clang/test/CodeGen/pr4349.c
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s
// PR 4349
union reg
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index 595d0325be12f..bd1eb23023d6f 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -721,19 +721,9 @@ int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold];
#if __cplusplus >= 201402L
// expected-error at -2 {{array size is not a constant expression}}
// expected-note at -3 {{cannot refer to element 12 of non-array}}
-#elif __cplusplus == 201103L
-#if defined(NEW_INTERP)
-// expected-error at -6 {{only the first dimension of an allocated array may have dynamic size}}
-// expected-note at -7 {{cannot refer to element 12 of non-array}}
-#endif
#elif __cplusplus < 201103L
-#if defined(NEW_INTERP)
-// expected-error at -11 {{only the first dimension of an allocated array may have dynamic size}}
-// expected-note at -12 {{cannot refer to element 12 of non-array}}
-#else
-// expected-error at -14 {{cannot allocate object of variably modified type}}
-// expected-warning at -15 {{variable length arrays in C++ are a Clang extension}}
-#endif
+// expected-error at -5 {{cannot allocate object of variably modified type}}
+// expected-warning at -6 {{variable length arrays in C++ are a Clang extension}}
#endif
#if __cplusplus >= 201103L
diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp
index 9c25e26f43c36..cede91cd41997 100644
--- a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp
+++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s -fexperimental-new-constant-interpreter
template<typename T, T val> struct A {}; // expected-note 3{{template parameter is declared here}}
diff --git a/clang/unittests/AST/ByteCode/toAPValue.cpp b/clang/unittests/AST/ByteCode/toAPValue.cpp
index 702a07a638915..a8e1d5e217597 100644
--- a/clang/unittests/AST/ByteCode/toAPValue.cpp
+++ b/clang/unittests/AST/ByteCode/toAPValue.cpp
@@ -102,8 +102,6 @@ TEST(ToAPValue, Pointers) {
ASSERT_EQ(A.getLValuePath()[0].getAsArrayIndex(), 2u);
ASSERT_EQ(A.getLValuePath()[1].getAsArrayIndex(), 4u);
ASSERT_EQ(A.getLValueOffset().getQuantity(), 56u);
- ASSERT_TRUE(
- GP.atIndex(0).getFieldDesc()->getElemQualType()->isIntegerType());
}
}
>From 631f6a832a310a89c7597a99c7df9ff979f1a3c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 11 Sep 2026 11:02:19 +0200
Subject: [PATCH 2/2] pointer eq
---
clang/lib/AST/ByteCode/Pointer.cpp | 43 ++++++++++++++++++++++++++++++
clang/lib/AST/ByteCode/Pointer.h | 32 +---------------------
2 files changed, 44 insertions(+), 31 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 8016680ef9c0a..9a068071df570 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -204,6 +204,49 @@ Pointer &Pointer::operator=(Pointer &&P) {
return *this;
}
+bool Pointer::operator==(const Pointer &P) const {
+ if (StorageKind != P.StorageKind)
+ return false;
+
+ switch (StorageKind) {
+ case Storage::Int:
+ return P.Int.Value == Int.Value && P.Int.Ty == Int.Ty && P.Offset == Offset;
+ case Storage::Block:
+ return P.view() == view();
+ case Storage::Fn:
+ return P.Fn.Func == Fn.Func && P.Offset == Offset;
+ case Storage::Typeid:
+ llvm_unreachable("typeid in operator==?");
+ case Storage::String:
+ return Str.Base == P.Str.Base && Offset == P.Offset;
+ case Storage::Opaque:
+ if (P.Opaque.Base != Opaque.Base ||
+ P.Opaque.PathLength != Opaque.PathLength || P.Offset != Offset)
+ return false;
+
+ for (unsigned I = 0; I != Opaque.PathLength; ++I) {
+ if (Opaque.Path[I].Kind != P.Opaque.Path[I].Kind)
+ return false;
+ switch (Opaque.Path[I].Kind) {
+ case PointerPathEntry::Base:
+ if (Opaque.Path[I].RD != P.Opaque.Path[I].RD)
+ return false;
+ break;
+ case PointerPathEntry::Array:
+ case PointerPathEntry::NegativeArray:
+ if (Opaque.Path[I].Index != P.Opaque.Path[I].Index)
+ return false;
+ break;
+ case PointerPathEntry::Field:
+ if (Opaque.Path[I].FD != P.Opaque.Path[I].FD)
+ return false;
+ break;
+ }
+ }
+ }
+ return true;
+}
+
APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
llvm::SmallVector<APValue::LValuePathEntry, 5> Path;
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index 3bf6a363d6afd..5b43df9db49c4 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -567,37 +567,7 @@ class Pointer {
Pointer &operator=(const Pointer &P);
Pointer &operator=(Pointer &&P);
- /// Equality operators are just for tests.
- bool operator==(const Pointer &P) const {
- if (StorageKind != P.StorageKind)
- return false;
-
- switch (StorageKind) {
- case Storage::Int:
- return P.Int.Value == Int.Value && P.Int.Ty == Int.Ty &&
- P.Offset == Offset;
- case Storage::Block:
- return P.view() == view();
- case Storage::Fn:
- return P.Fn.Func == Fn.Func && P.Offset == Offset;
- case Storage::Typeid:
- llvm_unreachable("typeid in operator==?");
- case Storage::String:
- return Str.Base == P.Str.Base && Offset == P.Offset;
- case Storage::Opaque:
- if (!(P.Opaque.Base == Opaque.Base &&
- P.Opaque.PathLength == Opaque.PathLength))
- return false;
- if (P.Offset != Offset)
- return false;
- if (Opaque.PathLength == 0)
- return true;
- return std::memcmp(P.Opaque.Path, Opaque.Path,
- sizeof(PointerPathEntry) * Opaque.PathLength) == 0;
- }
- llvm_unreachable("Unhandled storage kind");
- }
-
+ bool operator==(const Pointer &P) const;
bool operator!=(const Pointer &P) const { return !(P == *this); }
/// Converts the pointer to an APValue.
More information about the cfe-commits
mailing list