Skip to content

[SILOptimizer] Turn "is self-recursive" check into analysis #80236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/SILOptimizer/Analysis/Analysis.def
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ SIL_ANALYSIS(RCIdentity)
SIL_ANALYSIS(PassManagerVerifier)
SIL_ANALYSIS(DeadEndBlocks)
SIL_ANALYSIS(Region)
SIL_ANALYSIS(IsSelfRecursive)

#undef SIL_ANALYSIS
75 changes: 75 additions & 0 deletions include/swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//===--- IsSelfRecursiveAnalysis.h ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_SILOPTIMIZER_ISSELFRECURSIVEANALYSIS_H
#define SWIFT_SILOPTIMIZER_ISSELFRECURSIVEANALYSIS_H

#include "swift/SILOptimizer/Analysis/Analysis.h"

namespace swift {
class SILFunction;

class IsSelfRecursive {
const SILFunction *f;
bool didComputeValue = false;
bool isSelfRecursive = false;

void compute();

public:
IsSelfRecursive(const SILFunction *f) : f(f) {}

~IsSelfRecursive();

bool isComputed() const { return didComputeValue; }

bool get() {
if (!didComputeValue) {
compute();
didComputeValue = true;
}
return isSelfRecursive;
}

SILFunction *getFunction() { return const_cast<SILFunction *>(f); }
};

class IsSelfRecursiveAnalysis final
: public FunctionAnalysisBase<IsSelfRecursive> {
public:
IsSelfRecursiveAnalysis()
: FunctionAnalysisBase<IsSelfRecursive>(
SILAnalysisKind::IsSelfRecursive) {}

IsSelfRecursiveAnalysis(const IsSelfRecursiveAnalysis &) = delete;
IsSelfRecursiveAnalysis &operator=(const IsSelfRecursiveAnalysis &) = delete;

static SILAnalysisKind getAnalysisKind() {
return SILAnalysisKind::IsSelfRecursive;
}

static bool classof(const SILAnalysis *s) {
return s->getKind() == SILAnalysisKind::IsSelfRecursive;
}

std::unique_ptr<IsSelfRecursive> newFunctionAnalysis(SILFunction *f) override {
return std::make_unique<IsSelfRecursive>(f);
}

bool shouldInvalidate(SILAnalysis::InvalidationKind k) override {
return k & InvalidationKind::Calls;
}
};

} // namespace swift

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern llvm::cl::opt<bool> EnableSILInliningOfGenerics;

namespace swift {
class BasicCalleeAnalysis;
class IsSelfRecursiveAnalysis;

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

/// Check if this ApplySite is eligible for inlining. If so, return the callee.
SILFunction *getEligibleFunction(FullApplySite AI,
InlineSelection WhatToInline);
InlineSelection WhatToInline,
IsSelfRecursiveAnalysis *SRA);

// Returns true if this is a pure call, i.e. the callee has no side-effects
// and all arguments are constants.
Expand Down
1 change: 1 addition & 0 deletions lib/SILOptimizer/Analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ target_sources(swiftSILOptimizer PRIVATE
EpilogueARCAnalysis.cpp
FunctionOrder.cpp
IVAnalysis.cpp
IsSelfRecursiveAnalysis.cpp
LoopAnalysis.cpp
LoopRegionAnalysis.cpp
NonLocalAccessBlockAnalysis.cpp
Expand Down
47 changes: 47 additions & 0 deletions lib/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===--- IsSelfRecursiveAnalysis.cpp --------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//

#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
#include "swift/SIL/ApplySite.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILInstruction.h"

using namespace swift;

// Force the compiler to generate the destructor in this C++ file.
// Otherwise it can happen that it is generated in a SwiftCompilerSources module
// and that results in unresolved-symbols linker errors.
IsSelfRecursive::~IsSelfRecursive() = default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just the destructor right, isn't this the way to convince clang to place the vtable in this TU?


void IsSelfRecursive::compute() {
isSelfRecursive = false;

for (auto &BB : *getFunction()) {
for (auto &I : BB) {
if (auto Apply = FullApplySite::isa(&I)) {
if (Apply.getReferencedFunctionOrNull() == f) {
isSelfRecursive = true;
return;
}
}
}
}
}

//===----------------------------------------------------------------------===//
// Main Entry Point
//===----------------------------------------------------------------------===//

SILAnalysis *swift::createIsSelfRecursiveAnalysis(SILModule *) {
return new IsSelfRecursiveAnalysis();
}
13 changes: 8 additions & 5 deletions lib/SILOptimizer/LoopTransforms/LoopUnroll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "swift/SIL/PatternMatch.h"
#include "swift/SIL/SILCloner.h"
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
Expand Down Expand Up @@ -209,7 +210,8 @@ static std::optional<uint64_t> getMaxLoopTripCount(SILLoop *Loop,
/// Check whether we can duplicate the instructions in the loop and use a
/// heuristic that looks at the trip count and the cost of the instructions in
/// the loop to determine whether we should unroll this loop.
static bool canAndShouldUnrollLoop(SILLoop *Loop, uint64_t TripCount) {
static bool canAndShouldUnrollLoop(SILLoop *Loop, uint64_t TripCount,
IsSelfRecursiveAnalysis *SRA) {
assert(Loop->getSubLoops().empty() && "Expect innermost loops");
if (TripCount > 32)
return false;
Expand All @@ -231,7 +233,7 @@ static bool canAndShouldUnrollLoop(SILLoop *Loop, uint64_t TripCount) {
++Cost;
if (auto AI = FullApplySite::isa(&Inst)) {
auto Callee = AI.getCalleeFunction();
if (Callee && getEligibleFunction(AI, InlineSelection::Everything)) {
if (Callee && getEligibleFunction(AI, InlineSelection::Everything, SRA)) {
// If callee is rather big and potentially inlinable, it may be better
// not to unroll, so that the body of the callee can be inlined later.
Cost += Callee->size() * InsnsPerBB;
Expand Down Expand Up @@ -386,7 +388,7 @@ updateSSA(SILFunction *Fn, SILLoop *Loop,

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

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

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

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

// Try to unroll innermost loops.
for (auto *Loop : InnermostLoops)
Changed |= tryToUnrollLoop(Loop);
Changed |= tryToUnrollLoop(Loop, SRA);

if (Changed) {
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
Expand Down
24 changes: 14 additions & 10 deletions lib/SILOptimizer/Transforms/PerformanceInliner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/OptimizationRemark.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
Expand Down Expand Up @@ -107,6 +108,7 @@ class SILPerformanceInliner {
DominanceAnalysis *DA;
SILLoopAnalysis *LA;
BasicCalleeAnalysis *BCA;
IsSelfRecursiveAnalysis *SRA;

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

public:
SILPerformanceInliner(StringRef PassName, SILOptFunctionBuilder &FuncBuilder,
InlineSelection WhatToInline,
SILPassManager *pm, DominanceAnalysis *DA,
PostDominanceAnalysis *PDA,
InlineSelection WhatToInline, SILPassManager *pm,
DominanceAnalysis *DA, PostDominanceAnalysis *PDA,
SILLoopAnalysis *LA, BasicCalleeAnalysis *BCA,
OptimizationMode OptMode, OptRemark::Emitter &ORE)
IsSelfRecursiveAnalysis *SRA, OptimizationMode OptMode,
OptRemark::Emitter &ORE)
: PassName(PassName), FuncBuilder(FuncBuilder),
WhatToInline(WhatToInline), pm(pm), DA(DA), LA(LA), BCA(BCA), CBI(DA, PDA), ORE(ORE),
OptMode(OptMode) {}
WhatToInline(WhatToInline), pm(pm), DA(DA), LA(LA), BCA(BCA), SRA(SRA),
CBI(DA, PDA), ORE(ORE), OptMode(OptMode) {}

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

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

FullApplySite AI = FullApplySite(&*I);

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

auto *Callee = getEligibleFunction(AI, WhatToInline);
auto *Callee = getEligibleFunction(AI, WhatToInline, SRA);
if (Callee && decideInColdBlock(AI, Callee, numCallerBlocks)) {
AppliesToInline.push_back(AI);
}
Expand Down Expand Up @@ -1358,6 +1360,7 @@ class SILPerformanceInlinerPass : public SILFunctionTransform {
PostDominanceAnalysis *PDA = PM->getAnalysis<PostDominanceAnalysis>();
SILLoopAnalysis *LA = PM->getAnalysis<SILLoopAnalysis>();
BasicCalleeAnalysis *BCA = PM->getAnalysis<BasicCalleeAnalysis>();
IsSelfRecursiveAnalysis *SRA = PM->getAnalysis<IsSelfRecursiveAnalysis>();
OptRemark::Emitter ORE(DEBUG_TYPE, *getFunction());

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

SILPerformanceInliner Inliner(getID(), FuncBuilder, WhatToInline,
getPassManager(), DA, PDA, LA, BCA, OptMode, ORE);
getPassManager(), DA, PDA, LA, BCA, SRA,
OptMode, ORE);

assert(getFunction()->isDefinition() &&
"Expected only functions with bodies!");
Expand Down
20 changes: 5 additions & 15 deletions lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "swift/SILOptimizer/Analysis/ArraySemantic.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SILOptimizer/Analysis/IsSelfRecursiveAnalysis.h"
#include "swift/SILOptimizer/Utils/PerformanceInlinerUtils.h"
#include "swift/AST/Module.h"
#include "swift/Basic/Assertions.h"
Expand Down Expand Up @@ -577,16 +578,6 @@ void ShortestPathAnalysis::Weight::updateBenefit(int &Benefit,
Benefit = newBenefit;
}

// Return true if the callee has self-recursive calls.
static bool calleeIsSelfRecursive(SILFunction *Callee) {
for (auto &BB : *Callee)
for (auto &I : BB)
if (auto Apply = FullApplySite::isa(&I))
if (Apply.getReferencedFunctionOrNull() == Callee)
return true;
return false;
}

SemanticFunctionLevel swift::getSemanticFunctionLevel(SILFunction *function) {
// Currently, we only consider "array" semantic calls to be "optimizable
// semantic functions" (non-transient) because we only have semantic passes
Expand Down Expand Up @@ -752,7 +743,8 @@ static bool isCallerAndCalleeLayoutConstraintsCompatible(FullApplySite AI) {

// Returns the callee of an apply_inst if it is basically inlinable.
SILFunction *swift::getEligibleFunction(FullApplySite AI,
InlineSelection WhatToInline) {
InlineSelection WhatToInline,
IsSelfRecursiveAnalysis *SRA) {
SILFunction *Callee = AI.getReferencedFunctionOrNull();

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

// Inlining self-recursive functions into other functions can result
// in excessive code duplication since we run the inliner multiple
// times in our pipeline
//
// FIXME: This should be cached!
if (calleeIsSelfRecursive(Callee)) {
// times in our pipeline.
if (SRA->get(Callee)->get()) {
return nullptr;
}

Expand Down