[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