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 57ea35b046168b729138ec47afcea5a430cf0edb 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/2] [clang][bytecode] Support virtual bases --- clang/include/clang/AST/APValue.h | 28 +- clang/include/clang/AST/PropertiesBase.td | 11 +- clang/lib/AST/APValue.cpp | 30 +- clang/lib/AST/ASTImporter.cpp | 6 +- clang/lib/AST/ByteCode/Compiler.cpp | 148 ++++++-- clang/lib/AST/ByteCode/Compiler.h | 9 +- clang/lib/AST/ByteCode/Context.cpp | 1 - clang/lib/AST/ByteCode/EvaluationResult.cpp | 45 ++- clang/lib/AST/ByteCode/Interp.cpp | 3 + clang/lib/AST/ByteCode/Interp.h | 13 + clang/lib/AST/ByteCode/Opcodes.td | 8 + clang/lib/AST/ByteCode/Pointer.cpp | 4 +- clang/lib/AST/ByteCode/Record.h | 2 +- clang/lib/AST/DeclCXX.cpp | 6 +- clang/lib/AST/ExprConstant.cpp | 69 +++- clang/lib/AST/TextNodeDumper.cpp | 6 + clang/lib/CodeGen/CGExprConstant.cpp | 92 +++-- clang/lib/Sema/SemaDeclCXX.cpp | 9 +- clang/lib/Sema/SemaType.cpp | 2 +- .../AST/ByteCode/virtual-bases-codegen.cpp | 17 + clang/test/AST/ByteCode/virtual-bases.cpp | 326 ++++++++++++++++++ clang/test/CXX/drs/cwg16xx.cpp | 16 +- clang/test/CXX/drs/cwg6xx.cpp | 12 + 23 files changed, 748 insertions(+), 115 deletions(-) create mode 100644 clang/test/AST/ByteCode/virtual-bases-codegen.cpp 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..c509addfe5d48 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -297,7 +297,8 @@ class APValue { APValue *Elts; unsigned NumBases; unsigned NumFields; - StructData(unsigned NumBases, unsigned NumFields); + unsigned NumVirtualBases; + StructData(unsigned NumBases, unsigned NumFields, unsigned NumVirtualBases); StructData(const StructData &) = delete; StructData &operator=(const StructData &) = delete; ~StructData(); @@ -418,10 +419,13 @@ class APValue { /// \param UninitStruct Marker. Pass an empty UninitStruct. /// \param NumBases Number of bases. /// \param NumMembers Number of members. - APValue(UninitStruct, unsigned NumBases, unsigned NumMembers) + /// \param NumVirtualBases Number of virtual bases. + APValue(UninitStruct, unsigned NumBases, unsigned NumMembers, + unsigned NumVirtualBases = 0) : Kind(None), AllowConstexprUnknown(false) { - MakeStruct(NumBases, NumMembers); + 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 +663,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 +677,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"); @@ -788,11 +805,12 @@ class APValue { } void MakeLValue(); void MakeArray(unsigned InitElts, unsigned Size); - void MakeStruct(unsigned B, unsigned M) { + void MakeStruct(unsigned B, unsigned M, unsigned V) { assert(isAbsent() && "Bad state change"); - new ((void *)(char *)&Data) StructData(B, M); + 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/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td index fd3cce10be303..25ef4c26a9aa1 100644 --- a/clang/include/clang/AST/PropertiesBase.td +++ b/clang/include/clang/AST/PropertiesBase.td @@ -416,6 +416,10 @@ let Class = PropertyTypeCase<APValue, "Struct"> in { unsigned numFields = node.getStructNumFields(); for (unsigned i = 0; i < numFields; ++i) structFields.push_back(node.getStructField(i)); + SmallVector<APValue, 4> structVirtualBases; + unsigned numVirtualBases = node.getStructNumVirtualBases(); + for (unsigned i = 0; i < numVirtualBases; ++i) + structVirtualBases.push_back(node.getStructVirtualBase(i)); }]>; def : Property<"bases", Array<APValue>> { let Read = [{ structBases }]; @@ -423,13 +427,18 @@ let Class = PropertyTypeCase<APValue, "Struct"> in { def : Property<"fields", Array<APValue>> { let Read = [{ structFields }]; } + def : Property<"vbases", Array<APValue>> { + let Read = [{ structVirtualBases }]; + } def : Creator<[{ APValue result; - result.MakeStruct(bases.size(), fields.size()); + result.MakeStruct(bases.size(), fields.size(), vbases.size()); for (unsigned i = 0; i < bases.size(); ++i) result.getStructBase(i) = bases[i]; for (unsigned i = 0; i < fields.size(); ++i) result.getStructField(i) = fields[i]; + for (unsigned i = 0; i < vbases.size(); ++i) + result.getStructVirtualBase(i) = vbases[i]; return result; }]>; } diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp index fd51584f564bb..727e5f8c00a10 100644 --- a/clang/lib/AST/APValue.cpp +++ b/clang/lib/AST/APValue.cpp @@ -282,9 +282,12 @@ APValue::Arr::Arr(unsigned NumElts, unsigned Size) : NumElts(NumElts), ArrSize(Size) {} 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; } @@ -349,11 +352,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(); @@ -503,6 +509,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: @@ -942,6 +950,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; } @@ -1172,6 +1191,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 20b110b38ff78..68c1fed15fee4 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -1934,12 +1934,6 @@ bool Compiler<Emitter>::VisitImplicitValueInitExpr( if (RD->isInvalidDecl()) return false; - if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD); - CXXRD && CXXRD->getNumVBases() > 0) { - // TODO: Diagnose. - return false; - } - const Record *R = getRecord(QT); if (!R) return false; @@ -4789,7 +4783,8 @@ bool Compiler<Emitter>::visitZeroInitializer(PrimType T, QualType QT, template <class Emitter> bool Compiler<Emitter>::visitZeroRecordInitializer(const Record *R, - const Expr *E) { + const Expr *E, + bool Toplevel) { assert(E); assert(R); // Fields @@ -4849,13 +4844,22 @@ bool Compiler<Emitter>::visitZeroRecordInitializer(const Record *R, for (const Record::Base &B : R->bases()) { if (!this->emitGetPtrBase(B.Offset, E)) return false; - if (!this->visitZeroRecordInitializer(B.R, E)) + if (!this->visitZeroRecordInitializer(B.R, E, false)) return false; if (!this->emitFinishInitPop(E)) return false; } - // FIXME: Virtual bases. + if (Toplevel) { + 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, false)) + return false; + if (!this->emitFinishInitPop(E)) + return false; + } + } return true; } @@ -5511,17 +5515,38 @@ bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, template <class Emitter> bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val, - SourceInfo Info, QualType T) { + SourceInfo Info, QualType T, + bool Toplevel) { if (Val.isStruct()) { const Record *R = this->getRecord(T); assert(R); + + assert(R->getNumBases() == Val.getStructNumBases()); + if (Toplevel) { + assert(R->getNumVirtualBases() == Val.getStructNumVirtualBases()); + } + + for (unsigned I = 0, N = Val.getStructNumBases(); I != N; ++I) { + const APValue &B = Val.getStructBase(I); + if (B.isIndeterminate()) + continue; + const Record::Base *RB = R->getBase(I); + QualType BaseType = Ctx.getASTContext().getCanonicalTagType(RB->Decl); + + if (!this->emitGetPtrBase(RB->Offset, Info)) + return false; + if (!this->visitAPValueInitializer(B, Info, BaseType, false)) + return false; + if (!this->emitFinishInitPop(Info)) + return false; + } + for (unsigned I = 0, N = Val.getStructNumFields(); I != N; ++I) { const APValue &F = Val.getStructField(I); if (F.isIndeterminate()) continue; const Record::Field *RF = R->getField(I); QualType FieldType = RF->Decl->getType(); - // Fields. if (OptPrimType PT = classify(FieldType)) { if (!this->visitAPValue(F, *PT, Info)) @@ -5538,25 +5563,23 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val, } } - // Bases. - for (unsigned I = 0, N = Val.getStructNumBases(); I != N; ++I) { - // FIXME: APValue doesn't know about virtual bases. - // We simply assume that if the APValue has more bases than the Record, - // those additional bases must be virtual. - if (I >= R->getNumBases()) - break; - const APValue &B = Val.getStructBase(I); - if (B.isIndeterminate()) - continue; - const Record::Base *RB = R->getBase(I); - QualType BaseType = Ctx.getASTContext().getCanonicalTagType(RB->Decl); + // Virtual Bases. + if (Toplevel) { + for (unsigned I = 0, N = Val.getStructNumVirtualBases(); I != N; ++I) { + const APValue &B = Val.getStructVirtualBase(I); + if (B.isIndeterminate()) + continue; + const Record::Base *RB = R->getVirtualBase(I); + QualType BaseType = Ctx.getASTContext().getCanonicalTagType(RB->Decl); - if (!this->emitGetPtrBase(RB->Offset, Info)) - return false; - if (!this->visitAPValueInitializer(B, Info, BaseType)) - return false; - if (!this->emitFinishInitPop(Info)) - return false; + if (!this->emitGetPtrVirtBase(cast<CXXRecordDecl>(RB->R->getDecl()), + Info)) + return false; + if (!this->visitAPValueInitializer(B, Info, BaseType, false)) + return false; + if (!this->emitFinishInitPop(Info)) + return false; + } } return true; @@ -6952,6 +6975,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { } unsigned FieldInits = 0; + bool HaveVirtBases = false; InitLinkScope<Emitter> InitScope(this, InitLink::This()); for (const auto *Init : Ctor->inits()) { // Scope needed for the initializers. @@ -6971,10 +6995,9 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { assert(BaseDecl); if (Init->isBaseVirtual()) { - assert(R->getVirtualBase(BaseDecl)); - if (!this->emitGetPtrThisVirtBase(BaseDecl, InitExpr)) - return false; - + // See below. + HaveVirtBases = true; + continue; } else { // Base class initializer. // Get This Base and call initializer on it. @@ -7045,6 +7068,39 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { return false; } + // Now, emit virtual base initializers if we have any + // _and_ this not a base class pointer. + if (HaveVirtBases) { + if (!this->emitThis(Ctor)) + return false; + LabelTy AfterVirtBasesLabel = this->getLabel(); + + // If this is a base class, skip the virtual bases. + if (!this->emitIsBaseClass({})) + return false; + if (!this->jumpTrue(AfterVirtBasesLabel, {})) + return false; + + for (const auto *Init : Ctor->inits()) { + if (const Type *Base = Init->getBaseClass(); + Base && Init->isBaseVirtual()) { + const auto *BaseDecl = Base->getAsCXXRecordDecl(); + assert(BaseDecl); + assert(R->getVirtualBase(BaseDecl)); + if (!this->emitGetPtrThisVirtBase(BaseDecl, Ctor)) + return false; + if (!this->visitInitializerPop(Init->getInit())) + return false; + } + } + + this->fallthrough(AfterVirtBasesLabel); + this->emitLabel(AfterVirtBasesLabel); + + if (!this->emitPopPtr(Ctor)) + return false; + } + if (FieldInits != R->getNumFields()) { assert(FieldInits < R->getNumFields()); // Start the lifetime of all members. @@ -7113,10 +7169,31 @@ bool Compiler<Emitter>::compileDestructor(const CXXDestructorDecl *Dtor) { return false; } + if (R->getNumVirtualBases() > 0) { + LabelTy EndLabel = this->getLabel(); + // If this is a base class, skip the virtual bases. + if (!this->emitIsBaseClass({})) + return false; + if (!this->jumpTrue(EndLabel, {})) + 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; + } + + this->fallthrough(EndLabel); + this->emitLabel(EndLabel); + } + if (!this->emitMarkDestroyed(Dtor)) return false; - // FIXME: Virtual bases. return this->emitPopPtr(Dtor) && this->emitRetVoid(Dtor); } @@ -8135,7 +8212,8 @@ bool Compiler<Emitter>::emitComplexComparison(const Expr *LHS, const Expr *RHS, /// Emit destruction of record types (or arrays of record types). template <class Emitter> bool Compiler<Emitter>::emitRecordDestructionPop(const Record *R, - SourceInfo Loc) { + SourceInfo Loc, + bool Toplevel) { assert(R); assert(!R->hasTrivialDtor()); const CXXDestructorDecl *Dtor = R->getDestructor(); diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 85105d9d42520..fc5fb81168ed1 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -318,7 +318,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, VarCreationState visitDecl(const VarDecl *VD); /// Visit an APValue. bool visitAPValue(const APValue &Val, PrimType ValType, SourceInfo Info); - bool visitAPValueInitializer(const APValue &Val, SourceInfo Info, QualType T); + bool visitAPValueInitializer(const APValue &Val, SourceInfo Info, QualType T, + bool Toplevel = true); /// Visit the given decl as if we have a reference to it. bool visitDeclRef(const ValueDecl *D, const Expr *E); @@ -361,7 +362,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, /// Emits a zero initializer. bool visitZeroInitializer(PrimType T, QualType QT, const Expr *E); - bool visitZeroRecordInitializer(const Record *R, const Expr *E); + bool visitZeroRecordInitializer(const Record *R, const Expr *E, + bool Toplevel = true); bool visitZeroArrayInitializer(QualType T, const Expr *E); bool visitAssignment(const Expr *LHS, const Expr *RHS, const Expr *E); @@ -418,7 +420,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool emitComplexBoolCast(const Expr *E); bool emitComplexComparison(const Expr *LHS, const Expr *RHS, const BinaryOperator *E); - bool emitRecordDestructionPop(const Record *R, SourceInfo Loc); + bool emitRecordDestructionPop(const Record *R, SourceInfo Loc, + bool Toplevel = true); bool emitDestructionPop(const Descriptor *Desc, SourceInfo Loc); bool emitDummyPtr(const DeclTy &D, const Expr *E, bool CU = false); bool emitFloat(const APFloat &F, SourceInfo Info); diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp index 40cf29efcfb4f..52f3e419d18f4 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -533,7 +533,6 @@ bool Context::Run(State &Parent, const Function *Func) { return false; } -// TODO: Virtual bases? const CXXMethodDecl * Context::getOverridingFunction(const CXXRecordDecl *DynamicDecl, const CXXRecordDecl *StaticDecl, diff --git a/clang/lib/AST/ByteCode/EvaluationResult.cpp b/clang/lib/AST/ByteCode/EvaluationResult.cpp index 0873b870fc8b2..d95c1e19889e8 100644 --- a/clang/lib/AST/ByteCode/EvaluationResult.cpp +++ b/clang/lib/AST/ByteCode/EvaluationResult.cpp @@ -27,7 +27,8 @@ static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc, } static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, - PtrView BasePtr, const Record *R); + PtrView BasePtr, const Record *R, + bool Toplevel = true); static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc, PtrView BasePtr) { @@ -66,7 +67,8 @@ static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc, } static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, - PtrView BasePtr, const Record *R) { + PtrView BasePtr, const Record *R, + bool Toplevel) { assert(R); bool Result = true; // Check all fields of this record are initialized. @@ -110,10 +112,30 @@ static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, } return false; } - Result &= CheckFieldsInitialized(S, Loc, P, B.R); + Result &= CheckFieldsInitialized(S, Loc, P, B.R, false); + } + + if (Toplevel) { + for (auto [I, B] : llvm::enumerate(R->virtual_bases())) { + PtrView P = BasePtr.atField(B.Offset); + if (!P.isInitialized()) { + const Descriptor *Desc = BasePtr.getDeclDesc(); + if (const auto *CD = dyn_cast_if_present<CXXRecordDecl>(R->getDecl())) { + const auto &BS = *std::next(CD->bases_begin(), I); + SourceLocation TypeBeginLoc = BS.getBaseTypeLoc(); + S.FFDiag(TypeBeginLoc, diag::note_constexpr_uninitialized_base) + << B.Desc->getType() << SourceRange(TypeBeginLoc, BS.getEndLoc()); + } else { + S.FFDiag(Desc->getLocation(), diag::note_constexpr_uninitialized_base) + << B.Desc->getType(); + } + return false; + } + + Result &= CheckFieldsInitialized(S, Loc, P, B.R, false); + } } - // TODO: Virtual bases return Result; } @@ -156,7 +178,8 @@ static bool isOrHasPtr(const Descriptor *D) { return false; } -static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks) { +static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks, + bool Toplevel = true) { auto isUsefulPtr = [](const Pointer &P) -> bool { return P.isLive() && P.isBlockPointer() && !P.isZero() && !P.isDummy() && P.isDereferencable() && !P.isUnknownSizeArray() && !P.isOnePastEnd(); @@ -180,7 +203,7 @@ static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks) { if (!B.R->hasPtrField()) continue; PtrView BasePtr = Ptr.atField(B.Offset); - collectBlocks(BasePtr, Blocks); + collectBlocks(BasePtr, Blocks, false); } for (const Record::Field &F : R->fields()) { @@ -189,6 +212,16 @@ static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks) { PtrView FieldPtr = Ptr.atField(F.Offset); collectBlocks(FieldPtr, Blocks); } + + if (Toplevel) { + for (const Record::Base &B : R->virtual_bases()) { + if (!B.R->hasPtrField()) + continue; + PtrView BasePtr = Ptr.atField(B.Offset); + collectBlocks(BasePtr, Blocks, false); + } + } + return; } diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 71021815baeef..68f7998904bbe 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1697,6 +1697,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/Interp.h b/clang/lib/AST/ByteCode/Interp.h index aa2dffc2b982a..787cab40ce33d 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -2193,6 +2193,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); @@ -4104,6 +4112,11 @@ inline bool CheckDestruction(InterpState &S, CodePtr OpPC) { return CheckDestructor(S, OpPC, Ptr); } +inline bool IsBaseClass(InterpState &S, CodePtr OpPC) { + S.Stk.push<bool>(S.Stk.peek<Pointer>().isBaseClass()); + return true; +} + //===----------------------------------------------------------------------===// // Read opcode arguments //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index e350d7b2e547d..ebc9a664b8857 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -383,6 +383,14 @@ def GetPtrVirtBasePop : Opcode { // RecordDecl of base class. let Args = [ArgRecordDecl]; } +def GetPtrVirtBase : Opcode { + // RecordDecl of base class. + let Args = [ArgRecordDecl]; +} + +def IsBaseClass : SuccessOpcode; + + // [] -> [Pointer] def GetPtrThisBase : Opcode { // Offset of field, which is a base. diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp index de2b3421f404b..eff3920d5dbd5 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); @@ -876,7 +876,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/ByteCode/Record.h b/clang/lib/AST/ByteCode/Record.h index c0c10f72ae3ee..601c18442e4a1 100644 --- a/clang/lib/AST/ByteCode/Record.h +++ b/clang/lib/AST/ByteCode/Record.h @@ -142,7 +142,7 @@ class Record final { BaseList Bases; /// List of all the fields in the record. FieldList Fields; - /// List o fall virtual bases. + /// List of all virtual bases. VirtualBaseList VirtualBases; /// Mapping from declarations to bases. diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index ce4ba971a4631..4cc64c0c24271 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -335,8 +335,10 @@ 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; + if (!C.getLangOpts().CPlusPlus26) { + data().DefaultedDefaultConstructorIsConstexpr = false; + data().DefaultedDestructorIsConstexpr = false; + } // 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 1d359339b9104..4cc2fdfef5422 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2183,7 +2183,8 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, QualType Type, const APValue &Value, ConstantExprKind Kind, const FieldDecl *SubobjectDecl, - CheckedTemporaries &CheckedTemps); + CheckedTemporaries &CheckedTemps, + bool Toplevel = true); /// Check that this reference or pointer core constant expression is a valid /// value for an address or reference constant expression. Return true if we @@ -2428,7 +2429,8 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, QualType Type, const APValue &Value, ConstantExprKind Kind, const FieldDecl *SubobjectDecl, - CheckedTemporaries &CheckedTemps) { + CheckedTemporaries &CheckedTemps, + bool Toplevel) { if (!Value.hasValue()) { if (SubobjectDecl) { Info.FFDiag(DiagLoc, diag::note_constexpr_uninitialized) @@ -2474,6 +2476,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(); @@ -2483,7 +2487,7 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, } if (!CheckEvaluationResult(CERK, Info, DiagLoc, BS.getType(), BaseValue, Kind, /*SubobjectDecl=*/nullptr, - CheckedTemps)) + CheckedTemps, false)) return false; ++BaseIndex; } @@ -2497,6 +2501,27 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, I, CheckedTemps)) return false; } + + if (Toplevel) { + if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) { + unsigned BaseIndex = 0; + for (const CXXBaseSpecifier &BS : CD->vbases()) { + assert(BS.isVirtual()); + 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, false)) + return false; + ++BaseIndex; + } + } + } } if (Value.isLValue() && @@ -5445,7 +5470,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. @@ -5461,15 +5487,23 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) { Result = APValue((const FieldDecl *)nullptr); return true; } - Result = - APValue(APValue::UninitStruct(), RD->getNumBases(), RD->getNumFields()); + + // bases() includes directly specified virtual bases as well. + unsigned NonVirtualBases = + llvm::count_if(RD->bases(), [](auto &B) { return !B.isVirtual(); }); + Result = APValue(APValue::UninitStruct(), NonVirtualBases, + RD->getNumFields(), Toplevel ? RD->getNumVBases() : 0); unsigned Index = 0; - for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(), + for (CXXRecordDecl::base_class_const_iterator B = RD->bases_begin(), End = RD->bases_end(); - I != End; ++I, ++Index) - Success &= - handleDefaultInitValue(I->getType(), Result.getStructBase(Index)); + B != End; ++B) { + if (B->isVirtual()) + continue; + Success &= handleDefaultInitValue(B->getType(), + Result.getStructBase(Index), false); + ++Index; + } for (const auto *I : RD->fields()) { if (I->isUnnamedBitField()) @@ -5477,6 +5511,20 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) { Success &= handleDefaultInitValue( I->getType(), Result.getStructField(I->getFieldIndex())); } + + if (Toplevel) { + Index = 0; + + for (const auto &B : RD->vbases()) { + Success &= handleDefaultInitValue( + B.getType(), Result.getStructVirtualBase(Index), false); + ++Index; + } + } else { + // Virtual bases should only exist at the top level of an APValue. + assert(Result.getStructNumVirtualBases() == 0); + } + return Success; } @@ -5486,7 +5534,6 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) { if (Result.hasArrayFiller()) Success &= handleDefaultInitValue(AT->getElementType(), Result.getArrayFiller()); - return Success; } diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index f0064362abbc6..528e61f929c4f 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: { diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 7a2b00647f189..2e57aa384b27f 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -600,7 +600,8 @@ class ConstStructBuilder { bool Build(const InitListExpr *ILE, bool AllowOverwrite); bool Build(const APValue &Val, const RecordDecl *RD, bool IsPrimaryBase, - const CXXRecordDecl *VTableClass, CharUnits BaseOffset); + const CXXRecordDecl *VTableClass, CharUnits BaseOffset, + bool IsCompleteClass = true); bool DoZeroInitPadding(const ASTRecordLayout &Layout, unsigned FieldNo, const FieldDecl &Field, bool AllowOverwrite, CharUnits &SizeSoFar, bool &ZeroFieldSize); @@ -841,44 +842,69 @@ struct BaseInfo { bool ConstStructBuilder::Build(const APValue &Val, const RecordDecl *RD, bool IsPrimaryBase, const CXXRecordDecl *VTableClass, - CharUnits Offset) { + CharUnits Offset, bool IsCompleteClass) { + assert(Val.isStruct() || Val.isUnion()); + const ASTRecordLayout &Layout = CGM.getContext().getASTRecordLayout(RD); - if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) { - // Add a vtable pointer, if we need one and it hasn't already been added. - if (Layout.hasOwnVFPtr()) { - llvm::Constant *VTableAddressPoint = - CGM.getCXXABI().getVTableAddressPoint(BaseSubobject(CD, Offset), - VTableClass); - if (auto Authentication = CGM.getVTablePointerAuthentication(CD)) { - VTableAddressPoint = Emitter.tryEmitConstantSignedPointer( - VTableAddressPoint, *Authentication); - if (!VTableAddressPoint) + if (Val.isStruct()) { + if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) { + // Add a vtable pointer, if we need one and it hasn't already been added. + if (Layout.hasOwnVFPtr()) { + llvm::Constant *VTableAddressPoint = + CGM.getCXXABI().getVTableAddressPoint(BaseSubobject(CD, Offset), + VTableClass); + if (auto Authentication = CGM.getVTablePointerAuthentication(CD)) { + VTableAddressPoint = Emitter.tryEmitConstantSignedPointer( + VTableAddressPoint, *Authentication); + if (!VTableAddressPoint) + return false; + } + if (!AppendBytes(Offset, VTableAddressPoint)) return false; } - if (!AppendBytes(Offset, VTableAddressPoint)) - return false; - } - // Accumulate and sort bases, in order to visit them in address order, which - // may not be the same as declaration order. - SmallVector<BaseInfo, 8> Bases; - Bases.reserve(CD->getNumBases()); - unsigned BaseNo = 0; - for (CXXRecordDecl::base_class_const_iterator Base = CD->bases_begin(), - BaseEnd = CD->bases_end(); Base != BaseEnd; ++Base, ++BaseNo) { - assert(!Base->isVirtual() && "should not have virtual bases here"); - const CXXRecordDecl *BD = Base->getType()->getAsCXXRecordDecl(); - CharUnits BaseOffset = Layout.getBaseClassOffset(BD); - Bases.push_back(BaseInfo(BD, BaseOffset, BaseNo)); - } - llvm::stable_sort(Bases); + // Accumulate and sort bases, in order to visit them in address order, + // which may not be the same as declaration order. + SmallVector<BaseInfo, 8> Bases; + Bases.reserve(Val.getStructNumBases()); + unsigned BaseNo = 0; + for (const CXXBaseSpecifier &Base : CD->bases()) { + if (Base.isVirtual()) + continue; + const CXXRecordDecl *BD = Base.getType()->getAsCXXRecordDecl(); + CharUnits BaseOffset = Layout.getBaseClassOffset(BD); + Bases.push_back(BaseInfo(BD, BaseOffset, BaseNo)); + ++BaseNo; + } + llvm::stable_sort(Bases); - for (const BaseInfo &Base : Bases) { - bool IsPrimaryBase = Layout.getPrimaryBase() == Base.Decl; - if (!Build(Val.getStructBase(Base.Index), Base.Decl, IsPrimaryBase, - VTableClass, Offset + Base.Offset)) - return false; + for (const BaseInfo &Base : Bases) { + bool IsPrimaryBase = Layout.getPrimaryBase() == Base.Decl; + if (!Build(Val.getStructBase(Base.Index), Base.Decl, IsPrimaryBase, + VTableClass, Offset + Base.Offset, false)) + return false; + } + + if (IsCompleteClass) { + Bases.clear(); + BaseNo = 0; + Bases.reserve(Val.getStructNumVirtualBases()); + for (const CXXBaseSpecifier &Base : CD->vbases()) { + const CXXRecordDecl *BD = Base.getType()->getAsCXXRecordDecl(); + CharUnits BaseOffset = Layout.getVBaseClassOffset(BD); + Bases.push_back(BaseInfo(BD, BaseOffset, BaseNo)); + ++BaseNo; + } + llvm::stable_sort(Bases); + + for (const BaseInfo &Base : Bases) { + bool IsPrimaryBase = Layout.getPrimaryBase() == Base.Decl; + if (!Build(Val.getStructVirtualBase(Base.Index), Base.Decl, + IsPrimaryBase, VTableClass, Offset + Base.Offset, false)) + return false; + } + } } } diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index ffce0a146865e..9d5cd9268f428 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: @@ -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; @@ -7702,12 +7700,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 +8065,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 7d9fff1051068..6785afcfc37bb 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -9906,7 +9906,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/virtual-bases-codegen.cpp b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp new file mode 100644 index 0000000000000..c8ec77bdb5dd0 --- /dev/null +++ b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -triple x86_64-linux -std=c++26 -fexperimental-new-constant-interpreter %s -emit-llvm -o - | FileCheck %s + +struct A { int a; }; +struct B : virtual A { + constexpr B() : A(127) {} +}; + + +// CHECK: @b1 = global { ptr, i32 } { {{.*}} i32 127 } +B b1{}; + +struct C : B { + constexpr C(int) : B(), A(128) {} +}; + +// CHECK: @c1 = global { ptr, i32 } { {{.*}} i32 128 } +C c1 = C(12); diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp new file mode 100644 index 0000000000000..0e99e682e033f --- /dev/null +++ b/clang/test/AST/ByteCode/virtual-bases.cpp @@ -0,0 +1,326 @@ +// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -verify %s + +namespace PaperSample { + struct Superbase { + int a = 10; + }; + + struct Common: Superbase { + unsigned counter = 1337; + }; + + 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 = 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); +} + +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); + static_assert((void*)(A*)&b == (void*)(A*)&b); +} + +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); +} + + +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); +} + +namespace UninitializedFields { + + struct A { + int a; // expected-note {{declared here}} + constexpr A() {} + }; + struct B : public A { + }; + constexpr B b{}; // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{subobject 'a' is not initialized}}} + + + struct X { + int *p; + constexpr X() { + p = new int; // expected-note {{heap allocation performed here}} + } + }; + struct Y: public virtual X { + }; + constexpr Y y; // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{pointer to heap-allocated object is not a constant expression}} +} + + +namespace DtorOrder { + enum { + R_A = 1, + R_B = 2, + R_C = 3, + R_F = 4, + R_G = 5, + }; + + struct A { + int a; int b; + int *results; + int &i; + + constexpr A(int *results, int &i) : results(results), i(i) {} + constexpr ~A() { + *(results + i) = R_A; + ++i; + } + + }; + struct B : public virtual A { + int c; int d; + int *results; + int &i; + + constexpr B(int *results, int &i) : A(results, i), results(results), i(i) {} + constexpr ~B() { + *(results + i) = R_B; + ++i; + } + }; + + + struct G { + int *results; + int &i; + constexpr G(int *results, int &i) : results(results), i(i) {} + + constexpr ~G() { + *(results + i) = R_G; + ++i; + } + }; + + + struct F : virtual G{ + int *results; + int &i; + constexpr F(int *results, int &i) : G(results, i), results(results), i(i) {} + constexpr ~F() { + *(results + i) = R_F; + ++i; + } + }; + + struct C : public virtual A, public virtual B { + int *results; + int &i; + int m = 10; + + F f; + + constexpr C(int *results, int &i) : A(results, i), B(results, i), results(results), i(i), f(results,i) {} + + constexpr ~C() { + *(results + i) = R_C; + ++i; + } + }; + + constexpr int foo() { + int results[] = {0, 0, 0, 0, 0, 0, 0}; + + int i = 0; + { + C c = C(results, i); + } + return i == 5 && + results[0] == R_C && + results[1] == R_F && + results[2] == R_G && + results[3] == R_B && + results[4] == R_A; + } + static_assert(foo() == 1); + + +} + +namespace ImplicitValueInit { + struct B {int m; }; + struct Ints2 : public virtual B{ + int a = 10; + int b; + }; + constexpr Ints2 ints22; // expected-error {{without a user-provided default constructor}} + static_assert(ints22.m == 0); +} + +namespace Ctors { + + struct K { + int k; + constexpr K(int k) : k(k) {} + }; + + struct A : public virtual K { + int a; + constexpr A(int a) : a(a), K(12) {} + }; + + struct B : public virtual A { + constexpr B() : A(100), K(200) {} + constexpr B(int) : K(200), A(100) {} + }; + + constexpr B b{}; + static_assert(b.a == 100); + static_assert(b.k == 200); + + constexpr B b2{-1}; + static_assert(b2.a == 100); + static_assert(b2.k == 200); + + constexpr A a{13}; + static_assert(a.a == 13); + static_assert(a.k == 12); +} + +namespace Ctors2 { + struct A { + constexpr A(int *p, int x) { *p += x; } + }; + struct B : virtual A { + constexpr B(int *p) : A(p, 1) {} + }; + struct C : virtual B { + constexpr C(int *p) : B(p), A(p, 2) {} + }; + constexpr int f() { + int x = 0; + C c(&x); + return x; + } + static_assert(f() == 2); +} + +namespace VirtCalls { + struct K { + virtual constexpr int bar() const { return 30; } + }; + + + struct X : virtual K { + virtual constexpr int foo() const { return 10; } + }; + + struct Y : virtual K {}; + + struct Z : X, Y { + constexpr int bar() const override { return 50; } + }; + + constexpr Z z{}; + static_assert(z.foo() == 10); + static_assert(z.bar() == 50); +} 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 00c4da8ec210fed67aa29a94a35cac6ad1f883f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Fri, 26 Jun 2026 11:34:30 +0200 Subject: [PATCH 2/2] Support the current interpreter as well --- clang/lib/AST/ByteCode/Compiler.cpp | 2 +- clang/lib/AST/ExprConstant.cpp | 241 ++++++++++++++---- .../AST/ByteCode/virtual-bases-codegen.cpp | 1 + clang/test/AST/ByteCode/virtual-bases.cpp | 1 + 4 files changed, 195 insertions(+), 50 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 68c1fed15fee4..7d1418545c82b 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -7075,7 +7075,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { return false; LabelTy AfterVirtBasesLabel = this->getLabel(); - // If this is a base class, skip the virtual bases. + // If the instance pointer is a base class, skip the virtual bases. if (!this->emitIsBaseClass({})) return false; if (!this->jumpTrue(AfterVirtBasesLabel, {})) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 4cc2fdfef5422..d0dae7e6f7320 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1447,6 +1447,16 @@ namespace { unsigned getLValueCallIndex() const { return Base.getCallIndex(); } unsigned getLValueVersion() const { return Base.getVersion(); } + + bool pointsToCompleteClass() const { + if (Designator.Entries.empty()) + return true; + + return !getAsBaseClass(Designator.Entries.back()); + } + + + void moveInto(APValue &V) const { if (Designator.Invalid) V = APValue(Base, Offset, APValue::NoLValuePath(), IsNullPtr); @@ -3169,11 +3179,27 @@ static bool HandleLValueDirectBase(EvalInfo &Info, const Expr *E, LValue &Obj, RL = &Info.Ctx.getASTRecordLayout(Derived); } - Obj.addDecl(Info, E, Base, /*Virtual*/ false); + Obj.addDecl(Info, E, Base, /*Virtual=*/false); Obj.getLValueOffset() += RL->getBaseClassOffset(Base); return true; } +static bool HandleLValueDirectVirtualBase(EvalInfo &Info, const Expr *E, LValue &Obj, + const CXXRecordDecl *Derived, + const CXXRecordDecl *Base, + const ASTRecordLayout *RL = nullptr) { + if (!RL) { + if (Derived->isInvalidDecl()) return false; + RL = &Info.Ctx.getASTRecordLayout(Derived); + } + + Obj.addDecl(Info, E, Base, /*Virtual=*/true); + Obj.getLValueOffset() += RL->getVBaseClassOffset(Base); + return true; +} + + + static bool HandleLValueBase(EvalInfo &Info, const Expr *E, LValue &Obj, const CXXRecordDecl *DerivedDecl, const CXXBaseSpecifier *Base) { @@ -3542,9 +3568,19 @@ static unsigned getBaseIndex(const CXXRecordDecl *Derived, Base = Base->getCanonicalDecl(); unsigned Index = 0; for (CXXRecordDecl::base_class_const_iterator I = Derived->bases_begin(), - E = Derived->bases_end(); I != E; ++I, ++Index) { + E = Derived->bases_end(); I != E; ++I) { + if (I->isVirtual()) + continue; + if (I->getType()->getAsCXXRecordDecl()->getCanonicalDecl() == Base) + return Index; + ++Index; + } + + for (CXXRecordDecl::base_class_const_iterator I = Derived->vbases_begin(), + E = Derived->vbases_end(); I != E; ++I) { if (I->getType()->getAsCXXRecordDecl()->getCanonicalDecl() == Base) return Index; + ++Index; } llvm_unreachable("base class missing from derived class's bases list"); @@ -4407,7 +4443,13 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, // Next subobject is a base class. const CXXRecordDecl *Derived = ObjType->getAsCXXRecordDecl(); const CXXRecordDecl *Base = getAsBaseClass(Sub.Entries[I]); - O = &O->getStructBase(getBaseIndex(Derived, Base)); + + unsigned BaseIndex = getBaseIndex(Derived, Base); + unsigned NumNonVirtualBases = O->getStructNumBases(); + if (BaseIndex >= NumNonVirtualBases) { + O = &O->getStructVirtualBase(BaseIndex - NumNonVirtualBases);//getBaseIndex(Derived, Base)); + } else + O = &O->getStructBase(BaseIndex); ObjType = getSubobjectType(ObjType, Info.Ctx.getCanonicalTagType(Base)); } @@ -6554,11 +6596,11 @@ static std::optional<DynamicType> ComputeDynamicType(EvalInfo &Info, // shouldn't happen other than in constant-folding situations, since literal // types can't have virtual bases. // - // Note that consumers of DynamicType assume that the type has no virtual + // XXX Note that consumers of DynamicType assume that the type has no virtual // bases, and will need modifications if this restriction is relaxed. const CXXRecordDecl *Class = This.Designator.MostDerivedType->getAsCXXRecordDecl(); - if (!Class || Class->getNumVBases()) { + if (!Class || (!Info.getLangOpts().CPlusPlus26 && Class->getNumVBases())) { Info.FFDiag(E); return std::nullopt; } @@ -6692,10 +6734,18 @@ static bool HandleCovariantReturnAdjustment(EvalInfo &Info, const Expr *E, static bool isBaseClassPublic(const CXXRecordDecl *Derived, const CXXRecordDecl *Base) { for (const CXXBaseSpecifier &BaseSpec : Derived->bases()) { + if (BaseSpec.isVirtual()) + continue; auto *BaseClass = BaseSpec.getType()->getAsCXXRecordDecl(); if (BaseClass && declaresSameEntity(BaseClass, Base)) return BaseSpec.getAccessSpecifier() == AS_public; } + for (const CXXBaseSpecifier &BaseSpec : Derived->vbases()) { + auto *BaseClass = BaseSpec.getType()->getAsCXXRecordDecl(); + if (BaseClass && declaresSameEntity(BaseClass, Base)) + return BaseSpec.getAccessSpecifier() == AS_public; + } + llvm_unreachable("Base is not a direct base of Derived"); } @@ -7111,17 +7161,40 @@ static bool HandleFunctionCall(SourceLocation CallLoc, return ESR == ESR_Returned; } + +static bool HandleConstructorCall(const Expr *E, const LValue &This, + CallRef Call, + const CXXConstructorDecl *Definition, + EvalInfo &Info, APValue &Result, bool IsCompleteClass = true); + + +static bool HandleConstructorCall(const Expr *E, const LValue &This, + ArrayRef<const Expr*> Args, + const CXXConstructorDecl *Definition, + EvalInfo &Info, APValue &Result, bool IsCompleteClass = true) { + CallScopeRAII CallScope(Info); + CallRef Call = Info.CurrentCall->createCall(Definition); + if (!EvaluateArgs(Args, Call, Info, Definition)) + return false; + + return HandleConstructorCall(E, This, Call, Definition, Info, Result, IsCompleteClass) && + CallScope.destroy(); +} + + + /// Evaluate a constructor call. static bool HandleConstructorCall(const Expr *E, const LValue &This, CallRef Call, const CXXConstructorDecl *Definition, - EvalInfo &Info, APValue &Result) { + EvalInfo &Info, APValue &Result, bool IsCompleteClass) { + SourceLocation CallLoc = E->getExprLoc(); if (!Info.CheckCallLimit(CallLoc)) return false; const CXXRecordDecl *RD = Definition->getParent(); - if (RD->getNumVBases()) { + if (!Info.getLangOpts().CPlusPlus26 && RD->getNumVBases()) { Info.FFDiag(CallLoc, diag::note_constexpr_virtual_base) << RD; return false; } @@ -7170,10 +7243,14 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, // Reserve space for the struct members. if (!Result.hasValue()) { - if (!RD->isUnion()) - Result = APValue(APValue::UninitStruct(), RD->getNumBases(), - RD->getNumFields()); - else + if (!RD->isUnion()) { + unsigned NonVirtualBases = + llvm::count_if(RD->bases(), [](auto &B) { return !B.isVirtual(); }); + + + Result = APValue(APValue::UninitStruct(), NonVirtualBases, + RD->getNumFields(), RD->getNumVBases()); + } else // A union starts with no active member. Result = APValue((const FieldDecl*)nullptr); } @@ -7186,6 +7263,10 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, bool Success = true; unsigned BasesSeen = 0; + unsigned VirtualBasesSeen = 0; + unsigned NonVirtualBases = + llvm::count_if(RD->bases(), [](auto &B) { return !B.isVirtual(); }); + #ifndef NDEBUG CXXRecordDecl::base_class_const_iterator BaseIt = RD->bases_begin(); #endif @@ -7199,7 +7280,7 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, return; } - // Default-initialize any fields with no explicit initializer. + // Default-initialize any fields with no explicit initializer.< for (; !declaresSameEntity(*FieldIt, FD); ++FieldIt) { assert(FieldIt != RD->field_end() && "missing field?"); if (!FieldIt->isUnnamedBitField()) @@ -7218,18 +7299,22 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, FieldDecl *FD = nullptr; if (I->isBaseInitializer()) { QualType BaseType(I->getBaseClass(), 0); -#ifndef NDEBUG - // Non-virtual base classes are initialized in the order in the class - // definition. We have already checked for virtual base classes. - assert(!BaseIt->isVirtual() && "virtual base for literal type"); - assert(Info.Ctx.hasSameUnqualifiedType(BaseIt->getType(), BaseType) && - "base class initializers not in expected order"); - ++BaseIt; -#endif - if (!HandleLValueDirectBase(Info, I->getInit(), Subobject, RD, - BaseType->getAsCXXRecordDecl(), &Layout)) - return false; - Value = &Result.getStructBase(BasesSeen++); + if (I->isBaseVirtual()) { + if (This.pointsToCompleteClass()) { + if (!HandleLValueDirectVirtualBase(Info, I->getInit(), Subobject, RD, + BaseType->getAsCXXRecordDecl(), &Layout)) + return false; + Value = &Result.getStructVirtualBase(VirtualBasesSeen++); + } else { + continue; + } + + } else { + if (!HandleLValueDirectBase(Info, I->getInit(), Subobject, RD, + BaseType->getAsCXXRecordDecl(), &Layout)) + return false; + Value = &Result.getStructBase(BasesSeen++); + } } else if ((FD = I->getMember())) { if (!HandleLValueMember(Info, I->getInit(), Subobject, FD, &Layout)) return false; @@ -7313,11 +7398,25 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, } } + + + + + + // This is the point at which the dynamic type of the object becomes this // class type. - if (I->isBaseInitializer() && BasesSeen == RD->getNumBases()) + if (I->isBaseInitializer() && BasesSeen == NonVirtualBases) EvalObj.finishedConstructingBases(); - } + + + + + + + + + } // END OF FOR LOOP // Default-initialize any remaining fields. if (!RD->isUnion()) { @@ -7336,22 +7435,9 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, LifetimeExtendedScope.destroy(); } -static bool HandleConstructorCall(const Expr *E, const LValue &This, - ArrayRef<const Expr*> Args, - const CXXConstructorDecl *Definition, - EvalInfo &Info, APValue &Result) { - CallScopeRAII CallScope(Info); - CallRef Call = Info.CurrentCall->createCall(Definition); - if (!EvaluateArgs(Args, Call, Info, Definition)) - return false; - - return HandleConstructorCall(E, This, Call, Definition, Info, Result) && - CallScope.destroy(); -} - static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange, const LValue &This, APValue &Value, - QualType T) { + QualType T, bool IsCompleteClass = true) { // Objects can only be destroyed while they're within their lifetimes. // FIXME: We have no representation for whether an object of type nullptr_t // is in its lifetime; it usually doesn't matter. Perhaps we should model it @@ -7415,7 +7501,7 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange, return true; } - if (RD->getNumVBases()) { + if (!Info.getLangOpts().CPlusPlus26 && RD->getNumVBases()) { Info.FFDiag(CallRange.getBegin(), diag::note_constexpr_virtual_base) << RD; return false; } @@ -7454,10 +7540,13 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange, CallRef()); // We're now in the period of destruction of this object. - unsigned BasesLeft = RD->getNumBases(); EvalInfo::EvaluatingDestructorRAII EvalObj( Info, ObjectUnderConstruction{This.getLValueBase(), This.Designator.Entries}); + unsigned NonVirtualBases = + llvm::count_if(RD->bases(), [](auto &B) { return !B.isVirtual(); }); + unsigned NumVirtualBases = RD->getNumVBases(); + unsigned BasesLeft = NonVirtualBases; if (!EvalObj.DidInsert) { // C++2a [class.dtor]p19: // the behavior is undefined if the destructor is invoked for an object @@ -7499,11 +7588,13 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange, return false; } - if (BasesLeft != 0) + if (BasesLeft != 0 || NumVirtualBases != 0) EvalObj.startedDestroyingBases(); // Destroy base classes in reverse order. for (const CXXBaseSpecifier &Base : llvm::reverse(RD->bases())) { + if (Base.isVirtual()) + continue; --BasesLeft; QualType BaseType = Base.getType(); @@ -7514,11 +7605,31 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange, APValue *SubobjectValue = &Value.getStructBase(BasesLeft); if (!HandleDestructionImpl(Info, CallRange, Subobject, *SubobjectValue, - BaseType)) + BaseType, /*IsCompleteClass=*/false)) return false; } assert(BasesLeft == 0 && "NumBases was wrong?"); + // Virtual bases. + if (IsCompleteClass) { + unsigned VirtualBasesLeft = NumVirtualBases; + for (const CXXBaseSpecifier &Base : llvm::reverse(RD->vbases())) { + --VirtualBasesLeft; + + QualType BaseType = Base.getType(); + LValue Subobject = This; + if (!HandleLValueDirectVirtualBase(Info, &LocE, Subobject, RD, + BaseType->getAsCXXRecordDecl(), &Layout)) + return false; + + APValue *SubobjectValue = &Value.getStructVirtualBase(VirtualBasesLeft); + if (!HandleDestructionImpl(Info, CallRange, Subobject, *SubobjectValue, + BaseType, /*IsCompleteClass=*/false)) + return false; + } + assert(VirtualBasesLeft == 0 && "NumVirtualBases was wrong?"); + } + // The period of destruction ends now. The object is gone. Value = APValue(); return true; @@ -11155,16 +11266,29 @@ static bool HandleClassZeroInitialization(EvalInfo &Info, const Expr *E, const LValue &This, APValue &Result) { assert(!RD->isUnion() && "Expected non-union class type"); const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD); - Result = APValue(APValue::UninitStruct(), CD ? CD->getNumBases() : 0, + + + if (CD) { + unsigned NonVirtualBases = + llvm::count_if(CD->bases(), [](auto &B) { return !B.isVirtual(); }); + Result = APValue(APValue::UninitStruct(), NonVirtualBases, + RD->getNumFields(),CD->getNumVBases()); + } else { + Result = APValue(APValue::UninitStruct(), 0, RD->getNumFields()); + } + + if (RD->isInvalidDecl()) return false; const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD); if (CD) { unsigned Index = 0; for (CXXRecordDecl::base_class_const_iterator I = CD->bases_begin(), - End = CD->bases_end(); I != End; ++I, ++Index) { + End = CD->bases_end(); I != End; ++I) { + if (I->isVirtual()) + continue; const CXXRecordDecl *Base = I->getType()->getAsCXXRecordDecl(); LValue Subobject = This; if (!HandleLValueDirectBase(Info, E, Subobject, CD, Base, &Layout)) @@ -11172,6 +11296,7 @@ static bool HandleClassZeroInitialization(EvalInfo &Info, const Expr *E, if (!HandleClassZeroInitialization(Info, E, Base, Subobject, Result.getStructBase(Index))) return false; + ++Index; } } @@ -11190,6 +11315,21 @@ static bool HandleClassZeroInitialization(EvalInfo &Info, const Expr *E, return false; } + if (CD && This.Designator.Entries.empty()) { + unsigned Index = 0; + for (CXXRecordDecl::base_class_const_iterator I = CD->vbases_begin(), + End = CD->vbases_end(); I != End; ++I) { + const CXXRecordDecl *Base = I->getType()->getAsCXXRecordDecl(); + LValue Subobject = This; + if (!HandleLValueDirectVirtualBase(Info, E, Subobject, CD, Base, &Layout)) + return false; + if (!HandleClassZeroInitialization(Info, E, Base, Subobject, + Result.getStructVirtualBase(Index))) + return false; + ++Index; + } + } + return true; } @@ -11215,9 +11355,12 @@ bool RecordExprEvaluator::ZeroInitialization(const Expr *E, QualType T) { return EvaluateInPlace(Result.getUnionValue(), Info, Subobject, &VIE); } - if (isa<CXXRecordDecl>(RD) && cast<CXXRecordDecl>(RD)->getNumVBases()) { - Info.FFDiag(E, diag::note_constexpr_virtual_base) << RD; - return false; + if (!Info.getLangOpts().CPlusPlus26) { + if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD); + CXXRD && CXXRD->getNumVBases()) { + Info.FFDiag(E, diag::note_constexpr_virtual_base) << RD; + return false; + } } return HandleClassZeroInitialization(Info, E, RD, This, Result); diff --git a/clang/test/AST/ByteCode/virtual-bases-codegen.cpp b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp index c8ec77bdb5dd0..089103077e4a8 100644 --- a/clang/test/AST/ByteCode/virtual-bases-codegen.cpp +++ b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -triple x86_64-linux -std=c++26 -fexperimental-new-constant-interpreter %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-linux -std=c++26 %s -emit-llvm -o - | FileCheck %s struct A { int a; }; struct B : virtual A { diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp index 0e99e682e033f..9b9b8afbeb0f0 100644 --- a/clang/test/AST/ByteCode/virtual-bases.cpp +++ b/clang/test/AST/ByteCode/virtual-bases.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -verify %s +// RUN: %clang_cc1 -std=c++26 -verify %s namespace PaperSample { struct Superbase { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
