Skip to content

Commit 97d4dad

Browse files
committed
Implement LLVM IR Witness Method Elimination for Swift witness tables.
- Witness method calls are done via @llvm.type.checked.load instrinsic call with a type identifier - Type id of a witness method is the requirement's mangled name - Witness tables get !type markers that list offsets and type ids of all methods in the wtable - Added -enable-llvm-wme to enable Witness Method Elimination - Added IR test and execution test
1 parent af1706e commit 97d4dad

File tree

10 files changed

+190
-19
lines changed

10 files changed

+190
-19
lines changed

include/swift/AST/IRGenOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ class IRGenOptions {
355355

356356
unsigned VirtualFunctionElimination : 1;
357357

358+
unsigned WitnessMethodElimination : 1;
359+
358360
/// The number of threads for multi-threaded code generation.
359361
unsigned NumThreads = 0;
360362

@@ -414,6 +416,7 @@ class IRGenOptions {
414416
DisableRoundTripDebugTypes(false), DisableDebuggerShadowCopies(false),
415417
DisableConcreteTypeMetadataMangledNameAccessors(false),
416418
EnableGlobalISel(false), VirtualFunctionElimination(false),
419+
WitnessMethodElimination(false),
417420
CmdArgs(),
418421
SanitizeCoverage(llvm::SanitizerCoverageOptions()),
419422
TypeInfoFilter(TypeInfoDumpFilter::All) {}

include/swift/Option/FrontendOptions.td

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,11 @@ def enable_implicit_dynamic : Flag<["-"], "enable-implicit-dynamic">,
495495

496496
def enable_llvm_vfe : Flag<["-"], "enable-llvm-vfe">,
497497
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
498-
HelpText<"Use LLVM Virtual Function Elimination on Swift class virtual tables">;
498+
HelpText<"Use LLVM IR Virtual Function Elimination on Swift class virtual tables">;
499+
500+
def enable_llvm_wme : Flag<["-"], "enable-llvm-wme">,
501+
Flags<[FrontendOption, NoInteractiveOption, HelpHidden]>,
502+
HelpText<"Use LLVM IR Witness Method Elimination on Swift protocol witness tables">;
499503

500504
def disable_previous_implementation_calls_in_dynamic_replacements :
501505
Flag<["-"], "disable-previous-implementation-calls-in-dynamic-replacements">,

lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,6 +1908,10 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
19081908
Opts.VirtualFunctionElimination = true;
19091909
}
19101910

1911+
if (Args.hasArg(OPT_enable_llvm_wme)) {
1912+
Opts.WitnessMethodElimination = true;
1913+
}
1914+
19111915
// Default to disabling swift async extended frame info on anything but
19121916
// darwin. Other platforms are unlikely to have support for extended frame
19131917
// pointer information.

lib/IRGen/GenClass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2699,6 +2699,8 @@ static llvm::Value *emitVTableSlotLoad(IRGenFunction &IGF, Address slot,
26992699
args.push_back(llvm::ConstantInt::get(IGF.IGM.Int32Ty, 0));
27002700
args.push_back(llvm::MetadataAsValue::get(*IGF.IGM.LLVMContext, typeId));
27012701

2702+
// TODO/FIXME: Using @llvm.type.checked.load loses the "invariant" marker
2703+
// which could mean redundant loads don't get removed.
27022704
llvm::Value *checkedLoad =
27032705
IGF.Builder.CreateCall(checkedLoadIntrinsic, args);
27042706
auto fnPtr = IGF.Builder.CreateExtractValue(checkedLoad, 0);

lib/IRGen/GenOpaque.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -309,26 +309,29 @@ llvm::PointerType *IRGenModule::getEnumValueWitnessTablePtrTy() {
309309
"swift.enum_vwtable", true);
310310
}
311311

312-
/// Load a specific witness from a known table. The result is
313-
/// always an i8*.
314-
llvm::Value *irgen::emitInvariantLoadOfOpaqueWitness(IRGenFunction &IGF,
315-
llvm::Value *table,
316-
WitnessIndex index,
317-
llvm::Value **slotPtr) {
312+
Address irgen::slotForLoadOfOpaqueWitness(IRGenFunction &IGF,
313+
llvm::Value *table,
314+
WitnessIndex index) {
318315
assert(table->getType() == IGF.IGM.WitnessTablePtrTy);
319316

320317
// GEP to the appropriate index, avoiding spurious IR in the trivial case.
321318
llvm::Value *slot = table;
322319
if (index.getValue() != 0)
323320
slot = IGF.Builder.CreateConstInBoundsGEP1_32(
324-
table->getType()->getPointerElementType(), table, index.getValue());
321+
table->getType()->getPointerElementType(), table, index.getValue());
325322

326-
if (slotPtr) *slotPtr = slot;
323+
return Address(slot, IGF.IGM.getPointerAlignment());
324+
}
327325

328-
auto witness =
329-
IGF.Builder.CreateLoad(Address(slot, IGF.IGM.getPointerAlignment()));
330-
IGF.setInvariantLoad(witness);
331-
return witness;
326+
/// Load a specific witness from a known table. The result is
327+
/// always an i8*.
328+
llvm::Value *irgen::emitInvariantLoadOfOpaqueWitness(IRGenFunction &IGF,
329+
llvm::Value *table,
330+
WitnessIndex index,
331+
llvm::Value **slotPtr) {
332+
auto slot = slotForLoadOfOpaqueWitness(IGF, table, index);
333+
if (slotPtr) *slotPtr = slot.getAddress();
334+
return IGF.emitInvariantLoad(slot);
332335
}
333336

334337
/// Load a specific witness from a known table. The result is

lib/IRGen/GenOpaque.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ namespace irgen {
3838
/// Return the alignment of a fixed buffer.
3939
Alignment getFixedBufferAlignment(IRGenModule &IGM);
4040

41+
/// Given a witness table (protocol or value), return the address of the slot
42+
/// for one of the witnesses.
43+
Address slotForLoadOfOpaqueWitness(IRGenFunction &IGF, llvm::Value *table,
44+
WitnessIndex index);
45+
4146
/// Given a witness table (protocol or value), load one of the
4247
/// witnesses.
4348
///

lib/IRGen/GenProto.cpp

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,6 +2163,42 @@ static bool isConstantWitnessTable(SILWitnessTable *wt) {
21632163
return true;
21642164
}
21652165

2166+
static void addWTableTypeMetadata(IRGenModule &IGM,
2167+
llvm::GlobalVariable *global,
2168+
SILWitnessTable *wt) {
2169+
auto conf = wt->getConformance();
2170+
for (auto entry : wt->getEntries()) {
2171+
if (entry.getKind() != SILWitnessTable::WitnessKind::Method)
2172+
continue;
2173+
2174+
auto mw = entry.getMethodWitness();
2175+
auto member = mw.Requirement;
2176+
auto &fnProtoInfo =
2177+
IGM.getProtocolInfo(conf->getProtocol(), ProtocolInfoKind::Full);
2178+
auto index = fnProtoInfo.getFunctionIndex(member).forProtocolWitnessTable();
2179+
auto offset = index.getValue() * IGM.getPointerSize().getValue();
2180+
global->addTypeMetadata(offset, typeIdForMethod(IGM, member));
2181+
}
2182+
2183+
auto linkage = stripExternalFromLinkage(wt->getLinkage());
2184+
switch (linkage) {
2185+
case SILLinkage::Private:
2186+
global->setVCallVisibilityMetadata(
2187+
llvm::GlobalObject::VCallVisibility::VCallVisibilityTranslationUnit);
2188+
break;
2189+
case SILLinkage::Hidden:
2190+
case SILLinkage::Shared:
2191+
global->setVCallVisibilityMetadata(
2192+
llvm::GlobalObject::VCallVisibility::VCallVisibilityLinkageUnit);
2193+
break;
2194+
case SILLinkage::Public:
2195+
default:
2196+
global->setVCallVisibilityMetadata(
2197+
llvm::GlobalObject::VCallVisibility::VCallVisibilityPublic);
2198+
break;
2199+
}
2200+
}
2201+
21662202
void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) {
21672203
// Don't emit a witness table if it is a declaration.
21682204
if (wt->isDeclaration())
@@ -2207,6 +2243,10 @@ void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) {
22072243
global->setAlignment(
22082244
llvm::MaybeAlign(getWitnessTableAlignment().getValue()));
22092245

2246+
if (getOptions().WitnessMethodElimination) {
2247+
addWTableTypeMetadata(*this, global, wt);
2248+
}
2249+
22102250
tableSize = wtableBuilder.getTableSize();
22112251
instantiationFunction = wtableBuilder.buildInstantiationFunction();
22122252
} else {
@@ -3378,6 +3418,35 @@ void irgen::expandTrailingWitnessSignature(IRGenModule &IGM,
33783418
out.push_back(IGM.WitnessTablePtrTy);
33793419
}
33803420

3421+
static llvm::Value *emitWTableSlotLoad(IRGenFunction &IGF, llvm::Value *wtable,
3422+
SILDeclRef member, Address slot) {
3423+
if (IGF.IGM.getOptions().WitnessMethodElimination) {
3424+
// For LLVM IR WME, emit a @llvm.type.checked.load with the type of the
3425+
// method.
3426+
llvm::Function *checkedLoadIntrinsic = llvm::Intrinsic::getDeclaration(
3427+
&IGF.IGM.Module, llvm::Intrinsic::type_checked_load);
3428+
auto slotAsPointer = IGF.Builder.CreateBitCast(slot, IGF.IGM.Int8PtrTy);
3429+
auto typeId = typeIdForMethod(IGF.IGM, member);
3430+
3431+
// Arguments for @llvm.type.checked.load: 1) target address, 2) offset -
3432+
// always 0 because target address is directly pointing to the right slot,
3433+
// 3) type identifier, i.e. the mangled name of the *base* method.
3434+
SmallVector<llvm::Value *, 8> args;
3435+
args.push_back(slotAsPointer.getAddress());
3436+
args.push_back(llvm::ConstantInt::get(IGF.IGM.Int32Ty, 0));
3437+
args.push_back(llvm::MetadataAsValue::get(*IGF.IGM.LLVMContext, typeId));
3438+
3439+
// TODO/FIXME: Using @llvm.type.checked.load loses the "invariant" marker
3440+
// which could mean redundant loads don't get removed.
3441+
llvm::Value *checkedLoad =
3442+
IGF.Builder.CreateCall(checkedLoadIntrinsic, args);
3443+
return IGF.Builder.CreateExtractValue(checkedLoad, 0);
3444+
}
3445+
3446+
// Not doing LLVM IR WME, can just be a direct load.
3447+
return IGF.emitInvariantLoad(slot);
3448+
}
3449+
33813450
FunctionPointer irgen::emitWitnessMethodValue(IRGenFunction &IGF,
33823451
llvm::Value *wtable,
33833452
SILDeclRef member) {
@@ -3389,10 +3458,9 @@ FunctionPointer irgen::emitWitnessMethodValue(IRGenFunction &IGF,
33893458
// Find the witness we're interested in.
33903459
auto &fnProtoInfo = IGF.IGM.getProtocolInfo(proto, ProtocolInfoKind::Full);
33913460
auto index = fnProtoInfo.getFunctionIndex(member);
3392-
llvm::Value *slot;
3393-
llvm::Value *witnessFnPtr =
3394-
emitInvariantLoadOfOpaqueWitness(IGF, wtable,
3395-
index.forProtocolWitnessTable(), &slot);
3461+
auto slot =
3462+
slotForLoadOfOpaqueWitness(IGF, wtable, index.forProtocolWitnessTable());
3463+
llvm::Value *witnessFnPtr = emitWTableSlotLoad(IGF, wtable, member, slot);
33963464

33973465
auto fnType = IGF.IGM.getSILTypes().getConstantFunctionType(
33983466
IGF.IGM.getMaximalTypeExpansionContext(), member);
@@ -3403,7 +3471,7 @@ FunctionPointer irgen::emitWitnessMethodValue(IRGenFunction &IGF,
34033471
auto &schema = fnType->isAsync()
34043472
? IGF.getOptions().PointerAuth.AsyncProtocolWitnesses
34053473
: IGF.getOptions().PointerAuth.ProtocolWitnesses;
3406-
auto authInfo = PointerAuthInfo::emit(IGF, schema, slot, member);
3474+
auto authInfo = PointerAuthInfo::emit(IGF, schema, slot.getAddress(), member);
34073475

34083476
return FunctionPointer(fnType, witnessFnPtr, authInfo, signature);
34093477
}

lib/IRGen/IRGen.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ void setModuleFlags(IRGenModule &IGM) {
205205
Module->addModuleFlag(llvm::Module::Error, "Swift Version",
206206
IRGenModule::swiftVersion);
207207

208-
if (IGM.getOptions().VirtualFunctionElimination) {
208+
if (IGM.getOptions().VirtualFunctionElimination ||
209+
IGM.getOptions().WitnessMethodElimination) {
209210
Module->addModuleFlag(llvm::Module::Error, "Virtual Function Elim", 1);
210211
}
211212
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Tests that under -enable-llvm-wme, LLVM GlobalDCE is able to remove unused
2+
// witness methods, and that used witness methods are not removed (by running
3+
// the program).
4+
5+
// RUN: %empty-directory(%t)
6+
// RUN: %target-build-swift -Xfrontend -disable-objc-interop -Xfrontend -enable-llvm-wme %s -emit-ir -o %t/main.ll
7+
// RUN: %target-clang %t/main.ll -isysroot %sdk -L%swift_obj_root/lib/swift/%target-sdk-name -flto -o %t/main
8+
// RUN: %target-run %t/main | %FileCheck %s
9+
10+
// RUN: %llvm-nm --defined-only %t/main | %FileCheck %s --check-prefix=NM
11+
12+
// REQUIRES: executable_test
13+
14+
// Test disabled until LLVM GlobalDCE supports Swift vtables.
15+
// REQUIRES: rdar81868930
16+
17+
protocol TheProtocol {
18+
func func1_live()
19+
func func2_dead()
20+
}
21+
22+
struct MyStruct : TheProtocol {
23+
func func1_live() { print("MyStruct.func1_live") }
24+
func func2_dead() { print("MyStruct.func2_dead") }
25+
}
26+
27+
let x: TheProtocol = MyStruct()
28+
x.func1_live()
29+
// CHECK: MyStruct.func1_live
30+
31+
// NM: $s4main8MyStructV10func1_liveyyF
32+
// NM-NOT: $s4main8MyStructV10func2_deadyyF
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Tests that under -enable-llvm-wme, IRGen marks wtables and wcall sites with
2+
// the right attributes and intrinsics.
3+
4+
// RUN: %target-build-swift -Xfrontend -disable-objc-interop -Xfrontend -enable-llvm-wme \
5+
// RUN: %s -emit-ir -o - | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize
6+
7+
protocol TheProtocol {
8+
func func1_live()
9+
func func2_dead()
10+
}
11+
12+
struct MyStruct : TheProtocol {
13+
func func1_live() { print("MyStruct.func1_live") }
14+
func func2_dead() { print("MyStruct.func2_dead") }
15+
}
16+
17+
// CHECK: @"$s4main8MyStructVAA11TheProtocolAAWP" =
18+
// CHECK-SAME: i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main8MyStructVAA11TheProtocolAAMc" to i8*)
19+
// CHECK-SAME: i8* bitcast (void (%T4main8MyStructV*, %swift.type*, i8**)* @"$s4main8MyStructVAA11TheProtocolA2aDP10func1_liveyyFTW" to i8*)
20+
// CHECK-SAME: i8* bitcast (void (%T4main8MyStructV*, %swift.type*, i8**)* @"$s4main8MyStructVAA11TheProtocolA2aDP10func2_deadyyFTW" to i8*)
21+
// CHECK-64-SAME: align 8, !type !0, !type !1, !vcall_visibility !2
22+
// CHECK-32-SAME: align 4, !type !0, !type !1, !vcall_visibility !2
23+
24+
func test1() {
25+
// CHECK: define hidden swiftcc void @"$s4main5test1yyF"()
26+
let x: MyStruct = MyStruct()
27+
x.func1_live()
28+
// CHECK: call swiftcc void @"$s4main8MyStructVACycfC"()
29+
// CHECK-NEXT: call swiftcc void @"$s4main8MyStructV10func1_liveyyF"()
30+
// CHECK-NEXT: ret void
31+
}
32+
33+
func test2() {
34+
// CHECK: define hidden swiftcc void @"$s4main5test2yyF"()
35+
let x: TheProtocol = MyStruct()
36+
x.func1_live()
37+
// CHECK: [[WTABLE:%.*]] = load i8**, i8*** {{.*}}
38+
// CHECK: [[SLOT:%.*]] = getelementptr inbounds i8*, i8** [[WTABLE]], i32 1
39+
// CHECK: [[SLOTASPTR:%.*]] = bitcast i8** [[SLOT]] to i8*
40+
// CHECK: call { i8*, i1 } @llvm.type.checked.load(i8* [[SLOTASPTR]], i32 0, metadata !"$s4main11TheProtocolP10func1_liveyyFTq")
41+
}
42+
43+
// CHECK-64: !0 = !{i64 8, !"$s4main11TheProtocolP10func1_liveyyFTq"}
44+
// CHECK-64: !1 = !{i64 16, !"$s4main11TheProtocolP10func2_deadyyFTq"}
45+
// CHECK-64: !2 = !{i64 1}
46+
47+
// CHECK-32: !0 = !{i64 4, !"$s4main11TheProtocolP10func1_liveyyFTq"}
48+
// CHECK-32: !1 = !{i64 8, !"$s4main11TheProtocolP10func2_deadyyFTq"}
49+
// CHECK-32: !2 = !{i64 1}

0 commit comments

Comments
 (0)