[llvm] [InstCombine] Fix assertion in GEP exact div/shr index canonicalization (PR #201431)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 3 11:56:53 PDT 2026


https://github.com/lijinpei-amd created https://github.com/llvm/llvm-project/pull/201431

When canonicalizing the index of `(gep ptr, (div/shr exact X, C))`, visitGetElementPtrInst builds the new index with Builder.CreateBinOp and then sets the exact flag via `cast<BinaryOperator>(NewOp)->setIsExact()`. If X is a constant (as can be proved during InstCombine), the folding builder constant-folds NewOp to a non-BinaryOperator, so the cast asserts.

Fix this by building the exact binop directly with BinaryOperator::CreateExact, which always yields a real instruction that carries the exact flag (matching the idiom in InstCombineMulDivRem). The output is unchanged for non-constant indices.

Fixes #190324.

>From ce55e89b3de4db3b6f10ef7eac7ad3f60919944a Mon Sep 17 00:00:00 2001
From: Li Jinpei <jinpli at amd.com>
Date: Wed, 3 Jun 2026 22:52:23 +0800
Subject: [PATCH] [InstCombine] Fix assertion in GEP exact div/shr index
 canonicalization

When canonicalizing the index of `(gep ptr, (div/shr exact X, C))`,
visitGetElementPtrInst builds the new index with Builder.CreateBinOp and then
sets the exact flag via `cast<BinaryOperator>(NewOp)->setIsExact()`. If X is a
constant (as can be proved during InstCombine), the folding builder
constant-folds NewOp to a non-BinaryOperator, so the cast asserts.

Fix this by building the exact binop directly with BinaryOperator::CreateExact,
which always yields a real instruction that carries the exact flag (matching
the idiom in InstCombineMulDivRem). The output is unchanged for non-constant
indices.

Fixes #190324.
---
 .../InstCombine/InstructionCombining.cpp      |  7 +-
 .../gep-canonicalize-index-constfold.ll       | 69 +++++++++++++++++++
 2 files changed, 73 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/InstCombine/gep-canonicalize-index-constfold.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 348dd162501a0..683f85a14c507 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3568,10 +3568,11 @@ Instruction *InstCombinerImpl::visitGetElementPtrInst(GetElementPtrInst &GEP) {
           }
 
           if (NewC.has_value()) {
-            Value *NewOp = Builder.CreateBinOp(
+            // Create directly: the folding builder may fold this to a
+            // non-BinaryOperator (e.g. a constant) that cannot be exact.
+            Value *NewOp = Builder.Insert(BinaryOperator::CreateExact(
                 static_cast<Instruction::BinaryOps>(ExactIns->getOpcode()), V,
-                ConstantInt::get(V->getType(), *NewC));
-            cast<BinaryOperator>(NewOp)->setIsExact();
+                ConstantInt::get(V->getType(), *NewC)));
             return GetElementPtrInst::Create(Builder.getInt8Ty(),
                                              GEP.getPointerOperand(), NewOp,
                                              GEP.getNoWrapFlags());
diff --git a/llvm/test/Transforms/InstCombine/gep-canonicalize-index-constfold.ll b/llvm/test/Transforms/InstCombine/gep-canonicalize-index-constfold.ll
new file mode 100644
index 0000000000000..73b15726a25ad
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/gep-canonicalize-index-constfold.ll
@@ -0,0 +1,69 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+target datalayout = "e-p:64:64-i64:64-n8:16:32:64"
+
+define ptr @gep_exact_sdiv_index_constfolds(i1 %cond, ptr %p, i64 %next.iv) {
+; CHECK-LABEL: @gep_exact_sdiv_index_constfolds(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[OUTER:%.*]]
+; CHECK:       outer:
+; CHECK-NEXT:    store i64 0, ptr [[P:%.*]], align 8
+; CHECK-NEXT:    br label [[INNER:%.*]]
+; CHECK:       inner:
+; CHECK-NEXT:    br i1 true, label [[NESTED:%.*]], label [[OUTER]]
+; CHECK:       nested:
+; CHECK-NEXT:    br i1 true, label [[ASSUME:%.*]], label [[SIDE:%.*]]
+; CHECK:       assume:
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[DEAD1:%.*]], label [[NESTED]]
+; CHECK:       dead1:
+; CHECK-NEXT:    ret ptr poison
+; CHECK:       side:
+; CHECK-NEXT:    br i1 [[COND]], label [[DEAD2:%.*]], label [[INNER]]
+; CHECK:       dead2:
+; CHECK-NEXT:    ret ptr poison
+;
+entry:
+  br label %outer
+
+outer:
+  %iv = phi i64 [ 0, %entry ], [ %next.iv, %inner ]
+  %idx = sdiv exact i64 %iv, 64
+  %gep = getelementptr i64, ptr %p, i64 %idx
+  store i64 0, ptr %gep, align 8
+  br label %inner
+
+inner:
+  %j = phi i8 [ 0, %outer ], [ %next.j, %side ]
+  %j32 = sext i8 %j to i32
+  %j.is.zero = icmp eq i32 %j32, 0
+  br i1 %j.is.zero, label %nested, label %outer
+
+nested:
+  %k = phi i8 [ 0, %inner ], [ %next.k, %assume ]
+  %k32 = sext i8 %k to i32
+  %k.is.zero = icmp eq i32 %k32, 0
+  br i1 %k.is.zero, label %assume, label %side
+
+assume:
+  call void @llvm.assume(i1 false)
+  %k.add = add i32 %k32, 0
+  %next.k = trunc i32 %k.add to i8
+  br i1 %cond, label %dead1, label %nested
+
+dead1:
+  %dead.ptr1 = inttoptr i64 %idx to ptr
+  ret ptr %dead.ptr1
+
+side:
+  %j.add = add i32 %j32, 0
+  %next.j = trunc i32 %j.add to i8
+  br i1 %cond, label %dead2, label %inner
+
+dead2:
+  %dead.ptr2 = inttoptr i64 %idx to ptr
+  ret ptr %dead.ptr2
+}
+
+declare void @llvm.assume(i1 noundef)



More information about the llvm-commits mailing list