Skip to content

Commit 90f0f7c

Browse files
authored
Merge pull request swiftlang#32359 from jckarter/enable-prune-vtables
Add PruneVTables to the performance optimizer passes.
2 parents 396709c + c99b654 commit 90f0f7c

File tree

15 files changed

+632
-110
lines changed

15 files changed

+632
-110
lines changed

include/swift/SIL/SILVTable.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ class SILVTableEntry {
6464
// Please update the PointerIntPair above if you add/remove enums.
6565
};
6666

67-
SILVTableEntry() : ImplAndKind(nullptr, Kind::Normal) {}
67+
SILVTableEntry() : ImplAndKind(nullptr, Kind::Normal),
68+
IsNonOverridden(false) {}
6869

6970
SILVTableEntry(SILDeclRef Method, SILFunction *Implementation, Kind TheKind,
7071
bool NonOverridden)
@@ -80,6 +81,19 @@ class SILVTableEntry {
8081
void setNonOverridden(bool value) { IsNonOverridden = value; }
8182

8283
SILFunction *getImplementation() const { return ImplAndKind.getPointer(); }
84+
85+
void print(llvm::raw_ostream &os) const;
86+
87+
bool operator==(const SILVTableEntry &e) const {
88+
return Method == e.Method
89+
&& getImplementation() == e.getImplementation()
90+
&& getKind() == e.getKind()
91+
&& isNonOverridden() == e.isNonOverridden();
92+
}
93+
94+
bool operator!=(const SILVTableEntry &e) const {
95+
return !(*this == e);
96+
}
8397
};
8498

8599
/// A mapping from each dynamically-dispatchable method of a class to the
@@ -140,9 +154,13 @@ class SILVTable final : public SILAllocated<SILVTable>,
140154
}
141155

142156
/// Return all of the method entries mutably.
157+
/// If you do modify entries, make sure to invoke `updateVTableCache` to update the
158+
/// SILModule's cache entry.
143159
MutableArrayRef<Entry> getMutableEntries() {
144160
return {getTrailingObjects<SILVTableEntry>(), NumEntries};
145161
}
162+
163+
void updateVTableCache(const Entry &entry);
146164

147165
/// Look up the implementation function for the given method.
148166
Optional<Entry> getEntry(SILModule &M, SILDeclRef method) const;

lib/IRGen/ClassMetadataVisitor.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ template <class Impl> class ClassMetadataVisitor
103103
/// of a generic-layout class.
104104
void noteEndOfFieldOffsets(ClassDecl *whichClass) {}
105105

106+
/// Notes the existence of a formally virtual method that has been elided from the
107+
/// reified vtable because it has no overrides.
108+
void noteNonoverriddenMethod(SILDeclRef method) {}
109+
106110
private:
107111
/// Add fields associated with the given class and its bases.
108112
void addClassMembers(ClassDecl *theClass) {
@@ -173,8 +177,11 @@ template <class Impl> class ClassMetadataVisitor
173177
// Does this method require a reified runtime vtable entry?
174178
if (!VTable || methodRequiresReifiedVTableEntry(IGM, VTable, declRef)) {
175179
asImpl().addReifiedVTableEntry(declRef);
180+
} else {
181+
asImpl().noteNonoverriddenMethod(declRef);
176182
}
177183
}
184+
178185

179186
void addFieldEntries(Decl *field) {
180187
if (auto var = dyn_cast<VarDecl>(field)) {

lib/IRGen/GenClass.cpp

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,18 +2512,26 @@ FunctionPointer irgen::emitVirtualMethodValue(IRGenFunction &IGF,
25122512
// Find the vtable entry we're interested in.
25132513
auto methodInfo =
25142514
IGF.IGM.getClassMetadataLayout(classDecl).getMethodInfo(IGF, method);
2515-
auto offset = methodInfo.getOffset();
2516-
2517-
auto slot = IGF.emitAddressAtOffset(metadata, offset,
2518-
signature.getType()->getPointerTo(),
2519-
IGF.IGM.getPointerAlignment());
2520-
auto fnPtr = IGF.emitInvariantLoad(slot);
2521-
2522-
auto &schema = IGF.getOptions().PointerAuth.SwiftClassMethods;
2523-
auto authInfo =
2524-
PointerAuthInfo::emit(IGF, schema, slot.getAddress(), method);
2525-
2526-
return FunctionPointer(fnPtr, authInfo, signature);
2515+
switch (methodInfo.getKind()) {
2516+
case ClassMetadataLayout::MethodInfo::Kind::Offset: {
2517+
auto offset = methodInfo.getOffsett();
2518+
2519+
auto slot = IGF.emitAddressAtOffset(metadata, offset,
2520+
signature.getType()->getPointerTo(),
2521+
IGF.IGM.getPointerAlignment());
2522+
auto fnPtr = IGF.emitInvariantLoad(slot);
2523+
auto &schema = IGF.getOptions().PointerAuth.SwiftClassMethods;
2524+
auto authInfo =
2525+
PointerAuthInfo::emit(IGF, schema, slot.getAddress(), method);
2526+
return FunctionPointer(fnPtr, authInfo, signature);
2527+
}
2528+
case ClassMetadataLayout::MethodInfo::Kind::DirectImpl: {
2529+
auto fnPtr = llvm::ConstantExpr::getBitCast(methodInfo.getDirectImpl(),
2530+
signature.getType()->getPointerTo());
2531+
return FunctionPointer::forDirect(fnPtr, signature);
2532+
}
2533+
}
2534+
25272535
}
25282536

25292537
FunctionPointer

lib/IRGen/GenMeta.cpp

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,55 @@ static Flags getMethodDescriptorFlags(ValueDecl *fn) {
263263
return Flags(kind).withIsInstance(!fn->isStatic());
264264
}
265265

266+
static void buildMethodDescriptorFields(IRGenModule &IGM,
267+
const SILVTable *VTable,
268+
SILDeclRef fn,
269+
ConstantStructBuilder &descriptor) {
270+
auto *func = cast<AbstractFunctionDecl>(fn.getDecl());
271+
// Classify the method.
272+
using Flags = MethodDescriptorFlags;
273+
auto flags = getMethodDescriptorFlags<Flags>(func);
274+
275+
// Remember if the declaration was dynamic.
276+
if (func->shouldUseObjCDispatch())
277+
flags = flags.withIsDynamic(true);
278+
279+
// Include the pointer-auth discriminator.
280+
if (auto &schema = IGM.getOptions().PointerAuth.SwiftClassMethods) {
281+
auto discriminator =
282+
PointerAuthInfo::getOtherDiscriminator(IGM, schema, fn);
283+
flags = flags.withExtraDiscriminator(discriminator->getZExtValue());
284+
}
285+
286+
// TODO: final? open?
287+
descriptor.addInt(IGM.Int32Ty, flags.getIntValue());
288+
289+
if (auto entry = VTable->getEntry(IGM.getSILModule(), fn)) {
290+
assert(entry->getKind() == SILVTable::Entry::Kind::Normal);
291+
auto *implFn = IGM.getAddrOfSILFunction(entry->getImplementation(),
292+
NotForDefinition);
293+
descriptor.addRelativeAddress(implFn);
294+
} else {
295+
// The method is removed by dead method elimination.
296+
// It should be never called. We add a pointer to an error function.
297+
descriptor.addRelativeAddressOrNull(nullptr);
298+
}
299+
}
300+
301+
void IRGenModule::emitNonoverriddenMethodDescriptor(const SILVTable *VTable,
302+
SILDeclRef declRef) {
303+
304+
ConstantInitBuilder ib(*this);
305+
ConstantStructBuilder sb(ib.beginStruct(MethodDescriptorStructTy));
306+
307+
buildMethodDescriptorFields(*this, VTable, declRef, sb);
308+
309+
auto init = sb.finishAndCreateFuture();
310+
311+
auto entity = LinkEntity::forMethodDescriptor(declRef);
312+
getAddrOfLLVMVariable(entity, init, DebugTypeInfo());
313+
}
314+
266315
namespace {
267316
template<class Impl>
268317
class ContextDescriptorBuilderBase {
@@ -1416,6 +1465,7 @@ namespace {
14161465

14171466
SILVTable *VTable;
14181467
bool Resilient;
1468+
bool HasNonoverriddenMethods = false;
14191469

14201470
SmallVector<SILDeclRef, 8> VTableEntries;
14211471
SmallVector<std::pair<SILDeclRef, SILDeclRef>, 8> OverrideTableEntries;
@@ -1442,7 +1492,7 @@ namespace {
14421492
void addMethod(SILDeclRef fn) {
14431493
if (!VTable || methodRequiresReifiedVTableEntry(IGM, VTable, fn)) {
14441494
VTableEntries.push_back(fn);
1445-
} else if (getType()->getEffectiveAccess() >= AccessLevel::Public) {
1495+
} else if (hasPublicVisibility(fn.getLinkage(NotForDefinition))) {
14461496
// Emit a stub method descriptor and lookup function for nonoverridden
14471497
// methods so that resilient code sequences can still use them.
14481498
emitNonoverriddenMethod(fn);
@@ -1539,14 +1589,15 @@ namespace {
15391589
}
15401590
);
15411591

1542-
if (VTableEntries.empty())
1543-
return;
1544-
15451592
// Only emit a method lookup function if the class is resilient
1546-
// and has a non-empty vtable.
1547-
if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal))
1593+
// and has a non-empty vtable, as well as no elided methods.
1594+
if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal)
1595+
&& (HasNonoverriddenMethods || !VTableEntries.empty()))
15481596
IGM.emitMethodLookupFunction(getType());
15491597

1598+
if (VTableEntries.empty())
1599+
return;
1600+
15501601
auto offset = MetadataLayout->hasResilientSuperclass()
15511602
? MetadataLayout->getRelativeVTableOffset()
15521603
: MetadataLayout->getStaticVTableOffset();
@@ -1564,51 +1615,31 @@ namespace {
15641615
B.getAddrOfCurrentPosition(IGM.MethodDescriptorStructTy));
15651616

15661617
// Actually build the descriptor.
1567-
auto *func = cast<AbstractFunctionDecl>(fn.getDecl());
15681618
auto descriptor = B.beginStruct(IGM.MethodDescriptorStructTy);
1569-
1570-
// Classify the method.
1571-
using Flags = MethodDescriptorFlags;
1572-
auto flags = getMethodDescriptorFlags<Flags>(func);
1573-
1574-
// Remember if the declaration was dynamic.
1575-
if (func->shouldUseObjCDispatch())
1576-
flags = flags.withIsDynamic(true);
1577-
1578-
// Include the pointer-auth discriminator.
1579-
if (auto &schema = IGM.getOptions().PointerAuth.SwiftClassMethods) {
1580-
auto discriminator =
1581-
PointerAuthInfo::getOtherDiscriminator(IGM, schema, fn);
1582-
flags = flags.withExtraDiscriminator(discriminator->getZExtValue());
1583-
}
1584-
1585-
// TODO: final? open?
1586-
descriptor.addInt(IGM.Int32Ty, flags.getIntValue());
1587-
1588-
if (auto entry = VTable->getEntry(IGM.getSILModule(), fn)) {
1589-
assert(entry->getKind() == SILVTable::Entry::Kind::Normal);
1590-
auto *implFn = IGM.getAddrOfSILFunction(entry->getImplementation(),
1591-
NotForDefinition);
1592-
descriptor.addRelativeAddress(implFn);
1593-
} else {
1594-
// The method is removed by dead method elimination.
1595-
// It should be never called. We add a pointer to an error function.
1596-
descriptor.addRelativeAddressOrNull(nullptr);
1597-
}
1598-
1619+
buildMethodDescriptorFields(IGM, VTable, fn, descriptor);
15991620
descriptor.finishAndAddTo(B);
16001621

16011622
// Emit method dispatch thunk if the class is resilient.
1623+
auto *func = cast<AbstractFunctionDecl>(fn.getDecl());
16021624
if (Resilient &&
16031625
func->getEffectiveAccess() >= AccessLevel::Public) {
16041626
IGM.emitDispatchThunk(fn);
16051627
}
16061628
}
16071629

16081630
void emitNonoverriddenMethod(SILDeclRef fn) {
1609-
// TODO: Emit a freestanding method descriptor structure, and a method
1610-
// lookup function, to present the ABI of an overridable method even
1611-
// though the method has no real overrides currently.
1631+
HasNonoverriddenMethods = true;
1632+
// Although this method is non-overridden and therefore left out of the
1633+
// vtable, we still need to maintain the ABI of a potentially-overridden
1634+
// method for external clients.
1635+
1636+
// Emit method dispatch thunk.
1637+
IGM.emitDispatchThunk(fn);
1638+
// Emit a freestanding method descriptor structure. This doesn't have to
1639+
// exist in the table in the class's context descriptor since it isn't
1640+
// in the vtable, but external clients need to be able to link against the
1641+
// symbol.
1642+
IGM.emitNonoverriddenMethodDescriptor(VTable, fn);
16121643
}
16131644

16141645
void addOverrideTable() {
@@ -5107,28 +5138,61 @@ void IRGenModule::emitOpaqueTypeDecl(OpaqueTypeDecl *D) {
51075138
bool irgen::methodRequiresReifiedVTableEntry(IRGenModule &IGM,
51085139
const SILVTable *vtable,
51095140
SILDeclRef method) {
5110-
auto entry = vtable->getEntry(IGM.getSILModule(), method);
5141+
Optional<SILVTable::Entry> entry
5142+
= vtable->getEntry(IGM.getSILModule(), method);
5143+
LLVM_DEBUG(llvm::dbgs() << "looking at vtable:\n";
5144+
vtable->print(llvm::dbgs()));
51115145
if (!entry) {
5146+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5147+
<< vtable->getClass()->getName()
5148+
<< " for ";
5149+
method.print(llvm::dbgs());
5150+
llvm::dbgs() << " is not available\n");
51125151
return true;
51135152
}
5153+
LLVM_DEBUG(llvm::dbgs() << "entry: ";
5154+
entry->print(llvm::dbgs());
5155+
llvm::dbgs() << "\n");
51145156

51155157
// We may be able to elide the vtable entry, ABI permitting, if it's not
51165158
// overridden.
51175159
if (!entry->isNonOverridden()) {
5160+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5161+
<< vtable->getClass()->getName()
5162+
<< " for ";
5163+
method.print(llvm::dbgs());
5164+
llvm::dbgs() << " is overridden\n");
51185165
return true;
51195166
}
51205167

5121-
// Does the ABI require a vtable entry to exist? If the class is public,
5168+
// Does the ABI require a vtable entry to exist? If the class the vtable
5169+
// entry originates from is public,
51225170
// and it's either marked fragile or part of a non-resilient module, then
51235171
// other modules will directly address vtable offsets and we can't remove
51245172
// vtable entries.
5125-
if (vtable->getClass()->getEffectiveAccess() >= AccessLevel::Public) {
5126-
// TODO: Check whether we use a resilient ABI to access this
5127-
// class's methods. We can drop unnecessary vtable entries if we do;
5128-
// otherwise fixed vtable offsets are part of the ABI.
5129-
return true;
5173+
auto originatingClass =
5174+
cast<ClassDecl>(method.getOverriddenVTableEntry().getDecl()->getDeclContext());
5175+
5176+
if (originatingClass->getEffectiveAccess() >= AccessLevel::Public) {
5177+
// If the class is public,
5178+
// and it's either marked fragile or part of a non-resilient module, then
5179+
// other modules will directly address vtable offsets and we can't remove
5180+
// vtable entries.
5181+
if (!originatingClass->isResilient()) {
5182+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5183+
<< vtable->getClass()->getName()
5184+
<< " for ";
5185+
method.print(llvm::dbgs());
5186+
llvm::dbgs() << " originates from a public fragile class\n");
5187+
return true;
5188+
}
51305189
}
51315190

51325191
// Otherwise, we can leave this method out of the runtime vtable.
5192+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5193+
<< vtable->getClass()->getName()
5194+
<< " for ";
5195+
method.print(llvm::dbgs());
5196+
llvm::dbgs() << " can be elided\n");
51335197
return false;
51345198
}

0 commit comments

Comments
 (0)