Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
13 changes: 13 additions & 0 deletions llvm/include/llvm/Transforms/IPO/FunctionAttrs.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ class ReversePostOrderFunctionAttrsPass
LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};

/// Additional 'norecurse' attribute deduction during postlink LTO phase.
///
/// This is a module pass that infers 'norecurse' attribute on functions.
/// It runs during LTO and analyzes the module's call graph to find functions
/// that are guaranteed not to call themselves, either directly or indirectly.
/// The pass uses a module-wide flag which checks if any function's address is
/// taken or any function in the module has external linkage, to safely handle
/// indirect and library function calls from current function.
class NoRecurseLTOInferencePass
: public PassInfoMixin<NoRecurseLTOInferencePass> {
public:
LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
};
} // end namespace llvm

#endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H
3 changes: 2 additions & 1 deletion llvm/lib/Passes/PassBuilderPipelines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1944,6 +1944,7 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
// is fixed.
MPM.addPass(WholeProgramDevirtPass(ExportSummary, nullptr));

MPM.addPass(NoRecurseLTOInferencePass());
// Stop here at -O1.
if (Level == OptimizationLevel::O1) {
// The LowerTypeTestsPass needs to run to lower type metadata and the
Expand Down Expand Up @@ -2355,4 +2356,4 @@ AAManager PassBuilder::buildDefaultAAPipeline() {
bool PassBuilder::isInstrumentedPGOUse() const {
return (PGOOpt && PGOOpt->Action == PGOOptions::IRUse) ||
!UseCtxProfile.empty();
}
}
1 change: 1 addition & 0 deletions llvm/lib/Passes/PassRegistry.def
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ MODULE_PASS("metarenamer", MetaRenamerPass())
MODULE_PASS("module-inline", ModuleInlinerPass())
MODULE_PASS("name-anon-globals", NameAnonGlobalPass())
MODULE_PASS("no-op-module", NoOpModulePass())
MODULE_PASS("norecurse-lto-inference", NoRecurseLTOInferencePass())
MODULE_PASS("nsan", NumericalStabilitySanitizerPass())
MODULE_PASS("openmp-opt", OpenMPOptPass())
MODULE_PASS("openmp-opt-postlink",
Expand Down
119 changes: 97 additions & 22 deletions llvm/lib/Transforms/IPO/FunctionAttrs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,36 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes,
AI.run(SCCNodes, Changed);
}

// Determines if the function 'F' can be marked 'norecurse'.
// It returns true if any call within 'F' could lead to a recursive
// call back to 'F', and false otherwise.
// The 'AnyFunctionsAddressIsTaken' parameter is a module-wide flag
// that is true if any function's address is taken, or if any function
// has external linkage. This is used to determine the safety of
// external/library calls.
static bool mayHaveRecursiveCallee(Function &F,
bool AnyFunctionsAddressIsTaken = true) {
for (const auto &BB : F) {
for (const auto &I : BB.instructionsWithoutDebug()) {
if (const auto *CB = dyn_cast<CallBase>(&I)) {
const Function *Callee = CB->getCalledFunction();
if (!Callee || Callee == &F)
return true;

if (Callee->doesNotRecurse())
continue;

if (!AnyFunctionsAddressIsTaken ||
(Callee->isDeclaration() &&
Callee->hasFnAttribute(Attribute::NoCallback)))
continue;
return true;
}
}
}
return false;
}

static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
SmallPtrSet<Function *, 8> &Changed) {
// Try and identify functions that do not recurse.
Expand All @@ -2078,28 +2108,14 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
Function *F = *SCCNodes.begin();
if (!F || !F->hasExactDefinition() || F->doesNotRecurse())
return;

// If all of the calls in F are identifiable and are to norecurse functions, F
// is norecurse. This check also detects self-recursion as F is not currently
// marked norecurse, so any called from F to F will not be marked norecurse.
for (auto &BB : *F)
for (auto &I : BB.instructionsWithoutDebug())
if (auto *CB = dyn_cast<CallBase>(&I)) {
Function *Callee = CB->getCalledFunction();
if (!Callee || Callee == F ||
(!Callee->doesNotRecurse() &&
!(Callee->isDeclaration() &&
Callee->hasFnAttribute(Attribute::NoCallback))))
// Function calls a potentially recursive function.
return;
}

// Every call was to a non-recursive function other than this function, and
// we have no indirect recursion as the SCC size is one. This function cannot
// recurse.
F->setDoesNotRecurse();
++NumNoRecurse;
Changed.insert(F);
if (!mayHaveRecursiveCallee(*F)) {
// Every call was to a non-recursive function other than this function, and
// we have no indirect recursion as the SCC size is one. This function
// cannot recurse.
F->setDoesNotRecurse();
++NumNoRecurse;
Changed.insert(F);
}
}

// Set the noreturn function attribute if possible.
Expand Down Expand Up @@ -2429,3 +2445,62 @@ ReversePostOrderFunctionAttrsPass::run(Module &M, ModuleAnalysisManager &AM) {
PA.preserve<LazyCallGraphAnalysis>();
return PA;
}

PreservedAnalyses NoRecurseLTOInferencePass::run(Module &M,
ModuleAnalysisManager &MAM) {

// Check if any function in the whole program has its address taken or has
// potentially external linkage.
// We use this information when inferring norecurse attribute: If there is
// no function whose address is taken and all functions have internal
// linkage, there is no path for a callback to any user function.
bool AnyFunctionsAddressIsTaken = false;
for (Function &F : M) {
if (F.isDeclaration() || F.doesNotRecurse())
continue;
if (!F.hasLocalLinkage() || F.hasAddressTaken()) {
AnyFunctionsAddressIsTaken = true;
break;
}
}

// Run norecurse inference on all RefSCCs in the LazyCallGraph for this
// module.
bool Changed = false;
LazyCallGraph &CG = MAM.getResult<LazyCallGraphAnalysis>(M);
CG.buildRefSCCs();

for (LazyCallGraph::RefSCC &RC : CG.postorder_ref_sccs()) {
// Skip any RefSCC that is part of a call cycle. A RefSCC containing more
// than one SCC indicates a recursive relationship, which could involve
// direct or indirect calls.
if (RC.size() > 1)
continue;

// A single-SCC RefSCC could still be a self-loop.
LazyCallGraph::SCC &S = *RC.begin();
if (S.size() > 1)
continue;

// Get the single function from this SCC.
Function &F = S.begin()->getFunction();
if (!F.hasExactDefinition() || F.doesNotRecurse())
continue;

// If the analysis confirms that this function has no recursive calls
// (either direct, indirect, or through external linkages),
// we can safely apply the norecurse attribute.
if (!mayHaveRecursiveCallee(F, AnyFunctionsAddressIsTaken)) {
F.setDoesNotRecurse();
++NumNoRecurse;
Changed = true;
}
}

PreservedAnalyses PA;
if (Changed)
PA.preserve<LazyCallGraphAnalysis>();
else
PA = PreservedAnalyses::all();
return PA;
}
1 change: 1 addition & 0 deletions llvm/test/Other/new-pm-lto-defaults.ll
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
; CHECK-O1-NEXT: Running analysis: TargetLibraryAnalysis
; CHECK-O-NEXT: Running pass: GlobalSplitPass
; CHECK-O-NEXT: Running pass: WholeProgramDevirtPass
; CHECK-O-NEXT: Running pass: NoRecurseLTOInferencePass
; CHECK-O23SZ-NEXT: Running pass: CoroEarlyPass
; CHECK-O1-NEXT: Running pass: LowerTypeTestsPass
; CHECK-O23SZ-NEXT: Running pass: GlobalOptPass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5
; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s

; This test includes a call to a library function which is not marked as
; NoCallback. Function bob() does not have internal linkage and hence prevents
; norecurse to be added.

@.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1

;.
; CHECK: @.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1
;.
define dso_local void @bob() {
; CHECK-LABEL: define dso_local void @bob() {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[CALL:%.*]] = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str)
; CHECK-NEXT: ret void
;
entry:
%call = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str)
ret void
}

declare i32 @printf(ptr readonly captures(none), ...)

define dso_local i32 @main() norecurse {
; CHECK: Function Attrs: norecurse
; CHECK-LABEL: define dso_local i32 @main(
; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: tail call void @bob()
; CHECK-NEXT: ret i32 0
;
entry:
tail call void @bob()
ret i32 0
}
;.
; CHECK: attributes #[[ATTR0]] = { norecurse }
;.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5
; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s

; This test includes a call to a library function which is not marked as
; NoCallback. All functions except main() are internal and main is marked
; norecurse, so as to not block norecurse to be added to bob().

@.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1

; Function Attrs: nofree noinline nounwind uwtable
;.
; CHECK: @.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1
;.
define internal void @bob() {
; CHECK: Function Attrs: norecurse
; CHECK-LABEL: define internal void @bob(
; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[CALL:%.*]] = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str)
; CHECK-NEXT: ret void
;
entry:
%call = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str)
ret void
}

; Function Attrs: nofree nounwind
declare i32 @printf(ptr readonly captures(none), ...)

; Function Attrs: nofree norecurse nounwind uwtable
define dso_local i32 @main() norecurse {
; CHECK: Function Attrs: norecurse
; CHECK-LABEL: define dso_local i32 @main(
; CHECK-SAME: ) #[[ATTR0]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: tail call void @bob()
; CHECK-NEXT: ret i32 0
;
entry:
tail call void @bob()
ret i32 0
}
;.
; CHECK: attributes #[[ATTR0]] = { norecurse }
;.
Loading