Skip to content

Commit 5af7e0d

Browse files
authored
Merge pull request #80236 from xedin/is-self-recursive-cache
[SILOptimizer] Turn "is self-recursive" check into analysis
2 parents 7648bce + 41c88f8 commit 5af7e0d

File tree

8 files changed

+154
-31
lines changed

8 files changed

+154
-31
lines changed

include/swift/SILOptimizer/Analysis/Analysis.def

+1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ SIL_ANALYSIS(RCIdentity)
4646
SIL_ANALYSIS(PassManagerVerifier)
4747
SIL_ANALYSIS(DeadEndBlocks)
4848
SIL_ANALYSIS(Region)
49+
SIL_ANALYSIS(IsSelfRecursive)
4950

5051
#undef SIL_ANALYSIS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===--- IsSelfRecursiveAnalysis.h ----------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 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+
#ifndef SWIFT_SILOPTIMIZER_ISSELFRECURSIVEANALYSIS_H
14+
#define SWIFT_SILOPTIMIZER_ISSELFRECURSIVEANALYSIS_H
15+
16+
#include "swift/SILOptimizer/Analysis/Analysis.h"
17+
18+
namespace swift {
19+
class SILFunction;
20+
21+
class IsSelfRecursive {
22+
const SILFunction *f;
23+
bool didComputeValue = false;
24+
bool isSelfRecursive = false;
25+
26+
void compute();
27+
28+
public:
29+
IsSelfRecursive(const SILFunction *f) : f(f) {}
30+
31+
~IsSelfRecursive();
32+
33+
bool isComputed() const { return didComputeValue; }
34+
35+
bool get() {
36+
if (!didComputeValue) {
37+
compute();
38+
didComputeValue = true;
39+
}
40+
return isSelfRecursive;
41+
}
42+
43+
SILFunction *getFunction() { return const_cast<SILFunction *>(f); }
44+
};
45+
46+
class IsSelfRecursiveAnalysis final
47+
: public FunctionAnalysisBase<IsSelfRecursive> {
48+
public:
49+
IsSelfRecursiveAnalysis()
50+
: FunctionAnalysisBase<IsSelfRecursive>(
51+
SILAnalysisKind::IsSelfRecursive) {}
52+
53+
IsSelfRecursiveAnalysis(const IsSelfRecursiveAnalysis &) = delete;
54+
IsSelfRecursiveAnalysis &operator=(const IsSelfRecursiveAnalysis &) = delete;
55+
56+
static SILAnalysisKind getAnalysisKind() {
57+
return SILAnalysisKind::IsSelfRecursive;
58+
}
59+
60+
static bool classof(const SILAnalysis *s) {
61+
return s->getKind() == SILAnalysisKind::IsSelfRecursive;
62+
}
63+
64+
std::unique_ptr<IsSelfRecursive> newFunctionAnalysis(SILFunction *f) override {
65+
return std::make_unique<IsSelfRecursive>(f);
66+
}
67+
68+
bool shouldInvalidate(SILAnalysis::InvalidationKind k) override {
69+
return k & InvalidationKind::Calls;
70+
}
71+
};
72+
73+
} // namespace swift
74+
75+
#endif

include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern llvm::cl::opt<bool> EnableSILInliningOfGenerics;
2929

3030
namespace swift {
3131
class BasicCalleeAnalysis;
32+
class IsSelfRecursiveAnalysis;
3233

3334
// Controls the decision to inline functions with @_semantics, @effect and
3435
// global_init attributes.
@@ -40,7 +41,8 @@ enum class InlineSelection {
4041

4142
/// Check if this ApplySite is eligible for inlining. If so, return the callee.
4243
SILFunction *getEligibleFunction(FullApplySite AI,
43-
InlineSelection WhatToInline);
44+
InlineSelection WhatToInline,
45+
IsSelfRecursiveAnalysis *SRA);
4446

4547
// Returns true if this is a pure call, i.e. the callee has no side-effects
4648
// and all arguments are constants.

lib/SILOptimizer/Analysis/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ target_sources(swiftSILOptimizer PRIVATE
1616
EpilogueARCAnalysis.cpp
1717
FunctionOrder.cpp
1818
IVAnalysis.cpp
19+
IsSelfRecursiveAnalysis.cpp
1920
LoopAnalysis.cpp
2021
LoopRegionAnalysis.cpp
2122
NonLocalAccessBlockAnalysis.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//===--- IsSelfRecursiveAnalysis.cpp --------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 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+
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
14+
#include "swift/SIL/ApplySite.h"
15+
#include "swift/SIL/SILBasicBlock.h"
16+
#include "swift/SIL/SILFunction.h"
17+
#include "swift/SIL/SILInstruction.h"
18+
19+
using namespace swift;
20+
21+
// Force the compiler to generate the destructor in this C++ file.
22+
// Otherwise it can happen that it is generated in a SwiftCompilerSources module
23+
// and that results in unresolved-symbols linker errors.
24+
IsSelfRecursive::~IsSelfRecursive() = default;
25+
26+
void IsSelfRecursive::compute() {
27+
isSelfRecursive = false;
28+
29+
for (auto &BB : *getFunction()) {
30+
for (auto &I : BB) {
31+
if (auto Apply = FullApplySite::isa(&I)) {
32+
if (Apply.getReferencedFunctionOrNull() == f) {
33+
isSelfRecursive = true;
34+
return;
35+
}
36+
}
37+
}
38+
}
39+
}
40+
41+
//===----------------------------------------------------------------------===//
42+
// Main Entry Point
43+
//===----------------------------------------------------------------------===//
44+
45+
SILAnalysis *swift::createIsSelfRecursiveAnalysis(SILModule *) {
46+
return new IsSelfRecursiveAnalysis();
47+
}

lib/SILOptimizer/LoopTransforms/LoopUnroll.cpp

+8-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/SIL/PatternMatch.h"
1919
#include "swift/SIL/SILCloner.h"
2020
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
21+
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
2122
#include "swift/SILOptimizer/PassManager/Passes.h"
2223
#include "swift/SILOptimizer/PassManager/Transforms.h"
2324
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
@@ -209,7 +210,8 @@ static std::optional<uint64_t> getMaxLoopTripCount(SILLoop *Loop,
209210
/// Check whether we can duplicate the instructions in the loop and use a
210211
/// heuristic that looks at the trip count and the cost of the instructions in
211212
/// the loop to determine whether we should unroll this loop.
212-
static bool canAndShouldUnrollLoop(SILLoop *Loop, uint64_t TripCount) {
213+
static bool canAndShouldUnrollLoop(SILLoop *Loop, uint64_t TripCount,
214+
IsSelfRecursiveAnalysis *SRA) {
213215
assert(Loop->getSubLoops().empty() && "Expect innermost loops");
214216
if (TripCount > 32)
215217
return false;
@@ -231,7 +233,7 @@ static bool canAndShouldUnrollLoop(SILLoop *Loop, uint64_t TripCount) {
231233
++Cost;
232234
if (auto AI = FullApplySite::isa(&Inst)) {
233235
auto Callee = AI.getCalleeFunction();
234-
if (Callee && getEligibleFunction(AI, InlineSelection::Everything)) {
236+
if (Callee && getEligibleFunction(AI, InlineSelection::Everything, SRA)) {
235237
// If callee is rather big and potentially inlinable, it may be better
236238
// not to unroll, so that the body of the callee can be inlined later.
237239
Cost += Callee->size() * InsnsPerBB;
@@ -386,7 +388,7 @@ updateSSA(SILFunction *Fn, SILLoop *Loop,
386388

387389
/// Try to fully unroll the loop if we can determine the trip count and the trip
388390
/// count is below a threshold.
389-
static bool tryToUnrollLoop(SILLoop *Loop) {
391+
static bool tryToUnrollLoop(SILLoop *Loop, IsSelfRecursiveAnalysis *SRA) {
390392
assert(Loop->getSubLoops().empty() && "Expecting innermost loops");
391393

392394
LLVM_DEBUG(llvm::dbgs() << "Trying to unroll loop : \n" << *Loop);
@@ -407,7 +409,7 @@ static bool tryToUnrollLoop(SILLoop *Loop) {
407409
return false;
408410
}
409411

410-
if (!canAndShouldUnrollLoop(Loop, MaxTripCount.value())) {
412+
if (!canAndShouldUnrollLoop(Loop, MaxTripCount.value(), SRA)) {
411413
LLVM_DEBUG(llvm::dbgs() << "Not unrolling, exceeds cost threshold\n");
412414
return false;
413415
}
@@ -487,6 +489,7 @@ class LoopUnrolling : public SILFunctionTransform {
487489
bool Changed = false;
488490
auto *Fun = getFunction();
489491
SILLoopInfo *LoopInfo = PM->getAnalysis<SILLoopAnalysis>()->get(Fun);
492+
IsSelfRecursiveAnalysis *SRA = PM->getAnalysis<IsSelfRecursiveAnalysis>();
490493

491494
LLVM_DEBUG(llvm::dbgs() << "Loop Unroll running on function : "
492495
<< Fun->getName() << "\n");
@@ -514,7 +517,7 @@ class LoopUnrolling : public SILFunctionTransform {
514517

515518
// Try to unroll innermost loops.
516519
for (auto *Loop : InnermostLoops)
517-
Changed |= tryToUnrollLoop(Loop);
520+
Changed |= tryToUnrollLoop(Loop, SRA);
518521

519522
if (Changed) {
520523
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);

lib/SILOptimizer/Transforms/PerformanceInliner.cpp

+14-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "swift/SIL/MemAccessUtils.h"
1818
#include "swift/SIL/OptimizationRemark.h"
1919
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
20+
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
2021
#include "swift/SILOptimizer/PassManager/Passes.h"
2122
#include "swift/SILOptimizer/PassManager/Transforms.h"
2223
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
@@ -107,6 +108,7 @@ class SILPerformanceInliner {
107108
DominanceAnalysis *DA;
108109
SILLoopAnalysis *LA;
109110
BasicCalleeAnalysis *BCA;
111+
IsSelfRecursiveAnalysis *SRA;
110112

111113
// For keys of SILFunction and SILLoop.
112114
llvm::DenseMap<SILFunction *, ShortestPathAnalysis *> SPAs;
@@ -238,14 +240,14 @@ class SILPerformanceInliner {
238240

239241
public:
240242
SILPerformanceInliner(StringRef PassName, SILOptFunctionBuilder &FuncBuilder,
241-
InlineSelection WhatToInline,
242-
SILPassManager *pm, DominanceAnalysis *DA,
243-
PostDominanceAnalysis *PDA,
243+
InlineSelection WhatToInline, SILPassManager *pm,
244+
DominanceAnalysis *DA, PostDominanceAnalysis *PDA,
244245
SILLoopAnalysis *LA, BasicCalleeAnalysis *BCA,
245-
OptimizationMode OptMode, OptRemark::Emitter &ORE)
246+
IsSelfRecursiveAnalysis *SRA, OptimizationMode OptMode,
247+
OptRemark::Emitter &ORE)
246248
: PassName(PassName), FuncBuilder(FuncBuilder),
247-
WhatToInline(WhatToInline), pm(pm), DA(DA), LA(LA), BCA(BCA), CBI(DA, PDA), ORE(ORE),
248-
OptMode(OptMode) {}
249+
WhatToInline(WhatToInline), pm(pm), DA(DA), LA(LA), BCA(BCA), SRA(SRA),
250+
CBI(DA, PDA), ORE(ORE), OptMode(OptMode) {}
249251

250252
bool inlineCallsIntoFunction(SILFunction *F);
251253
};
@@ -1087,7 +1089,7 @@ void SILPerformanceInliner::collectAppliesToInline(
10871089
// At this occasion we record additional weight increases.
10881090
addWeightCorrection(FAS, WeightCorrections);
10891091

1090-
if (SILFunction *Callee = getEligibleFunction(FAS, WhatToInline)) {
1092+
if (SILFunction *Callee = getEligibleFunction(FAS, WhatToInline, SRA)) {
10911093
// Compute the shortest-path analysis for the callee.
10921094
SILLoopInfo *CalleeLI = LA->get(Callee);
10931095
ShortestPathAnalysis *CalleeSPA = getSPA(Callee, CalleeLI);
@@ -1138,7 +1140,7 @@ void SILPerformanceInliner::collectAppliesToInline(
11381140

11391141
FullApplySite AI = FullApplySite(&*I);
11401142

1141-
auto *Callee = getEligibleFunction(AI, WhatToInline);
1143+
auto *Callee = getEligibleFunction(AI, WhatToInline, SRA);
11421144
if (Callee) {
11431145
// Check if we have an always_inline or transparent function. If we do,
11441146
// just add it to our final Applies list and continue.
@@ -1328,7 +1330,7 @@ void SILPerformanceInliner::visitColdBlocks(
13281330
if (!AI)
13291331
continue;
13301332

1331-
auto *Callee = getEligibleFunction(AI, WhatToInline);
1333+
auto *Callee = getEligibleFunction(AI, WhatToInline, SRA);
13321334
if (Callee && decideInColdBlock(AI, Callee, numCallerBlocks)) {
13331335
AppliesToInline.push_back(AI);
13341336
}
@@ -1358,6 +1360,7 @@ class SILPerformanceInlinerPass : public SILFunctionTransform {
13581360
PostDominanceAnalysis *PDA = PM->getAnalysis<PostDominanceAnalysis>();
13591361
SILLoopAnalysis *LA = PM->getAnalysis<SILLoopAnalysis>();
13601362
BasicCalleeAnalysis *BCA = PM->getAnalysis<BasicCalleeAnalysis>();
1363+
IsSelfRecursiveAnalysis *SRA = PM->getAnalysis<IsSelfRecursiveAnalysis>();
13611364
OptRemark::Emitter ORE(DEBUG_TYPE, *getFunction());
13621365

13631366
if (getOptions().InlineThreshold == 0) {
@@ -1369,7 +1372,8 @@ class SILPerformanceInlinerPass : public SILFunctionTransform {
13691372
SILOptFunctionBuilder FuncBuilder(*this);
13701373

13711374
SILPerformanceInliner Inliner(getID(), FuncBuilder, WhatToInline,
1372-
getPassManager(), DA, PDA, LA, BCA, OptMode, ORE);
1375+
getPassManager(), DA, PDA, LA, BCA, SRA,
1376+
OptMode, ORE);
13731377

13741378
assert(getFunction()->isDefinition() &&
13751379
"Expected only functions with bodies!");

lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp

+5-15
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/SILOptimizer/Analysis/ArraySemantic.h"
1414
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
15+
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
1516
#include "swift/SILOptimizer/Utils/PerformanceInlinerUtils.h"
1617
#include "swift/AST/Module.h"
1718
#include "swift/Basic/Assertions.h"
@@ -577,16 +578,6 @@ void ShortestPathAnalysis::Weight::updateBenefit(int &Benefit,
577578
Benefit = newBenefit;
578579
}
579580

580-
// Return true if the callee has self-recursive calls.
581-
static bool calleeIsSelfRecursive(SILFunction *Callee) {
582-
for (auto &BB : *Callee)
583-
for (auto &I : BB)
584-
if (auto Apply = FullApplySite::isa(&I))
585-
if (Apply.getReferencedFunctionOrNull() == Callee)
586-
return true;
587-
return false;
588-
}
589-
590581
SemanticFunctionLevel swift::getSemanticFunctionLevel(SILFunction *function) {
591582
// Currently, we only consider "array" semantic calls to be "optimizable
592583
// semantic functions" (non-transient) because we only have semantic passes
@@ -752,7 +743,8 @@ static bool isCallerAndCalleeLayoutConstraintsCompatible(FullApplySite AI) {
752743

753744
// Returns the callee of an apply_inst if it is basically inlinable.
754745
SILFunction *swift::getEligibleFunction(FullApplySite AI,
755-
InlineSelection WhatToInline) {
746+
InlineSelection WhatToInline,
747+
IsSelfRecursiveAnalysis *SRA) {
756748
SILFunction *Callee = AI.getReferencedFunctionOrNull();
757749

758750
if (!Callee) {
@@ -864,10 +856,8 @@ SILFunction *swift::getEligibleFunction(FullApplySite AI,
864856

865857
// Inlining self-recursive functions into other functions can result
866858
// in excessive code duplication since we run the inliner multiple
867-
// times in our pipeline
868-
//
869-
// FIXME: This should be cached!
870-
if (calleeIsSelfRecursive(Callee)) {
859+
// times in our pipeline.
860+
if (SRA->get(Callee)->get()) {
871861
return nullptr;
872862
}
873863

0 commit comments

Comments
 (0)