Files
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

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
}