[Attributor] Detect possibly unbounded cycles in functions
authoromarahmed1111 <omarpirate2010@yahoo.com>
Fri, 13 Mar 2020 15:30:36 +0000 (10:30 -0500)
committerJohannes Doerfert <johannes@jdoerfert.de>
Fri, 13 Mar 2020 16:17:33 +0000 (11:17 -0500)
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

llvm/include/llvm/Analysis/MustExecute.h
llvm/lib/Analysis/MustExecute.cpp
llvm/lib/Transforms/IPO/Attributor.cpp
llvm/test/Transforms/Attributor/read_write_returned_arguments_scc.ll
llvm/test/Transforms/Attributor/willreturn.ll

index 587ebcb..7a8c372 100644 (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.
index ec285c5..bea973e 100644 (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
index b8f6b21..a3ee90a 100644 (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,28 +2492,34 @@ 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;
   }
-  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);
+  // 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;
 }
 
 struct AAWillReturnImpl : public AAWillReturn {
@@ -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();
   }
 
index 82da96e..1ddde3a 100644 (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 #
index b1b68b3..f521293 100644 (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 }