[llvm] 03ab85c - [InstCombine] fold `gepi _, (srem x, y)` to `gepi _, (urem x, y)` if `y` is power-of-2 (#180148)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Feb 9 06:26:13 PST 2026
Author: Kiva
Date: 2026-02-09T22:26:07+08:00
New Revision: 03ab85cb873eee64da5a4a34c44955682bdc7e34
URL: https://github.com/llvm/llvm-project/commit/03ab85cb873eee64da5a4a34c44955682bdc7e34
DIFF: https://github.com/llvm/llvm-project/commit/03ab85cb873eee64da5a4a34c44955682bdc7e34.diff
LOG: [InstCombine] fold `gepi _, (srem x, y)` to `gepi _, (urem x, y)` if `y` is power-of-2 (#180148)
This PR adds a small, targeted InstCombine fold for the pattern:
```
%idx = srem i64 %x, 2^k
%p = getelementptr inbounds nuw i8, ptr %base, i64 %idx
```
When the GEP is inbounds + nuw, and the divisor is a non-zero
power-of-two constant, the signed remainder cannot produce a negative
offset without violating the inbounds/nuw constraints. In that case we
can canonicalize the index to a non-negative form and expose the common
power-of-two rewrite:
- Rewrite the GEP index from `srem %x, 2^k` to `urem %x, 2^k`
- Create a new GEP with the new index and replace the original GEP
- the `urem %x, 2^k` will further folds to `and %x (2^k-1)`
resulting the following pattern
```
%idx = and i64 %x, (2^k-1)
%p = getelementptr inbounds nuw i8, ptr %base, i64 %idx
```
Fixes #180097.
generalized alive2 proof: https://alive2.llvm.org/ce/z/8EBxug
Added:
llvm/test/Transforms/InstCombine/gep-srem-to-and-deref.ll
Modified:
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 45b98dd8f419a..fd699381c22fa 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3671,6 +3671,25 @@ Instruction *InstCombinerImpl::visitGetElementPtrInst(GetElementPtrInst &GEP) {
if (Instruction *R = foldSelectGEP(GEP, Builder))
return R;
+ // srem -> (and/urem) for inbounds+nuw GEP
+ if (Indices.size() == 1 && GEP.isInBounds() && GEP.hasNoUnsignedWrap()) {
+ Value *X, *Y;
+
+ // Match: idx = srem X, Y -- where Y is a power-of-two value.
+ if (match(Indices[0], m_OneUse(m_SRem(m_Value(X), m_Value(Y)))) &&
+ isKnownToBeAPowerOfTwo(Y, /*OrZero=*/true, &GEP)) {
+ // If GEP is inbounds+nuw, the offset cannot be negative
+ // -> srem by power-of-two can be treated as urem,
+ // and urem by power-of-two folds to 'and' later.
+ // OrZero=true is fine here because division by zero is UB.
+ Instruction *OldIdxI = cast<Instruction>(Indices[0]);
+ Value *NewIdx = Builder.CreateURem(X, Y, OldIdxI->getName());
+
+ return GetElementPtrInst::Create(GEPEltType, PtrOp, {NewIdx},
+ GEP.getNoWrapFlags());
+ }
+ }
+
return nullptr;
}
diff --git a/llvm/test/Transforms/InstCombine/gep-srem-to-and-deref.ll b/llvm/test/Transforms/InstCombine/gep-srem-to-and-deref.ll
new file mode 100644
index 0000000000000..9f529dad5d3d1
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/gep-srem-to-and-deref.ll
@@ -0,0 +1,128 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=instcombine %s -S | FileCheck %s
+
+define ptr @pos_pow2_2(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @pos_pow2_2(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX_MASK:%.*]] = and i64 [[X]], 1
+; CHECK-NEXT: [[P1:%.*]] = getelementptr inbounds nuw i8, ptr [[FOO]], i64 [[IDX_MASK]]
+; CHECK-NEXT: ret ptr [[P1]]
+;
+ %idx = srem i64 %x, 2
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+define ptr @pos_pow2_4(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @pos_pow2_4(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX_MASK:%.*]] = and i64 [[X]], 3
+; CHECK-NEXT: [[P1:%.*]] = getelementptr inbounds nuw i8, ptr [[FOO]], i64 [[IDX_MASK]]
+; CHECK-NEXT: ret ptr [[P1]]
+;
+ %idx = srem i64 %x, 4
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+define ptr @pos_pow2_8(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @pos_pow2_8(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX_MASK:%.*]] = and i64 [[X]], 7
+; CHECK-NEXT: [[P1:%.*]] = getelementptr inbounds nuw i8, ptr [[FOO]], i64 [[IDX_MASK]]
+; CHECK-NEXT: ret ptr [[P1]]
+;
+ %idx = srem i64 %x, 8
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+define ptr @pos_not_i8(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @pos_not_i8(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX1:%.*]] = and i64 [[X]], 3
+; CHECK-NEXT: [[P2:%.*]] = getelementptr inbounds nuw i32, ptr [[FOO]], i64 [[IDX1]]
+; CHECK-NEXT: ret ptr [[P2]]
+;
+ %idx = srem i64 %x, 4
+ %p = getelementptr inbounds nuw i32, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+; srem x, 1 is always 0, and mask is 0. GEP should be folded to %foo.
+define ptr @pos_pow2_1(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @pos_pow2_1(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: ret ptr [[FOO]]
+;
+ %idx = srem i64 %x, 1
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+; Non-power-of-two constant divisor
+define ptr @neg_non_pow2_6(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @neg_non_pow2_6(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX:%.*]] = srem i64 [[X]], 6
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds nuw i8, ptr [[FOO]], i64 [[IDX]]
+; CHECK-NEXT: ret ptr [[P]]
+;
+ %idx = srem i64 %x, 6
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+; Missing 'no unsigned wrap'
+define ptr @neg_missing_nuw(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @neg_missing_nuw(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX:%.*]] = srem i64 [[X]], 4
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, ptr [[FOO]], i64 [[IDX]]
+; CHECK-NEXT: ret ptr [[P]]
+;
+ %idx = srem i64 %x, 4
+ %p = getelementptr inbounds i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+; Missing 'inbounds'
+define ptr @neg_missing_inbounds(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @neg_missing_inbounds(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX:%.*]] = srem i64 [[X]], 4
+; CHECK-NEXT: [[P:%.*]] = getelementptr nuw i8, ptr [[FOO]], i64 [[IDX]]
+; CHECK-NEXT: ret ptr [[P]]
+;
+ %idx = srem i64 %x, 4
+ %p = getelementptr nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+define ptr @neg_nonconst_divisor(ptr %foo, i64 %x, i64 %d) {
+; CHECK-LABEL: define ptr @neg_nonconst_divisor(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]], i64 [[D:%.*]]) {
+; CHECK-NEXT: [[IDX:%.*]] = srem i64 [[X]], [[D]]
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds nuw i8, ptr [[FOO]], i64 [[IDX]]
+; CHECK-NEXT: ret ptr [[P]]
+;
+ %idx = srem i64 %x, %d
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
+
+declare void @use(i8)
+
+define ptr @neg_multi_use(ptr %foo, i64 %x) {
+; CHECK-LABEL: define ptr @neg_multi_use(
+; CHECK-SAME: ptr [[FOO:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT: [[IDX:%.*]] = srem i64 [[X]], 4
+; CHECK-NEXT: call void @use(i64 [[IDX]])
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds nuw i8, ptr [[FOO]], i64 [[IDX]]
+; CHECK-NEXT: ret ptr [[P]]
+;
+ %idx = srem i64 %x, 4
+ call void @use(i64 %idx)
+ %p = getelementptr inbounds nuw i8, ptr %foo, i64 %idx
+ ret ptr %p
+}
More information about the llvm-commits
mailing list