| ; RUN: opt -attributor --attributor-disable=false -S < %s | FileCheck %s |
| |
| declare void @no_return_call() nofree noreturn nounwind readnone |
| |
| declare void @normal_call() readnone |
| |
| declare i32 @foo() |
| |
| declare i32 @foo_noreturn_nounwind() noreturn nounwind |
| |
| declare i32 @foo_noreturn() noreturn |
| |
| declare i32 @bar() nosync readnone |
| |
| ; CHECK: Function Attrs: nofree norecurse nounwind uwtable willreturn |
| define i32 @volatile_load(i32*) norecurse nounwind uwtable { |
| %2 = load volatile i32, i32* %0, align 4 |
| ret i32 %2 |
| } |
| |
| ; CHECK: Function Attrs: nofree norecurse nosync nounwind uwtable willreturn |
| ; CHECK-NEXT: define internal i32 @internal_load(i32* nonnull %0) |
| define internal i32 @internal_load(i32*) norecurse nounwind uwtable { |
| %2 = load i32, i32* %0, align 4 |
| ret i32 %2 |
| } |
| ; TEST 1: Only first block is live. |
| |
| ; CHECK: Function Attrs: nofree noreturn nosync nounwind |
| ; CHECK-NEXT: define i32 @first_block_no_return(i32 %a, i32* nonnull %ptr1, i32* %ptr2) |
| define i32 @first_block_no_return(i32 %a, i32* nonnull %ptr1, i32* %ptr2) #0 { |
| entry: |
| call i32 @internal_load(i32* %ptr1) |
| ; CHECK: call i32 @internal_load(i32* nonnull %ptr1) |
| call void @no_return_call() |
| ; CHECK: call void @no_return_call() |
| ; CHECK-NEXT: unreachable |
| %cmp = icmp eq i32 %a, 0 |
| br i1 %cmp, label %cond.true, label %cond.false |
| |
| cond.true: ; preds = %entry |
| call i32 @internal_load(i32* %ptr2) |
| ; CHECK: call i32 @internal_load(i32* %ptr2) |
| %load = call i32 @volatile_load(i32* %ptr1) |
| call void @normal_call() |
| %call = call i32 @foo() |
| br label %cond.end |
| |
| cond.false: ; preds = %entry |
| call void @normal_call() |
| %call1 = call i32 @bar() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.false, %cond.true |
| %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ] |
| ret i32 %cond |
| } |
| |
| ; TEST 2: cond.true is dead, but cond.end is not, since cond.false is live |
| |
| ; This is just an example. For example we can put a sync call in a |
| ; dead block and check if it is deduced. |
| |
| ; CHECK: Function Attrs: nosync |
| ; CHECK-NEXT: define i32 @dead_block_present(i32 %a, i32* %ptr1) |
| define i32 @dead_block_present(i32 %a, i32* %ptr1) #0 { |
| entry: |
| %cmp = icmp eq i32 %a, 0 |
| br i1 %cmp, label %cond.true, label %cond.false |
| |
| cond.true: ; preds = %entry |
| call void @no_return_call() |
| ; CHECK: call void @no_return_call() |
| ; CHECK-NEXT: unreachable |
| %call = call i32 @volatile_load(i32* %ptr1) |
| br label %cond.end |
| |
| cond.false: ; preds = %entry |
| call void @normal_call() |
| %call1 = call i32 @bar() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.false, %cond.true |
| %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ] |
| ret i32 %cond |
| } |
| |
| ; TEST 3: both cond.true and cond.false are dead, therfore cond.end is dead as well. |
| |
| define i32 @all_dead(i32 %a) #0 { |
| entry: |
| %cmp = icmp eq i32 %a, 0 |
| br i1 %cmp, label %cond.true, label %cond.false |
| |
| cond.true: ; preds = %entry |
| call void @no_return_call() |
| ; CHECK: call void @no_return_call() |
| ; CHECK-NEXT: unreachable |
| %call = call i32 @foo() |
| br label %cond.end |
| |
| cond.false: ; preds = %entry |
| call void @no_return_call() |
| ; CHECK: call void @no_return_call() |
| ; CHECK-NEXT: unreachable |
| %call1 = call i32 @bar() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.false, %cond.true |
| %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ] |
| ret i32 %cond |
| } |
| |
| declare i32 @__gxx_personality_v0(...) |
| |
| ; TEST 4: All blocks are live. |
| |
| ; CHECK: define i32 @all_live(i32 %a) |
| define i32 @all_live(i32 %a) #0 { |
| entry: |
| %cmp = icmp eq i32 %a, 0 |
| br i1 %cmp, label %cond.true, label %cond.false |
| |
| cond.true: ; preds = %entry |
| call void @normal_call() |
| %call = call i32 @foo_noreturn() |
| br label %cond.end |
| |
| cond.false: ; preds = %entry |
| call void @normal_call() |
| %call1 = call i32 @bar() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.false, %cond.true |
| %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ] |
| ret i32 %cond |
| } |
| |
| ; TEST 5 noreturn invoke instruction with a unreachable normal successor block. |
| |
| ; CHECK: define i32 @invoke_noreturn(i32 %a) |
| define i32 @invoke_noreturn(i32 %a) personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { |
| entry: |
| %cmp = icmp eq i32 %a, 0 |
| br i1 %cmp, label %cond.true, label %cond.false |
| |
| cond.true: ; preds = %entry |
| call void @normal_call() |
| %call = invoke i32 @foo_noreturn() to label %continue |
| unwind label %cleanup |
| ; CHECK: %call = invoke i32 @foo_noreturn() |
| ; CHECK-NEXT: to label %continue unwind label %cleanup |
| |
| cond.false: ; preds = %entry |
| call void @normal_call() |
| %call1 = call i32 @bar() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.false, %continue |
| %cond = phi i32 [ %call, %continue ], [ %call1, %cond.false ] |
| ret i32 %cond |
| |
| continue: |
| ; CHECK: continue: |
| ; CHECK-NEXT: unreachable |
| br label %cond.end |
| |
| cleanup: |
| %res = landingpad { i8*, i32 } |
| catch i8* null |
| ret i32 0 |
| } |
| |
| ; TEST 4.1 noreturn invoke instruction replaced by a call and an unreachable instruction |
| ; put after it. |
| |
| ; CHECK: define i32 @invoke_noreturn_nounwind(i32 %a) |
| define i32 @invoke_noreturn_nounwind(i32 %a) personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { |
| entry: |
| %cmp = icmp eq i32 %a, 0 |
| br i1 %cmp, label %cond.true, label %cond.false |
| |
| cond.true: ; preds = %entry |
| call void @normal_call() |
| %call = invoke i32 @foo_noreturn_nounwind() to label %continue |
| unwind label %cleanup |
| ; CHECK: call void @normal_call() |
| ; CHECK-NEXT: call i32 @foo_noreturn_nounwind() |
| ; CHECK-NEXT: unreachable |
| |
| ; We keep the invoke around as other attributes might have references to it. |
| ; CHECK: cond.true.split: ; No predecessors! |
| ; CHECK-NEXT: invoke i32 @foo_noreturn_nounwind() |
| |
| cond.false: ; preds = %entry |
| call void @normal_call() |
| %call1 = call i32 @bar() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.false, %continue |
| %cond = phi i32 [ %call, %continue ], [ %call1, %cond.false ] |
| ret i32 %cond |
| |
| continue: |
| br label %cond.end |
| |
| cleanup: |
| %res = landingpad { i8*, i32 } |
| catch i8* null |
| ret i32 0 |
| } |
| |
| ; TEST 6: Undefined behvior, taken from LangRef. |
| ; FIXME: Should be able to detect undefined behavior. |
| |
| ; CHECK: define void @ub(i32* %0) |
| define void @ub(i32* %0) { |
| %poison = sub nuw i32 0, 1 ; Results in a poison value. |
| %still_poison = and i32 %poison, 0 ; 0, but also poison. |
| %poison_yet_again = getelementptr i32, i32* %0, i32 %still_poison |
| store i32 0, i32* %poison_yet_again ; Undefined behavior due to store to poison. |
| ret void |
| } |
| |
| define void @inf_loop() #0 { |
| entry: |
| br label %while.body |
| |
| while.body: ; preds = %entry, %while.body |
| br label %while.body |
| } |
| |
| ; TEST 7: Infinite loop. |
| ; FIXME: Detect infloops, and mark affected blocks dead. |
| |
| define i32 @test5(i32, i32) #0 { |
| %3 = icmp sgt i32 %0, %1 |
| br i1 %3, label %cond.if, label %cond.elseif |
| |
| cond.if: ; preds = %2 |
| %4 = tail call i32 @bar() |
| br label %cond.end |
| |
| cond.elseif: ; preds = %2 |
| call void @inf_loop() |
| %5 = icmp slt i32 %0, %1 |
| br i1 %5, label %cond.end, label %cond.else |
| |
| cond.else: ; preds = %cond.elseif |
| %6 = tail call i32 @foo() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.if, %cond.else, %cond.elseif |
| %7 = phi i32 [ %1, %cond.elseif ], [ 0, %cond.else ], [ 0, %cond.if ] |
| ret i32 %7 |
| } |
| |
| define void @rec() #0 { |
| entry: |
| call void @rec() |
| ret void |
| } |
| |
| ; TEST 8: Recursion |
| ; FIXME: everything after first block should be marked dead |
| ; and unreachable should be put after call to @rec(). |
| |
| define i32 @test6(i32, i32) #0 { |
| call void @rec() |
| %3 = icmp sgt i32 %0, %1 |
| br i1 %3, label %cond.if, label %cond.elseif |
| |
| cond.if: ; preds = %2 |
| %4 = tail call i32 @bar() |
| br label %cond.end |
| |
| cond.elseif: ; preds = %2 |
| call void @rec() |
| %5 = icmp slt i32 %0, %1 |
| br i1 %5, label %cond.end, label %cond.else |
| |
| cond.else: ; preds = %cond.elseif |
| %6 = tail call i32 @foo() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.if, %cond.else, %cond.elseif |
| %7 = phi i32 [ %1, %cond.elseif ], [ 0, %cond.else ], [ 0, %cond.if ] |
| ret i32 %7 |
| } |
| ; TEST 9: Recursion |
| ; FIXME: contains recursive call to itself in cond.elseif block |
| |
| define i32 @test7(i32, i32) #0 { |
| %3 = icmp sgt i32 %0, %1 |
| br i1 %3, label %cond.if, label %cond.elseif |
| |
| cond.if: ; preds = %2 |
| %4 = tail call i32 @bar() |
| br label %cond.end |
| |
| cond.elseif: ; preds = %2 |
| %5 = tail call i32 @test7(i32 %0, i32 %1) |
| %6 = icmp slt i32 %0, %1 |
| br i1 %6, label %cond.end, label %cond.else |
| |
| cond.else: ; preds = %cond.elseif |
| %7 = tail call i32 @foo() |
| br label %cond.end |
| |
| cond.end: ; preds = %cond.if, %cond.else, %cond.elseif |
| %8 = phi i32 [ %1, %cond.elseif ], [ 0, %cond.else ], [ 0, %cond.if ] |
| ret i32 %8 |
| } |