Skip to content

Commit e405a9f

Browse files
authored
Extend LLVM IR WME to use thunks for cross-module witness method calls. (swiftlang#39528)
This enables optimizing / dead-stripping of witness methods across modules at LTO time. - Under -internalize-at-link, restrict visibility of wtables to linkage unit. - Emit thunks for cross-module wcalls when WME is enabled. - Use thunks for wcalls across modules when WME is enabled. - Adjust TBDGen to account for witness method thunks when WME is enabled. - Add an IR test to check that thunks are used when doing cross-module calls. - Add an end-to-end test case for cross-module WME.
1 parent d993112 commit e405a9f

File tree

8 files changed

+145
-6
lines changed

8 files changed

+145
-6
lines changed

include/swift/TBDGen/TBDGen.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ struct TBDGenOptions {
4444
/// Whether LLVM IR Virtual Function Elimination is enabled.
4545
bool VirtualFunctionElimination = false;
4646

47+
/// Whether LLVM IR Witness Method Elimination is enabled.
48+
bool WitnessMethodElimination = false;
49+
4750
/// The install_name to use in the TBD file.
4851
std::string InstallName;
4952

@@ -74,6 +77,7 @@ struct TBDGenOptions {
7477
lhs.LinkerDirectivesOnly == rhs.LinkerDirectivesOnly &&
7578
lhs.PublicSymbolsOnly == rhs.PublicSymbolsOnly &&
7679
lhs.VirtualFunctionElimination == rhs.VirtualFunctionElimination &&
80+
lhs.WitnessMethodElimination == rhs.WitnessMethodElimination &&
7781
lhs.InstallName == rhs.InstallName &&
7882
lhs.ModuleLinkName == rhs.ModuleLinkName &&
7983
lhs.CurrentVersion == rhs.CurrentVersion &&
@@ -91,6 +95,7 @@ struct TBDGenOptions {
9195
return hash_combine(
9296
opts.HasMultipleIGMs, opts.IsInstallAPI, opts.LinkerDirectivesOnly,
9397
opts.PublicSymbolsOnly, opts.VirtualFunctionElimination,
98+
opts.WitnessMethodElimination,
9499
opts.InstallName, opts.ModuleLinkName,
95100
opts.CurrentVersion, opts.CompatibilityVersion,
96101
opts.ModuleInstallNameMapPath,

lib/Frontend/CompilerInvocation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,7 @@ static bool ParseTBDGenArgs(TBDGenOptions &Opts, ArgList &Args,
15421542
Opts.IsInstallAPI = Args.hasArg(OPT_tbd_is_installapi);
15431543

15441544
Opts.VirtualFunctionElimination = Args.hasArg(OPT_enable_llvm_vfe);
1545+
Opts.WitnessMethodElimination = Args.hasArg(OPT_enable_llvm_wme);
15451546

15461547
if (const Arg *A = Args.getLastArg(OPT_tbd_compatibility_version)) {
15471548
Opts.CompatibilityVersion = A->getValue();

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ namespace {
824824
SILDeclRef func(entry.getFunction());
825825

826826
// Emit the dispatch thunk.
827-
if (Resilient)
827+
if (Resilient || IGM.getOptions().WitnessMethodElimination)
828828
IGM.emitDispatchThunk(func);
829829

830830
// Classify the function.

lib/IRGen/GenProto.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,8 +2193,13 @@ static void addWTableTypeMetadata(IRGenModule &IGM,
21932193
break;
21942194
case SILLinkage::Public:
21952195
default:
2196-
global->setVCallVisibilityMetadata(
2197-
llvm::GlobalObject::VCallVisibility::VCallVisibilityPublic);
2196+
if (IGM.getOptions().InternalizeAtLink) {
2197+
global->setVCallVisibilityMetadata(
2198+
llvm::GlobalObject::VCallVisibility::VCallVisibilityLinkageUnit);
2199+
} else {
2200+
global->setVCallVisibilityMetadata(
2201+
llvm::GlobalObject::VCallVisibility::VCallVisibilityPublic);
2202+
}
21982203
break;
21992204
}
22002205
}

lib/IRGen/IRGenSIL.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6568,8 +6568,18 @@ void IRGenSILFunction::visitWitnessMethodInst(swift::WitnessMethodInst *i) {
65686568

65696569
assert(member.requiresNewWitnessTableEntry());
65706570

6571-
if (IGM.isResilient(conformance.getRequirement(),
6572-
ResilienceExpansion::Maximal)) {
6571+
bool shouldUseDispatchThunk = false;
6572+
if (IGM.isResilient(conformance.getRequirement(), ResilienceExpansion::Maximal)) {
6573+
shouldUseDispatchThunk = true;
6574+
} else if (IGM.getOptions().WitnessMethodElimination) {
6575+
// For WME, use a thunk if the target protocol is defined in another module.
6576+
// This way, we guarantee all wmethod call sites are visible to the LLVM VFE
6577+
// optimization in GlobalDCE.
6578+
auto protoDecl = cast<ProtocolDecl>(member.getDecl()->getDeclContext());
6579+
shouldUseDispatchThunk = protoDecl->getModuleContext() != IGM.getSwiftModule();
6580+
}
6581+
6582+
if (shouldUseDispatchThunk) {
65736583
llvm::Constant *fnPtr = IGM.getAddrOfDispatchThunk(member, NotForDefinition);
65746584
llvm::Constant *secondaryValue = nullptr;
65756585

lib/TBDGen/TBDGen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ void TBDGenVisitor::visitProtocolDecl(ProtocolDecl *PD) {
10771077
: TBD(TBD), PD(PD), Resilient(PD->getParentModule()->isResilient()) {}
10781078

10791079
void addMethod(SILDeclRef declRef) {
1080-
if (Resilient) {
1080+
if (Resilient || TBD.Opts.WitnessMethodElimination) {
10811081
TBD.addDispatchThunk(declRef);
10821082
TBD.addMethodDescriptor(declRef);
10831083
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Tests that under -enable-llvm-wme, protocol witness table calls to protocols
2+
// defined by other modules are using thunks (instead of direct wtable loads).
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-build-swift -Xfrontend -enable-llvm-wme -parse-as-library %s -DLIBRARY -module-name Library -emit-module -o %t/Library.swiftmodule
6+
// RUN: %target-build-swift -Xfrontend -enable-llvm-wme -parse-as-library %s -DCLIENT -module-name Main -I%t -emit-ir -o - | %FileCheck %s
7+
8+
#if LIBRARY
9+
10+
public protocol MyLibraryProtocol {
11+
func library_req()
12+
}
13+
14+
#endif
15+
16+
#if CLIENT
17+
18+
import Library
19+
20+
public protocol MyLocalProtocol {
21+
func local_req()
22+
}
23+
24+
extension MyLocalProtocol {
25+
func func1() {
26+
// CHECK: define hidden swiftcc void @"$s4Main15MyLocalProtocolPAAE5func1yyF"
27+
self.local_req()
28+
// CHECK: [[SLOT:%.*]] = getelementptr inbounds i8*, i8** {{.*}}, i32 1
29+
// CHECK: [[SLOTASPTR:%.*]] = bitcast i8** [[SLOT]] to i8*
30+
// CHECK: call { i8*, i1 } @llvm.type.checked.load(i8* [[SLOTASPTR]], i32 0, metadata !"$s4Main15MyLocalProtocolP9local_reqyyFTq")
31+
// CHECK: ret void
32+
}
33+
}
34+
35+
extension MyLibraryProtocol {
36+
func func2() {
37+
// CHECK: define hidden swiftcc void @"$s7Library02MyA8ProtocolP4MainE5func2yyF"
38+
self.library_req()
39+
// CHECK: call swiftcc void @"$s7Library02MyA8ProtocolP11library_reqyyFTj"
40+
41+
// CHECK-NOT: @llvm.type.checked.load
42+
// CHECK: ret void
43+
}
44+
}
45+
46+
#endif
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Tests that under -enable-llvm-wme + -internalize-at-link, cross-module
2+
// witness method calls are done via thunks and LLVM GlobalDCE is able to remove
3+
// unused witness methods from a library based on a list of used symbols by a
4+
// client.
5+
6+
// RUN: %empty-directory(%t)
7+
8+
// (1) Build library swiftmodule
9+
// RUN: %target-build-swift -parse-as-library -Xfrontend -enable-llvm-wme \
10+
// RUN: %s -DLIBRARY -module-name Library \
11+
// RUN: -emit-module -o %t/Library.swiftmodule \
12+
// RUN: -emit-tbd -emit-tbd-path %t/libLibrary.tbd -Xfrontend -tbd-install_name=%t/libLibrary.dylib
13+
14+
// (2) Build client
15+
// RUN: %target-build-swift -parse-as-library -Xfrontend -enable-llvm-wme \
16+
// RUN: %s -DCLIENT -module-name Main -I%t -L%t -lLibrary -o %t/main
17+
18+
// (3) Extract a list of used symbols by client from library
19+
// RUN: %llvm-nm --undefined-only -m %t/main | grep 'from libLibrary' | awk '{print $3}' > %t/used-symbols
20+
21+
// (4) Now produce the .dylib with just the symbols needed by the client
22+
// RUN: %target-build-swift -parse-as-library -Xfrontend -enable-llvm-wme -Xfrontend -internalize-at-link \
23+
// RUN: %s -DLIBRARY -lto=llvm-full %lto_flags -module-name Library \
24+
// RUN: -emit-library -o %t/libLibrary.dylib \
25+
// RUN: -Xlinker -exported_symbols_list -Xlinker %t/used-symbols -Xlinker -dead_strip
26+
27+
// (5) Check list of symbols in library
28+
// RUN: %llvm-nm --defined-only %t/libLibrary.dylib | %FileCheck %s --check-prefix=NM
29+
30+
// (6) Execution test
31+
// RUN: %target-run %t/main | %FileCheck %s
32+
33+
// REQUIRES: executable_test
34+
35+
// Test disabled until LLVM GlobalDCE supports Swift wtables.
36+
// REQUIRES: rdar81868930
37+
38+
#if LIBRARY
39+
40+
public protocol MyProtocol {
41+
func func1_used()
42+
func func2_unused()
43+
}
44+
45+
public struct MyStruct : MyProtocol {
46+
public init() {}
47+
public func func1_used() { print("MyStruct.func1_used") }
48+
public func func2_unused() { print("MyStruct.func2_unused") }
49+
}
50+
51+
// NM: $s7Library8MyStructV10func1_usedyyF
52+
// NM-NOT: $s7Library8MyStructV12func2_unusedyyF
53+
// NM: $s7Library8MyStructVAA0B8ProtocolA2aDP10func1_usedyyFTW
54+
// NM-NOT: $s7Library8MyStructVAA0B8ProtocolA2aDP12func2_unusedyyFTW
55+
56+
#endif
57+
58+
#if CLIENT
59+
60+
import Library
61+
62+
@_cdecl("main")
63+
func main() -> Int32 {
64+
let o: MyProtocol = MyStruct()
65+
o.func1_used()
66+
print("Done")
67+
// CHECK: MyStruct.func1_used
68+
// CHECK-NEXT: Done
69+
return 0
70+
}
71+
72+
#endif

0 commit comments

Comments
 (0)