Skip to content

Commit 5412ed1

Browse files
authored
Implement LLVM IR Virtual Function Elimination for Swift classes. (#39128)
- Virtual calls are done via a @llvm.type.checked.load instrinsic call with a type identifier - Type identifier of a vfunc is the base method's mangling - Type descriptors and class metadata get !type markers that list offsets and type identifiers of all vfuncs - The -enable-llvm-vfe frontend flag enables VFE - Two added tests verify the behavior on IR and by executing a program
1 parent 276d8c2 commit 5412ed1

File tree

11 files changed

+280
-16
lines changed

11 files changed

+280
-16
lines changed

include/swift/AST/IRGenOptions.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ class IRGenOptions {
345345

346346
unsigned EnableGlobalISel : 1;
347347

348+
unsigned VirtualFunctionElimination : 1;
349+
348350
/// The number of threads for multi-threaded code generation.
349351
unsigned NumThreads = 0;
350352

@@ -401,7 +403,8 @@ class IRGenOptions {
401403
GenerateProfile(false), EnableDynamicReplacementChaining(false),
402404
DisableRoundTripDebugTypes(false), DisableDebuggerShadowCopies(false),
403405
DisableConcreteTypeMetadataMangledNameAccessors(false),
404-
EnableGlobalISel(false), CmdArgs(),
406+
EnableGlobalISel(false), VirtualFunctionElimination(false),
407+
CmdArgs(),
405408
SanitizeCoverage(llvm::SanitizerCoverageOptions()),
406409
TypeInfoFilter(TypeInfoDumpFilter::All) {}
407410

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,10 @@ def enable_implicit_dynamic : Flag<["-"], "enable-implicit-dynamic">,
489489
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
490490
HelpText<"Add 'dynamic' to all declarations">;
491491

492+
def enable_llvm_vfe : Flag<["-"], "enable-llvm-vfe">,
493+
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
494+
HelpText<"Use LLVM Virtual Function Elimination on Swift class virtual tables">;
495+
492496
def disable_previous_implementation_calls_in_dynamic_replacements :
493497
Flag<["-"], "disable-previous-implementation-calls-in-dynamic-replacements">,
494498
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,

lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,6 +1896,10 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
18961896
Opts.EnableGlobalISel = true;
18971897
}
18981898

1899+
if (Args.hasArg(OPT_enable_llvm_vfe)) {
1900+
Opts.VirtualFunctionElimination = true;
1901+
}
1902+
18991903
return false;
19001904
}
19011905

lib/IRGen/GenClass.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2672,6 +2672,44 @@ irgen::emitClassResilientInstanceSizeAndAlignMask(IRGenFunction &IGF,
26722672
return {size, alignMask};
26732673
}
26742674

2675+
llvm::MDString *irgen::typeIdForMethod(IRGenModule &IGM, SILDeclRef method) {
2676+
assert(!method.getOverridden() && "must always be base method");
2677+
2678+
auto entity = LinkEntity::forMethodDescriptor(method);
2679+
auto mangled = entity.mangleAsString();
2680+
auto typeId = llvm::MDString::get(*IGM.LLVMContext, mangled);
2681+
return typeId;
2682+
}
2683+
2684+
static llvm::Value *emitVTableSlotLoad(IRGenFunction &IGF, Address slot,
2685+
SILDeclRef method, Signature signature) {
2686+
if (IGF.IGM.getOptions().VirtualFunctionElimination) {
2687+
// For LLVM IR VFE, emit a @llvm.type.checked.load with the type of the
2688+
// method.
2689+
llvm::Function *checkedLoadIntrinsic = llvm::Intrinsic::getDeclaration(
2690+
&IGF.IGM.Module, llvm::Intrinsic::type_checked_load);
2691+
auto slotAsPointer = IGF.Builder.CreateBitCast(slot, IGF.IGM.Int8PtrTy);
2692+
auto typeId = typeIdForMethod(IGF.IGM, method);
2693+
2694+
// Arguments for @llvm.type.checked.load: 1) target address, 2) offset -
2695+
// always 0 because target address is directly pointing to the right slot,
2696+
// 3) type identifier, i.e. the mangled name of the *base* method.
2697+
SmallVector<llvm::Value *, 8> args;
2698+
args.push_back(slotAsPointer.getAddress());
2699+
args.push_back(llvm::ConstantInt::get(IGF.IGM.Int32Ty, 0));
2700+
args.push_back(llvm::MetadataAsValue::get(*IGF.IGM.LLVMContext, typeId));
2701+
2702+
llvm::Value *checkedLoad =
2703+
IGF.Builder.CreateCall(checkedLoadIntrinsic, args);
2704+
auto fnPtr = IGF.Builder.CreateExtractValue(checkedLoad, 0);
2705+
return IGF.Builder.CreateBitCast(fnPtr,
2706+
signature.getType()->getPointerTo());
2707+
}
2708+
2709+
// Not doing LLVM IR VFE, can just be a direct load.
2710+
return IGF.emitInvariantLoad(slot);
2711+
}
2712+
26752713
FunctionPointer irgen::emitVirtualMethodValue(IRGenFunction &IGF,
26762714
llvm::Value *metadata,
26772715
SILDeclRef method,
@@ -2690,7 +2728,7 @@ FunctionPointer irgen::emitVirtualMethodValue(IRGenFunction &IGF,
26902728
auto slot = IGF.emitAddressAtOffset(metadata, offset,
26912729
signature.getType()->getPointerTo(),
26922730
IGF.IGM.getPointerAlignment());
2693-
auto fnPtr = IGF.emitInvariantLoad(slot);
2731+
auto fnPtr = emitVTableSlotLoad(IGF, slot, method, signature);
26942732
auto &schema = methodType->isAsync()
26952733
? IGF.getOptions().PointerAuth.AsyncSwiftClassMethods
26962734
: IGF.getOptions().PointerAuth.SwiftClassMethods;

lib/IRGen/GenClass.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace llvm {
2525
class Constant;
2626
class Value;
2727
class Function;
28+
class MDString;
2829
}
2930

3031
namespace swift {
@@ -201,6 +202,9 @@ namespace irgen {
201202
ClassDecl *theClass,
202203
llvm::Value *metadata);
203204

205+
/// For VFE, returns a type identifier for the given base method on a class.
206+
llvm::MDString *typeIdForMethod(IRGenModule &IGM, SILDeclRef method);
207+
204208
/// Given a metadata pointer, emit the callee for the given method.
205209
FunctionPointer emitVirtualMethodValue(IRGenFunction &IGF,
206210
llvm::Value *metadata,

lib/IRGen/GenDecl.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4202,11 +4202,10 @@ llvm::GlobalValue *IRGenModule::defineAlias(LinkEntity entity,
42024202
/// public symbol for the metadata references. This function will rewrite any
42034203
/// existing external declaration to the address point as an alias into the
42044204
/// full metadata object.
4205-
llvm::GlobalValue *IRGenModule::defineTypeMetadata(CanType concreteType,
4206-
bool isPattern,
4207-
bool isConstant,
4208-
ConstantInitFuture init,
4209-
llvm::StringRef section) {
4205+
llvm::GlobalValue *IRGenModule::defineTypeMetadata(
4206+
CanType concreteType, bool isPattern, bool isConstant,
4207+
ConstantInitFuture init, llvm::StringRef section,
4208+
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries) {
42104209
assert(init);
42114210

42124211
auto isPrespecialized = concreteType->getAnyGeneric() &&
@@ -4241,6 +4240,12 @@ llvm::GlobalValue *IRGenModule::defineTypeMetadata(CanType concreteType,
42414240
if (!section.empty())
42424241
var->setSection(section);
42434242

4243+
if (getOptions().VirtualFunctionElimination) {
4244+
if (auto classDecl = concreteType->getClassOrBoundGenericClass()) {
4245+
addVTableTypeMetadata(classDecl, var, vtableEntries);
4246+
}
4247+
}
4248+
42444249
LinkInfo link = LinkInfo::get(*this, entity, ForDefinition);
42454250
if (link.isUsed())
42464251
addUsedGlobal(var);

lib/IRGen/GenMeta.cpp

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,28 @@ void IRGenModule::emitNonoverriddenMethodDescriptor(const SILVTable *VTable,
334334
getAddrOfLLVMVariable(entity, init, DebugTypeInfo());
335335
}
336336

337+
void IRGenModule::addVTableTypeMetadata(
338+
ClassDecl *decl, llvm::GlobalVariable *var,
339+
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries) {
340+
for (auto ventry : vtableEntries) {
341+
auto method = ventry.second;
342+
auto offset = ventry.first.getValue();
343+
var->addTypeMetadata(offset, typeIdForMethod(*this, method));
344+
}
345+
346+
auto AS = decl->getFormalAccessScope();
347+
if (AS.isFileScope()) {
348+
var->setVCallVisibilityMetadata(
349+
llvm::GlobalObject::VCallVisibility::VCallVisibilityTranslationUnit);
350+
} else if (AS.isPrivate() || AS.isInternal()) {
351+
var->setVCallVisibilityMetadata(
352+
llvm::GlobalObject::VCallVisibility::VCallVisibilityLinkageUnit);
353+
} else {
354+
var->setVCallVisibilityMetadata(
355+
llvm::GlobalObject::VCallVisibility::VCallVisibilityPublic);
356+
}
357+
}
358+
337359
namespace {
338360
template<class Impl>
339361
class ContextDescriptorBuilderBase {
@@ -1232,7 +1254,11 @@ namespace {
12321254
auto addr = IGM.getAddrOfTypeContextDescriptor(Type, HasMetadata,
12331255
B.finishAndCreateFuture());
12341256
auto var = cast<llvm::GlobalVariable>(addr);
1235-
1257+
1258+
if (IGM.getOptions().VirtualFunctionElimination) {
1259+
asImpl().addVTableTypeMetadata(var);
1260+
}
1261+
12361262
var->setConstant(true);
12371263
IGM.setTrueConstGlobal(var);
12381264
return var;
@@ -1440,6 +1466,10 @@ namespace {
14401466
B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(
14411467
getType()->getDeclaredType()->getCanonicalType()));
14421468
}
1469+
1470+
void addVTableTypeMetadata(llvm::GlobalVariable *var) {
1471+
// Structs don't have vtables.
1472+
}
14431473
};
14441474

14451475
class EnumContextDescriptorBuilder
@@ -1522,6 +1552,10 @@ namespace {
15221552
B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(
15231553
getType()->getDeclaredType()->getCanonicalType()));
15241554
}
1555+
1556+
void addVTableTypeMetadata(llvm::GlobalVariable *var) {
1557+
// Enums don't have vtables.
1558+
}
15251559
};
15261560

15271561
class ClassContextDescriptorBuilder
@@ -1547,6 +1581,11 @@ namespace {
15471581
SmallVector<SILDeclRef, 8> VTableEntries;
15481582
SmallVector<std::pair<SILDeclRef, SILDeclRef>, 8> OverrideTableEntries;
15491583

1584+
// As we're constructing the vtable, VTableEntriesForVFE stores the offset
1585+
// (from the beginning of the global) for each vtable slot. The offsets are
1586+
// later turned into !type metadata attributes.
1587+
SmallVector<std::pair<Size, SILDeclRef>, 8> VTableEntriesForVFE;
1588+
15501589
public:
15511590
ClassContextDescriptorBuilder(IRGenModule &IGM, ClassDecl *Type,
15521591
RequireMetadata_t requireMetadata)
@@ -1703,6 +1742,13 @@ namespace {
17031742
IGM.defineMethodDescriptor(fn, Type,
17041743
B.getAddrOfCurrentPosition(IGM.MethodDescriptorStructTy));
17051744

1745+
if (IGM.getOptions().VirtualFunctionElimination) {
1746+
auto offset = B.getNextOffsetFromGlobal() +
1747+
// 1st field of MethodDescriptorStructTy
1748+
Size(IGM.DataLayout.getTypeAllocSize(IGM.Int32Ty));
1749+
VTableEntriesForVFE.push_back(std::pair<Size, SILDeclRef>(offset, fn));
1750+
}
1751+
17061752
// Actually build the descriptor.
17071753
auto descriptor = B.beginStruct(IGM.MethodDescriptorStructTy);
17081754
buildMethodDescriptorFields(IGM, VTable, fn, descriptor);
@@ -1715,7 +1761,15 @@ namespace {
17151761
IGM.emitDispatchThunk(fn);
17161762
}
17171763
}
1718-
1764+
1765+
void addVTableTypeMetadata(llvm::GlobalVariable *var) {
1766+
if (!IGM.getOptions().VirtualFunctionElimination)
1767+
return;
1768+
assert(VTable && "no vtable?!");
1769+
1770+
IGM.addVTableTypeMetadata(getType(), var, VTableEntriesForVFE);
1771+
}
1772+
17191773
void emitNonoverriddenMethod(SILDeclRef fn) {
17201774
// TODO: Derivative functions do not distinguish themselves in the mangled
17211775
// names of method descriptor symbols yet, causing symbol name collisions.
@@ -1731,7 +1785,14 @@ namespace {
17311785
if (hasPublicVisibility(fn.getLinkage(NotForDefinition))) {
17321786
IGM.emitDispatchThunk(fn);
17331787
}
1734-
1788+
1789+
if (IGM.getOptions().VirtualFunctionElimination) {
1790+
auto offset = B.getNextOffsetFromGlobal() +
1791+
// 1st field of MethodDescriptorStructTy
1792+
Size(IGM.DataLayout.getTypeAllocSize(IGM.Int32Ty));
1793+
VTableEntriesForVFE.push_back(std::pair<Size, SILDeclRef>(offset, fn));
1794+
}
1795+
17351796
// Emit a freestanding method descriptor structure. This doesn't have to
17361797
// exist in the table in the class's context descriptor since it isn't
17371798
// in the vtable, but external clients need to be able to link against the
@@ -1761,6 +1822,17 @@ namespace {
17611822
}
17621823

17631824
void emitMethodOverrideDescriptor(SILDeclRef baseRef, SILDeclRef declRef) {
1825+
if (IGM.getOptions().VirtualFunctionElimination) {
1826+
auto offset =
1827+
B.getNextOffsetFromGlobal() +
1828+
// 1st field of MethodOverrideDescriptorStructTy
1829+
Size(IGM.DataLayout.getTypeAllocSize(IGM.RelativeAddressTy)) +
1830+
// 2nd field of MethodOverrideDescriptorStructTy
1831+
Size(IGM.DataLayout.getTypeAllocSize(IGM.RelativeAddressTy));
1832+
VTableEntriesForVFE.push_back(
1833+
std::pair<Size, SILDeclRef>(offset, baseRef));
1834+
}
1835+
17641836
auto descriptor = B.beginStruct(IGM.MethodOverrideDescriptorStructTy);
17651837

17661838
// The class containing the base method.
@@ -2900,6 +2972,11 @@ namespace {
29002972

29012973
Size AddressPoint;
29022974

2975+
// As we're constructing the vtable, VTableEntriesForVFE stores the offset
2976+
// (from the beginning of the global) for each vtable slot. The offsets are
2977+
// later turned into !type metadata attributes.
2978+
SmallVector<std::pair<Size, SILDeclRef>, 8> VTableEntriesForVFE;
2979+
29032980
public:
29042981
ClassMetadataBuilderBase(IRGenModule &IGM, ClassDecl *theClass,
29052982
ConstantStructBuilder &builder,
@@ -3201,12 +3278,21 @@ namespace {
32013278
}
32023279
}
32033280

3281+
if (IGM.getOptions().VirtualFunctionElimination) {
3282+
auto offset = B.getNextOffsetFromGlobal();
3283+
VTableEntriesForVFE.push_back(std::pair<Size, SILDeclRef>(offset, fn));
3284+
}
3285+
32043286
PointerAuthSchema schema =
32053287
afd->hasAsync() ? IGM.getOptions().PointerAuth.AsyncSwiftClassMethods
32063288
: IGM.getOptions().PointerAuth.SwiftClassMethods;
32073289
B.addSignedPointer(ptr, schema, fn);
32083290
}
32093291

3292+
SmallVector<std::pair<Size, SILDeclRef>, 8> getVTableEntriesForVFE() {
3293+
return VTableEntriesForVFE;
3294+
}
3295+
32103296
void addPlaceholder(MissingMemberDecl *m) {
32113297
assert(m->getNumberOfVTableEntries() == 0
32123298
&& "cannot generate metadata with placeholders in it");
@@ -3805,6 +3891,7 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
38053891
bool canBeConstant;
38063892

38073893
auto strategy = IGM.getClassMetadataStrategy(classDecl);
3894+
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries;
38083895

38093896
switch (strategy) {
38103897
case ClassMetadataStrategy::Resilient: {
@@ -3846,6 +3933,9 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
38463933
canBeConstant = builder.canBeConstant();
38473934

38483935
builder.createMetadataAccessFunction();
3936+
if (IGM.getOptions().VirtualFunctionElimination) {
3937+
vtableEntries = builder.getVTableEntriesForVFE();
3938+
}
38493939
break;
38503940
}
38513941
}
@@ -3859,7 +3949,8 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
38593949

38603950
bool isPattern = (strategy == ClassMetadataStrategy::Resilient);
38613951
auto var = IGM.defineTypeMetadata(declaredType, isPattern, canBeConstant,
3862-
init.finishAndCreateFuture(), section);
3952+
init.finishAndCreateFuture(), section,
3953+
vtableEntries);
38633954

38643955
// If the class does not require dynamic initialization, or if it only
38653956
// requires dynamic initialization on a newer Objective-C runtime, add it

lib/IRGen/IRGen.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ void setModuleFlags(IRGenModule &IGM) {
192192
// error during LTO if the user tries to combine files across ABIs.
193193
Module->addModuleFlag(llvm::Module::Error, "Swift Version",
194194
IRGenModule::swiftVersion);
195+
196+
if (IGM.getOptions().VirtualFunctionElimination) {
197+
Module->addModuleFlag(llvm::Module::Error, "Virtual Function Elim", 1);
198+
}
195199
}
196200

197201
void swift::performLLVMOptimizations(const IRGenOptions &Opts,

lib/IRGen/IRGenModule.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,11 +1448,14 @@ private: \
14481448
bool isDestroyer,
14491449
bool isForeign,
14501450
ForDefinition_t forDefinition);
1451-
llvm::GlobalValue *defineTypeMetadata(CanType concreteType,
1452-
bool isPattern,
1453-
bool isConstant,
1454-
ConstantInitFuture init,
1455-
llvm::StringRef section = {});
1451+
void addVTableTypeMetadata(
1452+
ClassDecl *decl, llvm::GlobalVariable *var,
1453+
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries);
1454+
1455+
llvm::GlobalValue *defineTypeMetadata(
1456+
CanType concreteType, bool isPattern, bool isConstant,
1457+
ConstantInitFuture init, llvm::StringRef section = {},
1458+
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries = {});
14561459

14571460
TypeEntityReference getTypeEntityReference(GenericTypeDecl *D);
14581461

0 commit comments

Comments
 (0)