[llvm] [InstCombine] Simplify fractions when there is no overflow (PR #210516)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 18 08:44:00 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: AZero13 (AZero13)
<details>
<summary>Changes</summary>
Use Greatest Common Factor to avoid making bigger immediates.
Alive2 Proof: https://alive2.llvm.org/ce/z/3gEVTV
Resolves [#<!-- -->210452](https://github.com/llvm/llvm-project/issues/210452)
---
Full diff: https://github.com/llvm/llvm-project/pull/210516.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp (+44)
- (modified) llvm/test/Transforms/InstCombine/div.ll (+228)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index f63bff2f16e7a..0669496fc87b7 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -1402,6 +1402,28 @@ Instruction *InstCombinerImpl::commonIDivTransforms(BinaryOperator &I) {
Mul->setHasNoSignedWrap(OBO->hasNoSignedWrap());
return Mul;
}
+
+ // (X * C1) / C2 -> (X * (C1/D)) / (C2/D) if D = gcd(C1, C2) > 1.
+ if (Op0->hasOneUse()) {
+ APInt GCD = IsSigned
+ ? APIntOps::GreatestCommonDivisor(C1->abs(), C2->abs())
+ : APIntOps::GreatestCommonDivisor(*C1, *C2);
+ if (GCD.ugt(1)) {
+ APInt NewC1 = IsSigned ? C1->sdiv(GCD) : C1->udiv(GCD);
+ APInt NewC2 = IsSigned ? C2->sdiv(GCD) : C2->udiv(GCD);
+
+ auto *OldMul = cast<OverflowingBinaryOperator>(Op0);
+ Value *NewMul = Builder.CreateMul(X, ConstantInt::get(Ty, NewC1), "",
+ OldMul->hasNoUnsignedWrap(),
+ OldMul->hasNoSignedWrap());
+
+ Constant *NewDivisor = ConstantInt::get(Ty, NewC2);
+ auto *NewDiv =
+ BinaryOperator::Create(I.getOpcode(), NewMul, NewDivisor);
+ NewDiv->setIsExact(I.isExact());
+ return NewDiv;
+ }
+ }
}
if ((IsSigned && match(Op0, m_NSWShl(m_Value(X), m_APInt(C1))) &&
@@ -1428,6 +1450,28 @@ Instruction *InstCombinerImpl::commonIDivTransforms(BinaryOperator &I) {
Mul->setHasNoSignedWrap(OBO->hasNoSignedWrap());
return Mul;
}
+
+ // (X << C1) / C2 -> (X << (C1 - K)) / (C2 / (1 << K))
+ // Where K = min(C1, countr_zero(C2)), the shared power of 2.
+ if (Op0->hasOneUse()) {
+ unsigned ShiftAmt = static_cast<unsigned>(C1->getZExtValue());
+ unsigned K = std::min(C2->countr_zero(), ShiftAmt);
+ if (K > 0) {
+ unsigned NewShiftAmt = ShiftAmt - K;
+ APInt NewC2 = IsSigned ? C2->ashr(K) : C2->lshr(K);
+
+ auto *OldShift = cast<OverflowingBinaryOperator>(Op0);
+ Value *NewShift = Builder.CreateShl(
+ X, ConstantInt::get(Ty, NewShiftAmt), "",
+ OldShift->hasNoUnsignedWrap(), OldShift->hasNoSignedWrap());
+
+ Constant *NewDivisor = ConstantInt::get(Ty, NewC2);
+ auto *NewDiv =
+ BinaryOperator::Create(I.getOpcode(), NewShift, NewDivisor);
+ NewDiv->setIsExact(I.isExact());
+ return NewDiv;
+ }
+ }
}
// Distribute div over add to eliminate a matching div/mul pair:
diff --git a/llvm/test/Transforms/InstCombine/div.ll b/llvm/test/Transforms/InstCombine/div.ll
index 2adf6036d4bcf..221e58e0c69a5 100644
--- a/llvm/test/Transforms/InstCombine/div.ll
+++ b/llvm/test/Transforms/InstCombine/div.ll
@@ -2033,6 +2033,234 @@ define <2 x i32> @sdiv_select_one_false_poison_vec(<2 x i32> %a, i1 %b) {
ret <2 x i32> %div
}
+define i32 @sdiv_mul_nsw_gcd(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, 150
+ %d = sdiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @sdiv_mul_nsw_gcd_neg_c1(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_neg_c1(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], -150
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, -150
+ %d = sdiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @sdiv_mul_nsw_gcd_neg_c2(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_neg_c2(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], -100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, 150
+ %d = sdiv i32 %m, -100
+ ret i32 %d
+}
+
+define i32 @sdiv_mul_nsw_gcd_neg_both(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_neg_both(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], -150
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], -100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, -150
+ %d = sdiv i32 %m, -100
+ ret i32 %d
+}
+
+define i32 @udiv_mul_nuw_gcd(i32 %x) {
+; CHECK-LABEL: @udiv_mul_nuw_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul nuw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D1:%.*]] = udiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D1]]
+;
+ %m = mul nuw i32 %x, 150
+ %d = udiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @udiv_mul_nuw_nsw_gcd(i32 %x) {
+; CHECK-LABEL: @udiv_mul_nuw_nsw_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul nuw nsw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D1:%.*]] = udiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D1]]
+;
+ %m = mul nuw nsw i32 %x, 150
+ %d = udiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @sdiv_mul_nsw_exact_gcd(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_exact_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D:%.*]] = sdiv exact i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, 150
+ %d = sdiv exact i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @udiv_mul_nuw_exact_gcd(i32 %x) {
+; CHECK-LABEL: @udiv_mul_nuw_exact_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul nuw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D1:%.*]] = udiv exact i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D1]]
+;
+ %m = mul nuw i32 %x, 150
+ %d = udiv exact i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @sdiv_shl_nsw_gcd(i32 %x) {
+; CHECK-LABEL: @sdiv_shl_nsw_gcd(
+; CHECK-NEXT: [[SHL:%.*]] = shl nsw i32 [[X:%.*]], 2
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[SHL]], 6
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %shl = shl nsw i32 %x, 2
+ %d = sdiv i32 %shl, 6
+ ret i32 %d
+}
+
+define i32 @udiv_shl_nuw_gcd(i32 %x) {
+; CHECK-LABEL: @udiv_shl_nuw_gcd(
+; CHECK-NEXT: [[SHL:%.*]] = shl nuw i32 [[X:%.*]], 2
+; CHECK-NEXT: [[D:%.*]] = udiv i32 [[SHL]], 6
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %shl = shl nuw i32 %x, 2
+ %d = udiv i32 %shl, 6
+ ret i32 %d
+}
+
+define i32 @sdiv_shl_nsw_exact_gcd(i32 %x) {
+; CHECK-LABEL: @sdiv_shl_nsw_exact_gcd(
+; CHECK-NEXT: [[SHL:%.*]] = shl nsw i32 [[X:%.*]], 2
+; CHECK-NEXT: [[D:%.*]] = sdiv exact i32 [[SHL]], 6
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %shl = shl nsw i32 %x, 2
+ %d = sdiv exact i32 %shl, 6
+ ret i32 %d
+}
+
+; Vector splat test
+define <2 x i32> @sdiv_mul_nsw_gcd_splat(<2 x i32> %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_splat(
+; CHECK-NEXT: [[M:%.*]] = mul nsw <2 x i32> [[X:%.*]], splat (i32 150)
+; CHECK-NEXT: [[D:%.*]] = sdiv <2 x i32> [[M]], splat (i32 100)
+; CHECK-NEXT: ret <2 x i32> [[D]]
+;
+ %m = mul nsw <2 x i32> %x, <i32 150, i32 150>
+ %d = sdiv <2 x i32> %m, <i32 100, i32 100>
+ ret <2 x i32> %d
+}
+
+; Vector non-splat test (should not fold)
+define <2 x i32> @sdiv_mul_nsw_gcd_non_splat(<2 x i32> %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_non_splat(
+; CHECK-NEXT: [[M:%.*]] = mul nsw <2 x i32> [[X:%.*]], <i32 150, i32 90>
+; CHECK-NEXT: [[D:%.*]] = sdiv <2 x i32> [[M]], <i32 100, i32 60>
+; CHECK-NEXT: ret <2 x i32> [[D]]
+;
+ %m = mul nsw <2 x i32> %x, <i32 150, i32 90>
+ %d = sdiv <2 x i32> %m, <i32 100, i32 60>
+ ret <2 x i32> %d
+}
+
+; Negative tests
+
+define i32 @sdiv_mul_no_nsw_gcd(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_no_nsw_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul i32 %x, 150
+ %d = sdiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @udiv_mul_no_nuw_gcd(i32 %x) {
+; CHECK-LABEL: @udiv_mul_no_nuw_gcd(
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D:%.*]] = udiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul i32 %x, 150
+ %d = udiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @sdiv_shl_no_nsw_gcd(i32 %x) {
+; CHECK-LABEL: @sdiv_shl_no_nsw_gcd(
+; CHECK-NEXT: [[SHL:%.*]] = shl i32 [[X:%.*]], 2
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[SHL]], 6
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %shl = shl i32 %x, 2
+ %d = sdiv i32 %shl, 6
+ ret i32 %d
+}
+
+define i32 @udiv_shl_no_nuw_gcd(i32 %x) {
+; CHECK-LABEL: @udiv_shl_no_nuw_gcd(
+; CHECK-NEXT: [[SHL:%.*]] = shl i32 [[X:%.*]], 2
+; CHECK-NEXT: [[D:%.*]] = udiv i32 [[SHL]], 6
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %shl = shl i32 %x, 2
+ %d = udiv i32 %shl, 6
+ ret i32 %d
+}
+
+define i32 @sdiv_mul_nsw_gcd_multi_use(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_multi_use(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], 150
+; CHECK-NEXT: call void @use(i32 [[M]])
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], 100
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, 150
+ call void @use(i32 %m)
+ %d = sdiv i32 %m, 100
+ ret i32 %d
+}
+
+define i32 @sdiv_shl_nsw_gcd_multi_use(i32 %x) {
+; CHECK-LABEL: @sdiv_shl_nsw_gcd_multi_use(
+; CHECK-NEXT: [[SHL:%.*]] = shl nsw i32 [[X:%.*]], 2
+; CHECK-NEXT: call void @use(i32 [[SHL]])
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[SHL]], 6
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %shl = shl nsw i32 %x, 2
+ call void @use(i32 %shl)
+ %d = sdiv i32 %shl, 6
+ ret i32 %d
+}
+
+define i32 @sdiv_mul_nsw_gcd_is_one(i32 %x) {
+; CHECK-LABEL: @sdiv_mul_nsw_gcd_is_one(
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X:%.*]], 150
+; CHECK-NEXT: [[D:%.*]] = sdiv i32 [[M]], 101
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %m = mul nsw i32 %x, 150
+ %d = sdiv i32 %m, 101
+ ret i32 %d
+}
+
!0 = !{!"function_entry_count", i64 1000}
;.
; CHECK: [[META0:![0-9]+]] = !{!"function_entry_count", i64 1000}
``````````
</details>
https://github.com/llvm/llvm-project/pull/210516
More information about the llvm-commits
mailing list