Skip to content

Commit 5cc14ab

Browse files
committed
[sil-devirtualizer] Improve devirtualization of witness_method instructions.
Handle such cases like partial applications of witness methods and applications of witness methods with substitutions. Some of these uses-cases occur when there is a protocol defining an operator, a generic struct conforming to this protocol, and the operator conformance of this struct is expressed as a global function.
1 parent 8885997 commit 5cc14ab

File tree

10 files changed

+160
-17
lines changed

10 files changed

+160
-17
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,16 @@ class ApplyInstBase<Impl, Base, false> : public Base {
652652
return {getSubstitutionsStorage(), NumSubstitutions};
653653
}
654654

655+
ArrayRef<Substitution> getSubstitutionsWithoutSelfSubstitution() const {
656+
assert(getNumArguments() && "Should only be called when Callee has "
657+
"at least a self parameter.");
658+
assert(hasSubstitutions() && "Should only be called when Callee has "
659+
"substitutions.");
660+
if (getSubstCalleeType()->hasSelfParam())
661+
return getSubstitutions().slice(1);
662+
return getSubstitutions();
663+
}
664+
655665
/// The arguments passed to this instruction.
656666
MutableArrayRef<Operand> getArgumentOperands() {
657667
return Operands.getDynamicAsArray();
@@ -4266,6 +4276,8 @@ class ApplySite {
42664276
return cast<ApplyInst>(Inst)->getSubstitutionsWithoutSelfSubstitution();
42674277
case ValueKind::TryApplyInst:
42684278
return cast<TryApplyInst>(Inst)->getSubstitutionsWithoutSelfSubstitution();
4279+
case ValueKind::PartialApplyInst:
4280+
return cast<PartialApplyInst>(Inst)->getSubstitutionsWithoutSelfSubstitution();
42694281
default:
42704282
llvm_unreachable("not implemented for this instruction!");
42714283
}

include/swift/SILPasses/Utils/Devirtualize.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ namespace swift {
4040
/// Two elements are required, because a result of the new devirtualized
4141
/// apply/try_apply instruction (second element) eventually needs to be
4242
/// casted to produce a properly typed value (first element).
43-
typedef std::pair<ValueBase *, FullApplySite> DevirtualizationResult;
43+
typedef std::pair<ValueBase *, ApplySite> DevirtualizationResult;
4444

4545
DevirtualizationResult tryDevirtualizeApply(FullApplySite AI);
4646
bool isClassWithUnboundGenericParameters(SILType C, SILModule &M);
@@ -49,6 +49,7 @@ DevirtualizationResult devirtualizeClassMethod(FullApplySite AI,
4949
SILValue ClassInstance);
5050
DevirtualizationResult tryDevirtualizeClassMethod(FullApplySite AI,
5151
SILValue ClassInstance);
52+
DevirtualizationResult tryDevirtualizeWitnessMethod(ApplySite AI);
5253
}
5354

5455
#endif

lib/IRGen/GenFunc.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3591,7 +3591,9 @@ static llvm::Function *emitPartialApplicationForwarder(IRGenModule &IGM,
35913591
}
35923592

35933593
// Emit the polymorphic arguments.
3594-
assert(subs.empty() != hasPolymorphicParameters(origType)
3594+
assert((subs.empty() != hasPolymorphicParameters(origType) ||
3595+
(subs.empty() && origType->getRepresentation() ==
3596+
SILFunctionTypeRepresentation::WitnessMethod))
35953597
&& "should have substitutions iff original function is generic");
35963598
WitnessMetadata witnessMetadata;
35973599
if (hasPolymorphicParameters(origType)) {

lib/IRGen/IRGenSIL.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,6 @@ getPartialApplicationFunction(IRGenSILFunction &IGF,
21042104
break;
21052105

21062106
case SILFunctionTypeRepresentation::WitnessMethod:
2107-
assert(false && "partial_apply of witness functions not implemented");
21082107
break;
21092108

21102109
case SILFunctionTypeRepresentation::Thick:

lib/SILPasses/EarlySIL/MandatoryInlining.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,14 @@ runOnFunctionRecursively(SILFunction *F, FullApplySite AI,
320320
I = II->getIterator();
321321
else
322322
I = NewInst->getParentBB()->begin();
323-
auto NewAI = NewInstPair.second;
323+
auto NewAI = FullApplySite::isa(NewInstPair.second.getInstruction());
324324
if (!NewAI)
325325
continue;
326326

327327
InnerAI = NewAI;
328328
}
329329

330-
SILLocation Loc = InnerAI.getLoc();
330+
SILLocation Loc = InnerAI.getLoc();
331331
SILValue CalleeValue = InnerAI.getCallee();
332332
bool IsThick;
333333
PartialApplyInst *PAI;

lib/SILPasses/IPO/PerformanceInliner.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ FullApplySite SILPerformanceInliner::devirtualizeUpdatingCallGraph(
826826
if (!NewInstPair.second)
827827
return FullApplySite();
828828

829-
auto NewAI = NewInstPair.second;
829+
auto NewAI = FullApplySite::isa(NewInstPair.second.getInstruction());
830830
CallGraphEditor(&CG).replaceApplyWithNew(Apply, NewAI);
831831

832832
replaceDeadApply(Apply, NewInstPair.first);

lib/SILPasses/SILCombiner/SILCombiner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ class SILCombiner :
243243
SILInstruction *visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI);
244244
SILInstruction *visitEnumInst(EnumInst *EI);
245245
SILInstruction *visitConvertFunctionInst(ConvertFunctionInst *CFI);
246-
246+
SILInstruction *visitWitnessMethodInst(WitnessMethodInst *WMI);
247247

248248
/// Instruction visitor helpers.
249249
SILInstruction *optimizeBuiltinCanBeObjCClass(BuiltinInst *AI);

lib/SILPasses/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/SILAnalysis/CFG.h"
2424
#include "swift/SILAnalysis/ValueTracking.h"
2525
#include "swift/SILPasses/Utils/Local.h"
26+
#include "swift/SILPasses/Utils/Devirtualize.h"
2627
#include "llvm/ADT/SmallPtrSet.h"
2728
#include "llvm/ADT/SmallVector.h"
2829
#include "llvm/ADT/DenseMap.h"
@@ -1070,3 +1071,33 @@ visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI) {
10701071
SILInstruction *SILCombiner::visitEnumInst(EnumInst *EI) {
10711072
return nullptr;
10721073
}
1074+
1075+
SILInstruction *SILCombiner::visitWitnessMethodInst(WitnessMethodInst *WMI) {
1076+
// Try to devirtualize a witness_method if it is statically possible.
1077+
// Many cases are handled by the inliner/devirtualizer, but certain
1078+
// special cases are not covered there, e.g. partial_apply(witness_method)
1079+
SILFunction *F;
1080+
ArrayRef<Substitution> Subs;
1081+
SILWitnessTable *WT;
1082+
1083+
std::tie(F, WT, Subs) =
1084+
WMI->getModule().lookUpFunctionInWitnessTable(WMI->getConformance(),
1085+
WMI->getMember());
1086+
1087+
if (!F)
1088+
return nullptr;
1089+
1090+
for (auto U = WMI->use_begin(), E = WMI->use_end(); U != E;) {
1091+
auto User = U->getUser();
1092+
++U;
1093+
if (auto AI = ApplySite::isa(User)) {
1094+
auto Result = tryDevirtualizeWitnessMethod(AI);
1095+
if (Result.first) {
1096+
User->replaceAllUsesWith(Result.first);
1097+
eraseInstFromFunction(*User);
1098+
}
1099+
}
1100+
}
1101+
return nullptr;
1102+
}
1103+

lib/SILPasses/Utils/Devirtualize.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ DevirtualizationResult swift::tryDevirtualizeClassMethod(FullApplySite AI,
503503
/// Generate a new apply of a function_ref to replace an apply of a
504504
/// witness_method when we've determined the actual function we'll end
505505
/// up calling.
506-
static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F,
506+
static ApplySite devirtualizeWitnessMethod(ApplySite AI, SILFunction *F,
507507
ArrayRef<Substitution> Subs) {
508508
// We know the witness thunk and the corresponding set of substitutions
509509
// required to invoke the protocol method at this point.
@@ -517,7 +517,10 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F,
517517
SmallVector<Substitution, 16> NewSubstList(Subs.begin(), Subs.end());
518518

519519
// Add the non-self-derived substitutions from the original application.
520-
for (auto &origSub : AI.getSubstitutionsWithoutSelfSubstitution())
520+
ArrayRef<Substitution> SubstList;
521+
SubstList = AI.getSubstitutionsWithoutSelfSubstitution();
522+
523+
for (auto &origSub : SubstList)
521524
if (!origSub.getArchetype()->isSelfDerived())
522525
NewSubstList.push_back(origSub);
523526

@@ -531,18 +534,17 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F,
531534
auto Arguments = SmallVector<SILValue, 4>();
532535

533536
auto ParamTypes = SubstCalleeCanType->getParameterSILTypes();
534-
// Type of the current parameter being processed
535-
auto ParamType = ParamTypes.begin();
536537

537538
// Iterate over the non self arguments and add them to the
538539
// new argument list, upcasting when required.
539540
SILBuilderWithScope B(AI.getInstruction());
540-
for (SILValue A : AI.getArguments()) {
541-
if (A.getType() != *ParamType)
542-
A = B.createUpcast(AI.getLoc(), A, *ParamType);
541+
for (unsigned ArgN = 0, ArgE = AI.getNumArguments(); ArgN != ArgE; ++ArgN) {
542+
SILValue A = AI.getArgument(ArgN);
543+
auto ParamType = ParamTypes[ParamTypes.size() - AI.getNumArguments() + ArgN];
544+
if (A.getType() != ParamType)
545+
A = B.createUpcast(AI.getLoc(), A, ParamType);
543546

544547
Arguments.push_back(A);
545-
++ParamType;
546548
}
547549

548550
// Replace old apply instruction by a new apply instruction that invokes
@@ -553,7 +555,7 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F,
553555

554556
auto SubstCalleeSILType = SILType::getPrimitiveObjectType(SubstCalleeCanType);
555557
auto ResultSILType = SubstCalleeCanType->getSILResult();
556-
FullApplySite SAI;
558+
ApplySite SAI;
557559

558560
if (auto *A = dyn_cast<ApplyInst>(AI))
559561
SAI = Builder.createApply(Loc, FRI, SubstCalleeSILType,
@@ -563,6 +565,9 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F,
563565
SAI = Builder.createTryApply(Loc, FRI, SubstCalleeSILType,
564566
NewSubstList, Arguments,
565567
TAI->getNormalBB(), TAI->getErrorBB());
568+
if (auto *PAI = dyn_cast<PartialApplyInst>(AI))
569+
SAI = Builder.createPartialApply(Loc, FRI, SubstCalleeSILType,
570+
NewSubstList, Arguments, PAI->getType());
566571

567572
NumWitnessDevirt++;
568573
return SAI;
@@ -571,7 +576,7 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F,
571576
/// In the cases where we can statically determine the function that
572577
/// we'll call to, replace an apply of a witness_method with an apply
573578
/// of a function_ref, returning the new apply.
574-
static DevirtualizationResult tryDevirtualizeWitnessMethod(FullApplySite AI) {
579+
DevirtualizationResult swift::tryDevirtualizeWitnessMethod(ApplySite AI) {
575580
SILFunction *F;
576581
ArrayRef<Substitution> Subs;
577582
SILWitnessTable *WT;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -sil-combine | FileCheck %s
2+
sil_stage canonical
3+
4+
import Builtin
5+
import Swift
6+
import SwiftShims
7+
8+
protocol CanAdd {
9+
func +(lhs: Self, rhs: Self) -> Self
10+
}
11+
12+
extension Int64 : CanAdd {
13+
}
14+
15+
struct S<T> : CanAdd {
16+
}
17+
18+
func +<T>(lhs: S<T>, rhs: S<T>) -> S<T>
19+
20+
21+
sil hidden [transparent] [thunk] @operator_plus_static_non_generic_witness_for_S : $@convention(thin) <T where T : CanAdd> (@out S<T>, @in S<T>, @in S<T>, @thick S<T>.Type) -> () {
22+
bb0(%0 : $*S<T>, %1 : $*S<T>, %2 : $*S<T>, %3 : $@thick S<T>.Type) :
23+
%17 = tuple ()
24+
return %17 : $()
25+
}
26+
27+
sil hidden [transparent] [thunk] @operator_plus_static_non_generic_witness : $@convention(witness_method) (@out Int64, @in Int64, @in Int64, @thick Int64.Type) -> () {
28+
bb0(%0 : $*Int64, %1 : $*Int64, %2 : $*Int64, %3 : $@thick Int64.Type):
29+
%4 = struct_element_addr %1 : $*Int64, #Int64._value
30+
%5 = load %4 : $*Builtin.Int64
31+
%6 = struct_element_addr %2 : $*Int64, #Int64._value
32+
%7 = load %6 : $*Builtin.Int64
33+
%8 = integer_literal $Builtin.Int1, -1
34+
%9 = builtin "sadd_with_overflow_Int64"(%5 : $Builtin.Int64, %7 : $Builtin.Int64, %8 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1)
35+
%10 = tuple_extract %9 : $(Builtin.Int64, Builtin.Int1), 0
36+
%11 = tuple_extract %9 : $(Builtin.Int64, Builtin.Int1), 1
37+
cond_fail %11 : $Builtin.Int1
38+
%15 = struct $Int64 (%10 : $Builtin.Int64)
39+
store %15 to %0 : $*Int64
40+
%17 = tuple ()
41+
return %17 : $()
42+
}
43+
44+
// Test that this partial application of a function reference referring to static non generic witness does not crash the IRGen.
45+
// Such code may be produced e.g. when users refer to global operators defined on builtin types.
46+
// CHECK-LABEL: sil hidden @test_partial_apply_of_static_witness
47+
sil hidden @test_partial_apply_of_static_witness : $@convention(thin) () -> @callee_owned (@out Int64, @in Int64, @in Int64) -> () {
48+
bb0:
49+
%1 = metatype $@thick Int64.Type
50+
%2 = function_ref @operator_plus_static_non_generic_witness : $@convention(witness_method) (@out Int64, @in Int64, @in Int64, @thick Int64.Type) -> ()
51+
%3 = partial_apply %2(%1) : $@convention(witness_method) (@out Int64, @in Int64, @in Int64, @thick Int64.Type) -> ()
52+
return %3 : $@callee_owned (@out Int64, @in Int64, @in Int64) -> ()
53+
}
54+
55+
// Test that this partial application of witness_method can be devirtualized.
56+
// CHECK-LABEL: sil hidden @test_devirt_of_partial_apply_of_witness_method
57+
// CHECK-NOT: witness_method $S<Int64>, #CanAdd."+"!1
58+
// CHECK: function_ref @operator_plus_static_non_generic_witness_for_S
59+
// CHECK: partial_apply
60+
// CHECK: return
61+
sil hidden @test_devirt_of_partial_apply_of_witness_method : $@convention(thin) () -> @callee_owned (@out S<Int64>, @in S<Int64>, @in S<Int64>) -> () {
62+
bb0:
63+
%1 = metatype $@thick S<Int64>.Type
64+
%2 = witness_method $S<Int64>, #CanAdd."+"!1 : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> ()
65+
%3 = partial_apply %2<S<Int64>>(%1) : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> ()
66+
return %3 : $@callee_owned (@out S<Int64>, @in S<Int64>, @in S<Int64>) -> ()
67+
}
68+
69+
// Test that this application of witness_method can be devirtualized.
70+
// CHECK-LABEL: sil hidden @test_devirt_of_apply_of_witness_method
71+
// CHECK-NOT: witness_method $S<Int64>, #CanAdd."+"!1
72+
// CHECK: function_ref @operator_plus_static_non_generic_witness_for_S
73+
// CHECK: apply
74+
// CHECK: return
75+
sil hidden @test_devirt_of_apply_of_witness_method : $@convention(thin) (@in S<Int64>) -> S<Int64> {
76+
bb0(%0 : $*S<Int64>):
77+
%1 = alloc_stack $S<Int64>
78+
%5 = metatype $@thick S<Int64>.Type
79+
%6 = witness_method $S<Int64>, #CanAdd."+"!1 : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> ()
80+
%7 = apply %6<S<Int64>>(%1#1, %0, %0, %5) : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> ()
81+
%8 = load %1#1: $*S<Int64>
82+
dealloc_stack %1#0 : $*@local_storage S<Int64>
83+
return %8 : $S<Int64>
84+
}
85+
86+
sil_witness_table hidden Int64: CanAdd module Test {
87+
method #CanAdd."+"!1: @operator_plus_static_non_generic_witness
88+
}
89+
90+
sil_witness_table hidden <E where E : CanAdd> S<E>: CanAdd module Test {
91+
method #CanAdd."+"!1: @operator_plus_static_non_generic_witness_for_S
92+
}
93+

0 commit comments

Comments
 (0)