Skip to content

Commit dd19a13

Browse files
authored
Merge pull request #7553 from swiftix/wip-generics-inlining-flag-6
Preparation for enabling the inlining of generics
2 parents a570f6b + de63281 commit dd19a13

File tree

7 files changed

+484
-34
lines changed

7 files changed

+484
-34
lines changed

include/swift/SILOptimizer/Utils/Devirtualize.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef std::pair<ValueBase *, ApplySite> DevirtualizationResult;
4646
DevirtualizationResult tryDevirtualizeApply(FullApplySite AI);
4747
DevirtualizationResult tryDevirtualizeApply(FullApplySite AI,
4848
ClassHierarchyAnalysis *CHA);
49+
bool canDevirtualizeApply(FullApplySite AI, ClassHierarchyAnalysis *CHA);
4950
bool isNominalTypeWithUnboundGenericParameters(SILType Ty, SILModule &M);
5051
bool canDevirtualizeClassMethod(FullApplySite AI, SILType ClassInstanceType);
5152
SILFunction *getTargetClassMethod(SILModule &M, SILType ClassOrMetatypeType,

lib/IRGen/IRGenSIL.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,19 +732,34 @@ class IRGenSILFunction :
732732
copy.push_back(alloca.getAddress());
733733
}
734734

735+
/// Determine whether a generic variable has been inlined.
736+
static bool isInlinedGeneric(VarDecl *VarDecl, const SILDebugScope *DS) {
737+
if (!DS->InlinedCallSite)
738+
return false;
739+
if (VarDecl->hasType())
740+
return VarDecl->getType()->hasArchetype();
741+
return VarDecl->getInterfaceType()->hasTypeParameter();
742+
}
743+
735744
/// Emit debug info for a function argument or a local variable.
736745
template <typename StorageType>
737746
void emitDebugVariableDeclaration(StorageType Storage,
738747
DebugTypeInfo Ty,
739748
SILType SILTy,
740749
const SILDebugScope *DS,
741-
ValueDecl *VarDecl,
750+
VarDecl *VarDecl,
742751
StringRef Name,
743752
unsigned ArgNo = 0,
744753
IndirectionKind Indirection = DirectValue) {
745754
// Force all archetypes referenced by the type to be bound by this point.
746755
// TODO: just make sure that we have a path to them that the debug info
747756
// can follow.
757+
758+
// FIXME: The debug info type of all inlined instances of a variable must be
759+
// the same as the type of the abstract variable.
760+
if (isInlinedGeneric(VarDecl, DS))
761+
return;
762+
748763
auto runtimeTy = getRuntimeReifiedType(IGM,
749764
Ty.getType()->getCanonicalType());
750765
if (!IGM.IRGen.Opts.Optimize && runtimeTy->hasArchetype())
@@ -3771,6 +3786,10 @@ void IRGenSILFunction::visitAllocBoxInst(swift::AllocBoxInst *i) {
37713786
CurSILFn->getDeclContext(), Decl,
37723787
i->getBoxType()->getFieldType(IGM.getSILModule(), 0).getSwiftType(),
37733788
type, /*Unwrap=*/false);
3789+
3790+
if (isInlinedGeneric(Decl, i->getDebugScope()))
3791+
return;
3792+
37743793
IGM.DebugInfo->emitVariableDeclaration(
37753794
Builder,
37763795
emitShadowCopy(boxWithAddr.getAddress(), i->getDebugScope(), Name, 0),

lib/SILOptimizer/Transforms/PerformanceInliner.cpp

Lines changed: 176 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#define DEBUG_TYPE "sil-inliner"
1414
#include "swift/SILOptimizer/PassManager/Passes.h"
1515
#include "swift/SILOptimizer/PassManager/Transforms.h"
16+
#include "swift/SILOptimizer/Utils/Devirtualize.h"
17+
#include "swift/SILOptimizer/Utils/Generics.h"
1618
#include "swift/SILOptimizer/Utils/PerformanceInlinerUtils.h"
1719
#include "swift/Strings.h"
1820
#include "llvm/ADT/Statistic.h"
@@ -91,6 +93,13 @@ class SILPerformanceInliner {
9193
/// The benefit of a onFastPath builtin.
9294
FastPathBuiltinBenefit = RemovedCallBenefit + 40,
9395

96+
/// The benefit of being able to devirtualize a call.
97+
DevirtualizedCallBenefit = RemovedCallBenefit + 300,
98+
99+
/// The benefit of being able to produce a generic
100+
/// specializatio for a call.
101+
GenericSpecializationBenefit = RemovedCallBenefit + 300,
102+
94103
/// Approximately up to this cost level a function can be inlined without
95104
/// increasing the code size.
96105
TrivialFunctionThreshold = 18,
@@ -125,8 +134,7 @@ class SILPerformanceInliner {
125134
bool isProfitableToInline(FullApplySite AI,
126135
Weight CallerWeight,
127136
ConstantTracker &callerTracker,
128-
int &NumCallerBlocks,
129-
bool IsGeneric);
137+
int &NumCallerBlocks);
130138

131139
bool decideInWarmBlock(FullApplySite AI,
132140
Weight CallerWeight,
@@ -161,6 +169,53 @@ static bool calleeIsSelfRecursive(SILFunction *Callee) {
161169
return false;
162170
}
163171

172+
// Returns true if the callee contains a partial apply instruction,
173+
// whose substitutions list would contain opened existentials after
174+
// inlining.
175+
static bool calleeHasPartialApplyWithOpenedExistentials(FullApplySite AI) {
176+
if (!AI.hasSubstitutions())
177+
return false;
178+
179+
SILFunction *Callee = AI.getReferencedFunction();
180+
auto Subs = AI.getSubstitutions();
181+
182+
// Bail if there are no open existnetials in the list of substitutions.
183+
bool HasNoOpenedExistentials = true;
184+
for (auto Sub : Subs) {
185+
if (Sub.getReplacement()->hasOpenedExistential()) {
186+
HasNoOpenedExistentials = false;
187+
break;
188+
}
189+
}
190+
191+
if (HasNoOpenedExistentials)
192+
return false;
193+
194+
auto SubsMap = Callee->getGenericEnvironment()->getSubstitutionMap(Subs);
195+
196+
for (auto &BB : *Callee) {
197+
for (auto &I : BB) {
198+
if (auto PAI = dyn_cast<PartialApplyInst>(&I)) {
199+
auto PAISubs = PAI->getSubstitutions();
200+
if (PAISubs.empty())
201+
continue;
202+
// Check if any of substitutions would contain open existentials
203+
// after inlining.
204+
for (auto PAISub : PAISubs) {
205+
if (!PAISub.getReplacement()->hasArchetype())
206+
continue;
207+
auto NewPAISub =
208+
PAISub.subst(AI.getModule().getSwiftModule(), SubsMap);
209+
if (NewPAISub.getReplacement()->hasOpenedExistential())
210+
return true;
211+
}
212+
}
213+
}
214+
}
215+
216+
return false;
217+
}
218+
164219
// Returns the callee of an apply_inst if it is basically inlineable.
165220
SILFunction *SILPerformanceInliner::getEligibleFunction(FullApplySite AI) {
166221

@@ -202,10 +257,6 @@ SILFunction *SILPerformanceInliner::getEligibleFunction(FullApplySite AI) {
202257
return nullptr;
203258
}
204259

205-
// We don't support this yet.
206-
if (AI.hasSubstitutions())
207-
return nullptr;
208-
209260
SILFunction *Caller = AI.getFunction();
210261

211262
// We don't support inlining a function that binds dynamic self because we
@@ -247,15 +298,36 @@ SILFunction *SILPerformanceInliner::getEligibleFunction(FullApplySite AI) {
247298
return nullptr;
248299
}
249300

301+
// IRGen cannot handle partial_applies containing opened_extistentials
302+
// in its substitutions list.
303+
if (calleeHasPartialApplyWithOpenedExistentials(AI)) {
304+
return nullptr;
305+
}
306+
250307
return Callee;
251308
}
252309

310+
// Returns true if it is possible to perform a generic
311+
// specialization for a given call.
312+
static bool canSpecializeGeneric(ApplySite AI, SILFunction *F,
313+
SubstitutionList Subs) {
314+
ReabstractionInfo ReInfo(AI, F, Subs);
315+
return ReInfo.canBeSpecialized();
316+
}
317+
253318
bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI,
254319
Weight CallerWeight,
255320
ConstantTracker &callerTracker,
256-
int &NumCallerBlocks,
257-
bool IsGeneric) {
321+
int &NumCallerBlocks) {
258322
SILFunction *Callee = AI.getReferencedFunction();
323+
bool IsGeneric = !AI.getSubstitutions().empty();
324+
325+
// Bail out if this generic call can be optimized by means of
326+
// the generic specialization, because we prefer generic specialization
327+
// to inlining of generics.
328+
if (IsGeneric && canSpecializeGeneric(AI, Callee, AI.getSubstitutions()))
329+
return false;
330+
259331
SILLoopInfo *LI = LA->get(Callee);
260332
ShortestPathAnalysis *SPA = getSPA(Callee, LI);
261333
assert(SPA->isValid());
@@ -268,11 +340,13 @@ bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI,
268340
// Calculate the inlining cost of the callee.
269341
int CalleeCost = 0;
270342
int Benefit = 0;
271-
343+
272344
// Start with a base benefit.
273345
int BaseBenefit = RemovedCallBenefit;
346+
347+
SubstitutionMap CalleeSubstMap;
274348
const SILOptions &Opts = Callee->getModule().getOptions();
275-
349+
276350
// For some reason -Ounchecked can accept a higher base benefit without
277351
// increasing the code size too much.
278352
if (Opts.Optimization == SILOptions::SILOptMode::OptimizeUnchecked)
@@ -288,16 +362,98 @@ bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI,
288362

289363
for (SILInstruction &I : *block) {
290364
constTracker.trackInst(&I);
291-
365+
292366
CalleeCost += (int)instructionInlineCost(I);
293367

294-
if (FullApplySite AI = FullApplySite::isa(&I)) {
295-
368+
if (FullApplySite FAI = FullApplySite::isa(&I)) {
296369
// Check if the callee is passed as an argument. If so, increase the
297370
// threshold, because inlining will (probably) eliminate the closure.
298-
SILInstruction *def = constTracker.getDefInCaller(AI.getCallee());
371+
SILInstruction *def = constTracker.getDefInCaller(FAI.getCallee());
299372
if (def && (isa<FunctionRefInst>(def) || isa<PartialApplyInst>(def)))
300373
BlockW.updateBenefit(Benefit, RemovedClosureBenefit);
374+
// Check if inlining the callee would allow for further
375+
// optimizations like devirtualization or generic specialization.
376+
if (!def)
377+
def = dyn_cast_or_null<SILInstruction>(FAI.getCallee());
378+
379+
if (!def)
380+
continue;
381+
382+
auto Subs = FAI.getSubstitutions();
383+
384+
// Bail if it is not a generic call.
385+
if (Subs.empty())
386+
continue;
387+
388+
if (!isa<FunctionRefInst>(def) && !isa<ClassMethodInst>(def) &&
389+
!isa<WitnessMethodInst>(def))
390+
continue;
391+
392+
SmallVector<Substitution, 32> NewSubs;
393+
SubstitutionMap SubstMap;
394+
GenericSignature *GenSig = nullptr;
395+
396+
if (auto FRI = dyn_cast<FunctionRefInst>(def)) {
397+
auto Callee = FRI->getReferencedFunction();
398+
if (Callee) {
399+
GenSig = Callee->getLoweredFunctionType()->getGenericSignature();
400+
}
401+
} else if (auto CMI = dyn_cast<ClassMethodInst>(def)) {
402+
GenSig = CMI->getType()
403+
.getSwiftRValueType()
404+
->castTo<SILFunctionType>()
405+
->getGenericSignature();
406+
} else if (auto WMI = dyn_cast<WitnessMethodInst>(def)) {
407+
GenSig = WMI->getType()
408+
.getSwiftRValueType()
409+
->castTo<SILFunctionType>()
410+
->getGenericSignature();
411+
}
412+
413+
// It is a generic call inside the callee. Check if after inlining
414+
// it will be possible to perform a generic specialization or
415+
// devirtualization of this call.
416+
417+
// Create the list of substitutions as they will be after
418+
// inlining.
419+
for (auto Sub : Subs) {
420+
if (!Sub.getReplacement()->hasArchetype()) {
421+
// This substitution is a concrete type.
422+
NewSubs.push_back(Sub);
423+
continue;
424+
}
425+
// This substitution is not a concrete type.
426+
if (IsGeneric && CalleeSubstMap.empty()) {
427+
CalleeSubstMap =
428+
Callee->getGenericEnvironment()->getSubstitutionMap(
429+
AI.getSubstitutions());
430+
}
431+
auto NewSub = Sub.subst(AI.getModule().getSwiftModule(), CalleeSubstMap);
432+
NewSubs.push_back(NewSub);
433+
}
434+
435+
// Check if the call can be devirtualized.
436+
if (isa<ClassMethodInst>(def) || isa<WitnessMethodInst>(def) ||
437+
isa<SuperMethodInst>(def)) {
438+
// TODO: Take AI.getSubstitutions() into account.
439+
if (canDevirtualizeApply(FAI, nullptr)) {
440+
DEBUG(llvm::dbgs() << "Devirtualization will be possible after "
441+
"inlining for the call:\n";
442+
FAI.getInstruction()->dumpInContext());
443+
BlockW.updateBenefit(Benefit, DevirtualizedCallBenefit);
444+
}
445+
}
446+
447+
// Check if a generic specialization would be possible.
448+
if (isa<FunctionRefInst>(def)) {
449+
auto CalleeF = FAI.getCalleeFunction();
450+
if (!canSpecializeGeneric(FAI, CalleeF, NewSubs))
451+
continue;
452+
DEBUG(llvm::dbgs() << "Generic specialization will be possible after "
453+
"inlining for the call:\n";
454+
FAI.getInstruction()->dumpInContext());
455+
BlockW.updateBenefit(Benefit, GenericSpecializationBenefit);
456+
}
301457
} else if (auto *LI = dyn_cast<LoadInst>(&I)) {
302458
// Check if it's a load from a stack location in the caller. Such a load
303459
// might be optimized away if inlined.
@@ -341,7 +497,6 @@ bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI,
341497
return false;
342498

343499
DEBUG(
344-
345500
dumpCaller(AI.getFunction());
346501
llvm::dbgs() << " decision {" << CalleeCost << " into thunk} " <<
347502
Callee->getName() << '\n';
@@ -379,14 +534,11 @@ bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI,
379534
/// It returns true if a function should be inlined.
380535
/// It returns false if a function should not be inlined.
381536
/// It returns None if the decision cannot be made without a more complex
382-
/// analysis.
537+
/// analysis.
383538
static Optional<bool> shouldInlineGeneric(FullApplySite AI) {
384539
assert(!AI.getSubstitutions().empty() &&
385540
"Expected a generic apply");
386541

387-
if (!EnableSILInliningOfGenerics)
388-
return false;
389-
390542
// If all substitutions are concrete, then there is no need to perform the
391543
// generic inlining. Let the generic specializer create a specialized
392544
// function and then decide if it is beneficial to inline it.
@@ -408,8 +560,10 @@ static Optional<bool> shouldInlineGeneric(FullApplySite AI) {
408560
if (Callee->getInlineStrategy() == AlwaysInline || Callee->isTransparent())
409561
return true;
410562

411-
// Only inline if we decided to inline or we are asked to inline all
412-
// generic functions.
563+
if (!EnableSILInliningOfGenerics)
564+
return false;
565+
566+
// It is not clear yet if this function should be decided or not.
413567
return None;
414568
}
415569

@@ -423,17 +577,14 @@ decideInWarmBlock(FullApplySite AI,
423577
auto ShouldInlineGeneric = shouldInlineGeneric(AI);
424578
if (ShouldInlineGeneric.hasValue())
425579
return ShouldInlineGeneric.getValue();
426-
427-
return false;
428580
}
429581

430582
SILFunction *Callee = AI.getReferencedFunction();
431583

432584
if (Callee->getInlineStrategy() == AlwaysInline)
433585
return true;
434586

435-
return isProfitableToInline(AI, CallerWeight, callerTracker, NumCallerBlocks,
436-
/* IsGeneric */ false);
587+
return isProfitableToInline(AI, CallerWeight, callerTracker, NumCallerBlocks);
437588
}
438589

439590
/// Return true if inlining this call site into a cold block is profitable.

0 commit comments

Comments
 (0)