CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
122 lines
5.2 KiB
LLVM
122 lines
5.2 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
|
|
; RUN: opt -S -passes=instcombine < %s | FileCheck %s
|
|
|
|
%struct.pair = type { i64, i64 }
|
|
|
|
declare void @init(ptr)
|
|
declare void @use_ptr(ptr)
|
|
|
|
; When a variable-indexed inbounds GEP into a single-element allocation is
|
|
; dereferenced, the index is provably zero. If the GEP and the dereference
|
|
; are in the same basic block, replaceGEPIdxWithZero should replace the GEP
|
|
; for all users -- not just the triggering load/store.
|
|
;
|
|
; Here %gep feeds both a direct load and a constant-offset GEP (%off) used
|
|
; by a second load. Both should resolve to constant offsets from %base.
|
|
define i64 @gep_idx_zero_multi_use(i64 %idx) {
|
|
; CHECK-LABEL: define i64 @gep_idx_zero_multi_use(
|
|
; CHECK-SAME: i64 [[IDX:%.*]]) {
|
|
; CHECK-NEXT: [[BASE:%.*]] = alloca [1 x [[STRUCT_PAIR:%.*]]], align 16
|
|
; CHECK-NEXT: call void @init(ptr nonnull [[BASE]])
|
|
; CHECK-NEXT: [[LOAD1:%.*]] = load i64, ptr [[BASE]], align 16
|
|
; CHECK-NEXT: [[OFF:%.*]] = getelementptr inbounds nuw i8, ptr [[BASE]], i64 8
|
|
; CHECK-NEXT: [[LOAD2:%.*]] = load i64, ptr [[OFF]], align 8
|
|
; CHECK-NEXT: [[SUM:%.*]] = add i64 [[LOAD1]], [[LOAD2]]
|
|
; CHECK-NEXT: ret i64 [[SUM]]
|
|
;
|
|
%base = alloca [1 x %struct.pair], align 16
|
|
call void @init(ptr %base)
|
|
%gep = getelementptr inbounds [16 x i8], ptr %base, i64 %idx
|
|
%load1 = load i64, ptr %gep, align 16
|
|
%off = getelementptr inbounds i8, ptr %gep, i64 8
|
|
%load2 = load i64, ptr %off, align 8
|
|
%sum = add i64 %load1, %load2
|
|
ret i64 %sum
|
|
}
|
|
|
|
; Same pattern but with a store as the triggering memory instruction.
|
|
define i64 @gep_idx_zero_multi_use_store(i64 %idx, i64 %val) {
|
|
; CHECK-LABEL: define i64 @gep_idx_zero_multi_use_store(
|
|
; CHECK-SAME: i64 [[IDX:%.*]], i64 [[VAL:%.*]]) {
|
|
; CHECK-NEXT: [[BASE:%.*]] = alloca [1 x [[STRUCT_PAIR:%.*]]], align 16
|
|
; CHECK-NEXT: store i64 [[VAL]], ptr [[BASE]], align 16
|
|
; CHECK-NEXT: [[OFF:%.*]] = getelementptr inbounds nuw i8, ptr [[BASE]], i64 8
|
|
; CHECK-NEXT: [[LOAD:%.*]] = load i64, ptr [[OFF]], align 8
|
|
; CHECK-NEXT: ret i64 [[LOAD]]
|
|
;
|
|
%base = alloca [1 x %struct.pair], align 16
|
|
%gep = getelementptr inbounds [16 x i8], ptr %base, i64 %idx
|
|
store i64 %val, ptr %gep, align 16
|
|
%off = getelementptr inbounds i8, ptr %gep, i64 8
|
|
%load = load i64, ptr %off, align 8
|
|
ret i64 %load
|
|
}
|
|
|
|
; -------------------------- NEGATIVE CASES --------------------------
|
|
|
|
; When the GEP and the dereference are in different basic blocks, the
|
|
; dereference does not prove the index is zero on all paths (the GEP
|
|
; could be reached without being dereferenced). replaceGEPIdxWithZero
|
|
; must NOT replace the GEP for all users; only the triggering load
|
|
; gets a zero-indexed clone. Here, the else branch passes %gep to a
|
|
; call where %idx could validly be 1 (one-past-the-end, not
|
|
; dereferenced), so the original GEP must be preserved.
|
|
define i64 @gep_idx_zero_different_bb(i64 %idx, i1 %cond) {
|
|
; CHECK-LABEL: define i64 @gep_idx_zero_different_bb(
|
|
; CHECK-SAME: i64 [[IDX:%.*]], i1 [[COND:%.*]]) {
|
|
; CHECK-NEXT: [[BASE:%.*]] = alloca [1 x [[STRUCT_PAIR:%.*]]], align 16
|
|
; CHECK-NEXT: call void @init(ptr nonnull [[BASE]])
|
|
; CHECK-NEXT: br i1 [[COND]], label %[[THEN:.*]], label %[[ELSE:.*]]
|
|
; CHECK: [[THEN]]:
|
|
; CHECK-NEXT: [[LOAD:%.*]] = load i64, ptr [[BASE]], align 16
|
|
; CHECK-NEXT: ret i64 [[LOAD]]
|
|
; CHECK: [[ELSE]]:
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds [16 x i8], ptr [[BASE]], i64 [[IDX]]
|
|
; CHECK-NEXT: call void @use_ptr(ptr nonnull [[GEP]])
|
|
; CHECK-NEXT: ret i64 0
|
|
;
|
|
%base = alloca [1 x %struct.pair], align 16
|
|
call void @init(ptr %base)
|
|
%gep = getelementptr inbounds [16 x i8], ptr %base, i64 %idx
|
|
br i1 %cond, label %then, label %else
|
|
|
|
then:
|
|
%load = load i64, ptr %gep, align 16
|
|
ret i64 %load
|
|
|
|
else:
|
|
call void @use_ptr(ptr %gep)
|
|
ret i64 0
|
|
}
|
|
|
|
; Same basic block, but a potentially-throwing call between the GEP and
|
|
; the load prevents isGuaranteedToTransferExecutionToSuccessor from
|
|
; proving the load always executes when the GEP does. @nounwind_use is
|
|
; nounwind+willreturn so it doesn't block the transfer check; only
|
|
; @may_throw does. Without @may_throw, RAUW would fire and @nounwind_use
|
|
; would receive the zero-indexed pointer. With @may_throw, the GEP must
|
|
; be preserved because @nounwind_use could observe a validly non-zero
|
|
; index on a path where @may_throw throws before the load is reached.
|
|
declare void @may_throw()
|
|
declare void @nounwind_use(ptr) nounwind willreturn
|
|
|
|
define i64 @gep_idx_zero_may_throw(i64 %idx) {
|
|
; CHECK-LABEL: define i64 @gep_idx_zero_may_throw(
|
|
; CHECK-SAME: i64 [[IDX:%.*]]) {
|
|
; CHECK-NEXT: [[BASE:%.*]] = alloca [1 x [[STRUCT_PAIR:%.*]]], align 16
|
|
; CHECK-NEXT: call void @init(ptr nonnull [[BASE]])
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds [16 x i8], ptr [[BASE]], i64 [[IDX]]
|
|
; CHECK-NEXT: call void @nounwind_use(ptr nonnull [[GEP]])
|
|
; CHECK-NEXT: call void @may_throw()
|
|
; CHECK-NEXT: [[LOAD:%.*]] = load i64, ptr [[BASE]], align 16
|
|
; CHECK-NEXT: ret i64 [[LOAD]]
|
|
;
|
|
%base = alloca [1 x %struct.pair], align 16
|
|
call void @init(ptr %base)
|
|
%gep = getelementptr inbounds [16 x i8], ptr %base, i64 %idx
|
|
call void @nounwind_use(ptr %gep)
|
|
call void @may_throw()
|
|
%load = load i64, ptr %gep, align 16
|
|
ret i64 %load
|
|
}
|