[Attributor] Detect possibly unbounded cycles in functions

This patch add mayContainUnboundedCycle helper function which checks whether a function has any cycle which we don't know if it is bounded or not.
Loops with maximum trip count are considered bounded, any other cycle not.
It also contains some fixed tests and some added tests contain bounded and
unbounded loops and non-loop cycles.

Reviewed By: jdoerfert, uenoku, baziotis

Differential Revision: https://reviews.llvm.org/D74691
This commit is contained in:
omarahmed1111 2020-03-13 10:30:36 -05:00 committed by Johannes Doerfert
parent 5a5a075c5b
commit b285b333dc
5 changed files with 313 additions and 39 deletions

View File

@ -176,6 +176,8 @@ public:
virtual ~ICFLoopSafetyInfo() {};
};
bool mayContainIrreducibleControl(const Function &F, const LoopInfo *LI);
struct MustBeExecutedContextExplorer;
/// Enum that allows us to spell out the direction.

View File

@ -484,13 +484,13 @@ static bool maybeEndlessLoop(const Loop &L) {
return true;
}
static bool mayContainIrreducibleControl(const Function &F, const LoopInfo *LI) {
bool llvm::mayContainIrreducibleControl(const Function &F, const LoopInfo *LI) {
if (!LI)
return false;
using RPOTraversal = ReversePostOrderTraversal<const Function *>;
RPOTraversal FuncRPOT(&F);
return !containsIrreducibleCFG<const BasicBlock *, const RPOTraversal,
const LoopInfo>(FuncRPOT, *LI);
return containsIrreducibleCFG<const BasicBlock *, const RPOTraversal,
const LoopInfo>(FuncRPOT, *LI);
}
/// Lookup \p Key in \p Map and return the result, potentially after

View File

@ -28,6 +28,7 @@
#include "llvm/Analysis/LazyValueInfo.h"
#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/MemoryBuiltins.h"
#include "llvm/Analysis/MustExecute.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/Argument.h"
@ -36,9 +37,9 @@
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/NoFolder.h"
#include "llvm/IR/Verifier.h"
#include "llvm/InitializePasses.h"
#include "llvm/IR/NoFolder.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
@ -2491,30 +2492,36 @@ struct AAUndefinedBehaviorFunction final : AAUndefinedBehaviorImpl {
/// ------------------------ Will-Return Attributes ----------------------------
// Helper function that checks whether a function has any cycle.
// TODO: Replace with more efficent code
static bool containsCycle(Function &F) {
SmallPtrSet<BasicBlock *, 32> Visited;
// Traverse BB by dfs and check whether successor is already visited.
for (BasicBlock *BB : depth_first(&F)) {
Visited.insert(BB);
for (auto *SuccBB : successors(BB)) {
if (Visited.count(SuccBB))
// Helper function that checks whether a function has any cycle which we don't
// know if it is bounded or not.
// Loops with maximum trip count are considered bounded, any other cycle not.
static bool mayContainUnboundedCycle(Function &F, Attributor &A) {
ScalarEvolution *SE =
A.getInfoCache().getAnalysisResultForFunction<ScalarEvolutionAnalysis>(F);
LoopInfo *LI = A.getInfoCache().getAnalysisResultForFunction<LoopAnalysis>(F);
// If either SCEV or LoopInfo is not available for the function then we assume
// any cycle to be unbounded cycle.
// We use scc_iterator which uses Tarjan algorithm to find all the maximal
// SCCs.To detect if there's a cycle, we only need to find the maximal ones.
if (!SE || !LI) {
for (scc_iterator<Function *> SCCI = scc_begin(&F); !SCCI.isAtEnd(); ++SCCI)
if (SCCI.hasCycle())
return true;
}
return false;
}
// If there's irreducible control, the function may contain non-loop cycles.
if (mayContainIrreducibleControl(F, LI))
return true;
// Any loop that does not have a max trip count is considered unbounded cycle.
for (auto *L : LI->getLoopsInPreorder()) {
if (!SE->getSmallConstantMaxTripCount(L))
return true;
}
return false;
}
// Helper function that checks the function have a loop which might become an
// endless loop
// FIXME: Any cycle is regarded as endless loop for now.
// We have to allow some patterns.
static bool containsPossiblyEndlessLoop(Function *F) {
return containsCycle(*F);
}
struct AAWillReturnImpl : public AAWillReturn {
AAWillReturnImpl(const IRPosition &IRP) : AAWillReturn(IRP) {}
@ -2523,7 +2530,7 @@ struct AAWillReturnImpl : public AAWillReturn {
AAWillReturn::initialize(A);
Function *F = getAssociatedFunction();
if (!F || !A.isFunctionIPOAmendable(*F) || containsPossiblyEndlessLoop(F))
if (!F || !A.isFunctionIPOAmendable(*F) || mayContainUnboundedCycle(*F, A))
indicatePessimisticFixpoint();
}

View File

@ -101,7 +101,7 @@ return: ; preds = %if.end, %if.then
ret i32* %retval.0
}
; CHECK: Function Attrs: argmemonly nofree norecurse nosync nounwind
; CHECK: Function Attrs: argmemonly nofree norecurse nosync nounwind willreturn
; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* nofree readnone %n0, i32* nocapture nofree readonly %r0, i32* nofree returned writeonly "no-capture-maybe-returned" %w0)
define i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
entry:
@ -160,6 +160,7 @@ entry:
;
; CHECK-NOT: attributes #
; CHECK: attributes #{{.*}} = { argmemonly nofree nosync nounwind }
; CHECK: attributes #{{.*}} = { argmemonly nofree norecurse nosync nounwind }
; CHECK: attributes #{{.*}} = { argmemonly nofree norecurse nosync nounwind willreturn }
; CHECK: attributes #{{.*}} = { nosync nounwind }
; CHECK: attributes #{{.*}} = { norecurse nosync nounwind willreturn }
; CHECK-NOT: attributes #

View File

@ -87,9 +87,8 @@ define i32 @fact_maybe_not_halt(i32 %0) local_unnamed_addr #0 {
; return ans;
; }
; FIXME: missing willreturn
; ATTRIBUTOR_MODULE: Function Attrs: nofree noinline nosync nounwind readnone uwtable
; ATTRIBUTOR_CGSCC: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable
; ATTRIBUTOR_MODULE: Function Attrs: nofree noinline nosync nounwind readnone uwtable willreturn
; ATTRIBUTOR_CGSCC: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable willreturn
; ATTRIBUTOR-NEXT: define i32 @fact_loop(i32 %0) local_unnamed_addr
define i32 @fact_loop(i32 %0) local_unnamed_addr #0 {
%2 = icmp slt i32 %0, 1
@ -296,7 +295,7 @@ declare i32 @__gxx_personality_v0(...)
; TEST 11 (positive case)
; counstant trip count
; constant trip count
; int loop_constant_trip_count(int*p){
; int ans = 0;
; for(int i = 0;i<10;i++){
@ -305,9 +304,8 @@ declare i32 @__gxx_personality_v0(...)
; return ans;
; }
; FIXME: missing willreturn
; ATTRIBUTOR_MODULE: Function Attrs: argmemonly nofree noinline nosync nounwind readonly uwtable
; ATTRIBUTOR_CGSCC: Function Attrs: argmemonly nofree noinline norecurse nosync nounwind readonly uwtable
; ATTRIBUTOR_MODULE: Function Attrs: argmemonly nofree noinline nosync nounwind readonly uwtable willreturn
; ATTRIBUTOR_CGSCC: Function Attrs: argmemonly nofree noinline norecurse nosync nounwind readonly uwtable willreturn
; ATTRIBUTOR-NEXT: define i32 @loop_constant_trip_count(i32* nocapture nofree readonly %0)
define i32 @loop_constant_trip_count(i32* nocapture readonly %0) #0 {
br label %3
@ -374,9 +372,8 @@ define i32 @loop_trip_count_unbound(i32 %0, i32 %1, i32* nocapture readonly %2,
; }
; FIXME: missing willreturn
; ATTRIBUTOR_MODULE: Function Attrs: argmemonly nofree noinline nosync nounwind readonly uwtable
; ATTRIBUTOR_CGSCC: Function Attrs: argmemonly nofree noinline norecurse nosync nounwind readonly uwtable
; ATTRIBUTOR_MODULE: Function Attrs: argmemonly nofree noinline nosync nounwind readonly uwtable willreturn
; ATTRIBUTOR_CGSCC: Function Attrs: argmemonly nofree noinline norecurse nosync nounwind readonly uwtable willreturn
; ATTRIBUTOR-NEXT: define i32 @loop_trip_dec(i32 %0, i32* nocapture nofree readonly %1) local_unnamed_addr
define i32 @loop_trip_dec(i32 %0, i32* nocapture readonly %1) local_unnamed_addr #0 {
@ -434,9 +431,8 @@ unreachable_label:
unreachable
}
; FIXME: missing willreturn
; ATTRIBUTOR_MODULE: Function Attrs: nofree noinline nosync nounwind readnone uwtable
; ATTRIBUTOR_CGSCC: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable
; ATTRIBUTOR_MODULE: Function Attrs: nofree noinline nosync nounwind readnone uwtable willreturn
; ATTRIBUTOR_CGSCC: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable willreturn
; ATTRIBUTOR-NEXT: define i32 @unreachable_exit_positive2(i32 %0)
define i32 @unreachable_exit_positive2(i32) local_unnamed_addr #0 {
%2 = icmp slt i32 %0, 1
@ -504,5 +500,273 @@ define void @call_longjmp(i8* nocapture readnone %0) local_unnamed_addr #0 {
}
; TEST 16 (negative case)
; int infinite_loop_inside_bounded_loop(int n) {
; int ans = 0;
; for (int i = 0; i < n; i++) {
; while (1)
; ans++;
; }
; return ans;
; }
; ATTRIBUTOR_MODULE: Function Attrs: nofree nosync nounwind readnone
; ATTRIBUTOR_CGSCC: Function Attrs: nofree norecurse nosync nounwind readnone
; ATTRIBUTOR-NOT: willreturn
; ATTRIBUTOR-NEXT: define i32 @infinite_loop_inside_bounded_loop(i32 %n)
define i32 @infinite_loop_inside_bounded_loop(i32 %n) {
entry:
br label %for.cond
for.cond: ; preds = %for.inc, %entry
%cmp = icmp sgt i32 %n, 0
br i1 %cmp, label %for.body, label %for.cond.cleanup
for.cond.cleanup: ; preds = %for.cond
br label %for.end
for.body: ; preds = %for.cond
br label %while.cond
while.cond: ; preds = %while.body, %for.body
br label %while.body
while.body: ; preds = %while.cond
br label %while.cond
for.inc: ; No predecessors!
br label %for.cond
for.end: ; preds = %for.cond.cleanup
ret i32 0
}
; TEST 17 (positive case)
; Nested loops with constant max trip count
; int bounded_nested_loops(int n) {
; int ans = 0;
; for (int i = 0; i < n; i++) {
; while (n--) {
; ans++;
; }
; }
; return ans;
; }
; ATTRIBUTOR_MODULE: Function Attrs: nofree nosync nounwind readnone willreturn
; ATTRIBUTOR_CGSCC: Function Attrs: nofree norecurse nosync nounwind readnone willreturn
; ATTRIBUTOR-NEXT: define i32 @bounded_nested_loops(i32 %n)
define i32 @bounded_nested_loops(i32 %n) {
entry:
br label %for.cond
for.cond: ; preds = %for.inc, %entry
%i.0 = phi i32 [ 0, %entry ], [ %inc1, %for.inc ]
%ans.0 = phi i32 [ 0, %entry ], [ %tmp, %for.inc ]
%n.addr.0 = phi i32 [ %n, %entry ], [ -1, %for.inc ]
%cmp = icmp slt i32 %i.0, %n.addr.0
br i1 %cmp, label %for.body, label %for.cond.cleanup
for.cond.cleanup: ; preds = %for.cond
%ans.0.lcssa = phi i32 [ %ans.0, %for.cond ]
br label %for.end
for.body: ; preds = %for.cond
br label %while.cond
while.cond: ; preds = %while.body, %for.body
br i1 true, label %while.end, label %while.body
while.body: ; preds = %while.cond
br label %while.cond
while.end: ; preds = %while.cond
%tmp = add i32 %n.addr.0, %ans.0
br label %for.inc
for.inc: ; preds = %while.end
%inc1 = add nuw nsw i32 %i.0, 1
br label %for.cond
for.end: ; preds = %for.cond.cleanup
ret i32 %ans.0.lcssa
}
; TEST 18 (negative case)
; int bounded_loop_inside_unbounded_loop(int n) {
; int ans = 0;
; while (n++) {
; for (int i = 0; i < n; i++) {
; ans++;
; }
; }
; return ans;
; }
; ATTRIBUTOR_MODULE: Function Attrs: nofree nosync nounwind readnone
; ATTRIBUTOR_CGSCC: Function Attrs: nofree norecurse nosync nounwind readnone
; ATTRIBUTOR-NOT: willreturn
; ATTRIBUTOR-NEXT: define i32 @bounded_loop_inside_unbounded_loop(i32 %n)
define i32 @bounded_loop_inside_unbounded_loop(i32 %n) {
entry:
br label %while.cond
while.cond: ; preds = %for.end, %entry
%ans.0 = phi i32 [ 0, %entry ], [ %tmp2, %for.end ]
%n.addr.0 = phi i32 [ %n, %entry ], [ %inc, %for.end ]
%tmp = icmp sgt i32 %n.addr.0, -1
%smax = select i1 %tmp, i32 %n.addr.0, i32 -1
%inc = add nsw i32 %n.addr.0, 1
%tobool = icmp eq i32 %n.addr.0, 0
br i1 %tobool, label %while.end, label %while.body
while.body: ; preds = %while.cond
%tmp1 = add i32 %ans.0, 1
br label %for.cond
for.cond: ; preds = %for.inc, %while.body
br i1 true, label %for.cond.cleanup, label %for.body
for.cond.cleanup: ; preds = %for.cond
%tmp2 = add i32 %tmp1, %smax
br label %for.end
for.body: ; preds = %for.cond
br label %for.inc
for.inc: ; preds = %for.body
br label %for.cond
for.end: ; preds = %for.cond.cleanup
br label %while.cond
while.end: ; preds = %while.cond
%ans.0.lcssa = phi i32 [ %ans.0, %while.cond ]
ret i32 %ans.0.lcssa
}
; TEST 19 (negative case)
; int nested_unbounded_loops(int n) {
; int ans = 0;
; while (n--) {
; while (n--) {
; ans++;
; }
; while (n--) {
; ans++;
; }
; }
; return ans;
; }
; ATTRIBUTOR_MODULE: Function Attrs: nofree nosync nounwind readnone
; ATTRIBUTOR_CGSCC: Function Attrs: nofree norecurse nosync nounwind readnone
; ATTRIBUTOR-NOT: willreturn
; ATTRIBUTOR-NEXT: define i32 @nested_unbounded_loops(i32 %n)
define i32 @nested_unbounded_loops(i32 %n) {
entry:
br label %while.cond
while.cond: ; preds = %while.end10, %entry
%ans.0 = phi i32 [ 0, %entry ], [ %tmp1, %while.end10 ]
%n.addr.0 = phi i32 [ %n, %entry ], [ -1, %while.end10 ]
%tobool = icmp eq i32 %n.addr.0, 0
br i1 %tobool, label %while.end11, label %while.body
while.body: ; preds = %while.cond
br label %while.cond1
while.cond1: ; preds = %while.body4, %while.body
br i1 true, label %while.end, label %while.body4
while.body4: ; preds = %while.cond1
br label %while.cond1
while.end: ; preds = %while.cond1
%tmp = add i32 %n.addr.0, -2
br label %while.cond5
while.cond5: ; preds = %while.body8, %while.end
br i1 true, label %while.end10, label %while.body8
while.body8: ; preds = %while.cond5
br label %while.cond5
while.end10: ; preds = %while.cond5
%tmp1 = add i32 %tmp, %ans.0
br label %while.cond
while.end11: ; preds = %while.cond
%ans.0.lcssa = phi i32 [ %ans.0, %while.cond ]
ret i32 %ans.0.lcssa
}
; TEST 20 (negative case)
; void non_loop_cycle(int n) {
; if (fact_loop(n)>5)
; goto entry1;
; else
; goto entry2;
;
; entry1:
; if (fact_loop(n)>5)
; goto exit;
; else
; goto entry2;
; entry2:
; if (fact_loop(n)>5)
; goto exit;
; else
; goto entry1;
; exit:
; return;
; }
; ATTRIBUTOR_MODULE: Function Attrs: nofree nosync nounwind readnone
; ATTRIBUTOR_CGSCC: Function Attrs: nofree norecurse nosync nounwind readnone
; ATTRIBUTOR-NOT: willreturn
; ATTRIBUTOR-NEXT: define void @non_loop_cycle(i32 %n)
define void @non_loop_cycle(i32 %n) {
entry:
%call = call i32 @fact_loop(i32 %n)
%cmp = icmp sgt i32 %call, 5
br i1 %cmp, label %if.then, label %if.else
if.then: ; preds = %entry
br label %entry1
if.else: ; preds = %entry
br label %entry2
entry1: ; preds = %if.else8, %if.then
%call1 = call i32 @fact_loop(i32 %n)
%cmp2 = icmp sgt i32 %call1, 5
br i1 %cmp2, label %if.then3, label %if.else4
if.then3: ; preds = %entry1
br label %exit
if.else4: ; preds = %entry1
br label %entry2
entry2: ; preds = %if.else4, %if.else
%call5 = call i32 @fact_loop(i32 %n)
%cmp6 = icmp sgt i32 %call5, 5
br i1 %cmp6, label %if.then7, label %if.else8
if.then7: ; preds = %entry2
br label %exit
if.else8: ; preds = %entry2
br label %entry1
exit: ; preds = %if.then7, %if.then3
ret void
}
attributes #0 = { nounwind uwtable noinline }
attributes #1 = { uwtable noinline }