Timm =?utf-8?q?Bäder?= <[email protected]>,
Timm =?utf-8?q?Bäder?= <[email protected]>,
Timm =?utf-8?q?Bäder?= <[email protected]>,
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/204289

>From 4640420ada0177403946fbd449c29d9b3875e773 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Wed, 17 Jun 2026 07:47:18 +0200
Subject: [PATCH 1/6] [clang][bytecode] Support virtual bases

---
 clang/include/clang/AST/APValue.h         | 27 +++++++++++++++
 clang/lib/AST/APValue.cpp                 |  7 ++++
 clang/lib/AST/ByteCode/Interp.cpp         |  3 ++
 clang/lib/AST/ByteCode/Pointer.cpp        |  4 +--
 clang/lib/AST/DeclCXX.cpp                 |  5 +--
 clang/lib/AST/ExprConstant.cpp            | 24 +++++++++++++
 clang/lib/Sema/SemaDeclCXX.cpp            |  7 ++--
 clang/lib/Sema/SemaType.cpp               |  2 +-
 clang/test/AST/ByteCode/cxx23.cpp         |  1 -
 clang/test/AST/ByteCode/virtual-bases.cpp | 42 +++++++++++++++++++++++
 10 files changed, 112 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/AST/ByteCode/virtual-bases.cpp

diff --git a/clang/include/clang/AST/APValue.h 
b/clang/include/clang/AST/APValue.h
index acbd922ba5319..a6eec10a966cf 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -297,7 +297,9 @@ class APValue {
     APValue *Elts;
     unsigned NumBases;
     unsigned NumFields;
+    unsigned NumVirtualBases;
     StructData(unsigned NumBases, unsigned NumFields);
+    StructData(unsigned NumBases, unsigned NumFields, unsigned 
NumVirtualBases);
     StructData(const StructData &) = delete;
     StructData &operator=(const StructData &) = delete;
     ~StructData();
@@ -422,6 +424,12 @@ class APValue {
       : Kind(None), AllowConstexprUnknown(false) {
     MakeStruct(NumBases, NumMembers);
   }
+  APValue(UninitStruct, unsigned NumBases, unsigned NumMembers,
+          unsigned NumVirtualBases)
+      : Kind(None), AllowConstexprUnknown(false) {
+    MakeStruct(NumBases, NumMembers, NumVirtualBases);
+  }
+
   /// Creates a new union APValue.
   /// \param ActiveDecl The FieldDecl of the active union member.
   /// \param ActiveValue The value of the active union member.
@@ -659,6 +667,10 @@ class APValue {
     assert(isStruct() && "Invalid accessor");
     return ((const StructData *)(const char *)&Data)->NumFields;
   }
+  unsigned getStructNumVirtualBases() const {
+    assert(isStruct() && "Invalid accessor");
+    return ((const StructData *)(const char *)&Data)->NumVirtualBases;
+  }
   APValue &getStructBase(unsigned i) {
     assert(isStruct() && "Invalid accessor");
     assert(i < getStructNumBases() && "base class index OOB");
@@ -669,12 +681,21 @@ class APValue {
     assert(i < getStructNumFields() && "field index OOB");
     return ((StructData *)(char *)&Data)->Elts[getStructNumBases() + i];
   }
+  APValue &getStructVirtualBase(unsigned i) {
+    assert(isStruct() && "Invalid accessor");
+    assert(i < getStructNumVirtualBases() && "base class index OOB");
+    return ((StructData *)(char *)&Data)
+        ->Elts[getStructNumBases() + getStructNumFields() + i];
+  }
   const APValue &getStructBase(unsigned i) const {
     return const_cast<APValue*>(this)->getStructBase(i);
   }
   const APValue &getStructField(unsigned i) const {
     return const_cast<APValue*>(this)->getStructField(i);
   }
+  const APValue &getStructVirtualBase(unsigned i) const {
+    return const_cast<APValue *>(this)->getStructVirtualBase(i);
+  }
 
   const FieldDecl *getUnionField() const {
     assert(isUnion() && "Invalid accessor");
@@ -793,6 +814,12 @@ class APValue {
     new ((void *)(char *)&Data) StructData(B, M);
     Kind = Struct;
   }
+  void MakeStruct(unsigned B, unsigned M, unsigned V) {
+    assert(isAbsent() && "Bad state change");
+    new ((void *)(char *)&Data) StructData(B, M, V);
+    Kind = Struct;
+  }
+
   void MakeUnion() {
     assert(isAbsent() && "Bad state change");
     new ((void *)(char *)&Data) UnionData();
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index fd51584f564bb..b762242887471 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -285,6 +285,13 @@ APValue::Arr::~Arr() { delete [] Elts; }
 APValue::StructData::StructData(unsigned NumBases, unsigned NumFields) :
   Elts(new APValue[NumBases+NumFields]),
   NumBases(NumBases), NumFields(NumFields) {}
+
+APValue::StructData::StructData(unsigned NumBases, unsigned NumFields,
+                                unsigned NumVirtualBases)
+    : Elts(new APValue[NumBases + NumFields + NumVirtualBases]),
+      NumBases(NumBases), NumFields(NumFields),
+      NumVirtualBases(NumVirtualBases) {}
+
 APValue::StructData::~StructData() {
   delete [] Elts;
 }
diff --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index e5bf9c0c590ac..f61468079f7aa 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1623,6 +1623,9 @@ static bool checkConstructor(InterpState &S, CodePtr 
OpPC, const Function *Func,
   if (!D->ElemRecord)
     return true;
 
+  if (S.getLangOpts().CPlusPlus26)
+    return true;
+
   if (D->ElemRecord->getNumVirtualBases() == 0)
     return true;
 
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp 
b/clang/lib/AST/ByteCode/Pointer.cpp
index 96cd8bb9e11c2..ddcd21c504b2a 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -847,7 +847,7 @@ std::optional<APValue> Pointer::toRValue(const Context &Ctx,
         unsigned NB = Record->getNumBases();
         unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases();
 
-        R = APValue(APValue::UninitStruct(), NB, NF);
+        R = APValue(APValue::UninitStruct(), NB, NF, NV);
 
         for (unsigned I = 0; I != NF; ++I) {
           const Record::Field *FD = Record->getField(I);
@@ -875,7 +875,7 @@ std::optional<APValue> Pointer::toRValue(const Context &Ctx,
           QualType VirtBaseTy =
               Ctx.getASTContext().getCanonicalTagType(VD->Decl);
           PtrView VP = Ptr.atField(VD->Offset);
-          Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I));
+          Ok &= Composite(VirtBaseTy, VP, R.getStructVirtualBase(I));
         }
       }
       return Ok;
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index ce4ba971a4631..82510ad885af8 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -335,8 +335,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const 
*Bases,
       //   In the definition of a constexpr function [...]
       //    -- if the function is a constructor or destructor,
       //       its class shall not have any virtual base classes
-      data().DefaultedDefaultConstructorIsConstexpr = false;
-      data().DefaultedDestructorIsConstexpr = false;
+      data().DefaultedDefaultConstructorIsConstexpr =
+          C.getLangOpts().CPlusPlus26;
+      data().DefaultedDestructorIsConstexpr = C.getLangOpts().CPlusPlus26;
 
       // C++1z [class.copy]p8:
       //   The implicitly-declared copy constructor for a class X will have
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index bc98c0d86bb65..66d3cbbf44070 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2472,6 +2472,8 @@ static bool 
CheckEvaluationResult(CheckEvaluationResultKind CERK,
     if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) {
       unsigned BaseIndex = 0;
       for (const CXXBaseSpecifier &BS : CD->bases()) {
+        if (BS.isVirtual())
+          continue;
         const APValue &BaseValue = Value.getStructBase(BaseIndex);
         if (!BaseValue.hasValue()) {
           SourceLocation TypeBeginLoc = BS.getBaseTypeLoc();
@@ -2495,6 +2497,28 @@ static bool 
CheckEvaluationResult(CheckEvaluationResultKind CERK,
                                  I, CheckedTemps))
         return false;
     }
+
+#if 0
+    if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) {
+      unsigned BaseIndex = 0;
+      for (const CXXBaseSpecifier &BS : CD->vbases()) {
+        if (!BS.isVirtual())
+          continue;
+        const APValue &BaseValue = Value.getStructVirtualBase(BaseIndex);
+        if (!BaseValue.hasValue()) {
+          SourceLocation TypeBeginLoc = BS.getBaseTypeLoc();
+          Info.FFDiag(TypeBeginLoc, diag::note_constexpr_uninitialized_base)
+              << BS.getType() << SourceRange(TypeBeginLoc, BS.getEndLoc());
+          return false;
+        }
+        if (!CheckEvaluationResult(CERK, Info, DiagLoc, BS.getType(), 
BaseValue,
+                                   Kind, /*SubobjectDecl=*/nullptr,
+                                   CheckedTemps))
+          return false;
+        ++BaseIndex;
+      }
+    }
+#endif
   }
 
   if (Value.isLValue() &&
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 418ff01f3d98a..8dc48525802d9 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1944,7 +1944,7 @@ static bool CheckConstexprMissingReturn(Sema &SemaRef, 
const FunctionDecl *Dcl);
 bool Sema::CheckConstexprFunctionDefinition(const FunctionDecl *NewFD,
                                             CheckConstexprKind Kind) {
   const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewFD);
-  if (MD && MD->isInstance()) {
+  if (!getLangOpts().CPlusPlus26 && MD && MD->isInstance()) {
     // C++11 [dcl.constexpr]p4:
     //  The definition of a constexpr constructor shall satisfy the following
     //  constraints:
@@ -7702,12 +7702,12 @@ static bool defaultedSpecialMemberIsConstexpr(
                : true;
 
   //   -- the class shall not have any virtual base classes;
-  if (Ctor && ClassDecl->getNumVBases())
+  if (!S.getLangOpts().CPlusPlus26 && Ctor && ClassDecl->getNumVBases())
     return false;
 
   // C++1y [class.copy]p26:
   //   -- [the class] is a literal type, and
-  if (!Ctor && !ClassDecl->isLiteral() && !S.getLangOpts().CPlusPlus23)
+  if (!S.getLangOpts().CPlusPlus23 && !Ctor && !ClassDecl->isLiteral())
     return false;
 
   //   -- every constructor involved in initializing [...] base class
@@ -8067,7 +8067,6 @@ bool 
Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD,
         HadError = true;
         // FIXME: Explain why the special member can't be constexpr.
   }
-
   if (First) {
     // C++2a [dcl.fct.def.default]p3:
     //   If a function is explicitly defaulted on its first declaration, it is
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d2bb312feadc1..dfe9b6943fd29 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9903,7 +9903,7 @@ bool Sema::RequireLiteralType(SourceLocation Loc, 
QualType T,
   // cannot have any constexpr constructors or a trivial default constructor,
   // so is non-literal. This is better to diagnose than the resulting absence
   // of constexpr constructors.
-  if (RD->getNumVBases()) {
+  if (!getLangOpts().CPlusPlus26 && RD->getNumVBases()) {
     Diag(RD->getLocation(), diag::note_non_literal_virtual_base)
       << getLiteralDiagFromTagKind(RD->getTagKind()) << RD->getNumVBases();
     for (const auto &I : RD->vbases())
diff --git a/clang/test/AST/ByteCode/cxx23.cpp 
b/clang/test/AST/ByteCode/cxx23.cpp
index 0050d34a47269..ba7db75a589a1 100644
--- a/clang/test/AST/ByteCode/cxx23.cpp
+++ b/clang/test/AST/ByteCode/cxx23.cpp
@@ -317,7 +317,6 @@ namespace AnonUnionDtor {
   void bar() { foo(); }
 }
 
-/// FIXME: The two interpreters disagree about there to diagnose the 
non-constexpr destructor call.
 namespace NonLiteralDtorInParam {
   class NonLiteral { // all20-note {{is not an aggregate and has no constexpr 
constructors other than copy or move constructors}}
   public:
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp 
b/clang/test/AST/ByteCode/virtual-bases.cpp
new file mode 100644
index 0000000000000..67bd996da1546
--- /dev/null
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter %s
+
+
+namespace PaperSample {
+  struct Superbase {
+    int a;
+  };
+
+  struct Common: Superbase {
+    unsigned counter{0};
+  };
+
+  struct Left: virtual Common {
+    unsigned value{0};
+
+    constexpr Left() = default;
+
+    constexpr const unsigned & get_counter() const {
+      return Common::counter;
+    }
+  };
+
+
+  struct Right: virtual Common {
+    unsigned value{0};
+
+    constexpr Right() = default;
+    constexpr const unsigned & get_counter() const {
+      return Common::counter;
+    }
+  };
+
+  struct Child: Left, Right {
+    unsigned x{0};
+    unsigned y{0};
+
+    constexpr Child() = default;
+  };
+
+  constexpr auto ch = Child();
+  static_assert(&ch.Left::get_counter() == &ch.Right::get_counter());
+}

>From a5450711fd20bc7fe9c5416a03aca1f86ad556be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Wed, 17 Jun 2026 07:58:15 +0200
Subject: [PATCH 2/6] test more stuff

---
 clang/test/AST/ByteCode/virtual-bases.cpp | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp 
b/clang/test/AST/ByteCode/virtual-bases.cpp
index 67bd996da1546..9ac5f626162da 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -3,11 +3,11 @@
 
 namespace PaperSample {
   struct Superbase {
-    int a;
+    int a = 10;
   };
 
   struct Common: Superbase {
-    unsigned counter{0};
+    unsigned counter = 1337;
   };
 
   struct Left: virtual Common {
@@ -31,12 +31,16 @@ namespace PaperSample {
   };
 
   struct Child: Left, Right {
-    unsigned x{0};
-    unsigned y{0};
+    unsigned x = 12;
+    unsigned y = 13;
 
     constexpr Child() = default;
   };
 
   constexpr auto ch = Child();
   static_assert(&ch.Left::get_counter() == &ch.Right::get_counter());
+  static_assert(ch.counter == 1337);
+
+  static_assert(((Common)ch).counter == 1337);
+  static_assert(ch.a == 10);
 }

>From f109f1a2437a90ef6a8e32b92b70c5b2de45d198 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Wed, 17 Jun 2026 13:26:46 +0200
Subject: [PATCH 3/6] Fix DR tests

---
 clang/test/CXX/drs/cwg16xx.cpp | 16 ++++++++++++++--
 clang/test/CXX/drs/cwg6xx.cpp  | 12 ++++++++++++
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/clang/test/CXX/drs/cwg16xx.cpp b/clang/test/CXX/drs/cwg16xx.cpp
index bcae9e0b6d177..91cc8261eb6be 100644
--- a/clang/test/CXX/drs/cwg16xx.cpp
+++ b/clang/test/CXX/drs/cwg16xx.cpp
@@ -269,8 +269,20 @@ namespace cwg1658 { // cwg1658: 5
     struct D : A { virtual void f() = 0; }; // #cwg1658-D
 
     struct X {
-      friend B::B(const B&) throw();
-      friend C::C(C&);
+#if __cplusplus >= 202400L
+      friend constexpr
+#else
+      friend
+#endif
+      B::B(const B&) throw();
+
+#if __cplusplus >= 202400L
+      friend constexpr
+#else
+      friend
+#endif
+      C::C(C&);
+
       friend D::D(D&);
       // since-cxx23-error@-1 {{non-constexpr declaration of 'D' follows 
constexpr declaration}}
       //   since-cxx23-note@#cwg1658-D {{previous declaration is here}}
diff --git a/clang/test/CXX/drs/cwg6xx.cpp b/clang/test/CXX/drs/cwg6xx.cpp
index 3ba2b372cb715..451554a36d70d 100644
--- a/clang/test/CXX/drs/cwg6xx.cpp
+++ b/clang/test/CXX/drs/cwg6xx.cpp
@@ -534,10 +534,18 @@ namespace cwg644 { // cwg644: partial
   static_assert(__is_literal_type(B), "");
 
   struct C : virtual A {};
+#if __cplusplus >= 202400L
+  static_assert(__is_literal_type(C), "");
+#else
   static_assert(!__is_literal_type(C), "");
+#endif
 
   struct D { C c; };
+#if __cplusplus >= 202400L
+  static_assert(__is_literal_type(D), "");
+#else
   static_assert(!__is_literal_type(D), "");
+#endif
 
   // FIXME: According to CWG644, E<C> is a literal type despite having virtual
   // base classes. This appears to be a wording defect.
@@ -545,8 +553,12 @@ namespace cwg644 { // cwg644: partial
   struct E : T {
     constexpr E() = default;
   };
+#if __cplusplus >= 202400L
+  static_assert(__is_literal_type(E<C>), "");
+#else
   static_assert(!__is_literal_type(E<C>), "");
 #endif
+#endif
 } // namespace cwg644
 
 // cwg645 increases permission to optimize; it's not clear that it's possible 
to

>From c7c958cdcf147dd92d1f041bdc6e3dabc6d283f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Wed, 17 Jun 2026 13:59:59 +0200
Subject: [PATCH 4/6] Zero init

---
 clang/lib/AST/ByteCode/Compiler.cpp       |  9 ++++++++-
 clang/lib/AST/ByteCode/Interp.h           |  8 ++++++++
 clang/lib/AST/ByteCode/Opcodes.td         |  6 ++++++
 clang/test/AST/ByteCode/virtual-bases.cpp | 14 ++++++++++++++
 4 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index 638e6ecafb295..3875eb1e70d00 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -4861,7 +4861,14 @@ bool Compiler<Emitter>::visitZeroRecordInitializer(const 
Record *R,
       return false;
   }
 
-  // FIXME: Virtual bases.
+  for (const Record::Base &B : R->virtual_bases()) {
+    if (!this->emitGetPtrVirtBase(cast<CXXRecordDecl>(B.R->getDecl()), E))
+      return false;
+    if (!this->visitZeroRecordInitializer(B.R, E))
+      return false;
+    if (!this->emitFinishInitPop(E))
+      return false;
+  }
 
   return true;
 }
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0f9e585e19769..4beda945c1a14 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2182,6 +2182,14 @@ inline bool GetPtrVirtBasePop(InterpState &S, CodePtr 
OpPC,
   return VirtBaseHelper(S, OpPC, D, Ptr);
 }
 
+inline bool GetPtrVirtBase(InterpState &S, CodePtr OpPC, const RecordDecl *D) {
+  assert(D);
+  const Pointer &Ptr = S.Stk.peek<Pointer>();
+  if (!CheckNull(S, OpPC, Ptr, CSK_Base))
+    return false;
+  return VirtBaseHelper(S, OpPC, D, Ptr);
+}
+
 inline bool GetPtrThisVirtBase(InterpState &S, CodePtr OpPC,
                                const RecordDecl *D) {
   assert(D);
diff --git a/clang/lib/AST/ByteCode/Opcodes.td 
b/clang/lib/AST/ByteCode/Opcodes.td
index e350d7b2e547d..39a164dd52718 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -383,6 +383,12 @@ def GetPtrVirtBasePop : Opcode {
   // RecordDecl of base class.
   let Args = [ArgRecordDecl];
 }
+def GetPtrVirtBase : Opcode {
+  // RecordDecl of base class.
+  let Args = [ArgRecordDecl];
+}
+
+
 // [] -> [Pointer]
 def GetPtrThisBase : Opcode {
   // Offset of field, which is a base.
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp 
b/clang/test/AST/ByteCode/virtual-bases.cpp
index 9ac5f626162da..aeb8cd4ca4707 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -44,3 +44,17 @@ namespace PaperSample {
   static_assert(((Common)ch).counter == 1337);
   static_assert(ch.a == 10);
 }
+
+namespace ZeroInit1 {
+  struct A {
+    int a;
+  };
+
+  struct B : public virtual A {
+    int b;
+  };
+
+  constexpr B b{};
+  static_assert(b.b == 0);
+  static_assert(b.a == 0);
+}

>From 398423e09dfa8a5b033aaa823e7dfb35d6856433 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Wed, 17 Jun 2026 14:17:27 +0200
Subject: [PATCH 5/6] Destruction

---
 clang/lib/AST/ByteCode/Compiler.cpp       | 10 +++++++++
 clang/lib/AST/ExprConstant.cpp            | 26 ++++++++++++++++++-----
 clang/lib/Sema/SemaDeclCXX.cpp            |  2 --
 clang/test/AST/ByteCode/virtual-bases.cpp | 21 ++++++++++++++++++
 4 files changed, 52 insertions(+), 7 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index 3875eb1e70d00..1c7a865090de8 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7105,6 +7105,16 @@ bool Compiler<Emitter>::compileDestructor(const 
CXXDestructorDecl *Dtor) {
       return false;
   }
 
+  for (const Record::Base &Base : llvm::reverse(R->virtual_bases())) {
+    if (Base.R->hasTrivialDtor())
+      continue;
+    if (!this->emitGetPtrVirtBase(cast<CXXRecordDecl>(Base.R->getDecl()),
+                                  SourceInfo{}))
+      return false;
+    if (!this->emitRecordDestructionPop(Base.R, {}))
+      return false;
+  }
+
   if (!this->emitMarkDestroyed(Dtor))
     return false;
 
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 66d3cbbf44070..bf361320b7d8e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5483,15 +5483,23 @@ static bool handleDefaultInitValue(QualType T, APValue 
&Result) {
       Result = APValue((const FieldDecl *)nullptr);
       return true;
     }
+
+    unsigned NumVirtualBases =
+        std::distance(RD->vbases_begin(), RD->vbases_end());
+    assert(NumVirtualBases <= RD->getNumBases());
+
     Result =
-        APValue(APValue::UninitStruct(), RD->getNumBases(), 
RD->getNumFields());
+        APValue(APValue::UninitStruct(), RD->getNumBases() - NumVirtualBases,
+                RD->getNumFields(), NumVirtualBases);
 
     unsigned Index = 0;
-    for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(),
-                                                  End = RD->bases_end();
-         I != End; ++I, ++Index)
+    for (const auto &B : RD->bases()) {
+      if (B.isVirtual())
+        continue;
       Success &=
-          handleDefaultInitValue(I->getType(), Result.getStructBase(Index));
+          handleDefaultInitValue(B.getType(), Result.getStructBase(Index));
+      ++Index;
+    }
 
     for (const auto *I : RD->fields()) {
       if (I->isUnnamedBitField())
@@ -5499,6 +5507,14 @@ static bool handleDefaultInitValue(QualType T, APValue 
&Result) {
       Success &= handleDefaultInitValue(
           I->getType(), Result.getStructField(I->getFieldIndex()));
     }
+
+    Index = 0;
+    for (const auto &B : RD->vbases()) {
+      Success &= handleDefaultInitValue(B.getType(),
+                                        Result.getStructVirtualBase(Index));
+      ++Index;
+    }
+
     return Success;
   }
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 8dc48525802d9..a699b7a465e4c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2473,8 +2473,6 @@ static bool CheckConstexprFunctionBody(Sema &SemaRef, 
const FunctionDecl *Dcl,
       }
     } else if (!Constructor->isDependentContext() &&
                !Constructor->isDelegatingConstructor()) {
-      assert(RD->getNumVBases() == 0 && "constexpr ctor with virtual bases");
-
       // Skip detailed checking if we have enough initializers, and we would
       // allow at most one initializer per member.
       bool AnyAnonStructUnionMembers = false;
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp 
b/clang/test/AST/ByteCode/virtual-bases.cpp
index aeb8cd4ca4707..b3998ca09d0de 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -58,3 +58,24 @@ namespace ZeroInit1 {
   static_assert(b.b == 0);
   static_assert(b.a == 0);
 }
+
+namespace Destruction {
+  struct A {
+    int &a;
+    constexpr A(int &a) :a(a) {}
+    constexpr ~A() { ++a; }
+  };
+
+  struct B : public virtual A {
+    constexpr B(int &a) : A(a) {}
+  };
+
+  constexpr int foo() {
+    int m = 0;
+    {
+      B b(m);
+    }
+    return m;
+  }
+  static_assert(foo() == 1);
+}

>From 0e86e13f46ed5eba3fe8feb712d16273296bc2e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Wed, 17 Jun 2026 17:10:37 +0200
Subject: [PATCH 6/6] Add CXXRecordDecl::InheritedVBases

---
 clang/include/clang/AST/APValue.h         |  7 +----
 clang/include/clang/AST/DeclCXX.h         |  2 ++
 clang/include/clang/AST/PropertiesBase.td |  2 +-
 clang/lib/AST/APValue.cpp                 | 20 +++++++++++-
 clang/lib/AST/ASTImporter.cpp             |  5 +--
 clang/lib/AST/ByteCode/Compiler.cpp       | 26 ++++++++++++----
 clang/lib/AST/DeclCXX.cpp                 |  7 +++++
 clang/lib/AST/ExprConstant.cpp            | 37 ++++++++++++++---------
 clang/lib/AST/TextNodeDumper.cpp          |  6 ++++
 9 files changed, 81 insertions(+), 31 deletions(-)

diff --git a/clang/include/clang/AST/APValue.h 
b/clang/include/clang/AST/APValue.h
index a6eec10a966cf..21f909fc6004d 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -422,7 +422,7 @@ class APValue {
   /// \param NumMembers Number of members.
   APValue(UninitStruct, unsigned NumBases, unsigned NumMembers)
       : Kind(None), AllowConstexprUnknown(false) {
-    MakeStruct(NumBases, NumMembers);
+    MakeStruct(NumBases, NumMembers, 0);
   }
   APValue(UninitStruct, unsigned NumBases, unsigned NumMembers,
           unsigned NumVirtualBases)
@@ -809,11 +809,6 @@ class APValue {
   }
   void MakeLValue();
   void MakeArray(unsigned InitElts, unsigned Size);
-  void MakeStruct(unsigned B, unsigned M) {
-    assert(isAbsent() && "Bad state change");
-    new ((void *)(char *)&Data) StructData(B, M);
-    Kind = Struct;
-  }
   void MakeStruct(unsigned B, unsigned M, unsigned V) {
     assert(isAbsent() && "Bad state change");
     new ((void *)(char *)&Data) StructData(B, M, V);
diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index 28d171253dc03..e8db46f0e5810 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -318,6 +318,7 @@ class CXXRecordDecl : public RecordDecl {
 
     /// The number of virtual base class specifiers in VBases.
     unsigned NumVBases = 0;
+    unsigned NumInheritedVBases = 0;
 
     /// Base classes of this class.
     ///
@@ -621,6 +622,7 @@ class CXXRecordDecl : public RecordDecl {
 
   /// Retrieves the number of virtual base classes of this class.
   unsigned getNumVBases() const { return data().NumVBases; }
+  unsigned getNumInheritedVBases() const { return data().NumInheritedVBases; }
 
   base_class_range vbases() {
     return base_class_range(vbases_begin(), vbases_end());
diff --git a/clang/include/clang/AST/PropertiesBase.td 
b/clang/include/clang/AST/PropertiesBase.td
index fd3cce10be303..4e9869a21eb1f 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -425,7 +425,7 @@ let Class = PropertyTypeCase<APValue, "Struct"> in {
   }
   def : Creator<[{
     APValue result;
-    result.MakeStruct(bases.size(), fields.size());
+    result.MakeStruct(bases.size(), fields.size(), 0);
     for (unsigned i = 0; i < bases.size(); ++i)
       result.getStructBase(i) = bases[i];
     for (unsigned i = 0; i < fields.size(); ++i)
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index b762242887471..3399c12a88461 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -356,11 +356,13 @@ APValue::APValue(const APValue &RHS)
       getArrayFiller() = RHS.getArrayFiller();
     break;
   case Struct:
-    MakeStruct(RHS.getStructNumBases(), RHS.getStructNumFields());
+    MakeStruct(RHS.getStructNumBases(), RHS.getStructNumFields(), 
RHS.getStructNumVirtualBases());
     for (unsigned I = 0, N = RHS.getStructNumBases(); I != N; ++I)
       getStructBase(I) = RHS.getStructBase(I);
     for (unsigned I = 0, N = RHS.getStructNumFields(); I != N; ++I)
       getStructField(I) = RHS.getStructField(I);
+    for (unsigned I = 0, N = RHS.getStructNumVirtualBases(); I != N; ++I)
+      getStructVirtualBase(I) = RHS.getStructVirtualBase(I);
     break;
   case Union:
     MakeUnion();
@@ -510,6 +512,8 @@ void APValue::Profile(llvm::FoldingSetNodeID &ID) const {
       getStructBase(I).Profile(ID);
     for (unsigned I = 0, N = getStructNumFields(); I != N; ++I)
       getStructField(I).Profile(ID);
+    for (unsigned I = 0, N = getStructNumVirtualBases(); I != N; ++I)
+      getStructVirtualBase(I).Profile(ID);
     return;
 
   case Union:
@@ -949,6 +953,17 @@ void APValue::printPretty(raw_ostream &Out, const 
PrintingPolicy &Policy,
         printPretty(Out, Policy, FI->getType(), Ctx);
       First = false;
     }
+    if (unsigned N = getStructNumVirtualBases()) {
+      const CXXRecordDecl *CD = cast<CXXRecordDecl>(RD);
+      CXXRecordDecl::base_class_const_iterator BI = CD->vbases_begin();
+      for (unsigned I = 0; I != N; ++I, ++BI) {
+        assert(BI != CD->vbases_end());
+        if (!First)
+          Out << ", ";
+        getStructVirtualBase(I).printPretty(Out, Policy, BI->getType(), Ctx);
+        First = false;
+      }
+    }
     Out << '}';
     return;
   }
@@ -1179,6 +1194,9 @@ LinkageInfo LinkageComputer::getLVForValue(const APValue 
&V,
     for (unsigned I = 0, N = V.getStructNumFields(); I != N; ++I)
       if (Merge(V.getStructField(I)))
         break;
+    for (unsigned I = 0, N = V.getStructNumVirtualBases(); I != N; ++I)
+      if (Merge(V.getStructVirtualBase(I)))
+        break;
     break;
   }
 
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 567d2d07298a3..ffa04d51bc368 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -10694,11 +10694,12 @@ ASTNodeImporter::ImportAPValue(const APValue 
&FromValue) {
     break;
   case APValue::Struct:
     Result.MakeStruct(FromValue.getStructNumBases(),
-                      FromValue.getStructNumFields());
+                      FromValue.getStructNumFields(),
+                      FromValue.getStructNumVirtualBases());
     ImportLoop(
         ((const APValue::StructData *)(const char *)&FromValue.Data)->Elts,
         ((const APValue::StructData *)(const char *)&Result.Data)->Elts,
-        FromValue.getStructNumBases() + FromValue.getStructNumFields());
+        FromValue.getStructNumBases() + FromValue.getStructNumFields() + 
FromValue.getStructNumVirtualBases());
     break;
   case APValue::Union: {
     Result.MakeUnion();
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index 1c7a865090de8..1b7e8ea6948a3 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5524,11 +5524,25 @@ bool Compiler<Emitter>::visitAPValueInitializer(const 
APValue &Val,
   if (Val.isStruct()) {
     const Record *R = this->getRecord(T);
     assert(R);
+
+    // Bases.
+    for (unsigned I = 0, N = Val.getStructNumBases(); I != N; ++I) {
+      const APValue &B = Val.getStructBase(I);
+      const Record::Base *RB = R->getBase(I);
+      QualType BaseType = Ctx.getASTContext().getCanonicalTagType(RB->Decl);
+
+      if (!this->emitGetPtrBase(RB->Offset, E))
+        return false;
+      if (!this->visitAPValueInitializer(B, E, BaseType))
+        return false;
+      if (!this->emitFinishInitPop(E))
+        return false;
+    }
+
     for (unsigned I = 0, N = Val.getStructNumFields(); I != N; ++I) {
       const APValue &F = Val.getStructField(I);
       const Record::Field *RF = R->getField(I);
       QualType FieldType = RF->Decl->getType();
-
       // Fields.
       if (OptPrimType PT = classify(FieldType)) {
         if (!this->visitAPValue(F, *PT, E))
@@ -5545,13 +5559,13 @@ bool Compiler<Emitter>::visitAPValueInitializer(const 
APValue &Val,
       }
     }
 
-    // Bases.
-    for (unsigned I = 0, N = Val.getStructNumBases(); I != N; ++I) {
-      const APValue &B = Val.getStructBase(I);
-      const Record::Base *RB = R->getBase(I);
+    // Virtual Bases.
+    for (unsigned I = 0, N = Val.getStructNumVirtualBases(); I != N; ++I) {
+      const APValue &B = Val.getStructVirtualBase(I);
+      const Record::Base *RB = R->getVirtualBase(I);
       QualType BaseType = Ctx.getASTContext().getCanonicalTagType(RB->Decl);
 
-      if (!this->emitGetPtrBase(RB->Offset, E))
+      if (!this->emitGetPtrVirtBase(cast<CXXRecordDecl>(RB->R->getDecl()), E))
         return false;
       if (!this->visitAPValueInitializer(B, E, BaseType))
         return false;
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 82510ad885af8..2e5a6c6f80606 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -206,6 +206,7 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const 
*Bases,
 
   // The virtual bases of this class.
   SmallVector<const CXXBaseSpecifier *, 8> VBases;
+  unsigned InheritedVBases = 0;
 
   data().Bases = new(C) CXXBaseSpecifier [NumBases];
   data().NumBases = NumBases;
@@ -288,6 +289,7 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const 
*Bases,
       // Add this base if it's not already in the list.
       if (SeenVBaseTypes.insert(C.getCanonicalType(VBase.getType())).second) {
         VBases.push_back(&VBase);
+        ++InheritedVBases;
 
         // C++11 [class.copy]p8:
         //   The implicitly-declared copy constructor for a class X will have
@@ -481,7 +483,12 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const 
*Bases,
 
   // Create base specifier for any direct or indirect virtual bases.
   data().VBases = new (C) CXXBaseSpecifier[VBases.size()];
+
+  assert(VBases.size() >= InheritedVBases);
+
+
   data().NumVBases = VBases.size();
+  data().NumInheritedVBases = InheritedVBases;
   for (int I = 0, E = VBases.size(); I != E; ++I) {
     QualType Type = VBases[I]->getType();
     if (!Type->isDependentType())
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index bf361320b7d8e..c8325fa24a028 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5467,7 +5467,7 @@ static bool HandleBaseToDerivedCast(EvalInfo &Info, const 
CastExpr *E,
 
 /// Get the value to use for a default-initialized object of type T.
 /// Return false if it encounters something invalid.
-static bool handleDefaultInitValue(QualType T, APValue &Result) {
+static bool handleDefaultInitValue(QualType T, APValue &Result, bool Toplevel 
= true) {
   bool Success = true;
 
   // If there is already a value present don't overwrite it.
@@ -5484,20 +5484,21 @@ static bool handleDefaultInitValue(QualType T, APValue 
&Result) {
       return true;
     }
 
-    unsigned NumVirtualBases =
-        std::distance(RD->vbases_begin(), RD->vbases_end());
-    assert(NumVirtualBases <= RD->getNumBases());
-
+    // llvm::errs() << RD->getName() << " has " << RD->getNumBases() << " 
bases and " << RD->getNumVBases() << " vbases. " << RD->getNumInheritedVBases() 
<< " of those vbases are inherited. (Our value: " << (RD->getNumBases() - 
(RD->getNumVBases() - RD->getNumInheritedVBases())) << ")\n";
     Result =
-        APValue(APValue::UninitStruct(), RD->getNumBases() - NumVirtualBases,
-                RD->getNumFields(), NumVirtualBases);
+        APValue(APValue::UninitStruct(), RD->getNumBases() - 
(RD->getNumVBases() - RD->getNumInheritedVBases()),
+                RD->getNumFields(), Toplevel ? RD->getNumVBases() : 0);
+    // llvm::errs() << "    NOW: " << Result.getStructNumBases() << " / " << 
Result.getStructNumVirtualBases() << '\n';
 
     unsigned Index = 0;
-    for (const auto &B : RD->bases()) {
-      if (B.isVirtual())
+    for (CXXRecordDecl::base_class_const_iterator B = RD->bases_begin(),
+                                                  End = RD->bases_end();
+         B != End; ++B) {
+
+      if (B->isVirtual())
         continue;
       Success &=
-          handleDefaultInitValue(B.getType(), Result.getStructBase(Index));
+          handleDefaultInitValue(B->getType(), Result.getStructBase(Index), 
false);
       ++Index;
     }
 
@@ -5505,15 +5506,17 @@ static bool handleDefaultInitValue(QualType T, APValue 
&Result) {
       if (I->isUnnamedBitField())
         continue;
       Success &= handleDefaultInitValue(
-          I->getType(), Result.getStructField(I->getFieldIndex()));
+          I->getType(), Result.getStructField(I->getFieldIndex()), false);
     }
 
+    if (Toplevel) {
     Index = 0;
     for (const auto &B : RD->vbases()) {
       Success &= handleDefaultInitValue(B.getType(),
-                                        Result.getStructVirtualBase(Index));
+                                        Result.getStructVirtualBase(Index), 
false);
       ++Index;
     }
+    }
 
     return Success;
   }
@@ -21658,11 +21661,15 @@ bool VarDecl::evaluateDestruction(
   // Otherwise, treat the value as default-initialized; if the destructor works
   // anyway, then the destruction is constant (and must be essentially empty).
   APValue DestroyedValue;
-  if (getEvaluatedValue() && !getEvaluatedValue()->isAbsent())
-    DestroyedValue = *getEvaluatedValue();
-  else if (!handleDefaultInitValue(getType(), DestroyedValue))
+  // if (getEvaluatedValue() && !getEvaluatedValue()->isAbsent())
+    // DestroyedValue = *getEvaluatedValue();
+  // else 
+    if (!handleDefaultInitValue(getType(), DestroyedValue))
     return false;
 
+  // llvm::errs() << getName() << ": " << DestroyedValue.getStructNumBases() 
<< " / " << DestroyedValue.getStructNumVirtualBases() << '\n';
+  // DestroyedValue.dump();
+
   if (Ctx.getLangOpts().EnableNewConstInterp) {
     EvalInfo Info(Ctx, EStatus,
                   IsConstantDestruction ? EvaluationMode::ConstantExpression
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 2b1c0cac25b6d..8d758609727ad 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -811,6 +811,12 @@ void TextNodeDumper::Visit(const APValue &Value, QualType 
Ty) {
         },
         Value.getStructNumFields(), "field", "fields");
 
+    dumpAPValueChildren(
+        Value, Ty,
+        [](const APValue &Value, unsigned Index) -> const APValue & {
+          return Value.getStructVirtualBase(Index);
+        },
+        Value.getStructNumVirtualBases(), "vbase", "vbases");
     return;
   }
   case APValue::Matrix: {

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

Reply via email to