| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py |
| ; RUN: opt -S -passes=licm < %s | FileCheck %s --check-prefixes=CHECK,MT |
| ; RUN: opt -S -passes=licm -licm-force-thread-model-single < %s | FileCheck %s --check-prefixes=CHECK,ST |
| |
| @g = external global i32 |
| @c = external constant i32 |
| |
| declare void @capture(ptr) |
| |
| ; Even in single-thread mode, can only perform load-only promotion for globals, |
| ; because we might not have provenance to write to the global. See |
| ; promote_global_noalias for an example of the issue. |
| define void @promote_global(i1 %c, i1 %c2) { |
| ; CHECK-LABEL: @promote_global( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[G_PROMOTED:%.*]] = load i32, ptr @g, align 4 |
| ; CHECK-NEXT: br label [[LOOP:%.*]] |
| ; CHECK: loop: |
| ; CHECK-NEXT: [[V_INC2:%.*]] = phi i32 [ [[V_INC1:%.*]], [[LATCH:%.*]] ], [ [[G_PROMOTED]], [[ENTRY:%.*]] ] |
| ; CHECK-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[LATCH]] |
| ; CHECK: if: |
| ; CHECK-NEXT: [[V_INC:%.*]] = add i32 [[V_INC2]], 1 |
| ; CHECK-NEXT: store i32 [[V_INC]], ptr @g, align 4 |
| ; CHECK-NEXT: br label [[LATCH]] |
| ; CHECK: latch: |
| ; CHECK-NEXT: [[V_INC1]] = phi i32 [ [[V_INC]], [[IF]] ], [ [[V_INC2]], [[LOOP]] ] |
| ; CHECK-NEXT: br i1 [[C2:%.*]], label [[EXIT:%.*]], label [[LOOP]] |
| ; CHECK: exit: |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| br label %loop |
| |
| loop: |
| br i1 %c, label %if, label %latch |
| |
| if: |
| %v = load i32, ptr @g |
| %v.inc = add i32 %v, 1 |
| store i32 %v.inc, ptr @g |
| br label %latch |
| |
| latch: |
| br i1 %c2, label %exit, label %loop |
| |
| exit: |
| ret void |
| } |
| |
| ; The store can never be promoted here, because the global is constant, and |
| ; the store could trap. |
| define void @promote_constant_global(i1 %c, i1 %c2) { |
| ; CHECK-LABEL: @promote_constant_global( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[V:%.*]] = load i32, ptr @c, align 4 |
| ; CHECK-NEXT: [[V_INC:%.*]] = add i32 [[V]], 1 |
| ; CHECK-NEXT: br label [[LOOP:%.*]] |
| ; CHECK: loop: |
| ; CHECK-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[LATCH:%.*]] |
| ; CHECK: if: |
| ; CHECK-NEXT: store i32 [[V_INC]], ptr @c, align 4 |
| ; CHECK-NEXT: br label [[LATCH]] |
| ; CHECK: latch: |
| ; CHECK-NEXT: br i1 [[C2:%.*]], label [[EXIT:%.*]], label [[LOOP]] |
| ; CHECK: exit: |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| br label %loop |
| |
| loop: |
| br i1 %c, label %if, label %latch |
| |
| if: |
| %v = load i32, ptr @c |
| %v.inc = add i32 %v, 1 |
| store i32 %v.inc, ptr @c |
| br label %latch |
| |
| latch: |
| br i1 %c2, label %exit, label %loop |
| |
| exit: |
| ret void |
| } |
| |
| ; if %c is false and %ptr == @g, then this should store 42 to the pointer. |
| ; However, if we perform load+store promotion, then we would instead store the |
| ; original value of the global. |
| define void @promote_global_noalias(i1 %c, i1 %c2, ptr noalias %ptr) { |
| ; CHECK-LABEL: @promote_global_noalias( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[G_PROMOTED:%.*]] = load i32, ptr @g, align 4 |
| ; CHECK-NEXT: br label [[LOOP:%.*]] |
| ; CHECK: loop: |
| ; CHECK-NEXT: [[V_INC2:%.*]] = phi i32 [ [[V_INC1:%.*]], [[LATCH:%.*]] ], [ [[G_PROMOTED]], [[ENTRY:%.*]] ] |
| ; CHECK-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]] |
| ; CHECK: if: |
| ; CHECK-NEXT: [[V_INC:%.*]] = add i32 [[V_INC2]], 1 |
| ; CHECK-NEXT: store i32 [[V_INC]], ptr @g, align 4 |
| ; CHECK-NEXT: br label [[LATCH]] |
| ; CHECK: else: |
| ; CHECK-NEXT: store i32 42, ptr [[PTR:%.*]], align 4 |
| ; CHECK-NEXT: br label [[LATCH]] |
| ; CHECK: latch: |
| ; CHECK-NEXT: [[V_INC1]] = phi i32 [ [[V_INC2]], [[ELSE]] ], [ [[V_INC]], [[IF]] ] |
| ; CHECK-NEXT: br i1 [[C2:%.*]], label [[EXIT:%.*]], label [[LOOP]] |
| ; CHECK: exit: |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| br label %loop |
| |
| loop: |
| br i1 %c, label %if, label %else |
| |
| if: |
| %v = load i32, ptr @g |
| %v.inc = add i32 %v, 1 |
| store i32 %v.inc, ptr @g |
| br label %latch |
| |
| else: |
| store i32 42, ptr %ptr |
| br label %latch |
| |
| latch: |
| br i1 %c2, label %exit, label %loop |
| |
| exit: |
| ret void |
| } |
| |
| ; In single-thread mode both loads and stores can be promoted. In multi-thread |
| ; mode only loads can be promoted, as a different thread might write to the |
| ; captured alloca. |
| define void @promote_captured_alloca(i1 %c, i1 %c2) { |
| ; MT-LABEL: @promote_captured_alloca( |
| ; MT-NEXT: entry: |
| ; MT-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; MT-NEXT: call void @capture(ptr [[A]]) |
| ; MT-NEXT: [[A_PROMOTED:%.*]] = load i32, ptr [[A]], align 4 |
| ; MT-NEXT: br label [[LOOP:%.*]] |
| ; MT: loop: |
| ; MT-NEXT: [[V_INC2:%.*]] = phi i32 [ [[V_INC1:%.*]], [[LATCH:%.*]] ], [ [[A_PROMOTED]], [[ENTRY:%.*]] ] |
| ; MT-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[LATCH]] |
| ; MT: if: |
| ; MT-NEXT: [[V_INC:%.*]] = add i32 [[V_INC2]], 1 |
| ; MT-NEXT: store i32 [[V_INC]], ptr [[A]], align 4 |
| ; MT-NEXT: br label [[LATCH]] |
| ; MT: latch: |
| ; MT-NEXT: [[V_INC1]] = phi i32 [ [[V_INC]], [[IF]] ], [ [[V_INC2]], [[LOOP]] ] |
| ; MT-NEXT: br i1 [[C2:%.*]], label [[EXIT:%.*]], label [[LOOP]] |
| ; MT: exit: |
| ; MT-NEXT: ret void |
| ; |
| ; ST-LABEL: @promote_captured_alloca( |
| ; ST-NEXT: entry: |
| ; ST-NEXT: [[A:%.*]] = alloca i32, align 4 |
| ; ST-NEXT: call void @capture(ptr [[A]]) |
| ; ST-NEXT: [[A_PROMOTED:%.*]] = load i32, ptr [[A]], align 4 |
| ; ST-NEXT: br label [[LOOP:%.*]] |
| ; ST: loop: |
| ; ST-NEXT: [[V_INC2:%.*]] = phi i32 [ [[V_INC1:%.*]], [[LATCH:%.*]] ], [ [[A_PROMOTED]], [[ENTRY:%.*]] ] |
| ; ST-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[LATCH]] |
| ; ST: if: |
| ; ST-NEXT: [[V_INC:%.*]] = add i32 [[V_INC2]], 1 |
| ; ST-NEXT: br label [[LATCH]] |
| ; ST: latch: |
| ; ST-NEXT: [[V_INC1]] = phi i32 [ [[V_INC]], [[IF]] ], [ [[V_INC2]], [[LOOP]] ] |
| ; ST-NEXT: br i1 [[C2:%.*]], label [[EXIT:%.*]], label [[LOOP]] |
| ; ST: exit: |
| ; ST-NEXT: [[V_INC1_LCSSA:%.*]] = phi i32 [ [[V_INC1]], [[LATCH]] ] |
| ; ST-NEXT: store i32 [[V_INC1_LCSSA]], ptr [[A]], align 4 |
| ; ST-NEXT: ret void |
| ; |
| entry: |
| %a = alloca i32 |
| call void @capture(ptr %a) |
| br label %loop |
| |
| loop: |
| br i1 %c, label %if, label %latch |
| |
| if: |
| %v = load i32, ptr %a |
| %v.inc = add i32 %v, 1 |
| store i32 %v.inc, ptr %a |
| br label %latch |
| |
| latch: |
| br i1 %c2, label %exit, label %loop |
| |
| exit: |
| ret void |
| } |
| |
| ; The store cannot be promoted here, because we do not know whether the |
| ; argument memory is writable. |
| define void @promote_arg(ptr noalias dereferenceable(4) align 4 %arg, i1 %c, i1 %c2) { |
| ; CHECK-LABEL: @promote_arg( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[ARG_PROMOTED:%.*]] = load i32, ptr [[ARG:%.*]], align 4 |
| ; CHECK-NEXT: br label [[LOOP:%.*]] |
| ; CHECK: loop: |
| ; CHECK-NEXT: [[V_INC2:%.*]] = phi i32 [ [[V_INC1:%.*]], [[LATCH:%.*]] ], [ [[ARG_PROMOTED]], [[ENTRY:%.*]] ] |
| ; CHECK-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[LATCH]] |
| ; CHECK: if: |
| ; CHECK-NEXT: [[V_INC:%.*]] = add i32 [[V_INC2]], 1 |
| ; CHECK-NEXT: store i32 [[V_INC]], ptr [[ARG]], align 4 |
| ; CHECK-NEXT: br label [[LATCH]] |
| ; CHECK: latch: |
| ; CHECK-NEXT: [[V_INC1]] = phi i32 [ [[V_INC]], [[IF]] ], [ [[V_INC2]], [[LOOP]] ] |
| ; CHECK-NEXT: br i1 [[C2:%.*]], label [[EXIT:%.*]], label [[LOOP]] |
| ; CHECK: exit: |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| br label %loop |
| |
| loop: |
| br i1 %c, label %if, label %latch |
| |
| if: |
| %v = load i32, ptr %arg |
| %v.inc = add i32 %v, 1 |
| store i32 %v.inc, ptr %arg |
| br label %latch |
| |
| latch: |
| br i1 %c2, label %exit, label %loop |
| |
| exit: |
| ret void |
| } |