Skip to content

Commit 1486290

Browse files
authored
Merge pull request swiftlang#28043 from zoecarver/fix/substitution-map-composition
Devirtualize calls to protocol composition type methods
2 parents 61832cb + 464b585 commit 1486290

File tree

9 files changed

+418
-81
lines changed

9 files changed

+418
-81
lines changed

benchmark/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ set(SWIFT_BENCH_MODULES
6262
single-source/Combos
6363
single-source/DataBenchmarks
6464
single-source/DeadArray
65+
single-source/DevirtualizeProtocolComposition
6566
single-source/DictOfArraysToArrayOfDicts
6667
single-source/DictTest
6768
single-source/DictTest2
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===--- DevirtualizeProtocolComposition.swift -------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import TestsUtils
14+
15+
public let DevirtualizeProtocolComposition = [
16+
BenchmarkInfo(name: "DevirtualizeProtocolComposition", runFunction: run_DevirtualizeProtocolComposition, tags: [.validation, .api]),
17+
]
18+
19+
protocol Pingable { func ping() -> Int; func pong() -> Int}
20+
21+
public class Game<T> {
22+
func length() -> Int { return 10 }
23+
}
24+
25+
public class PingPong: Game<String> { }
26+
27+
extension PingPong : Pingable {
28+
func ping() -> Int { return 1 }
29+
func pong() -> Int { return 2 }
30+
}
31+
32+
func playGame<T>(_ x: Game<T> & Pingable) -> Int {
33+
var sum = 0
34+
for _ in 0..<x.length() {
35+
sum += x.ping() + x.pong()
36+
}
37+
return sum
38+
}
39+
40+
@inline(never)
41+
public func run_DevirtualizeProtocolComposition(N: Int) {
42+
for _ in 0..<N * 20_000 {
43+
blackHole(playGame(PingPong()))
44+
}
45+
}

benchmark/utils/main.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import Codable
5050
import Combos
5151
import DataBenchmarks
5252
import DeadArray
53+
import DevirtualizeProtocolComposition
5354
import DictOfArraysToArrayOfDicts
5455
import DictTest
5556
import DictTest2
@@ -231,6 +232,7 @@ registerBenchmark(Combos)
231232
registerBenchmark(ClassArrayGetter)
232233
registerBenchmark(DataBenchmarks)
233234
registerBenchmark(DeadArray)
235+
registerBenchmark(DevirtualizeProtocolComposition)
234236
registerBenchmark(DictOfArraysToArrayOfDicts)
235237
registerBenchmark(Dictionary)
236238
registerBenchmark(Dictionary2)

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -783,10 +783,10 @@ bool SILCombiner::canReplaceArg(FullApplySite Apply,
783783
/// (@in or @owned convention).
784784
struct ConcreteArgumentCopy {
785785
SILValue origArg;
786-
CopyAddrInst *tempArgCopy;
786+
AllocStackInst *tempArg;
787787

788-
ConcreteArgumentCopy(SILValue origArg, CopyAddrInst *tempArgCopy)
789-
: origArg(origArg), tempArgCopy(tempArgCopy) {
788+
ConcreteArgumentCopy(SILValue origArg, AllocStackInst *tempArg)
789+
: origArg(origArg), tempArg(tempArg) {
790790
assert(origArg->getType().isAddress());
791791
}
792792

@@ -825,9 +825,18 @@ struct ConcreteArgumentCopy {
825825
SILBuilderWithScope B(apply.getInstruction(), BuilderCtx);
826826
auto loc = apply.getLoc();
827827
auto *ASI = B.createAllocStack(loc, CEI.ConcreteValue->getType());
828-
auto *CAI = B.createCopyAddr(loc, CEI.ConcreteValue, ASI, IsNotTake,
829-
IsInitialization_t::IsInitialization);
830-
return ConcreteArgumentCopy(origArg, CAI);
828+
// If the type is an address, simple copy it.
829+
if (CEI.ConcreteValue->getType().isAddress()) {
830+
B.createCopyAddr(loc, CEI.ConcreteValue, ASI, IsNotTake,
831+
IsInitialization_t::IsInitialization);
832+
} else {
833+
// Otherwise, we probably got the value from the source of a store
834+
// instruction so, create a store into the temporary argument.
835+
B.createStrongRetain(loc, CEI.ConcreteValue, B.getDefaultAtomicity());
836+
B.createStore(loc, CEI.ConcreteValue, ASI,
837+
StoreOwnershipQualifier::Unqualified);
838+
}
839+
return ConcreteArgumentCopy(origArg, ASI);
831840
}
832841
};
833842

@@ -858,20 +867,19 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
858867
FullApplySite Apply,
859868
const llvm::SmallDenseMap<unsigned, ConcreteOpenedExistentialInfo> &COAIs,
860869
SILBuilderContext &BuilderCtx) {
861-
862870
// Ensure that the callee is polymorphic.
863871
assert(Apply.getOrigCalleeType()->isPolymorphic());
864872

865873
// Create the new set of arguments to apply including their substitutions.
866874
SubstitutionMap NewCallSubs = Apply.getSubstitutionMap();
867875
SmallVector<SILValue, 8> NewArgs;
868-
bool UpdatedArgs = false;
869876
unsigned ArgIdx = 0;
870877
// Push the indirect result arguments.
871878
for (unsigned EndIdx = Apply.getSubstCalleeConv().getSILArgIndexOfFirstParam();
872879
ArgIdx < EndIdx; ++ArgIdx) {
873880
NewArgs.push_back(Apply.getArgument(ArgIdx));
874881
}
882+
875883
// Transform the parameter arguments.
876884
SmallVector<ConcreteArgumentCopy, 4> concreteArgCopies;
877885
for (unsigned EndIdx = Apply.getNumArguments(); ArgIdx < EndIdx; ++ArgIdx) {
@@ -890,16 +898,18 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
890898
NewArgs.push_back(Apply.getArgument(ArgIdx));
891899
continue;
892900
}
893-
UpdatedArgs = true;
901+
894902
// Ensure that we have a concrete value to propagate.
895903
assert(CEI.ConcreteValue);
904+
896905
auto argSub =
897906
ConcreteArgumentCopy::generate(CEI, Apply, ArgIdx, BuilderCtx);
898907
if (argSub) {
899908
concreteArgCopies.push_back(*argSub);
900-
NewArgs.push_back(argSub->tempArgCopy->getDest());
901-
} else
909+
NewArgs.push_back(argSub->tempArg);
910+
} else {
902911
NewArgs.push_back(CEI.ConcreteValue);
912+
}
903913

904914
// Form a new set of substitutions where the argument is
905915
// replaced with a concrete type.
@@ -922,7 +932,33 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
922932
});
923933
}
924934

925-
if (!UpdatedArgs) {
935+
// We need to make sure that we can a) update Apply to use the new args and b)
936+
// at least one argument has changed. If no arguments have changed, we need
937+
// to return nullptr. Otherwise, we will have an infinite loop.
938+
auto substTy =
939+
Apply.getCallee()
940+
->getType()
941+
.substGenericArgs(Apply.getModule(), NewCallSubs,
942+
Apply.getFunction()->getTypeExpansionContext())
943+
.getAs<SILFunctionType>();
944+
SILFunctionConventions conv(substTy,
945+
SILModuleConventions(Apply.getModule()));
946+
bool canUpdateArgs = true;
947+
bool madeUpdate = false;
948+
for (unsigned index = 0; index < conv.getNumSILArguments(); ++index) {
949+
// Make sure that *all* the arguments in both the new substitution function
950+
// and our vector of new arguments have the same type.
951+
canUpdateArgs &=
952+
conv.getSILArgumentType(index) == NewArgs[index]->getType();
953+
// Make sure that we have changed at least one argument.
954+
madeUpdate |=
955+
NewArgs[index]->getType() != Apply.getArgument(index)->getType();
956+
}
957+
958+
// If we can't update the args (because of a type mismatch) or the args don't
959+
// change, bail out by removing the instructions we've added and returning
960+
// nullptr.
961+
if (!canUpdateArgs || !madeUpdate) {
926962
// Remove any new instructions created while attempting to optimize this
927963
// apply. Since the apply was never rewritten, if they aren't removed here,
928964
// they will be removed later as dead when visited by SILCombine, causing
@@ -969,8 +1005,7 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
9691005
auto cleanupLoc = RegularLocation::getAutoGeneratedLocation();
9701006
for (ConcreteArgumentCopy &argCopy : llvm::reverse(concreteArgCopies)) {
9711007
cleanupBuilder.createDestroyAddr(cleanupLoc, argCopy.origArg);
972-
cleanupBuilder.createDeallocStack(cleanupLoc,
973-
argCopy.tempArgCopy->getDest());
1008+
cleanupBuilder.createDeallocStack(cleanupLoc, argCopy.tempArg);
9741009
}
9751010
}
9761011
return NewApply.getInstruction();

lib/SILOptimizer/Utils/Existential.cpp

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ findInitExistentialFromGlobalAddr(GlobalAddrInst *GAI, SILInstruction *Insn) {
6969

7070
/// Returns the instruction that initializes the given stack address. This is
7171
/// currently either a init_existential_addr, unconditional_checked_cast_addr,
72-
/// or copy_addr (if the instruction initializing the source of the copy cannot
73-
/// be determined). Returns nullptr if the initializer does not dominate the
74-
/// alloc_stack user \p ASIUser. If the value is copied from another stack
72+
/// store, or copy_addr (if the instruction initializing the source of the copy
73+
/// cannot be determined). Returns nullptr if the initializer does not dominate
74+
/// the alloc_stack user \p ASIUser. If the value is copied from another stack
7575
/// location, \p isCopied is set to true.
7676
///
7777
/// allocStackAddr may either itself be an AllocStackInst or an
@@ -111,6 +111,19 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
111111
}
112112
continue;
113113
}
114+
if (auto *store = dyn_cast<StoreInst>(User)) {
115+
if (store->getDest() == allocStackAddr) {
116+
if (SingleWrite)
117+
return nullptr;
118+
SingleWrite = store;
119+
// When we support OSSA here, we need to insert a new copy of the value
120+
// before `store` (and make sure that the copy is destroyed when
121+
// replacing the apply operand).
122+
assert(store->getOwnershipQualifier() ==
123+
StoreOwnershipQualifier::Unqualified);
124+
}
125+
continue;
126+
}
114127
if (isa<InitExistentialAddrInst>(User)) {
115128
if (SingleWrite)
116129
return nullptr;
@@ -144,6 +157,9 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
144157
if (BB != allocStackAddr->getParentBlock() && BB != ASIUser->getParent())
145158
return nullptr;
146159

160+
if (auto *store = dyn_cast<StoreInst>(SingleWrite))
161+
return store;
162+
147163
if (auto *IE = dyn_cast<InitExistentialAddrInst>(SingleWrite))
148164
return IE;
149165

@@ -180,24 +196,6 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
180196
return CAI;
181197
}
182198

183-
/// Return the address of the value used to initialize the given stack location.
184-
/// If the value originates from init_existential_addr, then it will be a
185-
/// different type than \p allocStackAddr.
186-
static SILValue getAddressOfStackInit(SILValue allocStackAddr,
187-
SILInstruction *ASIUser, bool &isCopied) {
188-
SILInstruction *initI = getStackInitInst(allocStackAddr, ASIUser, isCopied);
189-
if (!initI)
190-
return SILValue();
191-
192-
if (auto *IEA = dyn_cast<InitExistentialAddrInst>(initI))
193-
return IEA;
194-
195-
if (auto *CAI = dyn_cast<CopyAddrInst>(initI))
196-
return CAI->getSrc();
197-
198-
return SILValue();
199-
}
200-
201199
/// Check if the given operand originates from a recognized OpenArchetype
202200
/// instruction. If so, return the Opened, otherwise return nullptr.
203201
OpenedArchetypeInfo::OpenedArchetypeInfo(Operand &use) {
@@ -207,11 +205,17 @@ OpenedArchetypeInfo::OpenedArchetypeInfo(Operand &use) {
207205
// Handle:
208206
// %opened = open_existential_addr
209207
// %instance = alloc $opened
210-
// copy_addr %opened to %stack
208+
// <copy|store> %opened to %stack
211209
// <opened_use> %instance
212-
if (auto stackInitVal =
213-
getAddressOfStackInit(instance, user, isOpenedValueCopied)) {
214-
openedVal = stackInitVal;
210+
if (auto *initI = getStackInitInst(instance, user, isOpenedValueCopied)) {
211+
// init_existential_addr isn't handled here because it isn't considered an
212+
// "opened" archtype. init_existential_addr should be handled by
213+
// ConcreteExistentialInfo.
214+
215+
if (auto *CAI = dyn_cast<CopyAddrInst>(initI))
216+
openedVal = CAI->getSrc();
217+
if (auto *store = dyn_cast<StoreInst>(initI))
218+
openedVal = store->getSrc();
215219
}
216220
}
217221
if (auto *Open = dyn_cast<OpenExistentialAddrInst>(openedVal)) {

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -827,9 +827,6 @@ SILValue swift::castValueToABICompatibleType(SILBuilder *builder,
827827
if (srcTy == destTy)
828828
return value;
829829

830-
assert(srcTy.isAddress() == destTy.isAddress()
831-
&& "Addresses aren't compatible with values");
832-
833830
if (srcTy.isAddress() && destTy.isAddress()) {
834831
// Cast between two addresses and that's it.
835832
return builder->createUncheckedAddrCast(loc, value, destTy);

0 commit comments

Comments
 (0)