Skip to content

Commit 9fb54e9

Browse files
authored
Add -internalize-at-link flag to allow dead stripping and VFE across modules at link time (#39214)
- Under -internalize-at-link, stop unconditionally marking all globals as used. - Under -internalize-at-link, restrict visibility of vtables to linkage unit. - Emit virtual method thunks for cross-module vcalls when VFE is enabled. - Use thunks for vcalls across modules when VFE is enabled. - Adjust TBDGen to account for virtual method thunks when VFE is enabled. - Add an end-to-end test case for cross-module VFE.
1 parent 84200b8 commit 9fb54e9

File tree

9 files changed

+152
-39
lines changed

9 files changed

+152
-39
lines changed

include/swift/AST/IRGenOptions.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ class IRGenOptions {
357357

358358
unsigned WitnessMethodElimination : 1;
359359

360+
unsigned InternalizeAtLink : 1;
361+
360362
/// The number of threads for multi-threaded code generation.
361363
unsigned NumThreads = 0;
362364

@@ -416,7 +418,7 @@ class IRGenOptions {
416418
DisableRoundTripDebugTypes(false), DisableDebuggerShadowCopies(false),
417419
DisableConcreteTypeMetadataMangledNameAccessors(false),
418420
EnableGlobalISel(false), VirtualFunctionElimination(false),
419-
WitnessMethodElimination(false),
421+
WitnessMethodElimination(false), InternalizeAtLink(false),
420422
CmdArgs(),
421423
SanitizeCoverage(llvm::SanitizerCoverageOptions()),
422424
TypeInfoFilter(TypeInfoDumpFilter::All) {}

include/swift/Option/FrontendOptions.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@ def enable_llvm_wme : Flag<["-"], "enable-llvm-wme">,
501501
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
502502
HelpText<"Use LLVM IR Witness Method Elimination on Swift protocol witness tables">;
503503

504+
def internalize_at_link : Flag<["-"], "internalize-at-link">,
505+
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
506+
HelpText<"Allow internalizing public symbols and vtables at link time (assume"
507+
" all client code of public types is part of the same link unit, or that"
508+
" external symbols are explicitly requested via -exported_symbols_list)">;
509+
504510
def disable_previous_implementation_calls_in_dynamic_replacements :
505511
Flag<["-"], "disable-previous-implementation-calls-in-dynamic-replacements">,
506512
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,

include/swift/TBDGen/TBDGen.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ struct TBDGenOptions {
4141
/// Whether to include only symbols with public linkage.
4242
bool PublicSymbolsOnly = true;
4343

44+
/// Whether LLVM IR Virtual Function Elimination is enabled.
45+
bool VirtualFunctionElimination = false;
46+
4447
/// The install_name to use in the TBD file.
4548
std::string InstallName;
4649

@@ -70,6 +73,7 @@ struct TBDGenOptions {
7073
lhs.IsInstallAPI == rhs.IsInstallAPI &&
7174
lhs.LinkerDirectivesOnly == rhs.LinkerDirectivesOnly &&
7275
lhs.PublicSymbolsOnly == rhs.PublicSymbolsOnly &&
76+
lhs.VirtualFunctionElimination == rhs.VirtualFunctionElimination &&
7377
lhs.InstallName == rhs.InstallName &&
7478
lhs.ModuleLinkName == rhs.ModuleLinkName &&
7579
lhs.CurrentVersion == rhs.CurrentVersion &&
@@ -86,7 +90,8 @@ struct TBDGenOptions {
8690
using namespace llvm;
8791
return hash_combine(
8892
opts.HasMultipleIGMs, opts.IsInstallAPI, opts.LinkerDirectivesOnly,
89-
opts.PublicSymbolsOnly, opts.InstallName, opts.ModuleLinkName,
93+
opts.PublicSymbolsOnly, opts.VirtualFunctionElimination,
94+
opts.InstallName, opts.ModuleLinkName,
9095
opts.CurrentVersion, opts.CompatibilityVersion,
9196
opts.ModuleInstallNameMapPath,
9297
hash_combine_range(opts.embedSymbolsFromModules.begin(),

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,8 @@ static bool ParseTBDGenArgs(TBDGenOptions &Opts, ArgList &Args,
15281528

15291529
Opts.IsInstallAPI = Args.hasArg(OPT_tbd_is_installapi);
15301530

1531+
Opts.VirtualFunctionElimination = Args.hasArg(OPT_enable_llvm_vfe);
1532+
15311533
if (const Arg *A = Args.getLastArg(OPT_tbd_compatibility_version)) {
15321534
Opts.CompatibilityVersion = A->getValue();
15331535
}
@@ -1912,6 +1914,10 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
19121914
Opts.WitnessMethodElimination = true;
19131915
}
19141916

1917+
if (Args.hasArg(OPT_internalize_at_link)) {
1918+
Opts.InternalizeAtLink = true;
1919+
}
1920+
19151921
// Default to disabling swift async extended frame info on anything but
19161922
// darwin. Other platforms are unlikely to have support for extended frame
19171923
// pointer information.

lib/IRGen/GenDecl.cpp

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,29 @@ IRGenModule::getAddrOfParentContextDescriptor(DeclContext *from,
880880
fromAnonymousContext);
881881
}
882882

883+
static void markGlobalAsUsedBasedOnLinkage(IRGenModule &IGM, LinkInfo &link,
884+
llvm::GlobalValue *global) {
885+
// If we're internalizing public symbols at link time, don't make globals
886+
// unconditionally externally visible.
887+
if (IGM.getOptions().InternalizeAtLink)
888+
return;
889+
890+
// Everything externally visible is considered used in Swift.
891+
// That mostly means we need to be good at not marking things external.
892+
if (link.isUsed())
893+
IGM.addUsedGlobal(global);
894+
}
895+
896+
bool LinkInfo::isUsed(IRLinkage IRL) {
897+
// Everything externally visible is considered used in Swift.
898+
// That mostly means we need to be good at not marking things external.
899+
return IRL.Linkage == llvm::GlobalValue::ExternalLinkage &&
900+
(IRL.Visibility == llvm::GlobalValue::DefaultVisibility ||
901+
IRL.Visibility == llvm::GlobalValue::ProtectedVisibility) &&
902+
(IRL.DLLStorage == llvm::GlobalValue::DefaultStorageClass ||
903+
IRL.DLLStorage == llvm::GlobalValue::DLLExportStorageClass);
904+
}
905+
883906
/// Add the given global value to @llvm.used.
884907
///
885908
/// This value must have a definition by the time the module is finalized.
@@ -2041,10 +2064,8 @@ void irgen::updateLinkageForDefinition(IRGenModule &IGM,
20412064
ForDefinition, weakImported, isKnownLocal);
20422065
ApplyIRLinkage(IRL).to(global);
20432066

2044-
// Everything externally visible is considered used in Swift.
2045-
// That mostly means we need to be good at not marking things external.
2046-
if (LinkInfo::isUsed(IRL))
2047-
IGM.addUsedGlobal(global);
2067+
LinkInfo link = LinkInfo::get(IGM, entity, ForDefinition);
2068+
markGlobalAsUsedBasedOnLinkage(IGM, link, global);
20482069
}
20492070

20502071
LinkInfo LinkInfo::get(IRGenModule &IGM, const LinkEntity &entity,
@@ -2143,25 +2164,11 @@ llvm::Function *irgen::createFunction(IRGenModule &IGM,
21432164
if (!updatedAttrs.isEmpty())
21442165
fn->setAttributes(updatedAttrs);
21452166

2146-
// Everything externally visible is considered used in Swift.
2147-
// That mostly means we need to be good at not marking things external.
2148-
if (linkInfo.isUsed()) {
2149-
IGM.addUsedGlobal(fn);
2150-
}
2167+
markGlobalAsUsedBasedOnLinkage(IGM, linkInfo, fn);
21512168

21522169
return fn;
21532170
}
21542171

2155-
bool LinkInfo::isUsed(IRLinkage IRL) {
2156-
// Everything externally visible is considered used in Swift.
2157-
// That mostly means we need to be good at not marking things external.
2158-
return IRL.Linkage == llvm::GlobalValue::ExternalLinkage &&
2159-
(IRL.Visibility == llvm::GlobalValue::DefaultVisibility ||
2160-
IRL.Visibility == llvm::GlobalValue::ProtectedVisibility) &&
2161-
(IRL.DLLStorage == llvm::GlobalValue::DefaultStorageClass ||
2162-
IRL.DLLStorage == llvm::GlobalValue::DLLExportStorageClass);
2163-
}
2164-
21652172
/// Get or create an LLVM global variable with these linkage rules.
21662173
llvm::GlobalVariable *swift::irgen::createVariable(
21672174
IRGenModule &IGM, LinkInfo &linkInfo, llvm::Type *storageType,
@@ -2191,11 +2198,7 @@ llvm::GlobalVariable *swift::irgen::createVariable(
21912198
.to(var, linkInfo.isForDefinition());
21922199
var->setAlignment(llvm::MaybeAlign(alignment.getValue()));
21932200

2194-
// Everything externally visible is considered used in Swift.
2195-
// That mostly means we need to be good at not marking things external.
2196-
if (linkInfo.isUsed()) {
2197-
IGM.addUsedGlobal(var);
2198-
}
2201+
markGlobalAsUsedBasedOnLinkage(IGM, linkInfo, var);
21992202

22002203
if (IGM.DebugInfo && !DbgTy.isNull() && linkInfo.isForDefinition())
22012204
IGM.DebugInfo->emitGlobalVariableDeclaration(
@@ -4161,9 +4164,7 @@ llvm::GlobalValue *IRGenModule::defineAlias(LinkEntity entity,
41614164
ApplyIRLinkage({link.getLinkage(), link.getVisibility(), link.getDLLStorage()})
41624165
.to(alias);
41634166

4164-
if (link.isUsed()) {
4165-
addUsedGlobal(alias);
4166-
}
4167+
markGlobalAsUsedBasedOnLinkage(*this, link, alias);
41674168

41684169
// Replace an existing external declaration for the address point.
41694170
if (entry) {
@@ -4247,8 +4248,7 @@ llvm::GlobalValue *IRGenModule::defineTypeMetadata(
42474248
}
42484249

42494250
LinkInfo link = LinkInfo::get(*this, entity, ForDefinition);
4250-
if (link.isUsed())
4251-
addUsedGlobal(var);
4251+
markGlobalAsUsedBasedOnLinkage(*this, link, var);
42524252

42534253
/// For concrete metadata, we want to use the initializer on the
42544254
/// "full metadata", and define the "direct" address point as an alias.

lib/IRGen/GenMeta.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,9 @@ void IRGenModule::addVTableTypeMetadata(
350350
} else if (AS.isPrivate() || AS.isInternal()) {
351351
var->setVCallVisibilityMetadata(
352352
llvm::GlobalObject::VCallVisibility::VCallVisibilityLinkageUnit);
353+
} else if (getOptions().InternalizeAtLink) {
354+
var->setVCallVisibilityMetadata(
355+
llvm::GlobalObject::VCallVisibility::VCallVisibilityLinkageUnit);
353356
} else {
354357
var->setVCallVisibilityMetadata(
355358
llvm::GlobalObject::VCallVisibility::VCallVisibilityPublic);
@@ -1756,8 +1759,9 @@ namespace {
17561759

17571760
// Emit method dispatch thunk if the class is resilient.
17581761
auto *func = cast<AbstractFunctionDecl>(fn.getDecl());
1759-
if (Resilient &&
1760-
func->getEffectiveAccess() >= AccessLevel::Public) {
1762+
1763+
if ((Resilient && func->getEffectiveAccess() >= AccessLevel::Public) ||
1764+
IGM.getOptions().VirtualFunctionElimination) {
17611765
IGM.emitDispatchThunk(fn);
17621766
}
17631767
}
@@ -1782,9 +1786,10 @@ namespace {
17821786
// method for external clients.
17831787

17841788
// Emit method dispatch thunk.
1785-
if (hasPublicVisibility(fn.getLinkage(NotForDefinition))) {
1786-
IGM.emitDispatchThunk(fn);
1787-
}
1789+
if (hasPublicVisibility(fn.getLinkage(NotForDefinition)) ||
1790+
IGM.getOptions().VirtualFunctionElimination) {
1791+
IGM.emitDispatchThunk(fn);
1792+
}
17881793

17891794
if (IGM.getOptions().VirtualFunctionElimination) {
17901795
auto offset = B.getNextOffsetFromGlobal() +

lib/IRGen/IRGenSIL.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6776,8 +6776,26 @@ void IRGenSILFunction::visitClassMethodInst(swift::ClassMethodInst *i) {
67766776
auto methodType = i->getType().castTo<SILFunctionType>();
67776777

67786778
auto *classDecl = cast<ClassDecl>(method.getDecl()->getDeclContext());
6779-
if (IGM.hasResilientMetadata(classDecl,
6780-
ResilienceExpansion::Maximal)) {
6779+
bool shouldUseDispatchThunk = false;
6780+
if (IGM.hasResilientMetadata(classDecl, ResilienceExpansion::Maximal)) {
6781+
shouldUseDispatchThunk = true;
6782+
} else if (IGM.getOptions().VirtualFunctionElimination) {
6783+
// For VFE, use a thunk if the target class is in another module. This
6784+
// enables VFE (which scans function bodies for used type identifiers) to
6785+
// work across modules by relying on:
6786+
//
6787+
// (1) virtual call sites are in thunks in the same module as the class,
6788+
// therefore they are always visible to VFE,
6789+
// (2) if a thunk symbol is unused by any other module, we can safely
6790+
// eliminate it.
6791+
//
6792+
// See the virtual-function-elimination-two-modules.swift testcase for an
6793+
// example of how cross-module VFE can be effectively used.
6794+
shouldUseDispatchThunk =
6795+
classDecl->getModuleContext() != IGM.getSwiftModule();
6796+
}
6797+
6798+
if (shouldUseDispatchThunk) {
67816799
llvm::Constant *fnPtr = IGM.getAddrOfDispatchThunk(method, NotForDefinition);
67826800

67836801
if (methodType->isAsync()) {

lib/TBDGen/TBDGen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ void TBDGenVisitor::visitClassDecl(ClassDecl *CD) {
934934
void addMethod(SILDeclRef method) {
935935
assert(method.getDecl()->getDeclContext() == CD);
936936

937-
if (CD->hasResilientMetadata()) {
937+
if (TBD.Opts.VirtualFunctionElimination || CD->hasResilientMetadata()) {
938938
if (FirstTime) {
939939
FirstTime = false;
940940

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Tests that under -enable-llvm-vfe + -internalize-at-link, cross-module
2+
// virtual calls are done via thunks and LLVM GlobalDCE is able to remove unused
3+
// virtual methods from a library based on a list of used symbols by a client.
4+
5+
// RUN: %empty-directory(%t)
6+
7+
// (1) Build library swiftmodule
8+
// RUN: %target-build-swift -parse-as-library -Xfrontend -enable-llvm-vfe \
9+
// RUN: %s -DLIBRARY -module-name Library \
10+
// RUN: -emit-module -o %t/Library.swiftmodule \
11+
// RUN: -emit-tbd -emit-tbd-path %t/libLibrary.tbd -Xfrontend -tbd-install_name=%t/libLibrary.dylib
12+
13+
// (2) Build client
14+
// RUN: %target-build-swift -parse-as-library -Xfrontend -enable-llvm-vfe \
15+
// RUN: %s -DCLIENT -module-name Main -I%t -L%t -lLibrary -o %t/main
16+
17+
// (3) Extract a list of used symbols by client from library
18+
// RUN: %llvm-nm --undefined-only -m %t/main | grep 'from libLibrary' | awk '{print $3}' > %t/used-symbols
19+
20+
// (4) Now produce the .dylib with just the symbols needed by the client
21+
// RUN: %target-build-swift -parse-as-library -Xfrontend -enable-llvm-vfe -Xfrontend -internalize-at-link \
22+
// RUN: %s -DLIBRARY -lto=llvm-full %lto_flags -module-name Library \
23+
// RUN: -emit-library -o %t/libLibrary.dylib \
24+
// RUN: -Xlinker -exported_symbols_list -Xlinker %t/used-symbols -Xlinker -dead_strip
25+
26+
// (5) Check list of symbols in library
27+
// RUN: %llvm-nm --defined-only %t/libLibrary.dylib | %FileCheck %s --check-prefix=NM
28+
29+
// (6) Execution test
30+
// RUN: %target-run %t/main | %FileCheck %s
31+
32+
// REQUIRES: executable_test
33+
34+
// Test disabled until LLVM GlobalDCE supports Swift vtables.
35+
// REQUIRES: rdar81868930
36+
37+
#if LIBRARY
38+
39+
public class MyClass {
40+
public init() {}
41+
public func foo() { print("MyClass.foo") }
42+
public func bar() { print("MyClass.bar") }
43+
}
44+
45+
public class MyDerivedClass: MyClass {
46+
override public func foo() { print("MyDerivedClass.foo") }
47+
override public func bar() { print("MyDerivedClass.bar") }
48+
}
49+
50+
// NM-NOT: $s7Library14MyDerivedClassC3baryyF
51+
// NM: $s7Library14MyDerivedClassC3fooyyF
52+
// NM-NOT: $s7Library7MyClassC3baryyF
53+
// NM: $s7Library7MyClassC3fooyyF
54+
55+
#endif
56+
57+
#if CLIENT
58+
59+
import Library
60+
61+
@_cdecl("main")
62+
func main() -> Int32 {
63+
let o: MyClass = MyDerivedClass()
64+
o.foo()
65+
print("Done")
66+
// CHECK: MyDerivedClass.foo
67+
// CHECK-NEXT: Done
68+
return 0
69+
}
70+
71+
#endif

0 commit comments

Comments
 (0)