[llvm] Add AggressiveInstCombine test for trunc‑sdiv‑srem and implement its handling (PR #222391)
Laxman Gupta via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 12 12:26:25 PDT 2026
https://github.com/laxman-gupta1006 updated https://github.com/llvm/llvm-project/pull/222391
>From 9914c50d5d491ddbf39665dfb7fd3f5b6deeae4d Mon Sep 17 00:00:00 2001
From: laxman-gupta1006 <laxman.gupta1006 at gmail.com>
Date: Wed, 9 Sep 2026 22:35:24 +0530
Subject: [PATCH] Add AggressiveInstCombine test for trunc_sdivrem and update
TruncInstCombine implementation
---
.../TruncInstCombine.cpp | 32 ++-
.../AggressiveInstCombine/trunc_sdivrem.ll | 218 ++++++++++++++++++
2 files changed, 246 insertions(+), 4 deletions(-)
create mode 100644 llvm/test/Transforms/AggressiveInstCombine/trunc_sdivrem.ll
diff --git a/llvm/lib/Transforms/AggressiveInstCombine/TruncInstCombine.cpp b/llvm/lib/Transforms/AggressiveInstCombine/TruncInstCombine.cpp
index 4b29e33c2e7f7..ad438cf3495fa 100644
--- a/llvm/lib/Transforms/AggressiveInstCombine/TruncInstCombine.cpp
+++ b/llvm/lib/Transforms/AggressiveInstCombine/TruncInstCombine.cpp
@@ -64,6 +64,8 @@ static bool isRelevantOperand(const Instruction *I, unsigned OpNo) {
case Instruction::AShr:
case Instruction::UDiv:
case Instruction::URem:
+ case Instruction::SDiv:
+ case Instruction::SRem:
return true;
case Instruction::InsertElement:
return OpNo < 2;
@@ -147,6 +149,8 @@ bool TruncInstCombine::buildTruncExpressionGraph() {
case Instruction::AShr:
case Instruction::UDiv:
case Instruction::URem:
+ case Instruction::SDiv:
+ case Instruction::SRem:
case Instruction::InsertElement:
case Instruction::ExtractElement:
case Instruction::Select:
@@ -166,9 +170,6 @@ bool TruncInstCombine::buildTruncExpressionGraph() {
break;
}
default:
- // TODO: Can handle more cases here:
- // 1. sdiv, srem
- // ...
return false;
}
}
@@ -343,6 +344,27 @@ Type *TruncInstCombine::getBestTruncatedType() {
}
Itr.second.MinBitWidth = MinBitWidth;
}
+ if (I->getOpcode() == Instruction::SDiv ||
+ I->getOpcode() == Instruction::SRem) {
+ unsigned NumSignBits0 = ComputeNumSignBits(I->getOperand(0));
+ unsigned NumSignBits1 = ComputeNumSignBits(I->getOperand(1));
+ unsigned MinBitWidthOp0 = OrigBitWidth - NumSignBits0 + 1;
+ unsigned MinBitWidthOp1 = OrigBitWidth - NumSignBits1 + 1;
+ unsigned MinBitWidth = std::max(MinBitWidthOp0, MinBitWidthOp1);
+
+ // In two's complement, sdiv and srem produce UB / poison on INT_MIN / -1.
+ // If LHS can be negative and RHS can be -1, we must ensure the reduced
+ // type has at least MinBitWidthOp0 + 1 bits so that LHS cannot be INT_MIN
+ // in the reduced type.
+ KnownBits KnownLHS = computeKnownBits(I->getOperand(0));
+ KnownBits KnownRHS = computeKnownBits(I->getOperand(1));
+ if (!KnownLHS.isNonNegative() && KnownRHS.Zero.isZero())
+ MinBitWidth = std::max(MinBitWidth, MinBitWidthOp0 + 1);
+
+ if (MinBitWidth >= OrigBitWidth)
+ return nullptr;
+ Itr.second.MinBitWidth = MinBitWidth;
+ }
}
// Calculate minimum allowed bit-width allowed for shrinking the currently
@@ -437,7 +459,9 @@ void TruncInstCombine::ReduceExpressionGraph(Type *SclTy) {
case Instruction::LShr:
case Instruction::AShr:
case Instruction::UDiv:
- case Instruction::URem: {
+ case Instruction::URem:
+ case Instruction::SDiv:
+ case Instruction::SRem: {
Value *LHS = getReducedOperand(I->getOperand(0), SclTy);
Value *RHS = getReducedOperand(I->getOperand(1), SclTy);
Res = Builder.CreateBinOp((Instruction::BinaryOps)Opc, LHS, RHS);
diff --git a/llvm/test/Transforms/AggressiveInstCombine/trunc_sdivrem.ll b/llvm/test/Transforms/AggressiveInstCombine/trunc_sdivrem.ll
new file mode 100644
index 0000000000000..747877f6c0acf
--- /dev/null
+++ b/llvm/test/Transforms/AggressiveInstCombine/trunc_sdivrem.ll
@@ -0,0 +1,218 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=aggressive-instcombine -S | FileCheck %s
+
+define i16 @sdiv_one_arg(i8 %x) {
+; CHECK-LABEL: @sdiv_one_arg(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i16 [[SEXT]], 42
+; CHECK-NEXT: ret i16 [[DIV]]
+;
+ %sext = sext i8 %x to i32
+ %div = sdiv i32 %sext, 42
+ %trunc = trunc i32 %div to i16
+ ret i16 %trunc
+}
+
+define i16 @sdiv_negative_const(i8 %x) {
+; CHECK-LABEL: @sdiv_negative_const(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i16 [[SEXT]], -5
+; CHECK-NEXT: ret i16 [[DIV]]
+;
+ %sext = sext i8 %x to i32
+ %div = sdiv i32 %sext, -5
+ %trunc = trunc i32 %div to i16
+ ret i16 %trunc
+}
+
+define i16 @sdiv_two_args_i8(i8 %x, i8 %y) {
+; CHECK-LABEL: @sdiv_two_args_i8(
+; CHECK-NEXT: [[SEXTX:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[SEXTY:%.*]] = sext i8 [[Y:%.*]] to i16
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i16 [[SEXTX]], [[SEXTY]]
+; CHECK-NEXT: ret i16 [[DIV]]
+;
+ %sextx = sext i8 %x to i32
+ %sexty = sext i8 %y to i32
+ %div = sdiv i32 %sextx, %sexty
+ %trunc = trunc i32 %div to i16
+ ret i16 %trunc
+}
+
+define i16 @sdiv_exact(i8 %x, i8 %y) {
+; CHECK-LABEL: @sdiv_exact(
+; CHECK-NEXT: [[SEXTX:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[SEXTY:%.*]] = sext i8 [[Y:%.*]] to i16
+; CHECK-NEXT: [[DIV:%.*]] = sdiv exact i16 [[SEXTX]], [[SEXTY]]
+; CHECK-NEXT: ret i16 [[DIV]]
+;
+ %sextx = sext i8 %x to i32
+ %sexty = sext i8 %y to i32
+ %div = sdiv exact i32 %sextx, %sexty
+ %trunc = trunc i32 %div to i16
+ ret i16 %trunc
+}
+
+; Negative test: divisor could be -1 and dividend INT_MIN_16, requiring 17 bits to avoid overflow
+define i16 @sdiv_two_args_overflow_hazard(i16 %x, i16 %y) {
+; CHECK-LABEL: @sdiv_two_args_overflow_hazard(
+; CHECK-NEXT: [[SEXTX:%.*]] = sext i16 [[X:%.*]] to i32
+; CHECK-NEXT: [[SEXTY:%.*]] = sext i16 [[Y:%.*]] to i32
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i32 [[SEXTX]], [[SEXTY]]
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[DIV]] to i16
+; CHECK-NEXT: ret i16 [[TRUNC]]
+;
+ %sextx = sext i16 %x to i32
+ %sexty = sext i16 %y to i32
+ %div = sdiv i32 %sextx, %sexty
+ %trunc = trunc i32 %div to i16
+ ret i16 %trunc
+}
+
+; Negative test: divisor too large for i16
+define i16 @sdiv_big_const(i8 %x) {
+; CHECK-LABEL: @sdiv_big_const(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i32
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i32 [[SEXT]], 70000
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[DIV]] to i16
+; CHECK-NEXT: ret i16 [[TRUNC]]
+;
+ %sext = sext i8 %x to i32
+ %div = sdiv i32 %sext, 70000
+ %trunc = trunc i32 %div to i16
+ ret i16 %trunc
+}
+
+; Negative test: x could be INT_MIN_8 (-128) and divisor is -1, which would overflow in i8
+define i8 @sdiv_overflow_hazard(i8 %x) {
+; CHECK-LABEL: @sdiv_overflow_hazard(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i32
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i32 [[SEXT]], -1
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[DIV]] to i8
+; CHECK-NEXT: ret i8 [[TRUNC]]
+;
+ %sext = sext i8 %x to i32
+ %div = sdiv i32 %sext, -1
+ %trunc = trunc i32 %div to i8
+ ret i8 %trunc
+}
+
+define <2 x i16> @sdiv_vector(<2 x i8> %x) {
+; CHECK-LABEL: @sdiv_vector(
+; CHECK-NEXT: [[SEXT:%.*]] = sext <2 x i8> [[X:%.*]] to <2 x i16>
+; CHECK-NEXT: [[DIV:%.*]] = sdiv <2 x i16> [[SEXT]], <i16 4, i16 -10>
+; CHECK-NEXT: ret <2 x i16> [[DIV]]
+;
+ %sext = sext <2 x i8> %x to <2 x i32>
+ %div = sdiv <2 x i32> %sext, <i32 4, i32 -10>
+ %trunc = trunc <2 x i32> %div to <2 x i16>
+ ret <2 x i16> %trunc
+}
+
+; Negative test: vector divisor too large for i16
+define <2 x i16> @sdiv_vector_big_const(<2 x i8> %x) {
+; CHECK-LABEL: @sdiv_vector_big_const(
+; CHECK-NEXT: [[SEXT:%.*]] = sext <2 x i8> [[X:%.*]] to <2 x i32>
+; CHECK-NEXT: [[DIV:%.*]] = sdiv <2 x i32> [[SEXT]], <i32 16, i32 70000>
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc <2 x i32> [[DIV]] to <2 x i16>
+; CHECK-NEXT: ret <2 x i16> [[TRUNC]]
+;
+ %sext = sext <2 x i8> %x to <2 x i32>
+ %div = sdiv <2 x i32> %sext, <i32 16, i32 70000>
+ %trunc = trunc <2 x i32> %div to <2 x i16>
+ ret <2 x i16> %trunc
+}
+
+define i16 @srem_one_arg(i8 %x) {
+; CHECK-LABEL: @srem_one_arg(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[REM:%.*]] = srem i16 [[SEXT]], 42
+; CHECK-NEXT: ret i16 [[REM]]
+;
+ %sext = sext i8 %x to i32
+ %rem = srem i32 %sext, 42
+ %trunc = trunc i32 %rem to i16
+ ret i16 %trunc
+}
+
+define i16 @srem_negative_const(i8 %x) {
+; CHECK-LABEL: @srem_negative_const(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[REM:%.*]] = srem i16 [[SEXT]], -7
+; CHECK-NEXT: ret i16 [[REM]]
+;
+ %sext = sext i8 %x to i32
+ %rem = srem i32 %sext, -7
+ %trunc = trunc i32 %rem to i16
+ ret i16 %trunc
+}
+
+define i16 @srem_two_args_i8(i8 %x, i8 %y) {
+; CHECK-LABEL: @srem_two_args_i8(
+; CHECK-NEXT: [[SEXTX:%.*]] = sext i8 [[X:%.*]] to i16
+; CHECK-NEXT: [[SEXTY:%.*]] = sext i8 [[Y:%.*]] to i16
+; CHECK-NEXT: [[REM:%.*]] = srem i16 [[SEXTX]], [[SEXTY]]
+; CHECK-NEXT: ret i16 [[REM]]
+;
+ %sextx = sext i8 %x to i32
+ %sexty = sext i8 %y to i32
+ %rem = srem i32 %sextx, %sexty
+ %trunc = trunc i32 %rem to i16
+ ret i16 %trunc
+}
+
+; Negative test: divisor could be -1 and dividend INT_MIN_16, requiring 17 bits to avoid overflow
+define i16 @srem_two_args_overflow_hazard(i16 %x, i16 %y) {
+; CHECK-LABEL: @srem_two_args_overflow_hazard(
+; CHECK-NEXT: [[SEXTX:%.*]] = sext i16 [[X:%.*]] to i32
+; CHECK-NEXT: [[SEXTY:%.*]] = sext i16 [[Y:%.*]] to i32
+; CHECK-NEXT: [[REM:%.*]] = srem i32 [[SEXTX]], [[SEXTY]]
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[REM]] to i16
+; CHECK-NEXT: ret i16 [[TRUNC]]
+;
+ %sextx = sext i16 %x to i32
+ %sexty = sext i16 %y to i32
+ %rem = srem i32 %sextx, %sexty
+ %trunc = trunc i32 %rem to i16
+ ret i16 %trunc
+}
+
+; Negative test: constant too large for i16
+define i16 @srem_big_const(i8 %x) {
+; CHECK-LABEL: @srem_big_const(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i32
+; CHECK-NEXT: [[REM:%.*]] = srem i32 [[SEXT]], 70000
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[REM]] to i16
+; CHECK-NEXT: ret i16 [[TRUNC]]
+;
+ %sext = sext i8 %x to i32
+ %rem = srem i32 %sext, 70000
+ %trunc = trunc i32 %rem to i16
+ ret i16 %trunc
+}
+
+; Negative test: x could be INT_MIN_8 (-128) and divisor is -1, which would overflow in i8
+define i8 @srem_overflow_hazard(i8 %x) {
+; CHECK-LABEL: @srem_overflow_hazard(
+; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[X:%.*]] to i32
+; CHECK-NEXT: [[REM:%.*]] = srem i32 [[SEXT]], -1
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[REM]] to i8
+; CHECK-NEXT: ret i8 [[TRUNC]]
+;
+ %sext = sext i8 %x to i32
+ %rem = srem i32 %sext, -1
+ %trunc = trunc i32 %rem to i8
+ ret i8 %trunc
+}
+
+define <2 x i16> @srem_vector(<2 x i8> %x) {
+; CHECK-LABEL: @srem_vector(
+; CHECK-NEXT: [[SEXT:%.*]] = sext <2 x i8> [[X:%.*]] to <2 x i16>
+; CHECK-NEXT: [[REM:%.*]] = srem <2 x i16> [[SEXT]], <i16 4, i16 -10>
+; CHECK-NEXT: ret <2 x i16> [[REM]]
+;
+ %sext = sext <2 x i8> %x to <2 x i32>
+ %rem = srem <2 x i32> %sext, <i32 4, i32 -10>
+ %trunc = trunc <2 x i32> %rem to <2 x i16>
+ ret <2 x i16> %trunc
+}
More information about the llvm-commits
mailing list