Timm =?utf-8?q?Bäder?= <[email protected]>,
Timm =?utf-8?q?Bäder?= <[email protected]>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/[email protected]>


https://github.com/tbaederr updated 
https://github.com/llvm/llvm-project/pull/179050

>From 3dd584bb40cc33da6e87037d0187c20016669ced Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
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       |   5 +
 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, 370 insertions(+), 66 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index a0138c402e143..73164eeead0ca 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:
@@ -7597,7 +7624,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 8eaff4bb07f7d..06897688b0e1d 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2338,7 +2338,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()) {
@@ -2357,16 +2356,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);
+    }
   }
 }
 
@@ -2377,9 +2390,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;
   }
@@ -2493,6 +2506,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 ca5b1fd6bf072..e9e2f4fe55dec 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()) {
@@ -1869,12 +1883,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 98dc5cfd3b3c4..9676de68ff09d 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -119,6 +119,11 @@ class InterpState final : public State, public 
SourceMapper {
     return Floating(Mem, llvm::APFloatBase::SemanticsToEnum(Sem));
   }
 
+  const CXXRecordDecl **allocMemberPointerPath(unsigned Length) {
+    return reinterpret_cast<const CXXRecordDecl **>(
+        this->allocate(Length * sizeof(CXXRecordDecl *)));
+  }
+
 private:
   friend class EvaluationResult;
   friend class InterpStateCCOverride;
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 e701c954e00c2..2281b3f8dd438 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 5d622187e97f2..73019fc65d362 100644
--- a/clang/test/AST/ByteCode/memberpointers.cpp
+++ b/clang/test/AST/ByteCode/memberpointers.cpp
@@ -267,3 +267,30 @@ namespace CastMemberPtrPtrFailed{
   static_assert(S().g(), ""); // both-error {{constant expression}} \
                               // both-note {{in call to 'S().g()'}}
 }
+
+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 bc0fb097f615ad1fbf90f60979498b81cd673612 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
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 aeba7fe49419be6d919478137730819201b6b511 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
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,

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to