Skip to content

Commit 376b3f7

Browse files
authored
[clang][bytecode] Devirtualize calls during con- and destruction (#147685)
When compiliung compiling a ctor or dtor, we need to devirtualize the virtual function calls so we always call the implementation of the current class.
1 parent 9e132f5 commit 376b3f7

File tree

4 files changed

+111
-9
lines changed

4 files changed

+111
-9
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4912,6 +4912,18 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
49124912
return true;
49134913
}
49144914

4915+
static const Expr *stripDerivedToBaseCasts(const Expr *E) {
4916+
if (const auto *PE = dyn_cast<ParenExpr>(E))
4917+
return stripDerivedToBaseCasts(PE->getSubExpr());
4918+
4919+
if (const auto *CE = dyn_cast<CastExpr>(E);
4920+
CE &&
4921+
(CE->getCastKind() == CK_DerivedToBase || CE->getCastKind() == CK_NoOp))
4922+
return stripDerivedToBaseCasts(CE->getSubExpr());
4923+
4924+
return E;
4925+
}
4926+
49154927
template <class Emitter>
49164928
bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
49174929
const FunctionDecl *FuncDecl = E->getDirectCallee();
@@ -4995,6 +5007,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
49955007
}
49965008
}
49975009

5010+
bool Devirtualized = false;
49985011
std::optional<unsigned> CalleeOffset;
49995012
// Add the (optional, implicit) This pointer.
50005013
if (const auto *MC = dyn_cast<CXXMemberCallExpr>(E)) {
@@ -5013,8 +5026,26 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
50135026
return false;
50145027
if (!this->emitGetMemberPtrBase(E))
50155028
return false;
5016-
} else if (!this->visit(MC->getImplicitObjectArgument())) {
5017-
return false;
5029+
} else {
5030+
const auto *InstancePtr = MC->getImplicitObjectArgument();
5031+
if (isa_and_nonnull<CXXDestructorDecl>(CompilingFunction) ||
5032+
isa_and_nonnull<CXXConstructorDecl>(CompilingFunction)) {
5033+
const auto *Stripped = stripDerivedToBaseCasts(InstancePtr);
5034+
if (isa<CXXThisExpr>(Stripped)) {
5035+
FuncDecl =
5036+
cast<CXXMethodDecl>(FuncDecl)->getCorrespondingMethodInClass(
5037+
Stripped->getType()->getPointeeType()->getAsCXXRecordDecl());
5038+
Devirtualized = true;
5039+
if (!this->visit(Stripped))
5040+
return false;
5041+
} else {
5042+
if (!this->visit(InstancePtr))
5043+
return false;
5044+
}
5045+
} else {
5046+
if (!this->visit(InstancePtr))
5047+
return false;
5048+
}
50185049
}
50195050
} else if (const auto *PD =
50205051
dyn_cast<CXXPseudoDestructorExpr>(E->getCallee())) {
@@ -5060,7 +5091,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
50605091

50615092
bool IsVirtual = false;
50625093
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl))
5063-
IsVirtual = MD->isVirtual();
5094+
IsVirtual = !Devirtualized && MD->isVirtual();
50645095

50655096
// In any case call the function. The return value will end up on the stack
50665097
// and if the function has RVO, we already have the pointer on the stack to
@@ -6027,6 +6058,8 @@ bool Compiler<Emitter>::visitFunc(const FunctionDecl *F) {
60276058
// Classify the return type.
60286059
ReturnType = this->classify(F->getReturnType());
60296060

6061+
this->CompilingFunction = F;
6062+
60306063
if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(F))
60316064
return this->compileConstructor(Ctor);
60326065
if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(F))

clang/lib/AST/ByteCode/Compiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
448448
OptLabelTy ContinueLabel;
449449
/// Default case label.
450450
OptLabelTy DefaultLabel;
451+
452+
const FunctionDecl *CompilingFunction = nullptr;
451453
};
452454

453455
extern template class Compiler<ByteCodeEmitter>;

clang/test/AST/ByteCode/cxx20.cpp

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -fcxx-exceptions -fexperimental-new-constant-interpreter -std=c++20 -verify=both,expected -fcxx-exceptions %s -DNEW_INTERP
2-
// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref -fcxx-exceptions %s
1+
// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,expected -fcxx-exceptions %s -DNEW_INTERP -fexperimental-new-constant-interpreter
2+
// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref -fcxx-exceptions %s
33

44
void test_alignas_operand() {
55
alignas(8) char dummy;
@@ -1015,3 +1015,72 @@ namespace OnePastEndDtor {
10151015
(&a+1)->~A(); // both-note {{destruction of dereferenced one-past-the-end pointer}}
10161016
}
10171017
}
1018+
1019+
namespace Virtual {
1020+
struct NonZeroOffset { int padding = 123; };
1021+
1022+
constexpr void assert(bool b) { if (!b) throw 0; }
1023+
1024+
// Ensure that we pick the right final overrider during construction.
1025+
struct A {
1026+
virtual constexpr char f() const { return 'A'; }
1027+
char a = f();
1028+
constexpr ~A() { assert(f() == 'A'); }
1029+
};
1030+
struct NoOverrideA : A {};
1031+
struct B : NonZeroOffset, NoOverrideA {
1032+
virtual constexpr char f() const { return 'B'; }
1033+
char b = f();
1034+
constexpr ~B() { assert(f() == 'B'); }
1035+
};
1036+
struct NoOverrideB : B {};
1037+
struct C : NonZeroOffset, A {
1038+
virtual constexpr char f() const { return 'C'; }
1039+
A *pba;
1040+
char c = ((A*)this)->f();
1041+
char ba = pba->f();
1042+
constexpr C(A *pba) : pba(pba) {}
1043+
constexpr ~C() { assert(f() == 'C'); }
1044+
};
1045+
struct D : NonZeroOffset, NoOverrideB, C { // both-warning {{inaccessible}}
1046+
virtual constexpr char f() const { return 'D'; }
1047+
char d = f();
1048+
constexpr D() : C((B*)this) {}
1049+
constexpr ~D() { assert(f() == 'D'); }
1050+
};
1051+
constexpr int n = (D(), 0);
1052+
1053+
constexpr D d;
1054+
static_assert(((B&)d).a == 'A');
1055+
static_assert(((C&)d).a == 'A');
1056+
static_assert(d.b == 'B');
1057+
static_assert(d.c == 'C');
1058+
// During the construction of C, the dynamic type of B's A is B.
1059+
static_assert(d.ba == 'B'); // expected-error {{failed}} \
1060+
// expected-note {{expression evaluates to}}
1061+
static_assert(d.d == 'D');
1062+
static_assert(d.f() == 'D');
1063+
constexpr const A &a = (B&)d;
1064+
constexpr const B &b = d;
1065+
static_assert(a.f() == 'D');
1066+
static_assert(b.f() == 'D');
1067+
1068+
1069+
class K {
1070+
public:
1071+
int a = f();
1072+
1073+
virtual constexpr int f() { return 10; }
1074+
};
1075+
1076+
class L : public K {
1077+
public:
1078+
int b = f();
1079+
int c =((L*)this)->f();
1080+
};
1081+
1082+
constexpr L l;
1083+
static_assert(l.a == 10);
1084+
static_assert(l.b == 10);
1085+
static_assert(l.c == 10);
1086+
}

clang/test/AST/ByteCode/records.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,10 +759,8 @@ namespace CtorDtor {
759759
static_assert(B.i == 1 && B.j == 1, "");
760760

761761
constexpr Derived D;
762-
static_assert(D.i == 1, ""); // expected-error {{static assertion failed}} \
763-
// expected-note {{2 == 1}}
764-
static_assert(D.j == 1, ""); // expected-error {{static assertion failed}} \
765-
// expected-note {{2 == 1}}
762+
static_assert(D.i == 1, "");
763+
static_assert(D.j == 1, "");
766764

767765
constexpr Derived2 D2; // ref-error {{must be initialized by a constant expression}} \
768766
// ref-note {{in call to 'Derived2()'}} \

0 commit comments

Comments
 (0)