[clang] [clang][bytecode] Support virtual bases in C++26 (PR #204289)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 17 19:18:05 PDT 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
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/204289 at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/204289
>From 05eaca5444a03984965ebe7c7b08961b2d2fdadb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 07:47:18 +0200
Subject: [PATCH 1/7] [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 e47843e1de44e1b58f32484d6e412c351480c26c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 07:58:15 +0200
Subject: [PATCH 2/7] 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 96f29978c95277b529b57e798d56ef4e025b5d05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 13:26:46 +0200
Subject: [PATCH 3/7] 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 at -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 179b3e8000184ed08859e98b8af3a7babb368198 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 13:59:59 +0200
Subject: [PATCH 4/7] 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 4eae4a45b07307ce3bb82bc26abeb68bd36d28e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 14:17:27 +0200
Subject: [PATCH 5/7] 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 d848955a31d749fed8cd0660e3ff9082a880a4e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 17:10:37 +0200
Subject: [PATCH 6/7] 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 | 21 ++++++++++-
clang/lib/AST/ASTImporter.cpp | 6 ++--
clang/lib/AST/ByteCode/Compiler.cpp | 26 ++++++++++----
clang/lib/AST/DeclCXX.cpp | 6 ++++
clang/lib/AST/ExprConstant.cpp | 43 +++++++++++++----------
clang/lib/AST/TextNodeDumper.cpp | 6 ++++
9 files changed, 85 insertions(+), 34 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..ca2fc2d9e7d5e 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -356,11 +356,14 @@ 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 +513,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 +954,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 +1195,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..9d38b02218bc2 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -10694,11 +10694,13 @@ 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..feead2bb72d99 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,11 @@ 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..a196d9babea1f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5467,7 +5467,8 @@ 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 +5485,20 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) {
return true;
}
- unsigned NumVirtualBases =
- std::distance(RD->vbases_begin(), RD->vbases_end());
- assert(NumVirtualBases <= RD->getNumBases());
-
- Result =
- APValue(APValue::UninitStruct(), RD->getNumBases() - NumVirtualBases,
- RD->getNumFields(), NumVirtualBases);
+ Result = APValue(APValue::UninitStruct(),
+ RD->getNumBases() -
+ (RD->getNumVBases() - RD->getNumInheritedVBases()),
+ RD->getNumFields(), Toplevel ? RD->getNumVBases() : 0);
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));
+ Success &= handleDefaultInitValue(B->getType(),
+ Result.getStructBase(Index), false);
++Index;
}
@@ -5505,14 +5506,16 @@ 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);
}
- Index = 0;
- for (const auto &B : RD->vbases()) {
- Success &= handleDefaultInitValue(B.getType(),
- Result.getStructVirtualBase(Index));
- ++Index;
+ if (Toplevel) {
+ Index = 0;
+ for (const auto &B : RD->vbases()) {
+ Success &= handleDefaultInitValue(
+ B.getType(), Result.getStructVirtualBase(Index), false);
+ ++Index;
+ }
}
return Success;
@@ -21663,6 +21666,10 @@ bool VarDecl::evaluateDestruction(
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: {
>From 55c9000821baaf9b881747e7a2f49dd460c357a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 19:30:50 +0200
Subject: [PATCH 7/7] more tests
---
clang/test/AST/ByteCode/virtual-bases.cpp | 52 +++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp
index b3998ca09d0de..41874e026b2ea 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -57,6 +57,7 @@ namespace ZeroInit1 {
constexpr B b{};
static_assert(b.b == 0);
static_assert(b.a == 0);
+ static_assert((void*)(A*)&b == (void*)(A*)&b);
}
namespace Destruction {
@@ -79,3 +80,54 @@ namespace Destruction {
}
static_assert(foo() == 1);
}
+
+
+namespace VirtualBaseWithVirtualFunctions {
+ struct VBase {
+ int x = 5;
+ constexpr virtual int compute() const { return x * 2; }
+ constexpr virtual ~VBase() = default;
+ };
+
+ struct Derived : virtual VBase {
+ int y = 3;
+ constexpr int compute() const override { return x + y; }
+ };
+
+ constexpr bool test_virtual_function() {
+ Derived d;
+ VBase *ptr = &d;
+ return ptr->compute() == 8;
+ }
+
+ static_assert(test_virtual_function());
+}
+
+namespace DynamicCast {
+ struct A {
+ virtual constexpr int f() const {return 10;}
+ };
+ struct B {
+ virtual constexpr int f() const {return 20;}
+ };
+ struct C : virtual A, virtual B {
+ constexpr int f() const override { return 30; }
+ };
+
+ struct D: C {};
+ struct E : D{
+ constexpr ~E() {}
+ };
+
+ constexpr E e{};
+ static_assert(e.f() == 30);
+
+ static_assert((void*)(A*)&e == (void*)(A*)&e);
+ static_assert((void*)(A*)&e != (void*)(B*)&e);
+
+ static_assert(dynamic_cast<const B*>(&e) != nullptr);
+ static_assert(dynamic_cast<const A*>(&e) != nullptr);
+
+ constexpr const B *b= (B*)&e;
+ static_assert(dynamic_cast<const C*>(b) != nullptr);
+}
More information about the cfe-commits
mailing list