Skip to content

Implement LLVM IR Virtual Function Elimination for Swift classes. #39128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/swift/AST/IRGenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ class IRGenOptions {

unsigned EnableGlobalISel : 1;

unsigned VirtualFunctionElimination : 1;

/// The number of threads for multi-threaded code generation.
unsigned NumThreads = 0;

Expand Down Expand Up @@ -401,7 +403,8 @@ class IRGenOptions {
GenerateProfile(false), EnableDynamicReplacementChaining(false),
DisableRoundTripDebugTypes(false), DisableDebuggerShadowCopies(false),
DisableConcreteTypeMetadataMangledNameAccessors(false),
EnableGlobalISel(false), CmdArgs(),
EnableGlobalISel(false), VirtualFunctionElimination(false),
CmdArgs(),
SanitizeCoverage(llvm::SanitizerCoverageOptions()),
TypeInfoFilter(TypeInfoDumpFilter::All) {}

Expand Down
4 changes: 4 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ def enable_implicit_dynamic : Flag<["-"], "enable-implicit-dynamic">,
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
HelpText<"Add 'dynamic' to all declarations">;

def enable_llvm_vfe : Flag<["-"], "enable-llvm-vfe">,
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
HelpText<"Use LLVM Virtual Function Elimination on Swift class virtual tables">;

def disable_previous_implementation_calls_in_dynamic_replacements :
Flag<["-"], "disable-previous-implementation-calls-in-dynamic-replacements">,
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
Expand Down
4 changes: 4 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,10 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
Opts.EnableGlobalISel = true;
}

if (Args.hasArg(OPT_enable_llvm_vfe)) {
Opts.VirtualFunctionElimination = true;
}

return false;
}

Expand Down
40 changes: 39 additions & 1 deletion lib/IRGen/GenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2672,6 +2672,44 @@ irgen::emitClassResilientInstanceSizeAndAlignMask(IRGenFunction &IGF,
return {size, alignMask};
}

llvm::MDString *irgen::typeIdForMethod(IRGenModule &IGM, SILDeclRef method) {
assert(!method.getOverridden() && "must always be base method");

auto entity = LinkEntity::forMethodDescriptor(method);
auto mangled = entity.mangleAsString();
auto typeId = llvm::MDString::get(*IGM.LLVMContext, mangled);
return typeId;
}

static llvm::Value *emitVTableSlotLoad(IRGenFunction &IGF, Address slot,
SILDeclRef method, Signature signature) {
if (IGF.IGM.getOptions().VirtualFunctionElimination) {
// For LLVM IR VFE, emit a @llvm.type.checked.load with the type of the
// method.
llvm::Function *checkedLoadIntrinsic = llvm::Intrinsic::getDeclaration(
&IGF.IGM.Module, llvm::Intrinsic::type_checked_load);
auto slotAsPointer = IGF.Builder.CreateBitCast(slot, IGF.IGM.Int8PtrTy);
auto typeId = typeIdForMethod(IGF.IGM, method);

// Arguments for @llvm.type.checked.load: 1) target address, 2) offset -
// always 0 because target address is directly pointing to the right slot,
// 3) type identifier, i.e. the mangled name of the *base* method.
SmallVector<llvm::Value *, 8> args;
args.push_back(slotAsPointer.getAddress());
args.push_back(llvm::ConstantInt::get(IGF.IGM.Int32Ty, 0));
args.push_back(llvm::MetadataAsValue::get(*IGF.IGM.LLVMContext, typeId));

llvm::Value *checkedLoad =
IGF.Builder.CreateCall(checkedLoadIntrinsic, args);
auto fnPtr = IGF.Builder.CreateExtractValue(checkedLoad, 0);
return IGF.Builder.CreateBitCast(fnPtr,
signature.getType()->getPointerTo());
}

// Not doing LLVM IR VFE, can just be a direct load.
return IGF.emitInvariantLoad(slot);
}

FunctionPointer irgen::emitVirtualMethodValue(IRGenFunction &IGF,
llvm::Value *metadata,
SILDeclRef method,
Expand All @@ -2690,7 +2728,7 @@ FunctionPointer irgen::emitVirtualMethodValue(IRGenFunction &IGF,
auto slot = IGF.emitAddressAtOffset(metadata, offset,
signature.getType()->getPointerTo(),
IGF.IGM.getPointerAlignment());
auto fnPtr = IGF.emitInvariantLoad(slot);
auto fnPtr = emitVTableSlotLoad(IGF, slot, method, signature);
auto &schema = methodType->isAsync()
? IGF.getOptions().PointerAuth.AsyncSwiftClassMethods
: IGF.getOptions().PointerAuth.SwiftClassMethods;
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/GenClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace llvm {
class Constant;
class Value;
class Function;
class MDString;
}

namespace swift {
Expand Down Expand Up @@ -201,6 +202,9 @@ namespace irgen {
ClassDecl *theClass,
llvm::Value *metadata);

/// For VFE, returns a type identifier for the given base method on a class.
llvm::MDString *typeIdForMethod(IRGenModule &IGM, SILDeclRef method);

/// Given a metadata pointer, emit the callee for the given method.
FunctionPointer emitVirtualMethodValue(IRGenFunction &IGF,
llvm::Value *metadata,
Expand Down
15 changes: 10 additions & 5 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4202,11 +4202,10 @@ llvm::GlobalValue *IRGenModule::defineAlias(LinkEntity entity,
/// public symbol for the metadata references. This function will rewrite any
/// existing external declaration to the address point as an alias into the
/// full metadata object.
llvm::GlobalValue *IRGenModule::defineTypeMetadata(CanType concreteType,
bool isPattern,
bool isConstant,
ConstantInitFuture init,
llvm::StringRef section) {
llvm::GlobalValue *IRGenModule::defineTypeMetadata(
CanType concreteType, bool isPattern, bool isConstant,
ConstantInitFuture init, llvm::StringRef section,
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries) {
assert(init);

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

if (getOptions().VirtualFunctionElimination) {
if (auto classDecl = concreteType->getClassOrBoundGenericClass()) {
addVTableTypeMetadata(classDecl, var, vtableEntries);
}
}

LinkInfo link = LinkInfo::get(*this, entity, ForDefinition);
if (link.isUsed())
addUsedGlobal(var);
Expand Down
99 changes: 95 additions & 4 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,28 @@ void IRGenModule::emitNonoverriddenMethodDescriptor(const SILVTable *VTable,
getAddrOfLLVMVariable(entity, init, DebugTypeInfo());
}

void IRGenModule::addVTableTypeMetadata(
ClassDecl *decl, llvm::GlobalVariable *var,
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries) {
for (auto ventry : vtableEntries) {
auto method = ventry.second;
auto offset = ventry.first.getValue();
var->addTypeMetadata(offset, typeIdForMethod(*this, method));
}

auto AS = decl->getFormalAccessScope();
if (AS.isFileScope()) {
var->setVCallVisibilityMetadata(
llvm::GlobalObject::VCallVisibility::VCallVisibilityTranslationUnit);
} else if (AS.isPrivate() || AS.isInternal()) {
var->setVCallVisibilityMetadata(
llvm::GlobalObject::VCallVisibility::VCallVisibilityLinkageUnit);
} else {
var->setVCallVisibilityMetadata(
llvm::GlobalObject::VCallVisibility::VCallVisibilityPublic);
}
}

namespace {
template<class Impl>
class ContextDescriptorBuilderBase {
Expand Down Expand Up @@ -1232,7 +1254,11 @@ namespace {
auto addr = IGM.getAddrOfTypeContextDescriptor(Type, HasMetadata,
B.finishAndCreateFuture());
auto var = cast<llvm::GlobalVariable>(addr);


if (IGM.getOptions().VirtualFunctionElimination) {
asImpl().addVTableTypeMetadata(var);
}

var->setConstant(true);
IGM.setTrueConstGlobal(var);
return var;
Expand Down Expand Up @@ -1440,6 +1466,10 @@ namespace {
B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(
getType()->getDeclaredType()->getCanonicalType()));
}

void addVTableTypeMetadata(llvm::GlobalVariable *var) {
// Structs don't have vtables.
}
};

class EnumContextDescriptorBuilder
Expand Down Expand Up @@ -1522,6 +1552,10 @@ namespace {
B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(
getType()->getDeclaredType()->getCanonicalType()));
}

void addVTableTypeMetadata(llvm::GlobalVariable *var) {
// Enums don't have vtables.
}
};

class ClassContextDescriptorBuilder
Expand All @@ -1547,6 +1581,11 @@ namespace {
SmallVector<SILDeclRef, 8> VTableEntries;
SmallVector<std::pair<SILDeclRef, SILDeclRef>, 8> OverrideTableEntries;

// As we're constructing the vtable, VTableEntriesForVFE stores the offset
// (from the beginning of the global) for each vtable slot. The offsets are
// later turned into !type metadata attributes.
SmallVector<std::pair<Size, SILDeclRef>, 8> VTableEntriesForVFE;

public:
ClassContextDescriptorBuilder(IRGenModule &IGM, ClassDecl *Type,
RequireMetadata_t requireMetadata)
Expand Down Expand Up @@ -1703,6 +1742,13 @@ namespace {
IGM.defineMethodDescriptor(fn, Type,
B.getAddrOfCurrentPosition(IGM.MethodDescriptorStructTy));

if (IGM.getOptions().VirtualFunctionElimination) {
auto offset = B.getNextOffsetFromGlobal() +
// 1st field of MethodDescriptorStructTy
Size(IGM.DataLayout.getTypeAllocSize(IGM.Int32Ty));
VTableEntriesForVFE.push_back(std::pair<Size, SILDeclRef>(offset, fn));
}

// Actually build the descriptor.
auto descriptor = B.beginStruct(IGM.MethodDescriptorStructTy);
buildMethodDescriptorFields(IGM, VTable, fn, descriptor);
Expand All @@ -1715,7 +1761,15 @@ namespace {
IGM.emitDispatchThunk(fn);
}
}


void addVTableTypeMetadata(llvm::GlobalVariable *var) {
if (!IGM.getOptions().VirtualFunctionElimination)
return;
assert(VTable && "no vtable?!");

IGM.addVTableTypeMetadata(getType(), var, VTableEntriesForVFE);
}

void emitNonoverriddenMethod(SILDeclRef fn) {
// TODO: Derivative functions do not distinguish themselves in the mangled
// names of method descriptor symbols yet, causing symbol name collisions.
Expand All @@ -1731,7 +1785,14 @@ namespace {
if (hasPublicVisibility(fn.getLinkage(NotForDefinition))) {
IGM.emitDispatchThunk(fn);
}


if (IGM.getOptions().VirtualFunctionElimination) {
auto offset = B.getNextOffsetFromGlobal() +
// 1st field of MethodDescriptorStructTy
Size(IGM.DataLayout.getTypeAllocSize(IGM.Int32Ty));
VTableEntriesForVFE.push_back(std::pair<Size, SILDeclRef>(offset, fn));
}

// Emit a freestanding method descriptor structure. This doesn't have to
// exist in the table in the class's context descriptor since it isn't
// in the vtable, but external clients need to be able to link against the
Expand Down Expand Up @@ -1761,6 +1822,17 @@ namespace {
}

void emitMethodOverrideDescriptor(SILDeclRef baseRef, SILDeclRef declRef) {
if (IGM.getOptions().VirtualFunctionElimination) {
auto offset =
B.getNextOffsetFromGlobal() +
// 1st field of MethodOverrideDescriptorStructTy
Size(IGM.DataLayout.getTypeAllocSize(IGM.RelativeAddressTy)) +
// 2nd field of MethodOverrideDescriptorStructTy
Size(IGM.DataLayout.getTypeAllocSize(IGM.RelativeAddressTy));
VTableEntriesForVFE.push_back(
std::pair<Size, SILDeclRef>(offset, baseRef));
}

auto descriptor = B.beginStruct(IGM.MethodOverrideDescriptorStructTy);

// The class containing the base method.
Expand Down Expand Up @@ -2900,6 +2972,11 @@ namespace {

Size AddressPoint;

// As we're constructing the vtable, VTableEntriesForVFE stores the offset
// (from the beginning of the global) for each vtable slot. The offsets are
// later turned into !type metadata attributes.
SmallVector<std::pair<Size, SILDeclRef>, 8> VTableEntriesForVFE;

public:
ClassMetadataBuilderBase(IRGenModule &IGM, ClassDecl *theClass,
ConstantStructBuilder &builder,
Expand Down Expand Up @@ -3201,12 +3278,21 @@ namespace {
}
}

if (IGM.getOptions().VirtualFunctionElimination) {
auto offset = B.getNextOffsetFromGlobal();
VTableEntriesForVFE.push_back(std::pair<Size, SILDeclRef>(offset, fn));
}

PointerAuthSchema schema =
afd->hasAsync() ? IGM.getOptions().PointerAuth.AsyncSwiftClassMethods
: IGM.getOptions().PointerAuth.SwiftClassMethods;
B.addSignedPointer(ptr, schema, fn);
}

SmallVector<std::pair<Size, SILDeclRef>, 8> getVTableEntriesForVFE() {
return VTableEntriesForVFE;
}

void addPlaceholder(MissingMemberDecl *m) {
assert(m->getNumberOfVTableEntries() == 0
&& "cannot generate metadata with placeholders in it");
Expand Down Expand Up @@ -3805,6 +3891,7 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
bool canBeConstant;

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

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

builder.createMetadataAccessFunction();
if (IGM.getOptions().VirtualFunctionElimination) {
vtableEntries = builder.getVTableEntriesForVFE();
}
break;
}
}
Expand All @@ -3859,7 +3949,8 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,

bool isPattern = (strategy == ClassMetadataStrategy::Resilient);
auto var = IGM.defineTypeMetadata(declaredType, isPattern, canBeConstant,
init.finishAndCreateFuture(), section);
init.finishAndCreateFuture(), section,
vtableEntries);

// If the class does not require dynamic initialization, or if it only
// requires dynamic initialization on a newer Objective-C runtime, add it
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/IRGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ void setModuleFlags(IRGenModule &IGM) {
// error during LTO if the user tries to combine files across ABIs.
Module->addModuleFlag(llvm::Module::Error, "Swift Version",
IRGenModule::swiftVersion);

if (IGM.getOptions().VirtualFunctionElimination) {
Module->addModuleFlag(llvm::Module::Error, "Virtual Function Elim", 1);
}
}

void swift::performLLVMOptimizations(const IRGenOptions &Opts,
Expand Down
13 changes: 8 additions & 5 deletions lib/IRGen/IRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1448,11 +1448,14 @@ private: \
bool isDestroyer,
bool isForeign,
ForDefinition_t forDefinition);
llvm::GlobalValue *defineTypeMetadata(CanType concreteType,
bool isPattern,
bool isConstant,
ConstantInitFuture init,
llvm::StringRef section = {});
void addVTableTypeMetadata(
ClassDecl *decl, llvm::GlobalVariable *var,
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries);

llvm::GlobalValue *defineTypeMetadata(
CanType concreteType, bool isPattern, bool isConstant,
ConstantInitFuture init, llvm::StringRef section = {},
SmallVector<std::pair<Size, SILDeclRef>, 8> vtableEntries = {});

TypeEntityReference getTypeEntityReference(GenericTypeDecl *D);

Expand Down
Loading