[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