| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py |
| ; RUN: opt -passes=instcombine -S < %s | FileCheck %s |
| |
| declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) |
| declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) |
| |
| declare void @unknown() |
| declare void @f(ptr) |
| declare void @f2(ptr, ptr) |
| declare ptr @f3(ptr, ptr) |
| |
| ; Basic case for DSEing a trivially dead writing call |
| define void @test_dead() { |
| ; CHECK-LABEL: @test_dead( |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn |
| ret void |
| } |
| |
| ; Add in canonical lifetime intrinsics |
| define void @test_lifetime() { |
| ; CHECK-LABEL: @test_lifetime( |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @llvm.lifetime.start.p0(i64 4, ptr %a) |
| call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn |
| call void @llvm.lifetime.end.p0(i64 4, ptr %a) |
| ret void |
| } |
| |
| ; Add some unknown calls just to point out that this is use based, not |
| ; instruction order sensitive |
| define void @test_lifetime2() { |
| ; CHECK-LABEL: @test_lifetime2( |
| ; CHECK-NEXT: call void @unknown() |
| ; CHECK-NEXT: call void @unknown() |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @llvm.lifetime.start.p0(i64 4, ptr %a) |
| call void @unknown() |
| call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn |
| call void @unknown() |
| call void @llvm.lifetime.end.p0(i64 4, ptr %a) |
| ret void |
| } |
| |
| ; As long as the result is unused, we can even remove reads of the alloca |
| ; itself since the write will be dropped. |
| define void @test_dead_readwrite() { |
| ; CHECK-LABEL: @test_dead_readwrite( |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @f(ptr nocapture %a) argmemonly nounwind willreturn |
| ret void |
| } |
| |
| define i32 @test_neg_read_after() { |
| ; CHECK-LABEL: @test_neg_read_after( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: call void @f(ptr nocapture nonnull writeonly [[A]]) #[[ATTR3:[0-9]+]] |
| ; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[A]], align 4 |
| ; CHECK-NEXT: ret i32 [[RES]] |
| ; |
| %a = alloca i32, align 4 |
| call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn |
| %res = load i32, ptr %a |
| ret i32 %res |
| } |
| |
| |
| define void @test_neg_infinite_loop() { |
| ; CHECK-LABEL: @test_neg_infinite_loop( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: call void @f(ptr nocapture nonnull writeonly [[A]]) #[[ATTR4:[0-9]+]] |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @f(ptr writeonly nocapture %a) argmemonly nounwind |
| ret void |
| } |
| |
| define void @test_neg_throw() { |
| ; CHECK-LABEL: @test_neg_throw( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: call void @f(ptr nocapture nonnull writeonly [[A]]) #[[ATTR5:[0-9]+]] |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @f(ptr writeonly nocapture %a) argmemonly willreturn |
| ret void |
| } |
| |
| define void @test_neg_extra_write() { |
| ; CHECK-LABEL: @test_neg_extra_write( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: call void @f(ptr nocapture nonnull writeonly [[A]]) #[[ATTR6:[0-9]+]] |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @f(ptr writeonly nocapture %a) nounwind willreturn |
| ret void |
| } |
| |
| ; In this case, we can't remove a1 because we need to preserve the write to |
| ; a2, and if we leave the call around, we need memory to pass to the first arg. |
| define void @test_neg_unmodeled_write() { |
| ; CHECK-LABEL: @test_neg_unmodeled_write( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: [[A2:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: call void @f2(ptr nocapture nonnull writeonly [[A]], ptr nonnull [[A2]]) #[[ATTR3]] |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| %a2 = alloca i32, align 4 |
| call void @f2(ptr nocapture writeonly %a, ptr %a2) argmemonly nounwind willreturn |
| ret void |
| } |
| |
| define i32 @test_neg_captured_by_call() { |
| ; CHECK-LABEL: @test_neg_captured_by_call( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: [[A2:%.*]] = alloca ptr, align 4 |
| ; CHECK-NEXT: call void @f2(ptr nonnull writeonly [[A]], ptr nonnull [[A2]]) #[[ATTR3]] |
| ; CHECK-NEXT: [[A_COPY_CAST:%.*]] = load ptr, ptr [[A2]], align 8 |
| ; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[A_COPY_CAST]], align 4 |
| ; CHECK-NEXT: ret i32 [[RES]] |
| ; |
| %a = alloca i32, align 4 |
| %a2 = alloca ptr, align 4 |
| call void @f2(ptr writeonly %a, ptr %a2) argmemonly nounwind willreturn |
| %a_copy_cast = load ptr, ptr %a2 |
| %res = load i32, ptr %a_copy_cast |
| ret i32 %res |
| } |
| |
| define i32 @test_neg_captured_before() { |
| ; CHECK-LABEL: @test_neg_captured_before( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: call void @f(ptr nocapture nonnull writeonly [[A]]) #[[ATTR3]] |
| ; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[A]], align 4 |
| ; CHECK-NEXT: ret i32 [[RES]] |
| ; |
| %a = alloca i32, align 4 |
| %a2 = alloca ptr, align 4 |
| store ptr %a, ptr %a2 |
| call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn |
| %a_copy_cast = load ptr, ptr %a2 |
| %res = load i32, ptr %a_copy_cast |
| ret i32 %res |
| } |
| |
| ; Show that reading from unrelated memory is okay |
| define void @test_unreleated_read() { |
| ; CHECK-LABEL: @test_unreleated_read( |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| %a2 = alloca i32, align 4 |
| call void @f2(ptr nocapture writeonly %a, ptr nocapture readonly %a2) argmemonly nounwind willreturn |
| ret void |
| } |
| |
| ; Removing a capture is also okay. The capture can only be in the return value |
| ; (which is unused) or written into the dead out parameter. |
| define void @test_unrelated_capture() { |
| ; CHECK-LABEL: @test_unrelated_capture( |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| %a2 = alloca i32, align 4 |
| call ptr @f3(ptr nocapture writeonly %a, ptr readonly %a2) argmemonly nounwind willreturn |
| ret void |
| } |
| |
| ; Cannot remove call, as %a2 is captured via the return value. |
| define i8 @test_neg_unrelated_capture_used_via_return() { |
| ; CHECK-LABEL: @test_neg_unrelated_capture_used_via_return( |
| ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: [[A2:%.*]] = alloca i32, align 4 |
| ; CHECK-NEXT: [[CAPTURE:%.*]] = call ptr @f3(ptr nocapture nonnull writeonly [[A]], ptr nonnull readonly [[A2]]) #[[ATTR3]] |
| ; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[CAPTURE]], align 1 |
| ; CHECK-NEXT: ret i8 [[V]] |
| ; |
| %a = alloca i32, align 4 |
| %a2 = alloca i32, align 4 |
| %capture = call ptr @f3(ptr nocapture writeonly %a, ptr readonly %a2) argmemonly nounwind willreturn |
| %v = load i8, ptr %capture |
| ret i8 %v |
| } |
| |
| ; As long as the result is unused, we can even remove reads of the alloca |
| ; itself since the write will be dropped. |
| define void @test_self_read() { |
| ; CHECK-LABEL: @test_self_read( |
| ; CHECK-NEXT: ret void |
| ; |
| %a = alloca i32, align 4 |
| call void @f2(ptr nocapture writeonly %a, ptr nocapture readonly %a) argmemonly nounwind willreturn |
| ret void |
| } |
| |
| |
| declare void @removable_readnone() readnone nounwind willreturn |
| declare void @removable_ro() readonly nounwind willreturn |
| |
| define void @test_readnone() { |
| ; CHECK-LABEL: @test_readnone( |
| ; CHECK-NEXT: ret void |
| ; |
| call void @removable_readnone() |
| ret void |
| } |
| |
| define void @test_readnone_with_deopt() { |
| ; CHECK-LABEL: @test_readnone_with_deopt( |
| ; CHECK-NEXT: ret void |
| ; |
| call void @removable_readnone() [ "deopt"() ] |
| ret void |
| } |
| |
| define void @test_readonly() { |
| ; CHECK-LABEL: @test_readonly( |
| ; CHECK-NEXT: ret void |
| ; |
| call void @removable_ro() |
| ret void |
| } |
| |
| define void @test_readonly_with_deopt() { |
| ; CHECK-LABEL: @test_readonly_with_deopt( |
| ; CHECK-NEXT: ret void |
| ; |
| call void @removable_ro() [ "deopt"() ] |
| ret void |
| } |