Skip to content

Commit 6bc0faf

Browse files
authored
[CIR] Implement deferred V-Table emission (llvm#185655)
We are currently only emitting Vtables that have an 'immediate' need to emit. There rest, we are supposed to add to a list and emit at the end of the translation unit if necessary. This patch implements that infrastructure. The test added is from classic-codegen and came in at the same time as the deferred vtable emission over there, and only works with deferred vtable emission, and while it does test the deferred emission, tests quite a bit more than that. AND since it came in with the same functionality in classic codegen, seemed to make sense to come in here too.
1 parent d8f3be7 commit 6bc0faf

9 files changed

Lines changed: 579 additions & 10 deletions

File tree

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ struct MissingFeatures {
150150

151151
// Various handling of deferred processing in CIRGenModule.
152152
static bool cgmRelease() { return false; }
153-
static bool deferredVtables() { return false; }
154153
static bool deferredFuncDecls() { return false; }
155154

156155
// CXXABI

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ class CIRGenCXXABI {
127127
virtual void emitRethrow(CIRGenFunction &cgf, bool isNoReturn) = 0;
128128
virtual void emitThrow(CIRGenFunction &cgf, const CXXThrowExpr *e) = 0;
129129

130+
/// Determine whether it's possible to emit a vtable for \p RD, even
131+
/// though we do not know that the vtable has been marked as used by semantic
132+
/// analysis.
133+
virtual bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const = 0;
134+
130135
virtual void emitBadCastCall(CIRGenFunction &cgf, mlir::Location loc) = 0;
131136

132137
virtual void emitBeginCatch(CIRGenFunction &cgf,

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
105105
const CXXDestructorDecl *dtor,
106106
CXXDtorType dtorType, Address thisAddr,
107107
DeleteOrMemberCallExpr e) override;
108+
109+
bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const override;
110+
bool canSpeculativelyEmitVTableAsBaseClass(const CXXRecordDecl *RD) const;
111+
108112
mlir::Value getVTableAddressPoint(BaseSubobject base,
109113
const CXXRecordDecl *vtableClass) override;
110114
mlir::Value getVTableAddressPointInStructorWithVTT(
@@ -221,6 +225,10 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
221225
/// the current ABI.
222226
RTTIUniquenessKind
223227
classifyRTTIUniqueness(QualType canTy, cir::GlobalLinkageKind linkage) const;
228+
229+
private:
230+
bool hasAnyUnusedVirtualInlineFunction(const CXXRecordDecl *rd) const;
231+
bool isVTableHidden(const CXXRecordDecl *rd) const;
224232
};
225233

226234
} // namespace
@@ -1789,7 +1797,7 @@ cir::GlobalOp CIRGenItaniumCXXABI::getAddrOfVTable(const CXXRecordDecl *rd,
17891797
return vtable;
17901798

17911799
// Queue up this vtable for possible deferred emission.
1792-
assert(!cir::MissingFeatures::deferredVtables());
1800+
cgm.addDeferredVTable(rd);
17931801

17941802
SmallString<256> name;
17951803
llvm::raw_svector_ostream out(name);
@@ -2575,6 +2583,126 @@ void CIRGenItaniumCXXABI::emitBeginCatch(CIRGenFunction &cgf,
25752583
cgf.emitAutoVarCleanups(var);
25762584
}
25772585

2586+
bool CIRGenItaniumCXXABI::hasAnyUnusedVirtualInlineFunction(
2587+
const CXXRecordDecl *rd) const {
2588+
const auto &vtableLayout = cgm.getItaniumVTableContext().getVTableLayout(rd);
2589+
2590+
for (const auto &vtableComponent : vtableLayout.vtable_components()) {
2591+
// Skip empty slot.
2592+
if (!vtableComponent.isUsedFunctionPointerKind())
2593+
continue;
2594+
2595+
const CXXMethodDecl *method = vtableComponent.getFunctionDecl();
2596+
const FunctionDecl *fd = method->getDefinition();
2597+
const bool isInlined =
2598+
method->getCanonicalDecl()->isInlined() || (fd && fd->isInlined());
2599+
if (!isInlined)
2600+
continue;
2601+
2602+
StringRef name = cgm.getMangledName(
2603+
vtableComponent.getGlobalDecl(/*HasVectorDeletingDtors=*/false));
2604+
auto entry = dyn_cast_or_null<cir::GlobalOp>(cgm.getGlobalValue(name));
2605+
// This checks if virtual inline function has already been emitted.
2606+
// Note that it is possible that this inline function would be emitted
2607+
// after trying to emit vtable speculatively. Because of this we do
2608+
// an extra pass after emitting all deferred vtables to find and emit
2609+
// these vtables opportunistically.
2610+
if (!entry || entry.isDeclaration())
2611+
return true;
2612+
}
2613+
return false;
2614+
}
2615+
2616+
bool CIRGenItaniumCXXABI::isVTableHidden(const CXXRecordDecl *rd) const {
2617+
const auto &vtableLayout = cgm.getItaniumVTableContext().getVTableLayout(rd);
2618+
2619+
for (const auto &vtableComponent : vtableLayout.vtable_components()) {
2620+
if (vtableComponent.isRTTIKind()) {
2621+
const CXXRecordDecl *rttiDecl = vtableComponent.getRTTIDecl();
2622+
if (rttiDecl->getVisibility() == Visibility::HiddenVisibility)
2623+
return true;
2624+
} else if (vtableComponent.isUsedFunctionPointerKind()) {
2625+
const CXXMethodDecl *method = vtableComponent.getFunctionDecl();
2626+
if (method->getVisibility() == Visibility::HiddenVisibility &&
2627+
!method->isDefined())
2628+
return true;
2629+
}
2630+
}
2631+
return false;
2632+
}
2633+
2634+
bool CIRGenItaniumCXXABI::canSpeculativelyEmitVTableAsBaseClass(
2635+
const CXXRecordDecl *rd) const {
2636+
// We don't emit available_externally vtables if we are in -fapple-kext mode
2637+
// because kext mode does not permit devirtualization.
2638+
if (cgm.getLangOpts().AppleKext)
2639+
return false;
2640+
2641+
// If the vtable is hidden then it is not safe to emit an available_externally
2642+
// copy of vtable.
2643+
if (isVTableHidden(rd))
2644+
return false;
2645+
2646+
if (cgm.getCodeGenOpts().ForceEmitVTables)
2647+
return true;
2648+
2649+
// A speculative vtable can only be generated if all virtual inline functions
2650+
// defined by this class are emitted. The vtable in the final program contains
2651+
// for each virtual inline function not used in the current TU a function that
2652+
// is equivalent to the unused function. The function in the actual vtable
2653+
// does not have to be declared under the same symbol (e.g., a virtual
2654+
// destructor that can be substituted with its base class's destructor). Since
2655+
// inline functions are emitted lazily and this emissions does not account for
2656+
// speculative emission of a vtable, we might generate a speculative vtable
2657+
// with references to inline functions that are not emitted under that name.
2658+
// This can lead to problems when devirtualizing a call to such a function,
2659+
// that result in linking errors. Hence, if there are any unused virtual
2660+
// inline function, we cannot emit the speculative vtable.
2661+
// FIXME we can still emit a copy of the vtable if we
2662+
// can emit definition of the inline functions.
2663+
if (hasAnyUnusedVirtualInlineFunction(rd))
2664+
return false;
2665+
2666+
// For a class with virtual bases, we must also be able to speculatively
2667+
// emit the VTT, because CodeGen doesn't have separate notions of "can emit
2668+
// the vtable" and "can emit the VTT". For a base subobject, this means we
2669+
// need to be able to emit non-virtual base vtables.
2670+
if (rd->getNumVBases()) {
2671+
for (const auto &b : rd->bases()) {
2672+
auto *brd = b.getType()->getAsCXXRecordDecl();
2673+
assert(brd && "no class for base specifier");
2674+
if (b.isVirtual() || !brd->isDynamicClass())
2675+
continue;
2676+
if (!canSpeculativelyEmitVTableAsBaseClass(brd))
2677+
return false;
2678+
}
2679+
}
2680+
2681+
return true;
2682+
}
2683+
2684+
bool CIRGenItaniumCXXABI::canSpeculativelyEmitVTable(
2685+
const CXXRecordDecl *rd) const {
2686+
if (!canSpeculativelyEmitVTableAsBaseClass(rd))
2687+
return false;
2688+
2689+
if (rd->shouldEmitInExternalSource())
2690+
return false;
2691+
2692+
// For a complete-object vtable (or more specifically, for the VTT), we need
2693+
// to be able to speculatively emit the vtables of all dynamic virtual bases.
2694+
for (const auto &b : rd->vbases()) {
2695+
auto *brd = b.getType()->getAsCXXRecordDecl();
2696+
assert(brd && "no class for base specifier");
2697+
if (!brd->isDynamicClass())
2698+
continue;
2699+
if (!canSpeculativelyEmitVTableAsBaseClass(brd))
2700+
return false;
2701+
}
2702+
2703+
return true;
2704+
}
2705+
25782706
static mlir::Value performTypeAdjustment(CIRGenFunction &cgf,
25792707
Address initialPtr,
25802708
const CXXRecordDecl *unadjustedClass,
@@ -2598,8 +2726,23 @@ static mlir::Value performTypeAdjustment(CIRGenFunction &cgf,
25982726
// Perform the virtual adjustment if we have one.
25992727
mlir::Value resultPtr;
26002728
if (virtualAdjustment) {
2601-
cgf.cgm.errorNYI("virtual adjustment in thunk");
2602-
resultPtr = v;
2729+
mlir::Value vtablePtr = cgf.getVTablePtr(
2730+
loc, Address(v, clang::CharUnits::One()), unadjustedClass);
2731+
vtablePtr = builder.createBitcast(vtablePtr, i8PtrTy);
2732+
2733+
mlir::Value offset;
2734+
mlir::Value offsetPtr =
2735+
cir::PtrStrideOp::create(builder, loc, i8PtrTy, vtablePtr,
2736+
builder.getSInt64(virtualAdjustment, loc));
2737+
if (cgf.cgm.getItaniumVTableContext().isRelativeLayout()) {
2738+
assert(!cir::MissingFeatures::vtableRelativeLayout());
2739+
cgf.cgm.errorNYI("virtual adjustment for relative layout vtables");
2740+
} else {
2741+
offset = builder.createAlignedLoad(loc, cgf.ptrDiffTy, offsetPtr,
2742+
cgf.getPointerAlign());
2743+
}
2744+
2745+
resultPtr = cir::PtrStrideOp::create(builder, loc, i8PtrTy, v, offset);
26032746
} else {
26042747
resultPtr = v;
26052748
}

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,13 @@ void CIRGenModule::emitDeferred() {
350350
// static function, iterate until no changes are made.
351351

352352
assert(!cir::MissingFeatures::openMP());
353-
assert(!cir::MissingFeatures::deferredVtables());
353+
354+
emitDeferredVTables();
355+
// Emitting a vtable doesn't directly cause more vtables to
356+
// become deferred, although it can cause functions to be
357+
// emitted that then need those vtables.
358+
assert(deferredVTables.empty());
359+
354360
assert(!cir::MissingFeatures::cudaSupport());
355361

356362
// Stop if we're out of both deferred vtables and deferred declarations.
@@ -368,9 +374,9 @@ void CIRGenModule::emitDeferred() {
368374
// If we found out that we need to emit more decls, do that recursively.
369375
// This has the advantage that the decls are emitted in a DFS and related
370376
// ones are close together, which is convenient for testing.
371-
if (!deferredDeclsToEmit.empty()) {
377+
if (!deferredVTables.empty() || !deferredDeclsToEmit.empty()) {
372378
emitDeferred();
373-
assert(deferredDeclsToEmit.empty());
379+
assert(deferredVTables.empty() && deferredDeclsToEmit.empty());
374380
}
375381
}
376382
}
@@ -2787,6 +2793,7 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {
27872793

27882794
void CIRGenModule::release() {
27892795
emitDeferred();
2796+
emitVTablesOpportunistically();
27902797
applyReplacements();
27912798

27922799
theModule->setAttr(cir::CIRDialect::getModuleLevelAsmAttrName(),

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ class CIRGenModule : public CIRGenTypeCache {
102102

103103
llvm::DenseSet<clang::GlobalDecl> diagnosedConflictingDefinitions;
104104

105+
/// A queue of (optional) vtables to consider emitting.
106+
std::vector<const CXXRecordDecl *> deferredVTables;
107+
108+
/// A queue of (optional) vtables that may be emitted opportunistically.
109+
std::vector<const CXXRecordDecl *> opportunisticVTables;
110+
105111
void createCUDARuntime();
106112

107113
/// A helper for constructAttributeList that handles return attributes.
@@ -489,6 +495,10 @@ class CIRGenModule : public CIRGenTypeCache {
489495
void emitExplicitCastExprType(const ExplicitCastExpr *e,
490496
CIRGenFunction *cgf = nullptr);
491497

498+
void addDeferredVTable(const CXXRecordDecl *rd) {
499+
deferredVTables.push_back(rd);
500+
}
501+
492502
/// Emit code for a single global function or variable declaration. Forward
493503
/// declarations are emitted lazily.
494504
void emitGlobal(clang::GlobalDecl gd);
@@ -675,6 +685,16 @@ class CIRGenModule : public CIRGenTypeCache {
675685
/// Emit any needed decls for which code generation was deferred.
676686
void emitDeferred();
677687

688+
bool shouldOpportunisticallyEmitVTables();
689+
/// Emit any vtables which we deferred and still have a use for.
690+
void emitDeferredVTables();
691+
692+
/// Try to emit external vtables as available_externally if they have emitted
693+
/// all inlined virtual functions. It runs after EmitDeferred() and therefore
694+
/// is not allowed to create new references to things that need to be emitted
695+
/// lazily.
696+
void emitVTablesOpportunistically();
697+
678698
/// Helper for `emitDeferred` to apply actual codegen.
679699
void emitGlobalDecl(const clang::GlobalDecl &d);
680700

clang/lib/CIR/CodeGen/CIRGenVTables.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,3 +940,66 @@ void CIRGenVTables::emitThunks(GlobalDecl gd) {
940940
for (const ThunkInfo &thunk : *thunkInfoVector)
941941
maybeEmitThunk(gd, thunk, /*forVTable=*/false);
942942
}
943+
944+
static bool shouldEmitAvailableExternallyVTable(const CIRGenModule &cgm,
945+
const CXXRecordDecl *rd) {
946+
return cgm.getCodeGenOpts().OptimizationLevel > 0 &&
947+
cgm.getCXXABI().canSpeculativelyEmitVTable(rd);
948+
}
949+
950+
/// Given that we're currently at the end of the translation unit, and
951+
/// we've emitted a reference to the vtable for this class, should
952+
/// we define that vtable?
953+
static bool shouldEmitVTableAtEndOfTranslationUnit(CIRGenModule &cgm,
954+
const CXXRecordDecl *rd) {
955+
// If vtable is internal then it has to be done.
956+
if (!cgm.getVTables().isVTableExternal(rd))
957+
return true;
958+
959+
// If it's external then maybe we will need it as available_externally.
960+
return shouldEmitAvailableExternallyVTable(cgm, rd);
961+
}
962+
963+
/// Given that at some point we emitted a reference to one or more
964+
/// vtables, and that we are now at the end of the translation unit,
965+
/// decide whether we should emit them.
966+
void CIRGenModule::emitDeferredVTables() {
967+
#ifndef NDEBUG
968+
// Remember the size of DeferredVTables, because we're going to assume
969+
// that this entire operation doesn't modify it.
970+
size_t savedSize = deferredVTables.size();
971+
#endif
972+
for (const CXXRecordDecl *rd : deferredVTables) {
973+
if (shouldEmitVTableAtEndOfTranslationUnit(*this, rd))
974+
vtables.generateClassData(rd);
975+
else if (shouldOpportunisticallyEmitVTables())
976+
opportunisticVTables.push_back(rd);
977+
}
978+
979+
assert(savedSize == deferredVTables.size() &&
980+
"deferred extra vtables during vtable emission?");
981+
deferredVTables.clear();
982+
}
983+
984+
void CIRGenModule::emitVTablesOpportunistically() {
985+
// Try to emit external vtables as available_externally if they have emitted
986+
// all inlined virtual functions. It runs after EmitDeferred() and therefore
987+
// is not allowed to create new references to things that need to be emitted
988+
// lazily. Note that it also uses fact that we eagerly emitting RTTI.
989+
990+
assert(
991+
(opportunisticVTables.empty() || shouldOpportunisticallyEmitVTables()) &&
992+
"Only emit opportunistic vtables with optimizations");
993+
994+
for (const CXXRecordDecl *rd : opportunisticVTables) {
995+
assert(getVTables().isVTableExternal(rd) &&
996+
"This queue should only contain external vtables");
997+
if (getCXXABI().canSpeculativelyEmitVTable(rd))
998+
vtables.generateClassData(rd);
999+
}
1000+
opportunisticVTables.clear();
1001+
}
1002+
1003+
bool CIRGenModule::shouldOpportunisticallyEmitVTables() {
1004+
return codeGenOpts.OptimizationLevel > 0;
1005+
}

clang/test/CIR/CodeGen/vbase.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ void ppp() { B b; }
5151
// Note: OGCG speculatively emits the VTT and VTables. This is not yet implemented in CIR.
5252

5353
// Vtable definition for B
54-
// CIR: cir.global "private" external @_ZTV1B
55-
56-
// LLVM: @_ZTV1B = external global { [3 x ptr] }
54+
// CIR: cir.global "private" {{.*}}@_ZTV1B = #cir.vtable<{#cir.const_array<[#cir.ptr<12 : i64> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZTI1B> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !rec_anon_struct3 {alignment = 8 : i64}
55+
// LLVM: @_ZTV1B = linkonce_odr global { [3 x ptr] } { [3 x ptr] [ptr inttoptr (i64 12 to ptr), ptr null, ptr @_ZTI1B] }, comdat, align 8
5756

5857
// OGCG: @_ZTV1B = linkonce_odr unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr inttoptr (i64 12 to ptr), ptr null, ptr @_ZTI1B] }, comdat, align 8
5958

0 commit comments

Comments
 (0)