[llvm] [InstCombine] Infer exact division from a known-zero remainder (PR #221509)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 5 21:23:42 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: ZERICO2005

<details>
<summary>Changes</summary>

Fixes https://github.com/llvm/llvm-project/issues/108449
Related https://github.com/llvm/llvm-project/pull/126438

As others have stated before, there is no direct way to have C or C++ emit `udiv exact` or `sdiv exact`.

My goal with this PR is to allow LLVM to infer `udiv exact` or `sdiv exact` for user defined functions for performing exact division:
```c++
unsigned udiv_3_exact(unsigned x) {
    if (x % 3 != 0) {
        __builtin_unreachable();
    }
    return x / 3;
}

unsigned udiv_exact(unsigned x, unsigned y) {
    if (y == 0 || x % y != 0) {
        __builtin_unreachable();
    }
    return x / y;
}
```

`InstCombine` checks whether a matching remainder is known to be zero at the division, using information from assumptions and dominating conditions.

The tests also cover interaction with `DivRemPairs`. The inferred exact flag survives in the X86 cases, but is dropped in the PowerPC cases where the remainder is reconstructed from the division.

The `DivRemPairs` tests are adapted from https://github.com/llvm/llvm-project/pull/126438, with updated expectations for the current behavior.

Tests marked `TODO:` cover missed optimizations that I think are worth exploring and fixing. The other tests are intended as coverage and edge-case tests.

AI Disclosure:
I used ChatGPT Codex (Sol 5.6 and GPT-6 Astra) to help implement the code changes and tests.


---

Patch is 52.82 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/221509.diff


6 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp (+43) 
- (added) llvm/test/Transforms/DivRemPairs/PowerPC/div-rem-pairs-infer-exact.ll (+159) 
- (added) llvm/test/Transforms/DivRemPairs/X86/div-rem-pairs-infer-exact.ll (+127) 
- (added) llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-lhs.ll (+104) 
- (added) llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-rhs.ll (+309) 
- (added) llvm/test/Transforms/InstCombine/div-exact-from-rem.ll (+695) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index 128e3e3dcdfe8..5e5c081b72f7d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -1318,6 +1318,39 @@ static Value *foldIDivShl(BinaryOperator &I, InstCombiner::BuilderTy &Builder) {
   return nullptr;
 }
 
+/// Return true if a matching remainder is known to be zero at the division.
+static bool isKnownExactFromRem(const BinaryOperator &Div,
+                                const SimplifyQuery &Q) {
+  assert((Div.getOpcode() == Instruction::SDiv ||
+          Div.getOpcode() == Instruction::UDiv) &&
+         "Expected integer divide");
+
+  unsigned RemOpcode = Div.getOpcode() == Instruction::SDiv ? Instruction::SRem
+                                                            : Instruction::URem;
+
+  Value *Op0 = Div.getOperand(0);
+  Value *Op1 = Div.getOperand(1);
+
+  // A matching remainder uses both operands. Avoid scanning constants, whose
+  // users may be shared across functions or whose use-lists may be unavailable.
+  Value *V = isa<Constant>(Op0) ? Op1 : Op0;
+  if (isa<Constant>(V))
+    return false;
+
+  for (User *U : V->users()) {
+    auto *Rem = dyn_cast<BinaryOperator>(U);
+    if (!Rem || Rem->getOpcode() != RemOpcode || Rem->getOperand(0) != Op0 ||
+        Rem->getOperand(1) != Op1 || (Q.DT && !Q.DT->dominates(Rem, &Div)))
+      continue;
+
+    KnownBits Known(Rem->getType()->getScalarSizeInBits());
+    computeKnownBitsFromContext(Rem, Known, Q.getWithInstruction(&Div));
+    if (Known.isZero())
+      return true;
+  }
+  return false;
+}
+
 /// Common integer divide/remainder transforms
 Instruction *InstCombinerImpl::commonIDivRemTransforms(BinaryOperator &I) {
   assert(I.isIntDivRem() && "Unexpected instruction");
@@ -1801,6 +1834,11 @@ Instruction *InstCombinerImpl::visitUDiv(BinaryOperator &I) {
                                   SQ.getWithInstruction(&I)))
     return replaceInstUsesWith(I, V);
 
+  if (!I.isExact() && isKnownExactFromRem(I, SQ)) {
+    I.setIsExact();
+    return &I;
+  }
+
   if (Instruction *X = foldVectorBinop(I))
     return X;
 
@@ -1896,6 +1934,11 @@ Instruction *InstCombinerImpl::visitSDiv(BinaryOperator &I) {
                                   SQ.getWithInstruction(&I)))
     return replaceInstUsesWith(I, V);
 
+  if (!I.isExact() && isKnownExactFromRem(I, SQ)) {
+    I.setIsExact();
+    return &I;
+  }
+
   if (Instruction *X = foldVectorBinop(I))
     return X;
 
diff --git a/llvm/test/Transforms/DivRemPairs/PowerPC/div-rem-pairs-infer-exact.ll b/llvm/test/Transforms/DivRemPairs/PowerPC/div-rem-pairs-infer-exact.ll
new file mode 100644
index 0000000000000..fcc1590517b4b
--- /dev/null
+++ b/llvm/test/Transforms/DivRemPairs/PowerPC/div-rem-pairs-infer-exact.ll
@@ -0,0 +1,159 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S -mtriple=powerpc64-unknown-unknown | FileCheck %s --check-prefix=INSTCOMBINE
+; RUN: opt < %s -passes="instcombine,div-rem-pairs" -S -mtriple=powerpc64-unknown-unknown | FileCheck %s
+
+; InstCombine infers exact, but DivRemPairs drops it when reconstructing the
+; remainder from the division. Check both stages so the loss stays visible.
+
+define i8 @udiv_exact_assume(i8 %x, i8 %y) {
+; INSTCOMBINE-LABEL: define i8 @udiv_exact_assume(
+; INSTCOMBINE-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; INSTCOMBINE-NEXT:    [[REM:%.*]] = urem i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 0
+; INSTCOMBINE-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; INSTCOMBINE-NEXT:    [[DIV3:%.*]] = udiv exact i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    ret i8 [[DIV3]]
+;
+; CHECK-LABEL: define i8 @udiv_exact_assume(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X_FROZEN:%.*]] = freeze i8 [[X]]
+; CHECK-NEXT:    [[Y_FROZEN:%.*]] = freeze i8 [[Y]]
+; CHECK-NEXT:    [[DIV3:%.*]] = udiv i8 [[X_FROZEN]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[TMP1:%.*]] = mul i8 [[DIV3]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[REM:%.*]] = sub i8 [[X_FROZEN]], [[TMP1]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    ret i8 [[DIV3]]
+;
+  %rem = urem i8 %x, %y
+  %cmp = icmp eq i8 %rem, 0
+  tail call void @llvm.assume(i1 %cmp)
+  %div3 = udiv i8 %x, %y
+  ret i8 %div3
+}
+
+define i8 @udiv_exact_assume_negative(i8 %x, i8 %y) {
+; INSTCOMBINE-LABEL: define i8 @udiv_exact_assume_negative(
+; INSTCOMBINE-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; INSTCOMBINE-NEXT:    [[REM:%.*]] = urem i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 1
+; INSTCOMBINE-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; INSTCOMBINE-NEXT:    [[DIV3:%.*]] = udiv i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    ret i8 [[DIV3]]
+;
+; CHECK-LABEL: define i8 @udiv_exact_assume_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X_FROZEN:%.*]] = freeze i8 [[X]]
+; CHECK-NEXT:    [[Y_FROZEN:%.*]] = freeze i8 [[Y]]
+; CHECK-NEXT:    [[DIV3:%.*]] = udiv i8 [[X_FROZEN]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[TMP1:%.*]] = mul i8 [[DIV3]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[REM_DECOMPOSED:%.*]] = sub i8 [[X_FROZEN]], [[TMP1]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM_DECOMPOSED]], 1
+; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    ret i8 [[DIV3]]
+;
+  %rem = urem i8 %x, %y
+  %cmp = icmp eq i8 %rem, 1
+  tail call void @llvm.assume(i1 %cmp)
+  %div3 = udiv i8 %x, %y
+  ret i8 %div3
+}
+
+
+; Exactness inferred from a dominating condition is also lost when the
+; remainder is decomposed.
+
+define i8 @infer_exact_from_dom_cond_negative(i8 %X, i8 %Y) {
+; INSTCOMBINE-LABEL: define i8 @infer_exact_from_dom_cond_negative(
+; INSTCOMBINE-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; INSTCOMBINE-NEXT:  [[ENTRY:.*]]:
+; INSTCOMBINE-NEXT:    [[REM:%.*]] = srem i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 0
+; INSTCOMBINE-NEXT:    br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[RETURN:.*]]
+; INSTCOMBINE:       [[IF_THEN]]:
+; INSTCOMBINE-NEXT:    [[DIV:%.*]] = sdiv exact i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    br label %[[RETURN]]
+; INSTCOMBINE:       [[RETURN]]:
+; INSTCOMBINE-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ [[DIV]], %[[IF_THEN]] ], [ 0, %[[ENTRY]] ]
+; INSTCOMBINE-NEXT:    ret i8 [[RETVAL_0]]
+;
+; CHECK-LABEL: define i8 @infer_exact_from_dom_cond_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[X_FROZEN:%.*]] = freeze i8 [[X]]
+; CHECK-NEXT:    [[Y_FROZEN:%.*]] = freeze i8 [[Y]]
+; CHECK-NEXT:    [[DIV:%.*]] = sdiv i8 [[X_FROZEN]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[TMP0:%.*]] = mul i8 [[DIV]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[REM_DECOMPOSED:%.*]] = sub i8 [[X_FROZEN]], [[TMP0]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM_DECOMPOSED]], 0
+; CHECK-NEXT:    br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[RETURN:.*]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ [[DIV]], %[[IF_THEN]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT:    ret i8 [[RETVAL_0]]
+;
+entry:
+  %rem = srem i8 %X, %Y
+  %cmp = icmp eq i8 %rem, 0
+  br i1 %cmp, label %if.then, label %return
+
+if.then:
+  %div = sdiv i8 %X, %Y
+  br label %return
+
+return:
+  %retval.0 = phi i8 [ %div, %if.then ], [ 0, %entry ]
+  ret i8 %retval.0
+}
+
+define i8 @infer_exact_from_dom_cond_false_path_negative(i8 %X, i8 %Y) {
+; INSTCOMBINE-LABEL: define i8 @infer_exact_from_dom_cond_false_path_negative(
+; INSTCOMBINE-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; INSTCOMBINE-NEXT:  [[ENTRY:.*:]]
+; INSTCOMBINE-NEXT:    [[REM:%.*]] = srem i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    [[CMP_NOT:%.*]] = icmp eq i8 [[REM]], 0
+; INSTCOMBINE-NEXT:    br i1 [[CMP_NOT]], label %[[IF_ELSE:.*]], label %[[IF_THEN:.*]]
+; INSTCOMBINE:       [[IF_THEN]]:
+; INSTCOMBINE-NEXT:    br label %[[RETURN:.*]]
+; INSTCOMBINE:       [[IF_ELSE]]:
+; INSTCOMBINE-NEXT:    [[DIV:%.*]] = sdiv exact i8 [[X]], [[Y]]
+; INSTCOMBINE-NEXT:    br label %[[RETURN]]
+; INSTCOMBINE:       [[RETURN]]:
+; INSTCOMBINE-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ 0, %[[IF_THEN]] ], [ [[DIV]], %[[IF_ELSE]] ]
+; INSTCOMBINE-NEXT:    ret i8 [[RETVAL_0]]
+;
+; CHECK-LABEL: define i8 @infer_exact_from_dom_cond_false_path_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[X_FROZEN:%.*]] = freeze i8 [[X]]
+; CHECK-NEXT:    [[Y_FROZEN:%.*]] = freeze i8 [[Y]]
+; CHECK-NEXT:    [[DIV:%.*]] = sdiv i8 [[X_FROZEN]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[TMP0:%.*]] = mul i8 [[DIV]], [[Y_FROZEN]]
+; CHECK-NEXT:    [[REM_DECOMPOSED:%.*]] = sub i8 [[X_FROZEN]], [[TMP0]]
+; CHECK-NEXT:    [[CMP_NOT:%.*]] = icmp eq i8 [[REM_DECOMPOSED]], 0
+; CHECK-NEXT:    br i1 [[CMP_NOT]], label %[[IF_ELSE:.*]], label %[[IF_THEN:.*]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    br label %[[RETURN:.*]]
+; CHECK:       [[IF_ELSE]]:
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ 0, %[[IF_THEN]] ], [ [[DIV]], %[[IF_ELSE]] ]
+; CHECK-NEXT:    ret i8 [[RETVAL_0]]
+;
+entry:
+  %rem = srem i8 %X, %Y
+  %cmp = icmp ne i8 %rem, 0
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  br label %return
+
+if.else:
+  %div = sdiv i8 %X, %Y
+  br label %return
+
+return:
+  %retval.0 = phi i8 [ 0, %if.then ], [ %div, %if.else ]
+  ret i8 %retval.0
+}
diff --git a/llvm/test/Transforms/DivRemPairs/X86/div-rem-pairs-infer-exact.ll b/llvm/test/Transforms/DivRemPairs/X86/div-rem-pairs-infer-exact.ll
new file mode 100644
index 0000000000000..e038f3f422639
--- /dev/null
+++ b/llvm/test/Transforms/DivRemPairs/X86/div-rem-pairs-infer-exact.ll
@@ -0,0 +1,127 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes="instcombine,div-rem-pairs" -S -mtriple=x86_64-unknown-unknown | FileCheck %s
+
+; Exactness inferred from assumptions and dominating conditions is preserved
+; when DivRemPairs can keep separate division and remainder instructions.
+
+define i8 @udiv_exact_assume(i8 %x, i8 %y) {
+; CHECK-LABEL: define i8 @udiv_exact_assume(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    [[DIV3:%.*]] = udiv exact i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i8 [[DIV3]]
+;
+  %rem = urem i8 %x, %y
+  %cmp = icmp eq i8 %rem, 0
+  tail call void @llvm.assume(i1 %cmp)
+  %div3 = udiv i8 %x, %y
+  ret i8 %div3
+}
+
+define i8 @udiv_exact_assume_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i8 @udiv_exact_assume_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 1
+; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    [[DIV3:%.*]] = udiv i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i8 [[DIV3]]
+;
+  %rem = urem i8 %x, %y
+  %cmp = icmp eq i8 %rem, 1
+  tail call void @llvm.assume(i1 %cmp)
+  %div3 = udiv i8 %x, %y
+  ret i8 %div3
+}
+
+define i8 @infer_exact_from_dom_cond(i8 %X, i8 %Y) {
+; CHECK-LABEL: define i8 @infer_exact_from_dom_cond(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[REM:%.*]] = srem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[DIV:%.*]] = sdiv exact i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT:    br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[RETURN:.*]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ [[DIV]], %[[IF_THEN]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT:    ret i8 [[RETVAL_0]]
+;
+entry:
+  %rem = srem i8 %X, %Y
+  %cmp = icmp eq i8 %rem, 0
+  br i1 %cmp, label %if.then, label %return
+
+if.then:
+  %div = sdiv i8 %X, %Y
+  br label %return
+
+return:
+  %retval.0 = phi i8 [ %div, %if.then ], [ 0, %entry ]
+  ret i8 %retval.0
+}
+
+define i8 @infer_exact_from_dom_cond_false_path(i8 %X, i8 %Y) {
+; CHECK-LABEL: define i8 @infer_exact_from_dom_cond_false_path(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[REM:%.*]] = srem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[DIV:%.*]] = sdiv exact i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP_NOT:%.*]] = icmp eq i8 [[REM]], 0
+; CHECK-NEXT:    br i1 [[CMP_NOT]], label %[[IF_ELSE:.*]], label %[[IF_THEN:.*]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    br label %[[RETURN:.*]]
+; CHECK:       [[IF_ELSE]]:
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ 0, %[[IF_THEN]] ], [ [[DIV]], %[[IF_ELSE]] ]
+; CHECK-NEXT:    ret i8 [[RETVAL_0]]
+;
+entry:
+  %rem = srem i8 %X, %Y
+  %cmp = icmp ne i8 %rem, 0
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  br label %return
+
+if.else:
+  %div = sdiv i8 %X, %Y
+  br label %return
+
+return:
+  %retval.0 = phi i8 [ 0, %if.then ], [ %div, %if.else ]
+  ret i8 %retval.0
+}
+
+
+define i8 @infer_exact_from_dom_cond_negative(i8 %X, i8 %Y) {
+; CHECK-LABEL: define i8 @infer_exact_from_dom_cond_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[REM:%.*]] = srem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[DIV:%.*]] = sdiv i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[REM]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[RETURN:.*]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i8 [ [[DIV]], %[[IF_THEN]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT:    ret i8 [[RETVAL_0]]
+;
+entry:
+  %rem = srem i8 %X, %Y
+  %cmp = icmp eq i8 %rem, 1
+  br i1 %cmp, label %if.then, label %return
+
+if.then:
+  %div = sdiv i8 %X, %Y
+  br label %return
+
+return:
+  %retval.0 = phi i8 [ %div, %if.then ], [ 0, %entry ]
+  ret i8 %retval.0
+}
diff --git a/llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-lhs.ll b/llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-lhs.ll
new file mode 100644
index 0000000000000..b9cea3924fd96
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-lhs.ll
@@ -0,0 +1,104 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @llvm.assume(i1 noundef)
+
+define i32 @ret_1_udiv_exact_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_1_udiv_exact_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp eq i32 [[X]], 1
+; CHECK-NEXT:    call void @llvm.assume(i1 [[DOTNOT]])
+; CHECK-NEXT:    ret i32 1
+;
+  %rem = urem i32 1, %x
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = udiv i32 1, %x
+  ret i32 %quot
+}
+
+define i32 @ret_pos_1_sdiv_exact_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_pos_1_sdiv_exact_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[X_FR:%.*]] = freeze i32 [[X]]
+; CHECK-NEXT:    [[REM:%.*]] = srem i32 1, [[X_FR]]
+; CHECK-NEXT:    [[IS_ZERO:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[IS_ZERO]])
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[X_FR]], 1
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 3
+; CHECK-NEXT:    [[QUOT:%.*]] = select i1 [[TMP2]], i32 [[X_FR]], i32 0
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+  %rem = srem i32 1, %x
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = sdiv i32 1, %x
+  ret i32 %quot
+}
+
+define i32 @ret_neg_1_sdiv_exact_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_neg_1_sdiv_exact_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = srem i32 -1, [[X]]
+; CHECK-NEXT:    [[IS_ZERO:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[IS_ZERO]])
+; CHECK-NEXT:    [[QUOT:%.*]] = sdiv exact i32 -1, [[X]]
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+  %rem = srem i32 -1, %x
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = sdiv i32 -1, %x
+  ret i32 %quot
+}
+
+; Note that INT_MIN / -1 is undefined behavior.
+define i32 @ret_intmin_sdiv_exact_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_intmin_sdiv_exact_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = srem i32 -2147483648, [[X]]
+; CHECK-NEXT:    [[IS_ZERO:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[IS_ZERO]])
+; CHECK-NEXT:    [[QUOT:%.*]] = sdiv exact i32 -2147483648, [[X]]
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+  %rem = srem i32 -2147483648, %x
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = sdiv i32 -2147483648, %x
+  ret i32 %quot
+}
+
+; 105 = 7 * 5 * 3, so it has a lot of factors.
+define i32 @ret_105_udiv_exact_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_105_udiv_exact_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 105, [[X]]
+; CHECK-NEXT:    [[IS_ZERO:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[IS_ZERO]])
+; CHECK-NEXT:    [[QUOT:%.*]] = udiv exact i32 105, [[X]]
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+  %rem = urem i32 105, %x
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = udiv i32 105, %x
+  ret i32 %quot
+}
+
+; 11 is prime, so x can only be 1 or 11 if the division is exact.
+define i32 @ret_11_udiv_exact_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_11_udiv_exact_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 11, [[X]]
+; CHECK-NEXT:    [[IS_ZERO:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[IS_ZERO]])
+; CHECK-NEXT:    [[QUOT:%.*]] = udiv exact i32 11, [[X]]
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+  %rem = urem i32 11, %x
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = udiv i32 11, %x
+  ret i32 %quot
+}
diff --git a/llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-rhs.ll b/llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-rhs.ll
new file mode 100644
index 0000000000000..d6bcc43da677d
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/div-exact-from-rem-constant-rhs.ll
@@ -0,0 +1,309 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @llvm.assume(i1 noundef)
+
+; Unsigned division with a constant RHS.
+
+define i32 @ret_udiv_exact_3_from_zero_remainder(i32 %x) {
+; CHECK-LABEL: define i32 @ret_udiv_exact_3_from_zero_remainder(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], 3
+; CHECK-NEXT:    [[IS_ZERO:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[IS_ZERO]])
+; CHECK-NEXT:    [[QUOT:%.*]] = udiv exact i32 [[X]], 3
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+  %rem = urem i32 %x, 3
+  %is.zero = icmp eq i32 %rem, 0
+  call void @llvm.assume(i1 %is.zero)
+  %quot = udiv i32 %x, 3
+  ret i32 %quot
+}
+
+define i32 @ret_udiv_exact_3_from_zero_remainder_unreachable(i32 %x) {
+; CHECK-LABEL: define i32 @ret_udiv_exact_3_from_zero_remainder_unreachable(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], 3
+; CHECK-NEXT:    [[NOT_ZERO_NOT:%.*]] = icmp eq i32 [[REM]], 0
+; CHECK-NEXT:    br i1 [[NOT_ZERO_NOT]], label %[[DIV:.*]], label %[[UNREACHABLE:.*]]
+; CHECK:       [[UNREACHABLE]]:
+; CHECK-NEXT:    unreachable
+; CHECK:       [[DIV]]:
+; CHECK-NEXT:    [[QUOT:%.*]] = udiv exact i32 [[X]], 3
+; CHECK-NEXT:    ret i32 [[QUOT]]
+;
+entry:
+  %rem = urem i32 %x, 3
+  %not.zero = icmp ne i32 %rem, 0
+  br i1 %not.zero, label %unreachable, label %div
+
+unreachable:
+  unreachable
+
+div:
+  %quot = udiv i32 %x, 3
+  ret i32 %quot
+}
+
+define i32 @ret_udiv_exact_10000_from_zero_remainder(i32 %x) {
+; CHECK-LABE...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/221509


More information about the llvm-commits mailing list