| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py |
| ; RUN: opt -S -passes='early-cse' -earlycse-debug-hash < %s | FileCheck %s --check-prefixes=CHECK,NO_ASSUME |
| ; RUN: opt -S -passes='early-cse<memssa>' < %s | FileCheck %s --check-prefixes=CHECK,NO_ASSUME |
| ; RUN: opt -S -passes='early-cse<memssa>' --enable-knowledge-retention < %s | FileCheck %s --check-prefixes=CHECK,USE_ASSUME |
| |
| declare void @clobber_and_use(i32) |
| |
| define void @f_0(ptr %ptr) { |
| ; NO_ASSUME-LABEL: @f_0( |
| ; NO_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @f_0( |
| ; USE_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[PTR]], i64 4), "nonnull"(ptr [[PTR]]), "align"(ptr [[PTR]], i64 4) ] |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| |
| %val0 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| %val1 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val1) |
| %val2 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val2) |
| ret void |
| } |
| |
| define void @f_1(ptr %ptr) { |
| ; We can forward invariant loads to non-invariant loads. |
| ; NO_ASSUME-LABEL: @f_1( |
| ; NO_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @f_1( |
| ; USE_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[PTR]], i64 4), "nonnull"(ptr [[PTR]]), "align"(ptr [[PTR]], i64 4) ] |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| |
| %val0 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| %val1 = load i32, ptr %ptr |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| } |
| |
| define void @f_2(ptr %ptr) { |
| ; We can forward a non-invariant load into an invariant load. |
| ; NO_ASSUME-LABEL: @f_2( |
| ; NO_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4 |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @f_2( |
| ; USE_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4 |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[PTR]], i64 4), "nonnull"(ptr [[PTR]]), "align"(ptr [[PTR]], i64 4) ] |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| |
| %val0 = load i32, ptr %ptr |
| call void @clobber_and_use(i32 %val0) |
| %val1 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| } |
| |
| define void @f_3(i1 %cond, ptr %ptr) { |
| ; NO_ASSUME-LABEL: @f_3( |
| ; NO_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: br i1 [[COND:%.*]], label [[LEFT:%.*]], label [[RIGHT:%.*]] |
| ; NO_ASSUME: left: |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; NO_ASSUME: right: |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @f_3( |
| ; USE_ASSUME-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: br i1 [[COND:%.*]], label [[LEFT:%.*]], label [[RIGHT:%.*]] |
| ; USE_ASSUME: left: |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[PTR]], i64 4), "nonnull"(ptr [[PTR]]), "align"(ptr [[PTR]], i64 4) ] |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; USE_ASSUME-NEXT: ret void |
| ; USE_ASSUME: right: |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| %val0 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| br i1 %cond, label %left, label %right |
| |
| |
| left: |
| %val1 = load i32, ptr %ptr |
| call void @clobber_and_use(i32 %val1) |
| ret void |
| |
| right: |
| ret void |
| } |
| |
| define void @f_4(i1 %cond, ptr %ptr) { |
| ; Negative test -- can't forward %val0 to %va1 because that'll break |
| ; def-dominates-use. |
| ; CHECK-LABEL: @f_4( |
| ; CHECK-NEXT: br i1 [[COND:%.*]], label [[LEFT:%.*]], label [[MERGE:%.*]] |
| ; CHECK: left: |
| ; CHECK-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR:%.*]], align 4, !invariant.load !0 |
| ; CHECK-NEXT: call void @clobber_and_use(i32 [[VAL0]]) |
| ; CHECK-NEXT: br label [[MERGE]] |
| ; CHECK: merge: |
| ; CHECK-NEXT: [[VAL1:%.*]] = load i32, ptr [[PTR]], align 4 |
| ; CHECK-NEXT: call void @clobber_and_use(i32 [[VAL1]]) |
| ; CHECK-NEXT: ret void |
| ; |
| br i1 %cond, label %left, label %merge |
| |
| left: |
| |
| %val0 = load i32, ptr %ptr, !invariant.load !{} |
| call void @clobber_and_use(i32 %val0) |
| br label %merge |
| |
| merge: |
| |
| %val1 = load i32, ptr %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(ptr %p) { |
| ; NO_ASSUME-LABEL: @test_dse1( |
| ; NO_ASSUME-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4, !invariant.load !0 |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @test_dse1( |
| ; USE_ASSUME-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4, !invariant.load !0 |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 4), "nonnull"(ptr [[P]]), "align"(ptr [[P]], i64 4) ] |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| %v1 = load i32, ptr %p, !invariant.load !{} |
| call void @clobber_and_use(i32 %v1) |
| store i32 %v1, ptr %p |
| ret void |
| } |
| |
| ; By assumption, v1 must equal v2 (TODO) |
| define void @test_false_negative_dse2(ptr %p, i32 %v2) { |
| ; CHECK-LABEL: @test_false_negative_dse2( |
| ; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4, !invariant.load !0 |
| ; CHECK-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; CHECK-NEXT: store i32 [[V2:%.*]], ptr [[P]], align 4 |
| ; CHECK-NEXT: ret void |
| ; |
| %v1 = load i32, ptr %p, !invariant.load !{} |
| call void @clobber_and_use(i32 %v1) |
| store i32 %v2, ptr %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(ptr %p) { |
| ; NO_ASSUME-LABEL: @test_scope_start_without_load( |
| ; NO_ASSUME-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4 |
| ; NO_ASSUME-NEXT: [[ADD:%.*]] = add i32 [[V1]], [[V1]] |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[ADD]]) |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @test_scope_start_without_load( |
| ; USE_ASSUME-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4 |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 4), "nonnull"(ptr [[P]]), "align"(ptr [[P]], i64 4) ] |
| ; USE_ASSUME-NEXT: [[ADD:%.*]] = add i32 [[V1]], [[V1]] |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[ADD]]) |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| %v1 = load i32, ptr %p |
| %v2 = load i32, ptr %p, !invariant.load !{} |
| %add = add i32 %v1, %v2 |
| call void @clobber_and_use(i32 %add) |
| %v3 = load i32, ptr %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(ptr %p) { |
| ; NO_ASSUME-LABEL: @test_scope_restart( |
| ; NO_ASSUME-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4, !invariant.load !0 |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; NO_ASSUME-NEXT: [[ADD:%.*]] = add i32 [[V1]], [[V1]] |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[ADD]]) |
| ; NO_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; NO_ASSUME-NEXT: ret void |
| ; |
| ; USE_ASSUME-LABEL: @test_scope_restart( |
| ; USE_ASSUME-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4, !invariant.load !0 |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; USE_ASSUME-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 4), "nonnull"(ptr [[P]]), "align"(ptr [[P]], i64 4) ] |
| ; USE_ASSUME-NEXT: [[ADD:%.*]] = add i32 [[V1]], [[V1]] |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[ADD]]) |
| ; USE_ASSUME-NEXT: call void @clobber_and_use(i32 [[V1]]) |
| ; USE_ASSUME-NEXT: ret void |
| ; |
| %v1 = load i32, ptr %p, !invariant.load !{} |
| call void @clobber_and_use(i32 %v1) |
| %v2 = load i32, ptr %p, !invariant.load !{} |
| %add = add i32 %v1, %v2 |
| call void @clobber_and_use(i32 %add) |
| %v3 = load i32, ptr %p |
| call void @clobber_and_use(i32 %v3) |
| ret void |
| } |