[llvm] [PredicateInfo] Infer operand bound from mul nuw square predicates (PR #173127)

Ken Matsui via llvm-commits llvm-commits at lists.llvm.org
Sat Dec 20 00:06:38 PST 2025


https://github.com/ken-matsui updated https://github.com/llvm/llvm-project/pull/173127

>From 964097ddb2e21bf821581ded4cf27644f87ed49a Mon Sep 17 00:00:00 2001
From: Ken Matsui <github at kmts.me>
Date: Wed, 17 Dec 2025 19:21:16 -0500
Subject: [PATCH 1/2] Add baseline tests for upcoming patch

---
 .../Transforms/SCCP/assume-mul-nuw-square.ll  | 101 ++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll

diff --git a/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll b/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll
new file mode 100644
index 0000000000000..e358571c32a8f
--- /dev/null
+++ b/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll
@@ -0,0 +1,101 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=sccp -S < %s | FileCheck %s
+
+declare void @llvm.assume(i1)
+
+define i1 @assume_mul_nuw_square_i8(i8 %s) {
+; CHECK-LABEL: @assume_mul_nuw_square_i8(
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i8 [[S:%.*]], [[S]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ule i8 [[MUL]], 120
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[S]], 16
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+start:
+  %mul = mul nuw i8 %s, %s
+  %cond = icmp ule i8 %mul, 120
+  call void @llvm.assume(i1 %cond)
+  %cmp = icmp ult i8 %s, 16
+  ret i1 %cmp
+}
+
+define i1 @assume_mul_nuw_square_i5(i5 %s) {
+; CHECK-LABEL: @assume_mul_nuw_square_i5(
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i5 [[S:%.*]], [[S]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ult i5 [[MUL]], 15
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i5 [[S]], 8
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+start:
+  %mul = mul nuw i5 %s, %s
+  %cond = icmp ult i5 %mul, 15
+  call void @llvm.assume(i1 %cond)
+  %cmp = icmp ult i5 %s, 8
+  ret i1 %cmp
+}
+
+define i1 @branch_mul_nuw_square(i8 %s, i8 %num) {
+; CHECK-LABEL: @branch_mul_nuw_square(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i8 [[S:%.*]], [[S]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ule i8 [[MUL]], [[NUM:%.*]]
+; CHECK-NEXT:    br i1 [[COND]], label [[TRUE:%.*]], label [[FALSE:%.*]]
+; CHECK:       true:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[S]], 16
+; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK:       false:
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp ult i8 [[S]], 16
+; CHECK-NEXT:    ret i1 [[CMP2]]
+;
+entry:
+  %mul = mul nuw i8 %s, %s
+  %cond = icmp ule i8 %mul, %num
+  br i1 %cond, label %true, label %false
+
+true:
+  %cmp = icmp ult i8 %s, 16
+  ret i1 %cmp
+
+false:
+  %cmp2 = icmp ult i8 %s, 16
+  ret i1 %cmp2
+}
+
+; negative test: missing nuw on the multiply.
+define i1 @assume_mul_square_no_nuw(i8 %s) {
+; CHECK-LABEL: @assume_mul_square_no_nuw(
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    [[MUL:%.*]] = mul i8 [[S:%.*]], [[S]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ule i8 [[MUL]], 120
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[S]], 16
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+start:
+  %mul = mul i8 %s, %s
+  %cond = icmp ule i8 %mul, 120
+  call void @llvm.assume(i1 %cond)
+  %cmp = icmp ult i8 %s, 16
+  ret i1 %cmp
+}
+
+; negative test: multiply is not a square.
+define i1 @assume_mul_nuw_not_square(i8 %s, i8 %t) {
+; CHECK-LABEL: @assume_mul_nuw_not_square(
+; CHECK-NEXT:  start:
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i8 [[S:%.*]], [[T:%.*]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ule i8 [[MUL]], 120
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[S]], 16
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+start:
+  %mul = mul nuw i8 %s, %t
+  %cond = icmp ule i8 %mul, 120
+  call void @llvm.assume(i1 %cond)
+  %cmp = icmp ult i8 %s, 16
+  ret i1 %cmp
+}

>From 3566efe0f669623c5eaabdd7f2fd4d8b9a4dde6a Mon Sep 17 00:00:00 2001
From: Ken Matsui <github at kmts.me>
Date: Fri, 19 Dec 2025 19:48:54 -0500
Subject: [PATCH 2/2] [PredicateInfo] Infer operand bound from mul nuw square
 predicates

A mul nuw X, X used in an assume/branch condition cannot overflow (or
the condition would be poison, which is UB for assumes and control
flow), which implies:

  X < 2^ceil(bitwidth(X)/2) (e.g., i16: X < 256).
---
 llvm/lib/Transforms/Utils/PredicateInfo.cpp   | 27 +++++++++++++++++++
 .../Transforms/SCCP/assume-mul-nuw-square.ll  | 12 +++------
 2 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/PredicateInfo.cpp b/llvm/lib/Transforms/Utils/PredicateInfo.cpp
index 27fed7340411b..8ccffdba0466f 100644
--- a/llvm/lib/Transforms/Utils/PredicateInfo.cpp
+++ b/llvm/lib/Transforms/Utils/PredicateInfo.cpp
@@ -345,6 +345,14 @@ void collectCmpOps(CmpInst *Comparison, SmallVectorImpl<Value *> &CmpOperands) {
 
   CmpOperands.push_back(Op0);
   CmpOperands.push_back(Op1);
+
+  auto AddSquareOp = [&CmpOperands](Value *V) {
+    Value *SquareOp;
+    if (match(V, m_NUWMul(m_Value(SquareOp), m_Deferred(SquareOp))))
+      CmpOperands.push_back(SquareOp);
+  };
+  AddSquareOp(Op0);
+  AddSquareOp(Op1);
 }
 
 // Add Op, PB to the list of value infos for Op, and mark Op to be renamed.
@@ -735,6 +743,25 @@ std::optional<PredicateConstraint> PredicateBase::getConstraint() const {
       return std::nullopt;
     }
 
+    // A `mul nuw RenamedOp, RenamedOp` used by the condition cannot overflow
+    // (or the condition would be poison, which is UB for assumes and control
+    // flow), which implies `RenamedOp < 2^ceil(bitwidth(RenamedOp)/2)`
+    // (e.g., i16: `RenamedOp < 256`).
+    auto IsNuwSquareOfRenamedOp = [this](Value *V) {
+      return match(V, m_NUWMul(m_Specific(RenamedOp), m_Specific(RenamedOp)));
+    };
+    if (RenamedOp->getType()->isIntegerTy() &&
+        (IsNuwSquareOfRenamedOp(Cmp->getOperand(0)) ||
+         IsNuwSquareOfRenamedOp(Cmp->getOperand(1)))) {
+      unsigned BitWidth = RenamedOp->getType()->getScalarSizeInBits();
+      unsigned LimitBits = (BitWidth + 1) / 2;
+      if (LimitBits < BitWidth) {
+        APInt Upper = APInt::getOneBitSet(BitWidth, LimitBits);
+        return {
+            {CmpInst::ICMP_ULT, ConstantInt::get(RenamedOp->getType(), Upper)}};
+      }
+    }
+
     CmpInst::Predicate Pred;
     Value *OtherOp;
     if (Cmp->getOperand(0) == RenamedOp) {
diff --git a/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll b/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll
index e358571c32a8f..7e9ed0dd73028 100644
--- a/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll
+++ b/llvm/test/Transforms/SCCP/assume-mul-nuw-square.ll
@@ -9,8 +9,7 @@ define i1 @assume_mul_nuw_square_i8(i8 %s) {
 ; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i8 [[S:%.*]], [[S]]
 ; CHECK-NEXT:    [[COND:%.*]] = icmp ule i8 [[MUL]], 120
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[S]], 16
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
 start:
   %mul = mul nuw i8 %s, %s
@@ -26,8 +25,7 @@ define i1 @assume_mul_nuw_square_i5(i5 %s) {
 ; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i5 [[S:%.*]], [[S]]
 ; CHECK-NEXT:    [[COND:%.*]] = icmp ult i5 [[MUL]], 15
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i5 [[S]], 8
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
 start:
   %mul = mul nuw i5 %s, %s
@@ -44,11 +42,9 @@ define i1 @branch_mul_nuw_square(i8 %s, i8 %num) {
 ; CHECK-NEXT:    [[COND:%.*]] = icmp ule i8 [[MUL]], [[NUM:%.*]]
 ; CHECK-NEXT:    br i1 [[COND]], label [[TRUE:%.*]], label [[FALSE:%.*]]
 ; CHECK:       true:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[S]], 16
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ; CHECK:       false:
-; CHECK-NEXT:    [[CMP2:%.*]] = icmp ult i8 [[S]], 16
-; CHECK-NEXT:    ret i1 [[CMP2]]
+; CHECK-NEXT:    ret i1 true
 ;
 entry:
   %mul = mul nuw i8 %s, %s



More information about the llvm-commits mailing list