| ; RUN: opt -S -early-cse < %s | FileCheck %s |
| ; RUN: opt -S -basicaa -early-cse-memssa < %s | FileCheck %s |
| |
| declare void @clobber_and_use(i32) |
| |
| define void @f_0(i32* %ptr) { |
| ; CHECK-LABEL: @f_0( |
| ; CHECK: %val0 = load i32, i32* %ptr, !invariant.load !0 |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| ; CHECK: ret void |
| |
| %val0 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| %val1 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val1) |
| %val2 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val2) |
| ret void |
| } |
| |
| define void @f_1(i32* %ptr) { |
| ; We can forward invariant loads to non-invariant loads. |
| |
| ; CHECK-LABEL: @f_1( |
| ; CHECK: %val0 = load i32, i32* %ptr, !invariant.load !0 |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| |
| %val0 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| %val1 = load i32, i32* %ptr |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| } |
| |
| define void @f_2(i32* %ptr) { |
| ; We can forward a non-invariant load into an invariant load. |
| |
| ; CHECK-LABEL: @f_2( |
| ; CHECK: %val0 = load i32, i32* %ptr |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| ; CHECK: call void @clobber_and_use(i32 %val0) |
| |
| %val0 = load i32, i32* %ptr |
| call void @clobber_and_use(i32 %val0) |
| %val1 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| } |
| |
| define void @f_3(i1 %cond, i32* %ptr) { |
| ; CHECK-LABEL: @f_3( |
| %val0 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| br i1 %cond, label %left, label %right |
| |
| ; CHECK: %val0 = load i32, i32* %ptr, !invariant.load !0 |
| ; CHECK: left: |
| ; CHECK-NEXT: call void @clobber_and_use(i32 %val0) |
| |
| left: |
| %val1 = load i32, i32* %ptr |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| |
| right: |
| ret void |
| } |
| |
| define void @f_4(i1 %cond, i32* %ptr) { |
| ; Negative test -- can't forward %val0 to %va1 because that'll break |
| ; def-dominates-use. |
| |
| ; CHECK-LABEL: @f_4( |
| br i1 %cond, label %left, label %merge |
| |
| left: |
| ; CHECK: left: |
| ; CHECK-NEXT: %val0 = load i32, i32* %ptr, !invariant.load ! |
| ; CHECK-NEXT: call void @clobber_and_use(i32 %val0) |
| |
| %val0 = load i32, i32* %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| br label %merge |
| |
| merge: |
| ; CHECK: merge: |
| ; CHECK-NEXT: %val1 = load i32, i32* %ptr |
| ; CHECK-NEXT: call void @clobber_and_use(i32 %val1) |
| |
| %val1 = load i32, i32* %ptr |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| } |
| |
| ; By assumption, the call can't change contents of p |
| ; LangRef is a bit unclear about whether the store is reachable, so |
| ; for the moment we chose to be conservative and just assume it's valid |
| ; to restore the same unchanging value. |
| define void @test_dse1(i32* %p) { |
| ; CHECK-LABEL: @test_dse1 |
| ; CHECK-NOT: store |
| %v1 = load i32, i32* %p, !invariant.load !{} |
| call void @clobber_and_use(i32 %v1) |
| store i32 %v1, i32* %p |
| ret void |
| } |
| |
| ; By assumption, v1 must equal v2 (TODO) |
| define void @test_false_negative_dse2(i32* %p, i32 %v2) { |
| ; CHECK-LABEL: @test_false_negative_dse2 |
| ; CHECK: store |
| %v1 = load i32, i32* %p, !invariant.load !{} |
| call void @clobber_and_use(i32 %v1) |
| store i32 %v2, i32* %p |
| ret void |
| } |
| |
| ; If we remove the load, we still start an invariant scope since |
| ; it lets us remove later loads not explicitly marked invariant |
| define void @test_scope_start_without_load(i32* %p) { |
| ; CHECK-LABEL: @test_scope_start_without_load |
| ; CHECK: %v1 = load i32, i32* %p |
| ; CHECK: %add = add i32 %v1, %v1 |
| ; CHECK: call void @clobber_and_use(i32 %add) |
| ; CHECK: call void @clobber_and_use(i32 %v1) |
| ; CHECK: ret void |
| %v1 = load i32, i32* %p |
| %v2 = load i32, i32* %p, !invariant.load !{} |
| %add = add i32 %v1, %v2 |
| call void @clobber_and_use(i32 %add) |
| %v3 = load i32, i32* %p |
| call void @clobber_and_use(i32 %v3) |
| ret void |
| } |
| |
| ; If we already have an invariant scope, don't want to start a new one |
| ; with a potentially greater generation. This hides the earlier invariant |
| ; load |
| define void @test_scope_restart(i32* %p) { |
| ; CHECK-LABEL: @test_scope_restart |
| ; CHECK: %v1 = load i32, i32* %p |
| ; CHECK: call void @clobber_and_use(i32 %v1) |
| ; CHECK: %add = add i32 %v1, %v1 |
| ; CHECK: call void @clobber_and_use(i32 %add) |
| ; CHECK: call void @clobber_and_use(i32 %v1) |
| ; CHECK: ret void |
| %v1 = load i32, i32* %p, !invariant.load !{} |
| call void @clobber_and_use(i32 %v1) |
| %v2 = load i32, i32* %p, !invariant.load !{} |
| %add = add i32 %v1, %v2 |
| call void @clobber_and_use(i32 %add) |
| %v3 = load i32, i32* %p |
| call void @clobber_and_use(i32 %v3) |
| ret void |
| } |