[clang] [clang][ExprConst] Support virtual bases in C++26 (PR #204289)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 30 00:54:38 PDT 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/204289 at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/204289
>From e8947977ff1aafd0d309dcceadfe2c7b30b99929 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 17 Jun 2026 07:47:18 +0200
Subject: [PATCH 1/6] [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 | 144 ++++++--
clang/lib/AST/ByteCode/Compiler.h | 6 +-
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 | 307 ++++++++++++----
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 | 18 +
clang/test/AST/ByteCode/virtual-bases.cpp | 327 ++++++++++++++++++
clang/test/CXX/drs/cwg16xx.cpp | 16 +-
clang/test/CXX/drs/cwg18xx.cpp | 14 +-
clang/test/CXX/drs/cwg6xx.cpp | 12 +
24 files changed, 933 insertions(+), 177 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 95f9a2f507335..d9a90f4a8f3b9 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -1935,12 +1935,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;
@@ -4767,7 +4761,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 IsCompleteClass) {
assert(E);
assert(R);
// Fields
@@ -4827,13 +4822,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 (IsCompleteClass) {
+ 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;
}
@@ -5598,17 +5602,37 @@ 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 IsCompleteClass) {
if (Val.isStruct()) {
const Record *R = this->getRecord(T);
assert(R);
+
+ assert(R->getNumBases() == Val.getStructNumBases());
+ if (IsCompleteClass)
+ 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))
@@ -5625,25 +5649,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 (IsCompleteClass) {
+ 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;
@@ -7042,6 +7064,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.
@@ -7061,10 +7084,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.
@@ -7135,6 +7157,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 the instance pointer 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.
@@ -7203,10 +7258,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);
}
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 0a2e75887711d..cdb0481229e6d 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -321,7 +321,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);
@@ -364,7 +365,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);
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index 92a2ba57e08bf..086035ecd47bc 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -571,7 +571,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 4815828adb613..f91eb106e0344 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1691,6 +1691,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 97d9e7cc14e32..321729d28a2db 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);
@@ -4100,6 +4108,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 b6190554178f3..7805a7742a90e 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 37659eb79e1df..385546e8faa6c 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -945,7 +945,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);
@@ -974,7 +974,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 563d6b3bb0cf9..7354d17db5f9f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1447,6 +1447,13 @@ 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);
@@ -2183,7 +2190,8 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK,
QualType Type, const APValue &Value,
ConstantExprKind Kind,
const FieldDecl *SubobjectDecl,
- CheckedTemporaries &CheckedTemps);
+ CheckedTemporaries &CheckedTemps,
+ bool IsCompleteClass = 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 +2436,8 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK,
QualType Type, const APValue &Value,
ConstantExprKind Kind,
const FieldDecl *SubobjectDecl,
- CheckedTemporaries &CheckedTemps) {
+ CheckedTemporaries &CheckedTemps,
+ bool IsCompleteClass) {
if (!Value.hasValue()) {
if (SubobjectDecl) {
Info.FFDiag(DiagLoc, diag::note_constexpr_uninitialized)
@@ -2474,6 +2483,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 +2494,7 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK,
}
if (!CheckEvaluationResult(CERK, Info, DiagLoc, BS.getType(), BaseValue,
Kind, /*SubobjectDecl=*/nullptr,
- CheckedTemps))
+ CheckedTemps, /*IsCompleteClass=*/false))
return false;
++BaseIndex;
}
@@ -2497,6 +2508,27 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK,
I, CheckedTemps))
return false;
}
+
+ if (IsCompleteClass) {
+ 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, /*IsCompleteClass=*/false))
+ return false;
+ ++BaseIndex;
+ }
+ }
+ }
}
if (Value.isLValue() &&
@@ -3144,11 +3176,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) {
@@ -3517,9 +3565,21 @@ 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");
@@ -4382,7 +4442,14 @@ 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));
}
@@ -5445,7 +5512,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 IsCompleteClass = true) {
bool Success = true;
// If there is already a value present don't overwrite it.
@@ -5461,15 +5529,24 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) {
Result = APValue((const FieldDecl *)nullptr);
return true;
}
+
+ // bases() includes directly specified virtual bases as well.
+ unsigned NonVirtualBases =
+ llvm::count_if(RD->bases(), [](auto &B) { return !B.isVirtual(); });
Result =
- APValue(APValue::UninitStruct(), RD->getNumBases(), RD->getNumFields());
+ APValue(APValue::UninitStruct(), NonVirtualBases, RD->getNumFields(),
+ IsCompleteClass ? 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 +5554,20 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) {
Success &= handleDefaultInitValue(
I->getType(), Result.getStructField(I->getFieldIndex()));
}
+
+ if (IsCompleteClass) {
+ 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 +5577,6 @@ static bool handleDefaultInitValue(QualType T, APValue &Result) {
if (Result.hasArrayFiller())
Success &=
handleDefaultInitValue(AT->getElementType(), Result.getArrayFiller());
-
return Success;
}
@@ -6503,15 +6593,12 @@ static std::optional<DynamicType> ComputeDynamicType(EvalInfo &Info,
if (This.Designator.Invalid)
return std::nullopt;
- // Refuse to compute a dynamic type in the presence of virtual bases. This
- // 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
- // bases, and will need modifications if this restriction is relaxed.
+ // Refuse to compute a dynamic type in the presence of virtual bases
+ // before C++26. This shouldn't happen other than in constant-folding
+ // situations, since literal types can't have virtual bases.
const CXXRecordDecl *Class =
This.Designator.MostDerivedType->getAsCXXRecordDecl();
- if (!Class || Class->getNumVBases()) {
+ if (!Class || (!Info.getLangOpts().CPlusPlus26 && Class->getNumVBases())) {
Info.FFDiag(E);
return std::nullopt;
}
@@ -6645,10 +6732,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");
}
@@ -7064,17 +7159,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;
}
@@ -7123,10 +7241,13 @@ 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);
}
@@ -7139,9 +7260,10 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
bool Success = true;
unsigned BasesSeen = 0;
-#ifndef NDEBUG
- CXXRecordDecl::base_class_const_iterator BaseIt = RD->bases_begin();
-#endif
+ unsigned VirtualBasesSeen = 0;
+ unsigned NonVirtualBases =
+ llvm::count_if(RD->bases(), [](auto &B) { return !B.isVirtual(); });
+
CXXRecordDecl::field_iterator FieldIt = RD->field_begin();
auto SkipToField = [&](FieldDecl *FD, bool Indirect) {
// We might be initializing the same field again if this is an indirect
@@ -7152,7 +7274,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())
@@ -7171,18 +7293,23 @@ 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;
@@ -7268,9 +7395,10 @@ 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()) {
@@ -7289,22 +7417,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
@@ -7368,7 +7483,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;
}
@@ -7407,10 +7522,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
@@ -7452,11 +7570,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();
@@ -7467,11 +7587,32 @@ 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;
@@ -11108,8 +11249,15 @@ 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,
- RD->getNumFields());
+
+ 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);
@@ -11117,7 +11265,10 @@ static bool HandleClassZeroInitialization(EvalInfo &Info, const Expr *E,
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))
@@ -11125,6 +11276,7 @@ static bool HandleClassZeroInitialization(EvalInfo &Info, const Expr *E,
if (!HandleClassZeroInitialization(Info, E, Base, Subobject,
Result.getStructBase(Index)))
return false;
+ ++Index;
}
}
@@ -11143,6 +11295,22 @@ 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;
}
@@ -11168,9 +11336,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/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 c645d96da5c00..c1aa0dc95b50a 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 fb40ffde00f3d..8477464c3ed0e 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..089103077e4a8
--- /dev/null
+++ b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp
@@ -0,0 +1,18 @@
+// 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 {
+ 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..9b9b8afbeb0f0
--- /dev/null
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -0,0 +1,327 @@
+// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -verify %s
+// RUN: %clang_cc1 -std=c++26 -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 at -1 {{non-constexpr declaration of 'D' follows constexpr declaration}}
// since-cxx23-note@#cwg1658-D {{previous declaration is here}}
diff --git a/clang/test/CXX/drs/cwg18xx.cpp b/clang/test/CXX/drs/cwg18xx.cpp
index f0424a1021abc..646b82eb40fd7 100644
--- a/clang/test/CXX/drs/cwg18xx.cpp
+++ b/clang/test/CXX/drs/cwg18xx.cpp
@@ -1,9 +1,9 @@
// RUN: %clang_cc1 -std=c++98 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-14,cxx98
-// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,cxx98-14,cxx11-17,since-cxx11,cxx11
-// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,since-cxx14,cxx98-14,cxx11-17,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,since-cxx14,since-cxx17,cxx11-17,since-cxx11,since-cxx14,cxx17
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,since-cxx14,since-cxx17,since-cxx20,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx23,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,cxx11-23,cxx98-14,cxx11-17,since-cxx11,cxx11
+// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,cxx11-23,since-cxx14,cxx98-14,cxx11-17,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,cxx11-23,since-cxx14,since-cxx17,cxx11-17,since-cxx11,since-cxx14,cxx17
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-20,cxx11-23,since-cxx14,since-cxx17,since-cxx20,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx23,cxx11-23,since-cxx14,since-cxx17,since-cxx20,since-cxx23,since-cxx11,since-cxx14
// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx23,since-cxx11,since-cxx14
#if __cplusplus == 199711L
@@ -502,9 +502,9 @@ namespace cwg1872 { // cwg1872: 9
static_assert(y2 == 0);
#endif
constexpr int z = A<Z>().f();
- // since-cxx11-error at -1 {{constexpr variable 'z' must be initialized by a constant expression}}
+ // cxx11-23-error at -1 {{constexpr variable 'z' must be initialized by a constant expression}}
// cxx11-20-note at -2 {{non-literal type 'A<Z>' cannot be used in a constant expression}}
- // since-cxx23-note at -3 {{cannot construct object of type 'A<cwg1872::Z>' with virtual base class in a constant expression}}
+ // cxx23-note at -3 {{cannot construct object of type 'A<cwg1872::Z>' with virtual base class in a constant expression}}
#endif
} // namespace cwg1872
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 ad2df1f8fbd601e43f82ba3eb81a7ec209a5de72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 30 Jun 2026 06:38:02 +0200
Subject: [PATCH 2/6] bytecode: Fix ctor order
---
clang/lib/AST/ByteCode/Compiler.cpp | 69 ++++++++++----------
clang/test/AST/ByteCode/virtual-bases.cpp | 79 +++++++++++++++++++++++
2 files changed, 112 insertions(+), 36 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index d9a90f4a8f3b9..078083c7ad71a 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7064,8 +7064,39 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
}
unsigned FieldInits = 0;
- bool HaveVirtBases = false;
InitLinkScope<Emitter> InitScope(this, InitLink::This());
+ // First, initialize virtual bases if the records has them.
+ if (R->getNumVirtualBases() > 0) {
+ if (!this->emitThis(Ctor))
+ return false;
+ LabelTy AfterVirtBasesLabel = this->getLabel();
+
+ // If the instance pointer 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;
+ }
+
for (const auto *Init : Ctor->inits()) {
// Scope needed for the initializers.
LocalScope<Emitter> Scope(this, ScopeKind::FullExpression);
@@ -7084,8 +7115,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
assert(BaseDecl);
if (Init->isBaseVirtual()) {
- // See below.
- HaveVirtBases = true;
+ // See above.
continue;
} else {
// Base class initializer.
@@ -7157,39 +7187,6 @@ 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 the instance pointer 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.
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp
index 9b9b8afbeb0f0..eb6b88aef97ea 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -243,8 +243,87 @@ namespace DtorOrder {
results[4] == R_A;
}
static_assert(foo() == 1);
+}
+
+
+namespace CtorOrder {
+ 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) {
+ *(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) {
+ *(results + i) = R_B;
+ ++i;
+ }
+ };
+
+
+ struct G {
+ int *results;
+ int &i;
+ constexpr G(int *results, int &i) : results(results), i(i) {
+ *(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) {
+ *(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) {
+ *(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_A &&
+ results[1] == R_B &&
+ results[2] == R_G &&
+ results[3] == R_F &&
+ results[4] == R_C;
+ }
+ static_assert(foo() == 1);
}
namespace ImplicitValueInit {
>From 4d220aa2e3077f5767939b5633ec5549d73df997 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 30 Jun 2026 06:45:03 +0200
Subject: [PATCH 3/6] bytecode: Factor CheckFieldsInitialized base checks into
lambda
---
clang/lib/AST/ByteCode/EvaluationResult.cpp | 63 +++++++++------------
1 file changed, 28 insertions(+), 35 deletions(-)
diff --git a/clang/lib/AST/ByteCode/EvaluationResult.cpp b/clang/lib/AST/ByteCode/EvaluationResult.cpp
index d95c1e19889e8..c137e34e798fa 100644
--- a/clang/lib/AST/ByteCode/EvaluationResult.cpp
+++ b/clang/lib/AST/ByteCode/EvaluationResult.cpp
@@ -28,7 +28,7 @@ static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc,
static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc,
PtrView BasePtr, const Record *R,
- bool Toplevel = true);
+ bool IsCompleteClass = true);
static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc,
PtrView BasePtr) {
@@ -68,7 +68,7 @@ static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc,
static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc,
PtrView BasePtr, const Record *R,
- bool Toplevel) {
+ bool IsCompleteClass) {
assert(R);
bool Result = true;
// Check all fields of this record are initialized.
@@ -96,43 +96,36 @@ static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc,
}
}
- // Check Fields in all bases
+ auto diagnoseBase = [&](const Record::Base &B, unsigned Index) -> bool {
+ const Descriptor *Desc = BasePtr.getDeclDesc();
+ if (const auto *CD = dyn_cast_if_present<CXXRecordDecl>(R->getDecl())) {
+ const auto &BS = *std::next(CD->bases_begin(), Index);
+ 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;
+ };
+
+ // Check Fields in all bases.
for (auto [I, B] : llvm::enumerate(R->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);
+ if (!P.isInitialized())
+ return diagnoseBase(B, I);
+ Result &= CheckFieldsInitialized(S, Loc, P, B.R, /*IsCompleteClass=*/false);
}
- if (Toplevel) {
+ // And virtual bases.
+ if (IsCompleteClass) {
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);
+ if (!P.isInitialized())
+ return diagnoseBase(B, I);
+ Result &=
+ CheckFieldsInitialized(S, Loc, P, B.R, /*IsCompleteClass=*/false);
}
}
@@ -179,7 +172,7 @@ static bool isOrHasPtr(const Descriptor *D) {
}
static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks,
- bool Toplevel = true) {
+ bool IsCompleteClass = true) {
auto isUsefulPtr = [](const Pointer &P) -> bool {
return P.isLive() && P.isBlockPointer() && !P.isZero() && !P.isDummy() &&
P.isDereferencable() && !P.isUnknownSizeArray() && !P.isOnePastEnd();
@@ -213,7 +206,7 @@ static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks,
collectBlocks(FieldPtr, Blocks);
}
- if (Toplevel) {
+ if (IsCompleteClass) {
for (const Record::Base &B : R->virtual_bases()) {
if (!B.R->hasPtrField())
continue;
>From 771b5399df3eb8ee130b22c667740d97259bd85f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 30 Jun 2026 08:52:04 +0200
Subject: [PATCH 4/6] Test offsets
---
.../AST/ByteCode/virtual-bases-codegen.cpp | 25 ++++++++
clang/test/AST/ByteCode/virtual-bases.cpp | 64 ++++++++++++++++---
2 files changed, 80 insertions(+), 9 deletions(-)
diff --git a/clang/test/AST/ByteCode/virtual-bases-codegen.cpp b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp
index 089103077e4a8..2cce18507f597 100644
--- a/clang/test/AST/ByteCode/virtual-bases-codegen.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases-codegen.cpp
@@ -16,3 +16,28 @@ struct C : B {
// CHECK: @c1 = global { ptr, i32 } { {{.*}} i32 128 }
C c1 = C(12);
+
+// CHECK: @a1 = global ptr getelementptr (i8, {{.*}}, i64 8)
+A* a1 = (A*)&c1;
+
+struct X {
+ char x[4] = {};
+};
+struct Y {
+ char y[4] = {};
+};
+struct Z : virtual X, virtual Y {
+ char z[4] = {};
+};
+
+struct W : Z {};
+
+
+// CHECK @w = global {{.*}}
+W w;
+// CHECK: @z = constant ptr @w
+Z &z = w;
+// CHECK: @x = constant ptr getelementptr (i8, ptr @w, i64 12)
+X &x = z;
+// CHECK: @y = constant ptr getelementptr (i8, ptr @w, i64 16)
+Y &y = z;
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp
index eb6b88aef97ea..dde93665c495b 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -verify %s
-// RUN: %clang_cc1 -std=c++26 -verify %s
+// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -verify=both,expected %s
+// RUN: %clang_cc1 -std=c++26 -verify=both,ref %s
namespace PaperSample {
struct Superbase {
@@ -135,25 +135,25 @@ namespace DynamicCast {
namespace UninitializedFields {
struct A {
- int a; // expected-note {{declared here}}
+ int a; // both-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}}}
+ constexpr B b{}; // both-error {{must be initialized by a constant expression}} \
+ // both-note {{subobject 'a' is not initialized}}}
struct X {
int *p;
constexpr X() {
- p = new int; // expected-note {{heap allocation performed here}}
+ p = new int; // both-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}}
+ constexpr Y y; // both-error {{must be initialized by a constant expression}} \
+ // both-note {{pointer to heap-allocated object is not a constant expression}}
}
@@ -332,7 +332,7 @@ namespace ImplicitValueInit {
int a = 10;
int b;
};
- constexpr Ints2 ints22; // expected-error {{without a user-provided default constructor}}
+ constexpr Ints2 ints22; // both-error {{without a user-provided default constructor}}
static_assert(ints22.m == 0);
}
@@ -404,3 +404,49 @@ namespace VirtCalls {
static_assert(z.foo() == 10);
static_assert(z.bar() == 50);
}
+
+namespace ConstantDestruction {
+ struct V {
+ bool b;
+ constexpr ~V() {
+ __builtin_abort(); // both-note {{subexpression not valid in a constant expression}}
+ }
+ };
+
+ struct X : virtual V { // expected-note {{in call to}}
+ constexpr X() : V(true) {}
+ };
+ constexpr X x; // both-error {{constexpr variable 'x' must have constant destruction}} \
+ // both-note {{in call to}} \
+ // ref-note {{in call to}}
+}
+
+namespace Offsets {
+ struct A { int a; };
+ struct X { char x[2] = {}; };
+
+ struct B : virtual A {
+ int b;
+ constexpr B() : A(127), b(123) {}
+ };
+
+ struct C : virtual X, B {
+ int c;
+ constexpr C(int) : c(100), B(), A(128) {}
+ };
+
+ constexpr C c{12};
+ static_assert( (fold((char*)&c.c) - fold((char*)&c)) == 12);
+ static_assert( (fold((char*)&c.b) - fold((char*)&c)) == 8);
+ static_assert( (fold((char*)&c.a) - fold((char*)&c)) == 20);
+ static_assert( (fold((char*)&c.x) - fold((char*)&c)) == 16);
+
+ static_assert( (fold((char*)&c.c) != fold((char*)&c)));
+ static_assert( (fold((char*)&c.a) != fold((char*)&c)));
+ static_assert( (fold((char*)&c.a) != fold((char*)&c.c)));
+ static_assert( (fold((char*)&c.x) != fold((char*)&c.c)));
+ static_assert( (fold((char*)&c.x) != fold((char*)&c)));
+ static_assert( (fold((char*)&c.b) != fold((char*)&c)));
+ static_assert( (fold((char*)&c.b) != fold((char*)&c.a)));
+ static_assert( (fold((char*)&c.b) != fold((char*)&c.x)));
+}
>From 0b83acdc2dda6b7c533033d1494188d97434898d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 30 Jun 2026 09:17:38 +0200
Subject: [PATCH 5/6] Add missing macro
---
clang/test/AST/ByteCode/virtual-bases.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp
index dde93665c495b..408730222dead 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -1,6 +1,7 @@
// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -verify=both,expected %s
// RUN: %clang_cc1 -std=c++26 -verify=both,ref %s
+#define fold(x) (__builtin_constant_p(x) ? (x) : (x))
namespace PaperSample {
struct Superbase {
int a = 10;
>From 3f4badbc17b0fe55c8f3ec96122656206951285b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 30 Jun 2026 09:54:13 +0200
Subject: [PATCH 6/6] Fix Windows CI
---
clang/test/AST/ByteCode/virtual-bases.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/test/AST/ByteCode/virtual-bases.cpp b/clang/test/AST/ByteCode/virtual-bases.cpp
index 408730222dead..4bf68a6d8e082 100644
--- a/clang/test/AST/ByteCode/virtual-bases.cpp
+++ b/clang/test/AST/ByteCode/virtual-bases.cpp
@@ -437,10 +437,17 @@ namespace Offsets {
};
constexpr C c{12};
+#if !defined(_WIN32)
static_assert( (fold((char*)&c.c) - fold((char*)&c)) == 12);
static_assert( (fold((char*)&c.b) - fold((char*)&c)) == 8);
static_assert( (fold((char*)&c.a) - fold((char*)&c)) == 20);
static_assert( (fold((char*)&c.x) - fold((char*)&c)) == 16);
+# else
+ static_assert( (fold((char*)&c.c) - fold((char*)&c)) == 16);
+ static_assert( (fold((char*)&c.b) - fold((char*)&c)) == 8);
+ static_assert( (fold((char*)&c.a) - fold((char*)&c)) == 28);
+ static_assert( (fold((char*)&c.x) - fold((char*)&c)) == 14);
+#endif
static_assert( (fold((char*)&c.c) != fold((char*)&c)));
static_assert( (fold((char*)&c.a) != fold((char*)&c)));
More information about the cfe-commits
mailing list