Skip to content

Commit 3a9440a

Browse files
committed
IRGen: Elide nonoverridden entries from public resilient vtables.
A formally virtual method still needs to provide the ABI of an overridable method, including a dispatch thunk, method descriptor, and support in the method lookup function for the class to handle `super.` calls from clients.
1 parent 570e0ad commit 3a9440a

File tree

8 files changed

+436
-63
lines changed

8 files changed

+436
-63
lines changed

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: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,18 +2512,28 @@ 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);
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 = IGF.Builder.CreateBitCast(methodInfo.getDirectImpl(),
2530+
signature.getType()->getPointerTo());
25252531

2526-
return FunctionPointer(fnPtr, authInfo, signature);
2532+
auto authInfo = PointerAuthInfo::forFunctionPointer(IGF.IGM, methodType);
2533+
return FunctionPointer(fnPtr, authInfo, signature);
2534+
}
2535+
}
2536+
25272537
}
25282538

25292539
FunctionPointer

lib/IRGen/GenMeta.cpp

Lines changed: 102 additions & 44 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() {
@@ -5108,13 +5139,27 @@ bool irgen::methodRequiresReifiedVTableEntry(IRGenModule &IGM,
51085139
const SILVTable *vtable,
51095140
SILDeclRef method) {
51105141
auto entry = vtable->getEntry(IGM.getSILModule(), method);
5142+
LLVM_DEBUG(llvm::dbgs() << "looking at vtable:\n";
5143+
vtable->print(llvm::dbgs()));
51115144
if (!entry) {
5145+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5146+
<< vtable->getClass()->getName()
5147+
<< " for ";
5148+
method.print(llvm::dbgs());
5149+
llvm::dbgs() << " is not available\n");
51125150
return true;
51135151
}
5152+
LLVM_DEBUG(llvm::dbgs() << "entry: ";
5153+
entry->print(llvm::dbgs()));
51145154

51155155
// We may be able to elide the vtable entry, ABI permitting, if it's not
51165156
// overridden.
51175157
if (!entry->isNonOverridden()) {
5158+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5159+
<< vtable->getClass()->getName()
5160+
<< " for ";
5161+
method.print(llvm::dbgs());
5162+
llvm::dbgs() << " is overridden\n");
51185163
return true;
51195164
}
51205165

@@ -5123,12 +5168,25 @@ bool irgen::methodRequiresReifiedVTableEntry(IRGenModule &IGM,
51235168
// other modules will directly address vtable offsets and we can't remove
51245169
// vtable entries.
51255170
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;
5171+
// If the class is public,
5172+
// and it's either marked fragile or part of a non-resilient module, then
5173+
// other modules will directly address vtable offsets and we can't remove
5174+
// vtable entries.
5175+
if (!vtable->getClass()->isResilient()) {
5176+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5177+
<< vtable->getClass()->getName()
5178+
<< " for ";
5179+
method.print(llvm::dbgs());
5180+
llvm::dbgs() << " is in a public fragile class\n");
5181+
return true;
5182+
}
51305183
}
51315184

51325185
// Otherwise, we can leave this method out of the runtime vtable.
5186+
LLVM_DEBUG(llvm::dbgs() << "vtable entry in "
5187+
<< vtable->getClass()->getName()
5188+
<< " for ";
5189+
method.print(llvm::dbgs());
5190+
llvm::dbgs() << " can be elided\n");
51335191
return false;
51345192
}

lib/IRGen/GenThunk.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
//===----------------------------------------------------------------------===//
1818

1919
#include "Callee.h"
20+
#include "ClassMetadataVisitor.h"
2021
#include "Explosion.h"
2122
#include "GenDecl.h"
2223
#include "GenClass.h"
2324
#include "GenHeap.h"
2425
#include "GenOpaque.h"
26+
#include "GenPointerAuth.h"
2527
#include "GenProto.h"
2628
#include "IRGenFunction.h"
2729
#include "IRGenModule.h"
@@ -171,6 +173,65 @@ void IRGenModule::emitMethodLookupFunction(ClassDecl *classDecl) {
171173
auto *description = getAddrOfTypeContextDescriptor(classDecl,
172174
RequireMetadata);
173175

176+
// Check for lookups of nonoverridden methods first.
177+
class LookUpNonoverriddenMethods
178+
: public ClassMetadataScanner<LookUpNonoverriddenMethods> {
179+
180+
IRGenFunction &IGF;
181+
llvm::Value *methodArg;
182+
183+
public:
184+
LookUpNonoverriddenMethods(IRGenFunction &IGF,
185+
ClassDecl *classDecl,
186+
llvm::Value *methodArg)
187+
: ClassMetadataScanner(IGF.IGM, classDecl), IGF(IGF),
188+
methodArg(methodArg) {}
189+
190+
void noteNonoverriddenMethod(SILDeclRef method) {
191+
// The method lookup function would be used only for `super.` calls
192+
// from other modules, so we only need to look at public-visibility
193+
// methods here.
194+
if (!hasPublicVisibility(method.getLinkage(NotForDefinition))) {
195+
return;
196+
}
197+
198+
auto methodDesc = IGM.getAddrOfMethodDescriptor(method, NotForDefinition);
199+
200+
auto isMethod = IGF.Builder.CreateICmpEQ(methodArg, methodDesc);
201+
202+
auto falseBB = IGF.createBasicBlock("");
203+
auto trueBB = IGF.createBasicBlock("");
204+
205+
IGF.Builder.CreateCondBr(isMethod, trueBB, falseBB);
206+
207+
IGF.Builder.emitBlock(trueBB);
208+
// Since this method is nonoverridden, we can produce a static result.
209+
auto entry = VTable->getEntry(IGM.getSILModule(), method);
210+
llvm::Value *impl = IGM.getAddrOfSILFunction(entry->getImplementation(),
211+
NotForDefinition);
212+
// Sign using the discriminator we would include in the method
213+
// descriptor.
214+
if (auto &schema = IGM.getOptions().PointerAuth.SwiftClassMethods) {
215+
auto discriminator =
216+
PointerAuthInfo::getOtherDiscriminator(IGM, schema, method);
217+
218+
impl = emitPointerAuthSign(IGF, impl,
219+
PointerAuthInfo(schema.getKey(), discriminator));
220+
}
221+
impl = IGF.Builder.CreateBitCast(impl, IGM.Int8PtrTy);
222+
IGF.Builder.CreateRet(impl);
223+
224+
IGF.Builder.emitBlock(falseBB);
225+
// Continue emission on the false branch.
226+
}
227+
228+
void noteResilientSuperclass() {}
229+
void noteStartOfImmediateMembers(ClassDecl *clas) {}
230+
};
231+
232+
LookUpNonoverriddenMethods(IGF, classDecl, method).layout();
233+
234+
// Use the runtime to look up vtable entries.
174235
auto *result = IGF.Builder.CreateCall(getLookUpClassMethodFn(),
175236
{metadata, method, description});
176237
IGF.Builder.CreateRet(result);

lib/IRGen/IRGenModule.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ namespace swift {
106106
class SILModule;
107107
class SILProperty;
108108
class SILType;
109+
class SILVTable;
109110
class SILWitnessTable;
110111
class SourceLoc;
111112
class SourceFile;
@@ -1373,6 +1374,8 @@ private: \
13731374
llvm::Constant *definition);
13741375
llvm::Constant *getAddrOfMethodDescriptor(SILDeclRef declRef,
13751376
ForDefinition_t forDefinition);
1377+
void emitNonoverriddenMethodDescriptor(const SILVTable *VTable,
1378+
SILDeclRef declRef);
13761379

13771380
Address getAddrOfEnumCase(EnumElementDecl *Case,
13781381
ForDefinition_t forDefinition);

0 commit comments

Comments
 (0)