[clang] [clang][bytecode] Add a path to MemberPointers (PR #179050)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Tue Feb 10 03:33:12 PST 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/179050 at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/179050
>From e809e6e6b11646d8383332358e50123eb197fb9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sat, 31 Jan 2026 07:31:56 +0100
Subject: [PATCH 1/3] Memberpointers
---
clang/lib/AST/ByteCode/Compiler.cpp | 60 ++++++++----
clang/lib/AST/ByteCode/Interp.cpp | 104 +++++++++++++++++++--
clang/lib/AST/ByteCode/Interp.h | 20 ++--
clang/lib/AST/ByteCode/InterpState.h | 4 +
clang/lib/AST/ByteCode/MemberPointer.cpp | 15 +--
clang/lib/AST/ByteCode/MemberPointer.h | 97 +++++++++++++++----
clang/lib/AST/ByteCode/Opcodes.td | 8 +-
clang/lib/AST/ByteCode/PrimType.h | 6 +-
clang/test/AST/ByteCode/memberpointers.cpp | 27 ++++++
clang/unittests/AST/ByteCode/toAPValue.cpp | 94 ++++++++++++++++++-
10 files changed, 369 insertions(+), 66 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 7aa7a8d75c8e1..dd10ad7d82653 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -269,34 +269,61 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {
}
case CK_DerivedToBaseMemberPointer: {
- assert(classifyPrim(CE->getType()) == PT_MemberPtr);
- assert(classifyPrim(SubExpr->getType()) == PT_MemberPtr);
- const auto *FromMP = SubExpr->getType()->castAs<MemberPointerType>();
- const auto *ToMP = CE->getType()->castAs<MemberPointerType>();
-
- unsigned DerivedOffset =
- Ctx.collectBaseOffset(ToMP->getMostRecentCXXRecordDecl(),
- FromMP->getMostRecentCXXRecordDecl());
+ assert(classifyPrim(CE) == PT_MemberPtr);
+ assert(classifyPrim(SubExpr) == PT_MemberPtr);
if (!this->delegate(SubExpr))
return false;
- return this->emitGetMemberPtrBasePop(DerivedOffset, CE);
+ const CXXRecordDecl *CurDecl = SubExpr->getType()
+ ->castAs<MemberPointerType>()
+ ->getMostRecentCXXRecordDecl();
+ for (const CXXBaseSpecifier *B : CE->path()) {
+ const CXXRecordDecl *ToDecl = B->getType()->getAsCXXRecordDecl();
+ unsigned DerivedOffset = Ctx.collectBaseOffset(ToDecl, CurDecl);
+
+ if (!this->emitCastMemberPtrBasePop(DerivedOffset, ToDecl, CE))
+ return false;
+ CurDecl = ToDecl;
+ }
+
+ return true;
}
case CK_BaseToDerivedMemberPointer: {
assert(classifyPrim(CE) == PT_MemberPtr);
assert(classifyPrim(SubExpr) == PT_MemberPtr);
- const auto *FromMP = SubExpr->getType()->castAs<MemberPointerType>();
- const auto *ToMP = CE->getType()->castAs<MemberPointerType>();
-
- unsigned DerivedOffset =
- Ctx.collectBaseOffset(FromMP->getMostRecentCXXRecordDecl(),
- ToMP->getMostRecentCXXRecordDecl());
if (!this->delegate(SubExpr))
return false;
- return this->emitGetMemberPtrBasePop(-DerivedOffset, CE);
+
+ const CXXRecordDecl *CurDecl = SubExpr->getType()
+ ->castAs<MemberPointerType>()
+ ->getMostRecentCXXRecordDecl();
+ // Base-to-derived member pointer casts store the path in derived-to-base
+ // order, so iterate backwards. The CXXBaseSpecifier also provides us with
+ // the wrong end of the derived->base arc, so stagger the path by one class.
+ typedef std::reverse_iterator<CastExpr::path_const_iterator> ReverseIter;
+ for (ReverseIter PathI(CE->path_end() - 1), PathE(CE->path_begin());
+ PathI != PathE; ++PathI) {
+ const CXXRecordDecl *ToDecl = (*PathI)->getType()->getAsCXXRecordDecl();
+ unsigned DerivedOffset = Ctx.collectBaseOffset(CurDecl, ToDecl);
+
+ if (!this->emitCastMemberPtrDerivedPop(-DerivedOffset, ToDecl, CE))
+ return false;
+ CurDecl = ToDecl;
+ }
+
+ const CXXRecordDecl *ToDecl = CE->getType()
+ ->castAs<MemberPointerType>()
+ ->getMostRecentCXXRecordDecl();
+ assert(ToDecl != CurDecl);
+ unsigned DerivedOffset = Ctx.collectBaseOffset(CurDecl, ToDecl);
+
+ if (!this->emitCastMemberPtrDerivedPop(-DerivedOffset, ToDecl, CE))
+ return false;
+
+ return true;
}
case CK_UncheckedDerivedToBase:
@@ -7624,7 +7651,6 @@ bool Compiler<Emitter>::emitDestructionPop(const Descriptor *Desc,
template <class Emitter>
bool Compiler<Emitter>::emitDummyPtr(const DeclTy &D, const Expr *E) {
assert(!DiscardResult && "Should've been checked before");
-
unsigned DummyID = P.getOrCreateDummy(D);
if (!this->emitGetPtrGlobal(DummyID, E))
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index d6939c023f43b..bb259822c3dfe 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2339,7 +2339,6 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
PrimType T) {
-
if (T == PT_IntAPS) {
auto &Val = Ptr.deref<IntegralAP<true>>();
if (!Val.singleWord()) {
@@ -2358,16 +2357,30 @@ static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr,
uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
Val.take(NewMemory);
}
+ } else if (T == PT_MemberPtr) {
+ auto &Val = Ptr.deref<MemberPointer>();
+ unsigned PathLength = Val.getPathLength();
+ auto *NewPath = new (S.P) const CXXRecordDecl *[PathLength];
+ std::copy_n(Val.path(), PathLength, NewPath);
+ Val.takePath(NewPath);
}
}
template <typename T>
static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr) {
assert(needsAlloc<T>());
- auto &Val = Ptr.deref<T>();
- if (!Val.singleWord()) {
- uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
- Val.take(NewMemory);
+ if constexpr (std::is_same_v<T, MemberPointer>) {
+ auto &Val = Ptr.deref<MemberPointer>();
+ unsigned PathLength = Val.getPathLength();
+ auto *NewPath = new (S.P) const CXXRecordDecl *[PathLength];
+ std::copy_n(Val.path(), PathLength, NewPath);
+ Val.takePath(NewPath);
+ } else {
+ auto &Val = Ptr.deref<T>();
+ if (!Val.singleWord()) {
+ uint64_t *NewMemory = new (S.P) uint64_t[Val.numWords()];
+ Val.take(NewMemory);
+ }
}
}
@@ -2378,9 +2391,9 @@ static void finishGlobalRecurse(InterpState &S, const Pointer &Ptr) {
TYPE_SWITCH_ALLOC(Fi.Desc->getPrimType(), {
copyPrimitiveMemory<T>(S, Ptr.atField(Fi.Offset));
});
- copyPrimitiveMemory(S, Ptr.atField(Fi.Offset), Fi.Desc->getPrimType());
- } else
+ } else {
finishGlobalRecurse(S, Ptr.atField(Fi.Offset));
+ }
}
return;
}
@@ -2494,6 +2507,83 @@ bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) {
return true;
}
+// Perform a cast towards the class of the Decl (either up or down the
+// hierarchy).
+static bool castBackMemberPointer(InterpState &S,
+ const MemberPointer &MemberPtr,
+ int32_t BaseOffset,
+ const RecordDecl *BaseDecl) {
+ const CXXRecordDecl *Expected;
+ if (MemberPtr.getPathLength() >= 2)
+ Expected = MemberPtr.getPathEntry(MemberPtr.getPathLength() - 2);
+ else
+ Expected = MemberPtr.getRecordDecl();
+
+ assert(Expected);
+ if (Expected->getCanonicalDecl() != BaseDecl->getCanonicalDecl()) {
+ // C++11 [expr.static.cast]p12: In a conversion from (D::*) to (B::*),
+ // if B does not contain the original member and is not a base or
+ // derived class of the class containing the original member, the result
+ // of the cast is undefined.
+ // C++11 [conv.mem]p2 does not cover this case for a cast from (B::*) to
+ // (D::*). We consider that to be a language defect.
+ return false;
+ }
+
+ unsigned OldPathLength = MemberPtr.getPathLength();
+ unsigned NewPathLength = OldPathLength - 1;
+ bool IsDerivedMember = NewPathLength != 0;
+ auto NewPath = S.allocMemberPointerPath(NewPathLength);
+ std::copy_n(MemberPtr.path(), NewPathLength, NewPath);
+
+ S.Stk.push<MemberPointer>(MemberPtr.atInstanceBase(BaseOffset, NewPathLength,
+ NewPath, IsDerivedMember));
+ return true;
+}
+
+static bool appendToMemberPointer(InterpState &S,
+ const MemberPointer &MemberPtr,
+ int32_t BaseOffset,
+ const RecordDecl *BaseDecl,
+ bool IsDerivedMember) {
+ unsigned OldPathLength = MemberPtr.getPathLength();
+ unsigned NewPathLength = OldPathLength + 1;
+
+ auto NewPath = S.allocMemberPointerPath(NewPathLength);
+ std::copy_n(MemberPtr.path(), OldPathLength, NewPath);
+ NewPath[OldPathLength] = cast<CXXRecordDecl>(BaseDecl);
+
+ S.Stk.push<MemberPointer>(MemberPtr.atInstanceBase(BaseOffset, NewPathLength,
+ NewPath, IsDerivedMember));
+ return true;
+}
+
+/// DerivedToBaseMemberPointer
+bool CastMemberPtrBasePop(InterpState &S, CodePtr OpPC, int32_t Off,
+ const RecordDecl *BaseDecl) {
+ const auto &Ptr = S.Stk.pop<MemberPointer>();
+
+ if (!Ptr.isDerivedMember() && Ptr.hasPath())
+ return castBackMemberPointer(S, Ptr, Off, BaseDecl);
+
+ bool IsDerivedMember = Ptr.isDerivedMember() || !Ptr.hasPath();
+ return appendToMemberPointer(S, Ptr, Off, BaseDecl, IsDerivedMember);
+}
+
+/// BaseToDerivedMemberPointer
+bool CastMemberPtrDerivedPop(InterpState &S, CodePtr OpPC, int32_t Off,
+ const RecordDecl *BaseDecl) {
+ const auto &Ptr = S.Stk.pop<MemberPointer>();
+
+ if (!Ptr.isDerivedMember()) {
+ // Simply append.
+ return appendToMemberPointer(S, Ptr, Off, BaseDecl,
+ /*IsDerivedMember=*/false);
+ }
+
+ return castBackMemberPointer(S, Ptr, Off, BaseDecl);
+}
+
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", off)
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0d0f19e4f61dd..1bfa347a9016a 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -217,6 +217,12 @@ bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR);
bool InvalidDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR,
bool InitializerFailed);
+/// DerivedToBaseMemberPointer
+bool CastMemberPtrBasePop(InterpState &S, CodePtr OpPC, int32_t Off,
+ const RecordDecl *BaseDecl);
+/// BaseToDerivedMemberPointer
+bool CastMemberPtrDerivedPop(InterpState &S, CodePtr OpPC, int32_t Off,
+ const RecordDecl *BaseDecl);
enum class ArithOp { Add, Sub };
//===----------------------------------------------------------------------===//
@@ -1544,6 +1550,14 @@ bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
Val.take(NewMemory);
}
+ } else if constexpr (std::is_same_v<T, MemberPointer>) {
+ auto &Val = P.deref<MemberPointer>();
+ unsigned PathLength = Val.getPathLength();
+ auto *NewPath = new (S.P) const CXXRecordDecl *[PathLength];
+ for (unsigned I = 0; I != PathLength; ++I) {
+ NewPath[I] = Val.getPathEntry(I);
+ }
+ Val.takePath(NewPath);
} else if constexpr (needsAlloc<T>()) {
auto &Val = P.deref<T>();
if (!Val.singleWord()) {
@@ -1867,12 +1881,6 @@ inline bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off,
return true;
}
-inline bool GetMemberPtrBasePop(InterpState &S, CodePtr OpPC, int32_t Off) {
- const auto &Ptr = S.Stk.pop<MemberPointer>();
- S.Stk.push<MemberPointer>(Ptr.atInstanceBase(Off));
- return true;
-}
-
inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) {
if (S.checkingPotentialConstantExpression())
return false;
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index 83ef56e7f8452..4306f8194647d 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -118,6 +118,10 @@ class InterpState final : public State, public SourceMapper {
// std::memset(Mem, 0, NumWords * sizeof(uint64_t)); // Debug
return Floating(Mem, llvm::APFloatBase::SemanticsToEnum(Sem));
}
+ const CXXRecordDecl **allocMemberPointerPath(unsigned Length) {
+ return reinterpret_cast<const CXXRecordDecl **>(
+ this->allocate(Length * sizeof(CXXRecordDecl *)));
+ }
/// Note that a step has been executed. If there are no more steps remaining,
/// diagnoses and returns \c false.
diff --git a/clang/lib/AST/ByteCode/MemberPointer.cpp b/clang/lib/AST/ByteCode/MemberPointer.cpp
index 8b1b0187818e9..ddd654238af17 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.cpp
+++ b/clang/lib/AST/ByteCode/MemberPointer.cpp
@@ -16,9 +16,9 @@ namespace clang {
namespace interp {
std::optional<Pointer> MemberPointer::toPointer(const Context &Ctx) const {
- if (!Dcl || isa<FunctionDecl>(Dcl))
+ if (!getDecl() || isa<FunctionDecl>(getDecl()))
return Base;
- assert((isa<FieldDecl, IndirectFieldDecl>(Dcl)));
+ assert((isa<FieldDecl, IndirectFieldDecl>(getDecl())));
if (!Base.isBlockPointer())
return std::nullopt;
@@ -42,7 +42,7 @@ std::optional<Pointer> MemberPointer::toPointer(const Context &Ctx) const {
unsigned Offset = 0;
Offset += BlockMDSize;
- if (const auto *FD = dyn_cast<FieldDecl>(Dcl)) {
+ if (const auto *FD = dyn_cast<FieldDecl>(getDecl())) {
if (FD->getParent() == BaseRecord->getDecl())
return CastedBase.atField(BaseRecord->getField(FD)->Offset);
@@ -58,7 +58,7 @@ std::optional<Pointer> MemberPointer::toPointer(const Context &Ctx) const {
Offset += Ctx.collectBaseOffset(FieldParent, BaseDecl);
} else {
- const auto *IFD = cast<IndirectFieldDecl>(Dcl);
+ const auto *IFD = cast<IndirectFieldDecl>(getDecl());
for (const NamedDecl *ND : IFD->chain()) {
const FieldDecl *F = cast<FieldDecl>(ND);
@@ -77,7 +77,8 @@ std::optional<Pointer> MemberPointer::toPointer(const Context &Ctx) const {
}
FunctionPointer MemberPointer::toFunctionPointer(const Context &Ctx) const {
- return FunctionPointer(Ctx.getProgram().getFunction(cast<FunctionDecl>(Dcl)));
+ return FunctionPointer(
+ Ctx.getProgram().getFunction(cast<FunctionDecl>(getDecl())));
}
APValue MemberPointer::toAPValue(const ASTContext &ASTCtx) const {
@@ -88,8 +89,8 @@ APValue MemberPointer::toAPValue(const ASTContext &ASTCtx) const {
if (hasBase())
return Base.toAPValue(ASTCtx);
- return APValue(getDecl(), /*IsDerivedMember=*/false,
- /*Path=*/{});
+ return APValue(getDecl(), /*IsDerivedMember=*/isDerivedMember(),
+ /*Path=*/ArrayRef(Path, PathLength));
}
} // namespace interp
diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h
index 8dd75cad092c0..7ebcd696d1b3e 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.h
+++ b/clang/lib/AST/ByteCode/MemberPointer.h
@@ -10,10 +10,12 @@
#define LLVM_CLANG_AST_INTERP_MEMBER_POINTER_H
#include "Pointer.h"
+#include "llvm/ADT/PointerIntPair.h"
#include <optional>
namespace clang {
class ASTContext;
+class CXXRecordDecl;
namespace interp {
class Context;
@@ -22,21 +24,33 @@ class FunctionPointer;
class MemberPointer final {
private:
Pointer Base;
- const ValueDecl *Dcl = nullptr;
+ /// The member declaration, and a flag indicating
+ /// whether the member is a member of some class derived from the class type
+ /// of the member pointer.
+ llvm::PointerIntPair<const ValueDecl *, 1, bool> DeclAndIsDerivedMember;
+ /// The path of base/derived classes from the member declaration's
+ /// class (exclusive) to the class type of the member pointer (inclusive).
+ /// This a allocated by the InterpState or the Program.
+ const CXXRecordDecl **Path = nullptr;
int32_t PtrOffset = 0;
+ uint8_t PathLength = 0;
- MemberPointer(Pointer Base, const ValueDecl *Dcl, int32_t PtrOffset)
- : Base(Base), Dcl(Dcl), PtrOffset(PtrOffset) {}
+ MemberPointer(Pointer Base, const ValueDecl *Dcl, int32_t PtrOffset,
+ uint8_t PathLength = 0, const CXXRecordDecl **Path = nullptr,
+ bool IsDerived = false)
+ : Base(Base), DeclAndIsDerivedMember(Dcl, IsDerived), Path(Path),
+ PtrOffset(PtrOffset), PathLength(PathLength) {}
public:
MemberPointer() = default;
- MemberPointer(Pointer Base, const ValueDecl *Dcl) : Base(Base), Dcl(Dcl) {}
+ MemberPointer(Pointer Base, const ValueDecl *Dcl)
+ : Base(Base), DeclAndIsDerivedMember(Dcl) {}
MemberPointer(uint32_t Address, const Descriptor *D) {
// We only reach this for Address == 0, when creating a null member pointer.
assert(Address == 0);
}
- MemberPointer(const ValueDecl *D) : Dcl(D) {
+ MemberPointer(const ValueDecl *D) : DeclAndIsDerivedMember(D) {
assert((isa<FieldDecl, IndirectFieldDecl, CXXMethodDecl>(D)));
}
@@ -47,6 +61,25 @@ class MemberPointer final {
return 17;
}
+ bool hasDecl() const { return DeclAndIsDerivedMember.getPointer(); }
+ bool isDerivedMember() const { return DeclAndIsDerivedMember.getInt(); }
+ const ValueDecl *getDecl() const {
+ return DeclAndIsDerivedMember.getPointer();
+ }
+ bool hasPath() const { return PathLength != 0; }
+ unsigned getPathLength() const { return PathLength; }
+ const CXXRecordDecl *getPathEntry(unsigned Index) const {
+ return Path[Index];
+ }
+ const CXXRecordDecl **path() const { return Path; }
+ void takePath(const CXXRecordDecl **NewPath) {
+ assert(Path != NewPath);
+ Path = NewPath;
+ }
+
+ // Pretend we always have a path.
+ bool singleWord() const { return false; }
+
std::optional<Pointer> toPointer(const Context &Ctx) const;
FunctionPointer toFunctionPointer(const Context &Ctx) const;
@@ -63,32 +96,44 @@ class MemberPointer final {
return Base.atFieldSub(PtrOffset);
}
bool isMemberFunctionPointer() const {
- return isa_and_nonnull<CXXMethodDecl>(Dcl);
+ return isa_and_nonnull<CXXMethodDecl>(DeclAndIsDerivedMember.getPointer());
}
const CXXMethodDecl *getMemberFunction() const {
- return dyn_cast_if_present<CXXMethodDecl>(Dcl);
+ return dyn_cast_if_present<CXXMethodDecl>(
+ DeclAndIsDerivedMember.getPointer());
}
const FieldDecl *getField() const {
- return dyn_cast_if_present<FieldDecl>(Dcl);
+ return dyn_cast_if_present<FieldDecl>(DeclAndIsDerivedMember.getPointer());
}
- bool hasDecl() const { return Dcl; }
- const ValueDecl *getDecl() const { return Dcl; }
+ const CXXRecordDecl *getRecordDecl() const {
+ if (const FieldDecl *FD = getField())
+ return cast<CXXRecordDecl>(FD->getParent());
- MemberPointer atInstanceBase(unsigned Offset) const {
+ if (const CXXMethodDecl *MD = getMemberFunction())
+ return MD->getParent();
+ return nullptr;
+ }
+
+ MemberPointer atInstanceBase(unsigned Offset, uint8_t PathLength = 0,
+ const CXXRecordDecl **Path = nullptr,
+ bool NewIsDerived = false) const {
if (Base.isZero())
- return MemberPointer(Base, Dcl, Offset);
- return MemberPointer(this->Base, Dcl, Offset + PtrOffset);
+ return MemberPointer(Base, DeclAndIsDerivedMember.getPointer(), Offset,
+ PathLength, Path, NewIsDerived);
+ return MemberPointer(this->Base, DeclAndIsDerivedMember.getPointer(),
+ Offset + PtrOffset, PathLength, Path, NewIsDerived);
}
MemberPointer takeInstance(Pointer Instance) const {
assert(this->Base.isZero());
- return MemberPointer(Instance, this->Dcl, this->PtrOffset);
+ return MemberPointer(Instance, DeclAndIsDerivedMember.getPointer(),
+ this->PtrOffset);
}
APValue toAPValue(const ASTContext &) const;
- bool isZero() const { return Base.isZero() && !Dcl; }
+ bool isZero() const { return Base.isZero() && !hasDecl(); }
bool hasBase() const { return !Base.isZero(); }
bool isWeak() const {
if (const auto *MF = getMemberFunction())
@@ -97,22 +142,34 @@ class MemberPointer final {
}
void print(llvm::raw_ostream &OS) const {
- OS << "MemberPtr(" << Base << " " << (const void *)Dcl << " + " << PtrOffset
- << ")";
+ OS << "MemberPtr(" << Base << " " << (const void *)getDecl() << " + "
+ << PtrOffset << ". PathLength: " << getPathLength()
+ << ". IsDerived: " << isDerivedMember() << ")";
}
std::string toDiagnosticString(const ASTContext &Ctx) const {
- return toAPValue(Ctx).getAsString(Ctx, Dcl->getType());
+ return toAPValue(Ctx).getAsString(Ctx, getDecl()->getType());
}
ComparisonCategoryResult compare(const MemberPointer &RHS) const {
- if (this->Dcl == RHS.Dcl)
+ if (this->getDecl() == RHS.getDecl()) {
+
+ if (this->PathLength != RHS.PathLength)
+ return ComparisonCategoryResult::Unordered;
+
+ if (PathLength != 0 &&
+ std::memcmp(Path, RHS.Path, PathLength * sizeof(CXXRecordDecl *)) !=
+ 0)
+ return ComparisonCategoryResult::Unordered;
+
return ComparisonCategoryResult::Equal;
+ }
return ComparisonCategoryResult::Unordered;
}
};
-inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MemberPointer FP) {
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ const MemberPointer &FP) {
FP.print(OS);
return OS;
}
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index b684b437dcbf7..da3b850945499 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -332,9 +332,13 @@ def GetPtrThisField : OffsetOpcode;
def GetPtrBase : OffsetOpcode;
// [Pointer] -> [Pointer]
def GetPtrBasePop : OffsetOpcode { let Args = [ArgUint32, ArgBool]; }
-def GetMemberPtrBasePop : Opcode {
+def CastMemberPtrBasePop : Opcode {
// Offset of field, which is a base.
- let Args = [ArgSint32];
+ let Args = [ArgSint32, ArgRecordDecl];
+}
+def CastMemberPtrDerivedPop : Opcode {
+ // Offset of field, which is a base.
+ let Args = [ArgSint32, ArgRecordDecl];
}
def FinishInitPop : Opcode;
diff --git a/clang/lib/AST/ByteCode/PrimType.h b/clang/lib/AST/ByteCode/PrimType.h
index f0454b484ff98..2433eb33c47b1 100644
--- a/clang/lib/AST/ByteCode/PrimType.h
+++ b/clang/lib/AST/ByteCode/PrimType.h
@@ -128,10 +128,11 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
constexpr bool isIntegralType(PrimType T) { return T <= PT_FixedPoint; }
template <typename T> constexpr bool needsAlloc() {
return std::is_same_v<T, IntegralAP<false>> ||
- std::is_same_v<T, IntegralAP<true>> || std::is_same_v<T, Floating>;
+ std::is_same_v<T, IntegralAP<true>> || std::is_same_v<T, Floating> ||
+ std::is_same_v<T, MemberPointer>;
}
constexpr bool needsAlloc(PrimType T) {
- return T == PT_IntAP || T == PT_IntAPS || T == PT_Float;
+ return T == PT_IntAP || T == PT_IntAPS || T == PT_Float || T == PT_MemberPtr;
}
/// Mapping from primitive types to their representation.
@@ -272,6 +273,7 @@ static inline bool aligned(const void *P) {
TYPE_SWITCH_CASE(PT_Float, B) \
TYPE_SWITCH_CASE(PT_IntAP, B) \
TYPE_SWITCH_CASE(PT_IntAPS, B) \
+ TYPE_SWITCH_CASE(PT_MemberPtr, B) \
default:; \
} \
} while (0)
diff --git a/clang/test/AST/ByteCode/memberpointers.cpp b/clang/test/AST/ByteCode/memberpointers.cpp
index 08a95e66183f8..fb489a1961434 100644
--- a/clang/test/AST/ByteCode/memberpointers.cpp
+++ b/clang/test/AST/ByteCode/memberpointers.cpp
@@ -276,3 +276,30 @@ namespace DiscardedAddrOfOperator {
void baz() { &Foo::bar, Foo(); } // both-warning {{left operand of comma operator has no effect}}
}
+
+namespace Equality {
+ struct B { int x; };
+ struct C : B { int z; };
+ static_assert(&C::x == &B::x, "");
+ static_assert(&C::x == &C::x, "");
+
+ constexpr auto A = (int C::*)&B::x;
+ constexpr auto B = (int C::*)&B::x;
+ static_assert(A == B, "");
+
+ struct K {
+ int C::*const M = (int C::*)&B::x;
+ };
+ constexpr K k;
+ static_assert(A== k.M, "");
+
+ constexpr int C::*const MPA[] = {&B::x, &C::x};
+ static_assert(MPA[1] == A, "");
+
+ template<int n> struct T : T<n-1> { const int X = n;};
+ template<> struct T<0> { int n; char k;};
+ template<> struct T<30> : T<29> { int m; };
+
+ constexpr int (T<17>::*deepm) = (int(T<10>::*))&T<30>::m;
+ static_assert(deepm == &T<50>::m, "");
+}
diff --git a/clang/unittests/AST/ByteCode/toAPValue.cpp b/clang/unittests/AST/ByteCode/toAPValue.cpp
index 939d08601bb7d..3571dcc41ad27 100644
--- a/clang/unittests/AST/ByteCode/toAPValue.cpp
+++ b/clang/unittests/AST/ByteCode/toAPValue.cpp
@@ -209,11 +209,25 @@ TEST(ToAPValue, FunctionPointersC) {
}
TEST(ToAPValue, MemberPointers) {
- constexpr char Code[] = "struct S {\n"
- " int m, n;\n"
- "};\n"
- "constexpr int S::*pm = &S::m;\n"
- "constexpr int S::*nn = nullptr;\n";
+ constexpr char Code[] =
+ "struct S {\n"
+ " int m, n;\n"
+ "};\n"
+ "constexpr int S::*pm = &S::m;\n"
+ "constexpr int S::*nn = nullptr;\n"
+
+ "struct B{int x;};\n"
+ "struct C : B {int z; };\n"
+ "constexpr auto c1 = (int C::*)&B::x;\n"
+ "constexpr auto D = (int B::*)c1;\n"
+
+ "template<int n> struct T : T<n-1> { const int X = n;};\n"
+ "template<> struct T<0> { int nn_; char kk;};\n"
+ "template<> struct T<30> : T<29> { int mm; };\n"
+ "constexpr auto t1 = (int(T<10>::*))&T<30>::mm;\n"
+ "constexpr auto t2 = (int(T<11>::*))t1;\n"
+ "constexpr auto t3 = (int(T<20>::*))&T<30>::mm;\n"
+ "constexpr int (T<10>::*t4) = &T<0>::nn_;\n";
auto AST = tooling::buildASTFromCodeWithArgs(
Code, {"-fexperimental-new-constant-interpreter"});
@@ -243,6 +257,8 @@ TEST(ToAPValue, MemberPointers) {
APValue A = FP.toAPValue(ASTCtx);
ASSERT_EQ(A.getMemberPointerDecl(), getDecl("m"));
ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 0u);
+ ASSERT_FALSE(A.isMemberPointerToDerivedMember());
}
{
@@ -252,6 +268,74 @@ TEST(ToAPValue, MemberPointers) {
ASSERT_TRUE(NP.isZero());
APValue A = NP.toAPValue(ASTCtx);
ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 0u);
+ ASSERT_FALSE(A.isMemberPointerToDerivedMember());
+ }
+
+ {
+ const Pointer &GP = getGlobalPtr("c1");
+ ASSERT_TRUE(GP.isLive());
+ const MemberPointer &MP = GP.deref<MemberPointer>();
+ ASSERT_FALSE(MP.isZero());
+ APValue A = MP.toAPValue(ASTCtx);
+ ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 1u);
+ ASSERT_FALSE(A.isMemberPointerToDerivedMember());
+ }
+
+ {
+ const Pointer &GP = getGlobalPtr("D");
+ ASSERT_TRUE(GP.isLive());
+ const MemberPointer &MP = GP.deref<MemberPointer>();
+ ASSERT_FALSE(MP.isZero());
+ APValue A = MP.toAPValue(ASTCtx);
+ ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 0u);
+ ASSERT_FALSE(A.isMemberPointerToDerivedMember());
+ }
+
+ {
+ const Pointer &GP = getGlobalPtr("t1");
+ ASSERT_TRUE(GP.isLive());
+ const MemberPointer &MP = GP.deref<MemberPointer>();
+ ASSERT_FALSE(MP.isZero());
+ APValue A = MP.toAPValue(ASTCtx);
+ ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 20u);
+ ASSERT_TRUE(A.isMemberPointerToDerivedMember());
+ }
+
+ {
+ const Pointer &GP = getGlobalPtr("t2");
+ ASSERT_TRUE(GP.isLive());
+ const MemberPointer &MP = GP.deref<MemberPointer>();
+ ASSERT_FALSE(MP.isZero());
+ APValue A = MP.toAPValue(ASTCtx);
+ ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 19u);
+ ASSERT_TRUE(A.isMemberPointerToDerivedMember());
+ }
+
+ {
+ const Pointer &GP = getGlobalPtr("t3");
+ ASSERT_TRUE(GP.isLive());
+ const MemberPointer &MP = GP.deref<MemberPointer>();
+ ASSERT_FALSE(MP.isZero());
+ APValue A = MP.toAPValue(ASTCtx);
+ ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 10u);
+ ASSERT_TRUE(A.isMemberPointerToDerivedMember());
+ }
+
+ {
+ const Pointer &GP = getGlobalPtr("t4");
+ ASSERT_TRUE(GP.isLive());
+ const MemberPointer &MP = GP.deref<MemberPointer>();
+ ASSERT_FALSE(MP.isZero());
+ APValue A = MP.toAPValue(ASTCtx);
+ ASSERT_EQ(A.getKind(), APValue::MemberPointer);
+ ASSERT_EQ(A.getMemberPointerPath().size(), 10u);
+ ASSERT_FALSE(A.isMemberPointerToDerivedMember());
}
}
>From 9b2a123f01ec3ed27a1b88325f468f931139faaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Mon, 9 Feb 2026 12:41:07 +0100
Subject: [PATCH 2/3] Docs
---
clang/lib/AST/ByteCode/MemberPointer.h | 32 ++++++++++++++++++--------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h
index 7ebcd696d1b3e..e232b09fde20f 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.h
+++ b/clang/lib/AST/ByteCode/MemberPointer.h
@@ -61,17 +61,35 @@ class MemberPointer final {
return 17;
}
+ /// Does this member pointer have a base declaration?
bool hasDecl() const { return DeclAndIsDerivedMember.getPointer(); }
bool isDerivedMember() const { return DeclAndIsDerivedMember.getInt(); }
+ /// Return the base declaration. Might be null.
const ValueDecl *getDecl() const {
return DeclAndIsDerivedMember.getPointer();
}
+ /// Does this member pointer have a path (i.e. path length is > 0)?
bool hasPath() const { return PathLength != 0; }
+ /// Return the length of the cast path.
unsigned getPathLength() const { return PathLength; }
+ /// Return the cast path entry at the given position.
const CXXRecordDecl *getPathEntry(unsigned Index) const {
+ assert(Index < PathLength);
return Path[Index];
}
+ /// Return the cast path. Might return null.
const CXXRecordDecl **path() const { return Path; }
+ bool isZero() const { return Base.isZero() && !hasDecl(); }
+ bool hasBase() const { return !Base.isZero(); }
+ bool isWeak() const {
+ if (const auto *MF = getMemberFunction())
+ return MF->isWeak();
+ return false;
+ }
+
+ /// Sets the path of this member pointer. After this call,
+ /// the memory pointed to by \p NewPath is assumed to be owned
+ /// by this member pointer.
void takePath(const CXXRecordDecl **NewPath) {
assert(Path != NewPath);
Path = NewPath;
@@ -81,7 +99,6 @@ class MemberPointer final {
bool singleWord() const { return false; }
std::optional<Pointer> toPointer(const Context &Ctx) const;
-
FunctionPointer toFunctionPointer(const Context &Ctx) const;
bool isBaseCastPossible() const {
@@ -95,17 +112,20 @@ class MemberPointer final {
return Base.atField(-PtrOffset);
return Base.atFieldSub(PtrOffset);
}
+ /// Is the base declaration a member function?
bool isMemberFunctionPointer() const {
return isa_and_nonnull<CXXMethodDecl>(DeclAndIsDerivedMember.getPointer());
}
+ /// Return the base declaration as a CXXMethodDecl. Might return null.
const CXXMethodDecl *getMemberFunction() const {
return dyn_cast_if_present<CXXMethodDecl>(
DeclAndIsDerivedMember.getPointer());
}
+ /// Return the base declaration as a FieldDecl. Might return null.
const FieldDecl *getField() const {
return dyn_cast_if_present<FieldDecl>(DeclAndIsDerivedMember.getPointer());
}
-
+ /// Returns the record decl this member pointer points into.
const CXXRecordDecl *getRecordDecl() const {
if (const FieldDecl *FD = getField())
return cast<CXXRecordDecl>(FD->getParent());
@@ -133,14 +153,6 @@ class MemberPointer final {
APValue toAPValue(const ASTContext &) const;
- bool isZero() const { return Base.isZero() && !hasDecl(); }
- bool hasBase() const { return !Base.isZero(); }
- bool isWeak() const {
- if (const auto *MF = getMemberFunction())
- return MF->isWeak();
- return false;
- }
-
void print(llvm::raw_ostream &OS) const {
OS << "MemberPtr(" << Base << " " << (const void *)getDecl() << " + "
<< PtrOffset << ". PathLength: " << getPathLength()
>From c315db4419241697d11f5255b89e9d5433a392b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Mon, 9 Feb 2026 12:42:16 +0100
Subject: [PATCH 3/3] Move compare to source file
---
clang/lib/AST/ByteCode/MemberPointer.cpp | 16 ++++++++++++++++
clang/lib/AST/ByteCode/MemberPointer.h | 17 +----------------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/clang/lib/AST/ByteCode/MemberPointer.cpp b/clang/lib/AST/ByteCode/MemberPointer.cpp
index ddd654238af17..c821b0fc689fd 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.cpp
+++ b/clang/lib/AST/ByteCode/MemberPointer.cpp
@@ -93,5 +93,21 @@ APValue MemberPointer::toAPValue(const ASTContext &ASTCtx) const {
/*Path=*/ArrayRef(Path, PathLength));
}
+ComparisonCategoryResult
+MemberPointer::compare(const MemberPointer &RHS) const {
+ if (this->getDecl() == RHS.getDecl()) {
+
+ if (this->PathLength != RHS.PathLength)
+ return ComparisonCategoryResult::Unordered;
+
+ if (PathLength != 0 &&
+ std::memcmp(Path, RHS.Path, PathLength * sizeof(CXXRecordDecl *)) != 0)
+ return ComparisonCategoryResult::Unordered;
+
+ return ComparisonCategoryResult::Equal;
+ }
+ return ComparisonCategoryResult::Unordered;
+}
+
} // namespace interp
} // namespace clang
diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h
index e232b09fde20f..a9b95471038e0 100644
--- a/clang/lib/AST/ByteCode/MemberPointer.h
+++ b/clang/lib/AST/ByteCode/MemberPointer.h
@@ -97,6 +97,7 @@ class MemberPointer final {
// Pretend we always have a path.
bool singleWord() const { return false; }
+ ComparisonCategoryResult compare(const MemberPointer &RHS) const;
std::optional<Pointer> toPointer(const Context &Ctx) const;
FunctionPointer toFunctionPointer(const Context &Ctx) const;
@@ -162,22 +163,6 @@ class MemberPointer final {
std::string toDiagnosticString(const ASTContext &Ctx) const {
return toAPValue(Ctx).getAsString(Ctx, getDecl()->getType());
}
-
- ComparisonCategoryResult compare(const MemberPointer &RHS) const {
- if (this->getDecl() == RHS.getDecl()) {
-
- if (this->PathLength != RHS.PathLength)
- return ComparisonCategoryResult::Unordered;
-
- if (PathLength != 0 &&
- std::memcmp(Path, RHS.Path, PathLength * sizeof(CXXRecordDecl *)) !=
- 0)
- return ComparisonCategoryResult::Unordered;
-
- return ComparisonCategoryResult::Equal;
- }
- return ComparisonCategoryResult::Unordered;
- }
};
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
More information about the cfe-commits
mailing list