Skip to content

Commit 9d525bf

Browse files
committed
Optimize emission of dynamic_cast to final classes.
- When the destination is a final class type that does not derive from the source type, the cast always fails and is now emitted as a null pointer or call to __cxa_bad_cast. - When the destination is a final class type that does derive from the source type, emit a direct comparison against the corresponding base class vptr value(s). There may be more than one such value in the case of multiple inheritance; check them all. For now, this is supported only for the Itanium ABI. I expect the same thing is possible for the MS ABI too, but I don't know what guarantees are made about vfptr uniqueness. Reviewed By: rjmccall Differential Revision: https://reviews.llvm.org/D154658
1 parent 57bd882 commit 9d525bf

File tree

14 files changed

+355
-38
lines changed

14 files changed

+355
-38
lines changed

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ CODEGENOPT(Dwarf64 , 1, 0) ///< -gdwarf64.
3636
CODEGENOPT(Dwarf32 , 1, 1) ///< -gdwarf32.
3737
CODEGENOPT(PreserveAsmComments, 1, 1) ///< -dA, -fno-preserve-as-comments.
3838
CODEGENOPT(AssumeSaneOperatorNew , 1, 1) ///< implicit __attribute__((malloc)) operator new
39+
CODEGENOPT(AssumeUniqueVTables , 1, 1) ///< Assume a class has only one vtable.
3940
CODEGENOPT(Autolink , 1, 1) ///< -fno-autolink
4041
CODEGENOPT(ObjCAutoRefCountExceptions , 1, 0) ///< Whether ARC should be EH-safe.
4142
CODEGENOPT(Backchain , 1, 0) ///< -mbackchain

clang/include/clang/Driver/Options.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,13 @@ def static_libsan : Flag<["-"], "static-libsan">,
12531253
def : Flag<["-"], "shared-libasan">, Alias<shared_libsan>;
12541254
def fasm : Flag<["-"], "fasm">, Group<f_Group>;
12551255

1256+
defm assume_unique_vtables : BoolFOption<"assume-unique-vtables",
1257+
CodeGenOpts<"AssumeUniqueVTables">, DefaultTrue,
1258+
PosFlag<SetTrue>,
1259+
NegFlag<SetFalse, [CC1Option],
1260+
"Disable optimizations based on vtable pointer identity">,
1261+
BothFlags<[CoreOption]>>;
1262+
12561263
def fassume_sane_operator_new : Flag<["-"], "fassume-sane-operator-new">, Group<f_Group>;
12571264
def fastcp : Flag<["-"], "fastcp">, Group<f_Group>;
12581265
def fastf : Flag<["-"], "fastf">, Group<f_Group>;

clang/lib/AST/ExprCXX.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -767,29 +767,35 @@ CXXDynamicCastExpr *CXXDynamicCastExpr::CreateEmpty(const ASTContext &C,
767767
/// struct C { };
768768
///
769769
/// C *f(B* b) { return dynamic_cast<C*>(b); }
770-
bool CXXDynamicCastExpr::isAlwaysNull() const
771-
{
770+
bool CXXDynamicCastExpr::isAlwaysNull() const {
771+
if (isValueDependent() || getCastKind() != CK_Dynamic)
772+
return false;
773+
772774
QualType SrcType = getSubExpr()->getType();
773775
QualType DestType = getType();
774776

775-
if (const auto *SrcPTy = SrcType->getAs<PointerType>()) {
776-
SrcType = SrcPTy->getPointeeType();
777-
DestType = DestType->castAs<PointerType>()->getPointeeType();
778-
}
779-
780-
if (DestType->isVoidType())
777+
if (DestType->isVoidPointerType())
781778
return false;
782779

783-
const auto *SrcRD =
784-
cast<CXXRecordDecl>(SrcType->castAs<RecordType>()->getDecl());
780+
if (DestType->isPointerType()) {
781+
SrcType = SrcType->getPointeeType();
782+
DestType = DestType->getPointeeType();
783+
}
785784

786-
if (!SrcRD->hasAttr<FinalAttr>())
787-
return false;
785+
const auto *SrcRD = SrcType->getAsCXXRecordDecl();
786+
const auto *DestRD = DestType->getAsCXXRecordDecl();
787+
assert(SrcRD && DestRD);
788788

789-
const auto *DestRD =
790-
cast<CXXRecordDecl>(DestType->castAs<RecordType>()->getDecl());
789+
if (SrcRD->isEffectivelyFinal()) {
790+
assert(!SrcRD->isDerivedFrom(DestRD) &&
791+
"upcasts should not use CK_Dynamic");
792+
return true;
793+
}
791794

792-
return !DestRD->isDerivedFrom(SrcRD);
795+
if (DestRD->isEffectivelyFinal() && !DestRD->isDerivedFrom(SrcRD))
796+
return true;
797+
798+
return false;
793799
}
794800

795801
CXXReinterpretCastExpr *

clang/lib/CodeGen/CGCXXABI.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ class CGCXXABI {
287287

288288
virtual bool shouldDynamicCastCallBeNullChecked(bool SrcIsPtr,
289289
QualType SrcRecordTy) = 0;
290+
virtual bool shouldEmitExactDynamicCast(QualType DestRecordTy) = 0;
290291

291292
virtual llvm::Value *emitDynamicCastCall(CodeGenFunction &CGF, Address Value,
292293
QualType SrcRecordTy,
@@ -298,6 +299,15 @@ class CGCXXABI {
298299
Address Value,
299300
QualType SrcRecordTy) = 0;
300301

302+
/// Emit a dynamic_cast from SrcRecordTy to DestRecordTy. The cast fails if
303+
/// the dynamic type of Value is not exactly DestRecordTy.
304+
virtual llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address Value,
305+
QualType SrcRecordTy,
306+
QualType DestTy,
307+
QualType DestRecordTy,
308+
llvm::BasicBlock *CastSuccess,
309+
llvm::BasicBlock *CastFail) = 0;
310+
301311
virtual bool EmitBadCastCall(CodeGenFunction &CGF) = 0;
302312

303313
virtual llvm::Value *GetVirtualBaseClassOffset(CodeGenFunction &CGF,

clang/lib/CodeGen/CGExprCXX.cpp

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,8 +2226,8 @@ static llvm::Value *EmitDynamicCastToNull(CodeGenFunction &CGF,
22262226
if (!CGF.CGM.getCXXABI().EmitBadCastCall(CGF))
22272227
return nullptr;
22282228

2229-
CGF.EmitBlock(CGF.createBasicBlock("dynamic_cast.end"));
2230-
return llvm::UndefValue::get(DestLTy);
2229+
CGF.Builder.ClearInsertionPoint();
2230+
return llvm::PoisonValue::get(DestLTy);
22312231
}
22322232

22332233
llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
@@ -2240,17 +2240,16 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
22402240
// C++ [expr.dynamic.cast]p7:
22412241
// If T is "pointer to cv void," then the result is a pointer to the most
22422242
// derived object pointed to by v.
2243-
const PointerType *DestPTy = DestTy->getAs<PointerType>();
2244-
2245-
bool isDynamicCastToVoid;
2243+
bool IsDynamicCastToVoid = DestTy->isVoidPointerType();
22462244
QualType SrcRecordTy;
22472245
QualType DestRecordTy;
2248-
if (DestPTy) {
2249-
isDynamicCastToVoid = DestPTy->getPointeeType()->isVoidType();
2246+
if (IsDynamicCastToVoid) {
2247+
SrcRecordTy = SrcTy->getPointeeType();
2248+
// No DestRecordTy.
2249+
} else if (const PointerType *DestPTy = DestTy->getAs<PointerType>()) {
22502250
SrcRecordTy = SrcTy->castAs<PointerType>()->getPointeeType();
22512251
DestRecordTy = DestPTy->getPointeeType();
22522252
} else {
2253-
isDynamicCastToVoid = false;
22542253
SrcRecordTy = SrcTy;
22552254
DestRecordTy = DestTy->castAs<ReferenceType>()->getPointeeType();
22562255
}
@@ -2263,18 +2262,29 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
22632262
EmitTypeCheck(TCK_DynamicOperation, DCE->getExprLoc(), ThisAddr.getPointer(),
22642263
SrcRecordTy);
22652264

2266-
if (DCE->isAlwaysNull())
2267-
if (llvm::Value *T = EmitDynamicCastToNull(*this, DestTy))
2265+
if (DCE->isAlwaysNull()) {
2266+
if (llvm::Value *T = EmitDynamicCastToNull(*this, DestTy)) {
2267+
// Expression emission is expected to retain a valid insertion point.
2268+
if (!Builder.GetInsertBlock())
2269+
EmitBlock(createBasicBlock("dynamic_cast.unreachable"));
22682270
return T;
2271+
}
2272+
}
22692273

22702274
assert(SrcRecordTy->isRecordType() && "source type must be a record type!");
22712275

2276+
// If the destination is effectively final, the cast succeeds if and only
2277+
// if the dynamic type of the pointer is exactly the destination type.
2278+
bool IsExact = !IsDynamicCastToVoid &&
2279+
DestRecordTy->getAsCXXRecordDecl()->isEffectivelyFinal() &&
2280+
CGM.getCXXABI().shouldEmitExactDynamicCast(DestRecordTy);
2281+
22722282
// C++ [expr.dynamic.cast]p4:
22732283
// If the value of v is a null pointer value in the pointer case, the result
22742284
// is the null pointer value of type T.
22752285
bool ShouldNullCheckSrcValue =
2276-
CGM.getCXXABI().shouldDynamicCastCallBeNullChecked(SrcTy->isPointerType(),
2277-
SrcRecordTy);
2286+
IsExact || CGM.getCXXABI().shouldDynamicCastCallBeNullChecked(
2287+
SrcTy->isPointerType(), SrcRecordTy);
22782288

22792289
llvm::BasicBlock *CastNull = nullptr;
22802290
llvm::BasicBlock *CastNotNull = nullptr;
@@ -2290,29 +2300,38 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
22902300
}
22912301

22922302
llvm::Value *Value;
2293-
if (isDynamicCastToVoid) {
2303+
if (IsDynamicCastToVoid) {
22942304
Value = CGM.getCXXABI().emitDynamicCastToVoid(*this, ThisAddr, SrcRecordTy);
2305+
} else if (IsExact) {
2306+
// If the destination type is effectively final, this pointer points to the
2307+
// right type if and only if its vptr has the right value.
2308+
Value = CGM.getCXXABI().emitExactDynamicCast(
2309+
*this, ThisAddr, SrcRecordTy, DestTy, DestRecordTy, CastEnd, CastNull);
22952310
} else {
22962311
assert(DestRecordTy->isRecordType() &&
22972312
"destination type must be a record type!");
22982313
Value = CGM.getCXXABI().emitDynamicCastCall(*this, ThisAddr, SrcRecordTy,
22992314
DestTy, DestRecordTy, CastEnd);
2300-
CastNotNull = Builder.GetInsertBlock();
23012315
}
2316+
CastNotNull = Builder.GetInsertBlock();
23022317

2318+
llvm::Value *NullValue = nullptr;
23032319
if (ShouldNullCheckSrcValue) {
23042320
EmitBranch(CastEnd);
23052321

23062322
EmitBlock(CastNull);
2323+
NullValue = EmitDynamicCastToNull(*this, DestTy);
2324+
CastNull = Builder.GetInsertBlock();
2325+
23072326
EmitBranch(CastEnd);
23082327
}
23092328

23102329
EmitBlock(CastEnd);
23112330

2312-
if (ShouldNullCheckSrcValue) {
2331+
if (CastNull) {
23132332
llvm::PHINode *PHI = Builder.CreatePHI(Value->getType(), 2);
23142333
PHI->addIncoming(Value, CastNotNull);
2315-
PHI->addIncoming(llvm::Constant::getNullValue(Value->getType()), CastNull);
2334+
PHI->addIncoming(NullValue, CastNull);
23162335

23172336
Value = PHI;
23182337
}

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7152,9 +7152,7 @@ llvm::Constant *CodeGenModule::GetAddrOfRTTIDescriptor(QualType Ty,
71527152
// Return a bogus pointer if RTTI is disabled, unless it's for EH.
71537153
// FIXME: should we even be calling this method if RTTI is disabled
71547154
// and it's not for EH?
7155-
if ((!ForEH && !getLangOpts().RTTI) || getLangOpts().CUDAIsDevice ||
7156-
(getLangOpts().OpenMP && getLangOpts().OpenMPIsTargetDevice &&
7157-
getTriple().isNVPTX()))
7155+
if (!shouldEmitRTTI(ForEH))
71587156
return llvm::Constant::getNullValue(GlobalsInt8PtrTy);
71597157

71607158
if (ForEH && Ty->isObjCObjectPointerType() &&

clang/lib/CodeGen/CodeGenModule.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,13 @@ class CodeGenModule : public CodeGenTypeCache {
926926
// Return the function body address of the given function.
927927
llvm::Constant *GetFunctionStart(const ValueDecl *Decl);
928928

929+
// Return whether RTTI information should be emitted for this target.
930+
bool shouldEmitRTTI(bool ForEH = false) {
931+
return (ForEH || getLangOpts().RTTI) && !getLangOpts().CUDAIsDevice &&
932+
!(getLangOpts().OpenMP && getLangOpts().OpenMPIsTargetDevice &&
933+
getTriple().isNVPTX());
934+
}
935+
929936
/// Get the address of the RTTI descriptor for the given type.
930937
llvm::Constant *GetAddrOfRTTIDescriptor(QualType Ty, bool ForEH = false);
931938

clang/lib/CodeGen/ItaniumCXXABI.cpp

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include "llvm/IR/Value.h"
3737
#include "llvm/Support/ScopedPrinter.h"
3838

39+
#include <optional>
40+
3941
using namespace clang;
4042
using namespace CodeGen;
4143

@@ -185,11 +187,56 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
185187
bool shouldDynamicCastCallBeNullChecked(bool SrcIsPtr,
186188
QualType SrcRecordTy) override;
187189

190+
/// Determine whether we know that all instances of type RecordTy will have
191+
/// the same vtable pointer values, that is distinct from all other vtable
192+
/// pointers. While this is required by the Itanium ABI, it doesn't happen in
193+
/// practice in some cases due to language extensions.
194+
bool hasUniqueVTablePointer(QualType RecordTy) {
195+
const CXXRecordDecl *RD = RecordTy->getAsCXXRecordDecl();
196+
197+
// Under -fapple-kext, multiple definitions of the same vtable may be
198+
// emitted.
199+
if (!CGM.getCodeGenOpts().AssumeUniqueVTables ||
200+
getContext().getLangOpts().AppleKext)
201+
return false;
202+
203+
// If the type_info* would be null, the vtable might be merged with that of
204+
// another type.
205+
if (!CGM.shouldEmitRTTI())
206+
return false;
207+
208+
// If there's only one definition of the vtable in the program, it has a
209+
// unique address.
210+
if (!llvm::GlobalValue::isWeakForLinker(CGM.getVTableLinkage(RD)))
211+
return true;
212+
213+
// Even if there are multiple definitions of the vtable, they are required
214+
// by the ABI to use the same symbol name, so should be merged at load
215+
// time. However, if the class has hidden visibility, there can be
216+
// different versions of the class in different modules, and the ABI
217+
// library might treat them as being the same.
218+
if (CGM.GetLLVMVisibility(RD->getVisibility()) !=
219+
llvm::GlobalValue::DefaultVisibility)
220+
return false;
221+
222+
return true;
223+
}
224+
225+
bool shouldEmitExactDynamicCast(QualType DestRecordTy) override {
226+
return hasUniqueVTablePointer(DestRecordTy);
227+
}
228+
188229
llvm::Value *emitDynamicCastCall(CodeGenFunction &CGF, Address Value,
189230
QualType SrcRecordTy, QualType DestTy,
190231
QualType DestRecordTy,
191232
llvm::BasicBlock *CastEnd) override;
192233

234+
llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address ThisAddr,
235+
QualType SrcRecordTy, QualType DestTy,
236+
QualType DestRecordTy,
237+
llvm::BasicBlock *CastSuccess,
238+
llvm::BasicBlock *CastFail) override;
239+
193240
llvm::Value *emitDynamicCastToVoid(CodeGenFunction &CGF, Address Value,
194241
QualType SrcRecordTy) override;
195242

@@ -1202,7 +1249,8 @@ void ItaniumCXXABI::emitVirtualObjectDelete(CodeGenFunction &CGF,
12021249
// Track back to entry -2 and pull out the offset there.
12031250
llvm::Value *OffsetPtr = CGF.Builder.CreateConstInBoundsGEP1_64(
12041251
CGF.IntPtrTy, VTable, -2, "complete-offset.ptr");
1205-
llvm::Value *Offset = CGF.Builder.CreateAlignedLoad(CGF.IntPtrTy, OffsetPtr, CGF.getPointerAlign());
1252+
llvm::Value *Offset = CGF.Builder.CreateAlignedLoad(CGF.IntPtrTy, OffsetPtr,
1253+
CGF.getPointerAlign());
12061254

12071255
// Apply the offset.
12081256
llvm::Value *CompletePtr =
@@ -1463,6 +1511,84 @@ llvm::Value *ItaniumCXXABI::emitDynamicCastCall(
14631511
return Value;
14641512
}
14651513

1514+
llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
1515+
CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
1516+
QualType DestTy, QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
1517+
llvm::BasicBlock *CastFail) {
1518+
ASTContext &Context = getContext();
1519+
1520+
// Find all the inheritance paths.
1521+
const CXXRecordDecl *SrcDecl = SrcRecordTy->getAsCXXRecordDecl();
1522+
const CXXRecordDecl *DestDecl = DestRecordTy->getAsCXXRecordDecl();
1523+
CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
1524+
/*DetectVirtual=*/false);
1525+
(void)DestDecl->isDerivedFrom(SrcDecl, Paths);
1526+
1527+
// Find an offset within `DestDecl` where a `SrcDecl` instance and its vptr
1528+
// might appear.
1529+
std::optional<CharUnits> Offset;
1530+
for (const CXXBasePath &Path : Paths) {
1531+
// dynamic_cast only finds public inheritance paths.
1532+
if (Path.Access != AS_public)
1533+
continue;
1534+
1535+
CharUnits PathOffset;
1536+
for (const CXXBasePathElement &PathElement : Path) {
1537+
// Find the offset along this inheritance step.
1538+
const CXXRecordDecl *Base =
1539+
PathElement.Base->getType()->getAsCXXRecordDecl();
1540+
if (PathElement.Base->isVirtual()) {
1541+
// For a virtual base class, we know that the derived class is exactly
1542+
// DestDecl, so we can use the vbase offset from its layout.
1543+
const ASTRecordLayout &L = Context.getASTRecordLayout(DestDecl);
1544+
PathOffset = L.getVBaseClassOffset(Base);
1545+
} else {
1546+
const ASTRecordLayout &L =
1547+
Context.getASTRecordLayout(PathElement.Class);
1548+
PathOffset += L.getBaseClassOffset(Base);
1549+
}
1550+
}
1551+
1552+
if (!Offset)
1553+
Offset = PathOffset;
1554+
else if (Offset != PathOffset) {
1555+
// Base appears in at least two different places. Find the most-derived
1556+
// object and see if it's a DestDecl. Note that the most-derived object
1557+
// must be at least as aligned as this base class subobject, and must
1558+
// have a vptr at offset 0.
1559+
ThisAddr = Address(emitDynamicCastToVoid(CGF, ThisAddr, SrcRecordTy),
1560+
CGF.VoidPtrTy, ThisAddr.getAlignment());
1561+
SrcDecl = DestDecl;
1562+
Offset = CharUnits::Zero();
1563+
break;
1564+
}
1565+
}
1566+
1567+
if (!Offset) {
1568+
// If there are no public inheritance paths, the cast always fails.
1569+
CGF.EmitBranch(CastFail);
1570+
return llvm::PoisonValue::get(CGF.VoidPtrTy);
1571+
}
1572+
1573+
// Compare the vptr against the expected vptr for the destination type at
1574+
// this offset. Note that we do not know what type ThisAddr points to in
1575+
// the case where the derived class multiply inherits from the base class
1576+
// so we can't use GetVTablePtr, so we load the vptr directly instead.
1577+
llvm::Instruction *VPtr = CGF.Builder.CreateLoad(
1578+
ThisAddr.withElementType(CGF.VoidPtrPtrTy), "vtable");
1579+
CGM.DecorateInstructionWithTBAA(
1580+
VPtr, CGM.getTBAAVTablePtrAccessInfo(CGF.VoidPtrPtrTy));
1581+
llvm::Value *Success = CGF.Builder.CreateICmpEQ(
1582+
VPtr, getVTableAddressPoint(BaseSubobject(SrcDecl, *Offset), DestDecl));
1583+
llvm::Value *Result = ThisAddr.getPointer();
1584+
if (!Offset->isZero())
1585+
Result = CGF.Builder.CreateInBoundsGEP(
1586+
CGF.CharTy, Result,
1587+
{llvm::ConstantInt::get(CGF.PtrDiffTy, -Offset->getQuantity())});
1588+
CGF.Builder.CreateCondBr(Success, CastSuccess, CastFail);
1589+
return Result;
1590+
}
1591+
14661592
llvm::Value *ItaniumCXXABI::emitDynamicCastToVoid(CodeGenFunction &CGF,
14671593
Address ThisAddr,
14681594
QualType SrcRecordTy) {

0 commit comments

Comments
 (0)