-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathFunctionSignatureOpts.cpp
907 lines (779 loc) · 34.7 KB
/
FunctionSignatureOpts.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
//===--- FunctionSignatureOpts.cpp - Optimizes function signatures --------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This pass defines function signature related optimizations.
/// When a function signature optimization is performed, changes are made to
/// the original function and after all function signature optimizations are
/// finished, a new function is created and the old function is turned into
/// a thunk.
///
/// Another possibility is to implement these optimizations as separate passes,
/// but then we would send slightly different functions to the pass pipeline
/// multiple times through notifyPassManagerOfFunction.
///
/// TODO: Optimize function with generic parameters.
///
/// TODO: Improve epilogue release matcher, i.e. do a data flow instead of
/// only finding releases in the return block.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-function-signature-opt"
#include "FunctionSignatureOpts.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/SILCloner.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILValue.h"
#include "swift/SILOptimizer/Analysis/ARCAnalysis.h"
#include "swift/SILOptimizer/Analysis/CallerAnalysis.h"
#include "swift/SILOptimizer/Analysis/EpilogueARCAnalysis.h"
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/SILInliner.h"
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
#include "swift/SILOptimizer/Utils/SpecializationMangler.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
using namespace swift;
STATISTIC(NumFunctionSignaturesOptimized, "Total func sig optimized");
STATISTIC(NumDeadArgsEliminated, "Total dead args eliminated");
STATISTIC(NumOwnedConvertedToGuaranteed, "Total owned args -> guaranteed args");
STATISTIC(NumOwnedConvertedToNotOwnedResult, "Total owned result -> not owned result");
STATISTIC(NumSROAArguments, "Total SROA arguments optimized");
using SILParameterInfoList = llvm::SmallVector<SILParameterInfo, 8>;
using ArgumentIndexMap = llvm::SmallDenseMap<int, int>;
//===----------------------------------------------------------------------===//
// Optimization Hueristic
//===----------------------------------------------------------------------===//
/// Set to true to enable the support for partial specialization.
static llvm::cl::opt<bool>
FSOEnableGenerics("sil-fso-enable-generics", llvm::cl::init(true),
llvm::cl::desc("Support function signature optimization "
"of generic functions"));
static llvm::cl::opt<bool>
FSOOptimizeIfNotCalled("sil-fso-optimize-if-not-called",
llvm::cl::init(false),
llvm::cl::desc("Optimize even if a function isn't "
"called. For testing only!"));
static bool isSpecializableRepresentation(SILFunctionTypeRepresentation Rep,
bool OptForPartialApply) {
switch (Rep) {
case SILFunctionTypeRepresentation::Method:
case SILFunctionTypeRepresentation::Closure:
case SILFunctionTypeRepresentation::Thin:
case SILFunctionTypeRepresentation::Thick:
case SILFunctionTypeRepresentation::CFunctionPointer:
case SILFunctionTypeRepresentation::CXXMethod:
return true;
case SILFunctionTypeRepresentation::WitnessMethod:
return OptForPartialApply;
case SILFunctionTypeRepresentation::ObjCMethod:
case SILFunctionTypeRepresentation::Block:
return false;
}
llvm_unreachable("Unhandled SILFunctionTypeRepresentation in switch.");
}
/// Returns true if F is a function which the pass knows how to specialize
/// function signatures for.
static bool canSpecializeFunction(SILFunction *F,
const CallerAnalysis::FunctionInfo *FuncInfo,
bool OptForPartialApply) {
// Do not specialize the signature of SILFunctions that are external
// declarations since there is no body to optimize.
if (F->isExternalDeclaration())
return false;
// For now ignore functions with indirect results.
if (F->getConventions().hasIndirectSILResults())
return false;
// For now ignore coroutines.
if (F->getLoweredFunctionType()->isCoroutine())
return false;
// Do not specialize the signature of always inline functions. We
// will just inline them and specialize each one of the individual
// functions that these sorts of functions are inlined into.
// It is OK to specialize always inline functions if they are
// used by partial_apply instructions.
assert(!OptForPartialApply || FuncInfo);
if (F->getInlineStrategy() == Inline_t::AlwaysInline &&
(!OptForPartialApply || !FuncInfo->getMinPartialAppliedArgs()))
return false;
// For now ignore generic functions to keep things simple...
if (!FSOEnableGenerics && F->getLoweredFunctionType()->isPolymorphic())
return false;
// Make sure F has a linkage that we can optimize.
if (!isSpecializableRepresentation(F->getRepresentation(),
OptForPartialApply))
return false;
return true;
}
//===----------------------------------------------------------------------===//
// Function Signature Transform Descriptor
//===----------------------------------------------------------------------===//
void FunctionSignatureTransformDescriptor::addThunkArgument(
ArgumentDescriptor &AD, SILBuilder &Builder, SILBasicBlock *BB,
llvm::SmallVectorImpl<SILValue> &NewArgs) {
// Dead argument.
if (AD.IsEntirelyDead) {
return;
}
// Explode the argument.
if (AD.Explode) {
llvm::SmallVector<SILValue, 4> LeafValues;
AD.ProjTree.createTreeFromValue(Builder, BB->getParent()->getLocation(),
BB->getArgument(AD.Index), LeafValues);
NewArgs.append(LeafValues.begin(), LeafValues.end());
return;
}
// All other arguments get pushed as what they are.
NewArgs.push_back(BB->getArgument(AD.Index));
}
std::string
FunctionSignatureTransformDescriptor::createOptimizedSILFunctionName() {
SILFunction *F = OriginalFunction;
auto P = Demangle::SpecializationPass::FunctionSignatureOpts;
Mangle::FunctionSignatureSpecializationMangler Mangler(P, F->isSerialized(),
F);
// Handle arguments' changes.
for (unsigned i : indices(ArgumentDescList)) {
const ArgumentDescriptor &Arg = ArgumentDescList[i];
if (Arg.IsEntirelyDead) {
Mangler.setArgumentDead(i);
// No point setting other attribute if argument is dead.
continue;
}
// If we have an @owned argument and found a callee release for it,
// convert the argument to guaranteed.
if (Arg.OwnedToGuaranteed) {
Mangler.setArgumentOwnedToGuaranteed(i);
}
// If this argument is not dead and we can explode it, add 's' to the
// mangling.
if (Arg.Explode) {
Mangler.setArgumentSROA(i);
}
}
// Handle return value's change.
// FIXME: handle multiple direct results here
if (ResultDescList.size() == 1 && !ResultDescList[0].CalleeRetain.empty()) {
Mangler.setReturnValueOwnedToUnowned();
}
return Mangler.mangle();
}
/// Collect all archetypes used by a function.
static bool usesGenerics(SILFunction *F,
ArrayRef<SILParameterInfo> InterfaceParams,
ArrayRef<SILResultInfo> InterfaceResults) {
CanSILFunctionType FTy = F->getLoweredFunctionType();
auto HasGenericSignature = FTy->getInvocationGenericSignature() != nullptr;
if (!HasGenericSignature)
return false;
bool UsesGenerics = false;
auto FindArchetypesAndGenericTypes = [FTy, &UsesGenerics](Type Ty) {
if (Ty.findIf([FTy](Type Ty) -> bool {
// Assume archetypes are always a problem.
// TODO: This can ignore non-contextual archetypes.
if (Ty->hasArchetype()) return true;
// Assume type parameters are always a problem. However, this
// can ignore types that would substitute to concrete types.
if (Ty->isTypeParameter()) {
auto subs = FTy->getPatternSubstitutions();
return (!subs || Ty.subst(subs)->isTypeParameter());
}
return false;
}))
UsesGenerics = true;
};
for (auto Param : InterfaceParams) {
Param.getInterfaceType().visit(FindArchetypesAndGenericTypes);
}
for (auto Result : InterfaceResults) {
Result.getInterfaceType().visit(FindArchetypesAndGenericTypes);
}
if (UsesGenerics)
return UsesGenerics;
for (auto &BB : *F) {
for (auto &I : BB) {
for (auto Arg : BB.getArguments()) {
if (&BB != &*F->begin()) {
// Scan types of all BB arguments. Ignore the entry BB, because
// it is handled in a special way.
Arg->getType().getASTType().visit(FindArchetypesAndGenericTypes);
if (UsesGenerics)
return UsesGenerics;
}
}
// Scan types of all operands.
for (auto &Op : I.getAllOperands()) {
Op.get()->getType().getASTType().visit(FindArchetypesAndGenericTypes);
}
// Scan all substitutions of apply instructions.
if (auto AI = ApplySite::isa(&I)) {
auto Subs = AI.getSubstitutionMap();
for (auto Replacement : Subs.getReplacementTypes()) {
Replacement.visit(FindArchetypesAndGenericTypes);
}
}
// Scan all substitutions of builtin instructions.
if (auto *BI = dyn_cast<BuiltinInst>(&I)) {
auto Subs = BI->getSubstitutions();
for (auto Ty : Subs.getReplacementTypes()) {
Ty.visit(FindArchetypesAndGenericTypes);
}
}
// Scan the result type of the instruction.
for (auto V : I.getResults()) {
V->getType().getASTType().visit(FindArchetypesAndGenericTypes);
}
if (UsesGenerics)
return UsesGenerics;
}
}
return UsesGenerics;
}
// Map the parameter, result and error types out of context to get the interface
// type.
static void mapInterfaceTypes(SILFunction *F,
MutableArrayRef<SILParameterInfo> InterfaceParams,
MutableArrayRef<SILResultInfo> InterfaceResults,
Optional<SILResultInfo> &InterfaceErrorResult) {
for (auto &Param : InterfaceParams) {
if (!Param.getInterfaceType()->hasArchetype())
continue;
Param = SILParameterInfo(
Param.getInterfaceType()->mapTypeOutOfContext()->getCanonicalType(),
Param.getConvention());
}
for (auto &Result : InterfaceResults) {
if (!Result.getInterfaceType()->hasArchetype())
continue;
auto InterfaceResult = Result.getWithInterfaceType(
Result.getInterfaceType()->mapTypeOutOfContext()->getCanonicalType());
Result = InterfaceResult;
}
if (InterfaceErrorResult.hasValue()) {
if (InterfaceErrorResult.getValue().getInterfaceType()->hasArchetype()) {
InterfaceErrorResult =
SILResultInfo(InterfaceErrorResult.getValue()
.getInterfaceType()
->mapTypeOutOfContext()
->getCanonicalType(),
InterfaceErrorResult.getValue().getConvention());
}
}
}
CanSILFunctionType
FunctionSignatureTransformDescriptor::createOptimizedSILFunctionType() {
SILFunction *F = OriginalFunction;
CanSILFunctionType FTy = F->getLoweredFunctionType();
auto ExpectedFTy = F->getLoweredType().castTo<SILFunctionType>();
auto HasGenericSignature = FTy->getSubstGenericSignature() != nullptr;
// The only way that we modify the arity of function parameters is here for
// dead arguments. Doing anything else is unsafe since by definition non-dead
// arguments will have SSA uses in the function. We would need to be smarter
// in our moving to handle such cases.
llvm::SmallVector<SILParameterInfo, 8> InterfaceParams;
for (auto &ArgDesc : ArgumentDescList) {
computeOptimizedArgInterface(ArgDesc, InterfaceParams);
}
// ResultDescs only covers the direct results; we currently can't ever
// change an indirect result. Piece the modified direct result information
// back into the all-results list.
llvm::SmallVector<SILResultInfo, 8> InterfaceResults;
for (SILResultInfo InterfaceResult : FTy->getResults()) {
if (InterfaceResult.isFormalDirect()) {
auto &RV = ResultDescList[0];
if (!RV.CalleeRetain.empty()) {
++NumOwnedConvertedToNotOwnedResult;
InterfaceResults.push_back(SILResultInfo(InterfaceResult.getInterfaceType(),
ResultConvention::Unowned));
continue;
}
}
InterfaceResults.push_back(InterfaceResult);
}
llvm::SmallVector<SILYieldInfo, 8> InterfaceYields;
for (SILYieldInfo InterfaceYield : FTy->getYields()) {
// For now, don't touch the yield types.
InterfaceYields.push_back(InterfaceYield);
}
bool UsesGenerics = false;
if (HasGenericSignature) {
// Not all of the generic type parameters are used by the function
// parameters.
// Check which of the generic type parameters are not used and check if they
// are used anywhere in the function body. If this is not the case, we can
// remove the unused generic type parameters from the generic signature.
// This makes the code both smaller and faster, because no implicit
// parameters for type metadata and conformances need to be passed to the
// callee at the LLVM IR level.
// TODO: Implement a more precise analysis, so that we can eliminate only
// those generic parameters which are not used.
UsesGenerics = usesGenerics(F, InterfaceParams, InterfaceResults);
// The set of used archetypes is complete now.
if (!UsesGenerics) {
// None of the generic type parameters are used.
LLVM_DEBUG(llvm::dbgs() << "None of generic parameters are used by "
<< F->getName() << "\n";
llvm::dbgs() << "Interface params:\n";
for (auto Param : InterfaceParams) {
Param.getInterfaceType().dump(llvm::dbgs());
}
llvm::dbgs() << "Interface results:\n";
for (auto Result : InterfaceResults) {
Result.getInterfaceType().dump(llvm::dbgs());
});
}
}
// Don't use a method representation if we modified self.
auto ExtInfo = FTy->getExtInfo();
auto witnessMethodConformance = FTy->getWitnessMethodConformanceOrInvalid();
if (shouldModifySelfArgument) {
ExtInfo = ExtInfo.withRepresentation(SILFunctionTypeRepresentation::Thin);
witnessMethodConformance = ProtocolConformanceRef::forInvalid();
}
Optional<SILResultInfo> InterfaceErrorResult;
if (ExpectedFTy->hasErrorResult()) {
InterfaceErrorResult = ExpectedFTy->getErrorResult();
}
// Map the parameter, result and error types out of context to get the
// proper interface type. This is required for generic functions.
mapInterfaceTypes(F, InterfaceParams, InterfaceResults, InterfaceErrorResult);
GenericSignature GenericSig =
UsesGenerics ? FTy->getInvocationGenericSignature() : nullptr;
return SILFunctionType::get(
GenericSig, ExtInfo, FTy->getCoroutineKind(), FTy->getCalleeConvention(),
InterfaceParams, InterfaceYields, InterfaceResults, InterfaceErrorResult,
FTy->getPatternSubstitutions(), SubstitutionMap(),
F->getModule().getASTContext(), witnessMethodConformance);
}
/// Compute what the function interface will look like based on the
/// optimization we are doing on the given argument descriptor. Default
/// implementation simply passes it through.
void FunctionSignatureTransformDescriptor::computeOptimizedArgInterface(
ArgumentDescriptor &AD, SmallVectorImpl<SILParameterInfo> &Out) {
// If this argument is live, but we cannot optimize it.
if (!AD.canOptimizeLiveArg()) {
if (AD.PInfo.hasValue())
Out.push_back(AD.PInfo.getValue());
return;
}
// If we have a dead argument, bail.
if (AD.IsEntirelyDead) {
++NumDeadArgsEliminated;
return;
}
// Explode the argument or not ?
if (AD.Explode) {
++NumSROAArguments;
llvm::SmallVector<const ProjectionTreeNode *, 8> LeafNodes;
AD.ProjTree.getLiveLeafNodes(LeafNodes);
for (auto Node : LeafNodes) {
SILType Ty = Node->getType();
LLVM_DEBUG(llvm::dbgs() << " " << Ty << "\n");
// If Ty is trivial, just pass it directly.
if (Ty.isTrivial(*AD.Arg->getFunction())) {
SILParameterInfo NewInfo(Ty.getASTType(),
ParameterConvention::Direct_Unowned);
Out.push_back(NewInfo);
continue;
}
// Ty is not trivial, pass it through as the original calling convention.
auto ParameterConvention = AD.PInfo.getValue().getConvention();
if (AD.OwnedToGuaranteed) {
if (ParameterConvention == ParameterConvention::Direct_Owned)
ParameterConvention = ParameterConvention::Direct_Guaranteed;
else if (ParameterConvention == ParameterConvention::Indirect_In)
ParameterConvention = ParameterConvention::Indirect_In_Guaranteed;
else {
llvm_unreachable("Unknown parameter convention transformation");
}
}
SILParameterInfo NewInfo(Ty.getASTType(), ParameterConvention);
Out.push_back(NewInfo);
}
return;
}
// If we cannot explode this value, handle callee release and return.
// If we found releases in the callee in the last BB on an @owned
// parameter, change the parameter to @guaranteed and continue...
if (AD.OwnedToGuaranteed) {
++NumOwnedConvertedToGuaranteed;
auto ParameterConvention = AD.PInfo.getValue().getConvention();
if (ParameterConvention == ParameterConvention::Direct_Owned)
ParameterConvention = ParameterConvention::Direct_Guaranteed;
else if (ParameterConvention == ParameterConvention::Indirect_In)
ParameterConvention = ParameterConvention::Indirect_In_Guaranteed;
else {
llvm_unreachable("Unknown parameter convention transformation");
}
SILParameterInfo NewInfo(AD.PInfo.getValue().getInterfaceType(),
ParameterConvention);
Out.push_back(NewInfo);
return;
}
// Otherwise just propagate through the parameter info.
Out.push_back(AD.PInfo.getValue());
}
//===----------------------------------------------------------------------===//
// Function Signature Transform
//===----------------------------------------------------------------------===//
void FunctionSignatureTransform::createFunctionSignatureOptimizedFunction() {
// Create the optimized function!
SILFunction *F = TransformDescriptor.OriginalFunction;
SILModule &M = F->getModule();
std::string Name = TransformDescriptor.createOptimizedSILFunctionName();
// The transformed function must not already exist. This would indicate
// repeated application of FSO on the same function. That situation should be
// detected earlier by avoiding reoptimization of FSO thunks.
assert(!F->getModule().hasFunction(Name));
SILLinkage linkage = getSpecializedLinkage(F, F->getLinkage());
LLVM_DEBUG(llvm::dbgs() << " -> create specialized function " << Name
<< "\n");
auto NewFTy = TransformDescriptor.createOptimizedSILFunctionType();
GenericEnvironment *NewFGenericEnv;
if (NewFTy->getInvocationGenericSignature()) {
NewFGenericEnv = F->getGenericEnvironment();
} else {
NewFGenericEnv = nullptr;
}
// The specialized function is an internal detail, so we need to disconnect it
// from a parent class, if one exists, thus the override of the
// classSubclassScope.
TransformDescriptor.OptimizedFunction = FunctionBuilder.createFunction(
linkage, Name, NewFTy, NewFGenericEnv, F->getLocation(), F->isBare(),
F->isTransparent(), F->isSerialized(), IsNotDynamic, IsNotDistributed,
F->getEntryCount(), F->isThunk(),
/*classSubclassScope=*/SubclassScope::NotApplicable,
F->getInlineStrategy(), F->getEffectsKind(), nullptr, F->getDebugScope());
SILFunction *NewF = TransformDescriptor.OptimizedFunction.get();
if (!F->hasOwnership()) {
NewF->setOwnershipEliminated();
}
if (F->isSpecialization()) {
NewF->setSpecializationInfo(F->getSpecializationInfo());
}
// Then we transfer the body of F to NewF.
NewF->moveAllBlocksFromOtherFunction(F);
// Array semantic clients rely on the signature being as in the original
// version.
for (auto &Attr : F->getSemanticsAttrs()) {
if (!StringRef(Attr).startswith("array."))
NewF->addSemanticsAttr(Attr);
}
// Do the last bit of work to the newly created optimized function.
DeadArgumentFinalizeOptimizedFunction();
ArgumentExplosionFinalizeOptimizedFunction();
// Update the ownership kinds of function entry BB arguments.
for (auto Arg : NewF->begin()->getSILFunctionArguments()) {
SILType MappedTy = Arg->getType();
auto Ownershipkind =
ValueOwnershipKind(*NewF, MappedTy, Arg->getArgumentConvention());
Arg->setOwnershipKind(Ownershipkind);
}
// Create the thunk body !
F->setThunk(IsSignatureOptimizedThunk);
// The thunk now carries the information on how the signature is
// optimized. If we inline the thunk, we will get the benefit of calling
// the signature optimized function without additional setup on the
// caller side.
F->setInlineStrategy(AlwaysInline);
SILBasicBlock *ThunkBody = F->createBasicBlock();
for (auto &ArgDesc : TransformDescriptor.ArgumentDescList) {
ThunkBody->createFunctionArgument(ArgDesc.Arg->getType(), ArgDesc.Decl);
}
SILLocation Loc = RegularLocation::getAutoGeneratedLocation();
SILBuilder Builder(ThunkBody);
Builder.setCurrentDebugScope(ThunkBody->getParent()->getDebugScope());
FunctionRefInst *FRI = Builder.createFunctionRef(Loc, NewF);
// Create the args for the thunk's apply, ignoring any dead arguments.
llvm::SmallVector<SILValue, 8> ThunkArgs;
for (auto &ArgDesc : TransformDescriptor.ArgumentDescList) {
TransformDescriptor.addThunkArgument(ArgDesc, Builder, ThunkBody,
ThunkArgs);
}
SILValue ReturnValue;
SILType LoweredType = NewF->getLoweredType();
SILType ResultType = NewF->getConventions().getSILResultType(
Builder.getTypeExpansionContext());
auto GenCalleeType = NewF->getLoweredFunctionType();
auto SubstCalleeSILType = LoweredType;
SubstitutionMap Subs;
// Handle generic functions.
if (GenCalleeType->isPolymorphic()) {
// Produce a substitutions list and a set of substituted SIL types
// required for creating a new SIL function.
Subs = F->getForwardingSubstitutionMap();
auto SubstCalleeType = GenCalleeType->substGenericArgs(
M, Subs, Builder.getTypeExpansionContext());
SubstCalleeSILType = SILType::getPrimitiveObjectType(SubstCalleeType);
SILFunctionConventions Conv(SubstCalleeType, M);
ResultType = Conv.getSILResultType(Builder.getTypeExpansionContext());
}
auto FunctionTy = LoweredType.castTo<SILFunctionType>();
if (FunctionTy->hasErrorResult()) {
// We need a try_apply to call a function with an error result.
SILFunction *Thunk = ThunkBody->getParent();
SILBasicBlock *NormalBlock = Thunk->createBasicBlock();
ReturnValue =
NormalBlock->createPhiArgument(ResultType, OwnershipKind::Owned);
SILBasicBlock *ErrorBlock = Thunk->createBasicBlock();
SILType Error =
SILType::getPrimitiveObjectType(FunctionTy->getErrorResult().getInterfaceType());
auto *ErrorArg = ErrorBlock->createPhiArgument(Error, OwnershipKind::Owned);
Builder.createTryApply(Loc, FRI, Subs, ThunkArgs, NormalBlock, ErrorBlock);
Builder.setInsertionPoint(ErrorBlock);
Builder.createThrow(Loc, ErrorArg);
Builder.setInsertionPoint(NormalBlock);
} else {
ReturnValue = Builder.createApply(Loc, FRI, Subs, ThunkArgs);
}
// Set up the return results.
if (NewF->isNoReturnFunction(Builder.getTypeExpansionContext())) {
Builder.createUnreachable(Loc);
} else {
Builder.createReturn(Loc, ReturnValue);
}
// Do the last bit work to finalize the thunk.
OwnedToGuaranteedFinalizeThunkFunction(Builder, F);
assert(F->getDebugScope()->Parent != NewF->getDebugScope()->Parent);
}
// Run the optimization.
bool FunctionSignatureTransform::run(bool hasCaller) {
// We use a reference here on purpose so our transformations can know if we
// are going to make a thunk and thus should just optimize.
bool &Changed = TransformDescriptor.Changed;
bool hasOnlyDirectInModuleCallers =
TransformDescriptor.hasOnlyDirectInModuleCallers;
SILFunction *F = TransformDescriptor.OriginalFunction;
// If we are asked to assume a caller for testing purposes, set the flag.
hasCaller |= FSOOptimizeIfNotCalled;
if (!hasCaller && (F->getDynamicallyReplacedFunction() ||
F->getReferencedAdHocRequirementWitnessFunction() ||
canBeCalledIndirectly(F->getRepresentation()))) {
LLVM_DEBUG(llvm::dbgs() << " function has no caller -> abort\n");
return false;
}
// Bail if we have a pseudo-generic function. We do not handle these today. If
// we let it through here we crash when attempting to compute the optimized
// function type.
//
// TODO: Add support for this.
if (F->getLoweredFunctionType()->isPseudogeneric()) {
LLVM_DEBUG(llvm::dbgs() << " function is pseudo-generic -> abort\n");
return false;
}
// Run OwnedToGuaranteed optimization.
if (OwnedToGuaranteedAnalyze()) {
Changed = true;
LLVM_DEBUG(llvm::dbgs() << " transform owned-to-guaranteed\n");
OwnedToGuaranteedTransform();
}
// Run DeadArgument elimination transformation. We only specialize
// if this function has a caller inside the current module or we have
// already created a thunk.
if ((hasCaller || Changed || hasOnlyDirectInModuleCallers) &&
DeadArgumentAnalyzeParameters()) {
Changed = true;
LLVM_DEBUG(llvm::dbgs() << " remove dead arguments\n");
DeadArgumentTransformFunction();
}
// Run ArgumentExplosion transformation. We only specialize
// if this function has a caller inside the current module or we have
// already created a thunk.
//
// NOTE: we run argument explosion last because we've already initialized
// the ArgumentDescList to have unexploded number of arguments. Exploding
// it without changing the argument count is not going to help with
// owned-to-guaranteed transformation.
//
// In order to not miss any opportunity, we send the optimized function
// to the passmanager to optimize any opportunities exposed by argument
// explosion.
if ((hasCaller || Changed || hasOnlyDirectInModuleCallers) &&
ArgumentExplosionAnalyzeParameters()) {
Changed = true;
}
// Check if generic signature of the function could be changed by
// removed some unused generic arguments.
if (F->getLoweredFunctionType()->isPolymorphic() &&
TransformDescriptor.createOptimizedSILFunctionType() !=
F->getLoweredFunctionType()) {
Changed = true;
}
// Create the specialized function and invalidate the old function.
if (Changed) {
createFunctionSignatureOptimizedFunction();
}
return Changed;
}
// Run dead argument elimination of partially applied functions.
//
// After this optimization CapturePropagation can replace the partial_apply by a
// direct reference to the specialized function.
bool FunctionSignatureTransform::removeDeadArgs(int minPartialAppliedArgs) {
if (minPartialAppliedArgs < 1)
return false;
if (!DeadArgumentAnalyzeParameters())
return false;
SILFunction *F = TransformDescriptor.OriginalFunction;
auto ArgumentDescList = TransformDescriptor.ArgumentDescList;
// Check if at least the minimum number of partially applied arguments
// are dead. Otherwise no partial_apply can be removed anyway.
unsigned Size = ArgumentDescList.size();
for (unsigned Idx : range(Size)) {
if (Idx < Size - minPartialAppliedArgs) {
// Don't remove arguments other than the partial applied ones, even if
// they are dead.
ArgumentDescList[Idx].IsEntirelyDead = false;
continue;
}
// Is the partially applied argument dead?
if (!ArgumentDescList[Idx].IsEntirelyDead)
return false;
// Currently we require that all dead parameters have trivial types. The
// reason is that it's very hard to find places where we can release those
// parameters (as a replacement for the removed partial_apply).
//
// TODO: Maybe we can skip this restriction when we have semantic ARC.
if (ArgumentDescList[Idx].Arg->getType().isTrivial(*F))
continue;
return false;
}
LLVM_DEBUG(llvm::dbgs() << " remove dead arguments for partial_apply\n");
DeadArgumentTransformFunction();
createFunctionSignatureOptimizedFunction();
return true;
}
//===----------------------------------------------------------------------===//
// Top Level Entry Point
//===----------------------------------------------------------------------===//
namespace {
class FunctionSignatureOpts : public SILFunctionTransform {
/// If true, perform a special kind of dead argument elimination to enable
/// removal of partial_apply instructions where all partially applied
/// arguments are dead.
bool OptForPartialApply;
public:
FunctionSignatureOpts(bool OptForPartialApply) :
OptForPartialApply(OptForPartialApply) { }
void run() override {
auto *F = getFunction();
// Don't run function signature optimizations at -Os.
if (F->optimizeForSize())
return;
// Don't optimize callees that should not be optimized.
if (!F->shouldOptimize())
return;
if (F->isDynamicallyReplaceable())
return;
// This is the function to optimize.
LLVM_DEBUG(llvm::dbgs() << "*** FSO on function: " << F->getName()
<< " ***\n");
// Check the signature of F to make sure that it is a function that we
// can specialize. These are conditions independent of the call graph.
// No need for CallerAnalysis if we are not optimizing for partial
// applies.
if (!OptForPartialApply &&
!canSpecializeFunction(F, nullptr, OptForPartialApply)) {
LLVM_DEBUG(llvm::dbgs() << " cannot specialize function -> abort\n");
return;
}
const CallerAnalysis *CA = PM->getAnalysis<CallerAnalysis>();
const CallerAnalysis::FunctionInfo &FuncInfo = CA->getFunctionInfo(F);
// Check the signature of F to make sure that it is a function that we
// can specialize. These are conditions independent of the call graph.
if (OptForPartialApply &&
!canSpecializeFunction(F, &FuncInfo, OptForPartialApply)) {
LLVM_DEBUG(llvm::dbgs() << " cannot specialize function -> abort\n");
return;
}
// Never repeat the same function signature optimization on the same
// function. Multiple function signature optimizations are composed by
// successively optmizing the newly created functions. Each optimization
// creates a new level of thunk which are all ultimately inlined away.
//
// This happens, for example, when a reference to the original function is
// discovered during devirtualization. That will cause the original function
// (now an FSO thunk) to be pushed back on the function pass pipeline.
if (F->isThunk() == IsSignatureOptimizedThunk) {
LLVM_DEBUG(llvm::dbgs() << " FSO already performed on this thunk\n");
return;
}
// Ok, we think we can perform optimization. Now perform a quick check
auto *RCIA = getAnalysis<RCIdentityAnalysis>();
auto *EA = PM->getAnalysis<EpilogueARCAnalysis>();
// As we optimize the function more and more, the name of the function is
// going to change, make sure the mangler is aware of all the changes done
// to the function.
auto P = Demangle::SpecializationPass::FunctionSignatureOpts;
Mangle::FunctionSignatureSpecializationMangler Mangler(P,
F->isSerialized(), F);
/// Keep a map between the exploded argument index and the original argument
/// index.
llvm::SmallDenseMap<int, int> AIM;
int asize = F->begin()->getArguments().size();
for (unsigned i : range(asize)) {
AIM[i] = i;
}
// Allocate the argument and result descriptors.
llvm::SpecificBumpPtrAllocator<ProjectionTreeNode> Allocator;
llvm::SmallVector<ArgumentDescriptor, 4> ArgumentDescList;
llvm::SmallVector<ResultDescriptor, 4> ResultDescList;
auto Args = F->begin()->getSILFunctionArguments();
for (unsigned i : indices(Args)) {
ArgumentDescList.emplace_back(Args[i], Allocator);
}
for (SILResultInfo IR : F->getLoweredFunctionType()->getResults()) {
ResultDescList.emplace_back(IR);
}
SILOptFunctionBuilder FuncBuilder(*this);
// Owned to guaranteed optimization.
FunctionSignatureTransform FST(FuncBuilder, F, RCIA, EA, Mangler, AIM,
ArgumentDescList, ResultDescList,
FuncInfo.foundAllCallers());
bool Changed = false;
if (OptForPartialApply) {
Changed = FST.removeDeadArgs(FuncInfo.getMinPartialAppliedArgs());
} else {
Changed = FST.run(FuncInfo.hasDirectCaller());
}
if (!Changed) {
return;
}
++NumFunctionSignaturesOptimized;
// The old function must be a thunk now.
assert(F->isThunk() && "Old function should have been turned into a thunk");
invalidateAnalysis(SILAnalysis::InvalidationKind::Everything);
// Make sure the PM knows about this function. This will also help us
// with self-recursion.
addFunctionToPassManagerWorklist(FST.getOptimizedFunction(), F);
if (!OptForPartialApply) {
// We have to restart the pipeline for this thunk in order to run the
// inliner (and other opts) again. This is important if the new
// specialized function (which is called from this thunk) is
// function-signature-optimized again and also becomes an
// always-inline-thunk.
restartPassPipeline();
}
}
};
} // end anonymous namespace
SILTransform *swift::createFunctionSignatureOpts() {
return new FunctionSignatureOpts(/* OptForPartialApply */ false);
}
SILTransform *swift::createDeadArgSignatureOpt() {
return new FunctionSignatureOpts(/* OptForPartialApply */ true);
}