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]>, 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 05eaca5444a03984965ebe7c7b08961b2d2fdadb 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/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?= <[email protected]> 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?= <[email protected]> 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@-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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
