[llvm] [InstCombine] Fold constant-LHS fsub of integer-to-float casts (PR #213519)

Batu Guan via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 2 02:33:54 PDT 2026


https://github.com/AsparticGuan created https://github.com/llvm/llvm-project/pull/213519

Fixes #213517.

## Summary

- Extend `foldFBinOpOfIntCasts` to handle `fsub C, (s|u)itofp(X)`.
- Preserve the existing exactness and integer-overflow checks.
- Add positive and negative InstCombine regression coverage.

## Motivation

This pattern occurs in [FFmpeg's AMR-WB decoder](https://github.com/FFmpeg/FFmpeg/blob/a7e72069f15efbef1e25b25d35c4e0511b43262e/libavcodec/amrwbdec.c#L915-L917):

```c
for (i = 0; i < AMRWB_SFR_SIZE_16k; i++)
    hb_exc[i] = 32768.0 - (uint16_t) av_lfg_get(&ctx->prng);
```

The relevant optimized IR contains:

```llvm
%x = and i32 %x_in, 65535
%xf = uitofp i32 %x to float
%r = fsub nsz float 32768.0, %xf
```

With this change, InstCombine produces:

```llvm
%x = and i32 %x_in, 65535
%sub = sub nsw i32 32768, %x
%r = sitofp i32 %sub to float
```

The regression tests also verify that the fold is rejected when the floating-point constant is non-integral or when the integer subtraction may overflow.

## Testing

- `ninja -C llvm-pr-c2da-build check-llvm-transforms-instcombine`
- 1728 passed, 124 unsupported, 0 failed
- Assertions-enabled build

## AI usage

The initial implementation was generated with AI assistance. It was subsequently refined, reviewed, rebased onto the latest LLVM `main`, and validated against the complete InstCombine test suite.


>From 2d8e234a52e45d717925085f8e028f6d30cbe54f Mon Sep 17 00:00:00 2001
From: AsparticGuan <124558084+AsparticGuan at users.noreply.github.com>
Date: Sun, 2 Aug 2026 17:26:22 +0800
Subject: [PATCH] [InstCombine] Fold constant-LHS fsub of integer-to-float
 casts

Extend foldFBinOpOfIntCasts to handle fsub with a constant left operand when the floating-point constant and integer-to-float conversion are exact and the integer subtraction cannot overflow.

This handles patterns such as the one found in FFmpeg AMR-WB decoding, where 32768.0f - uitofp(X) can be folded to sitofp(32768 - X) for X in [0, 65535]. Add regression coverage for the successful fold, a non-integral constant, and a potentially overflowing subtraction.

The initial implementation was generated with AI assistance and subsequently refined and validated against the InstCombine test suite.
---
 .../InstCombine/InstCombineInternal.h         |  3 +-
 .../InstCombine/InstructionCombining.cpp      | 63 +++++++++++--------
 .../Transforms/InstCombine/binop-itofp.ll     | 38 +++++++++++
 3 files changed, 76 insertions(+), 28 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..3920b8fc1f979 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -381,7 +381,8 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
   // Should only be called by `foldFBinOpOfIntCasts`.
   Instruction *foldFBinOpOfIntCastsFromSign(
       BinaryOperator &BO, bool OpsFromSigned, std::array<Value *, 2> IntOps,
-      Constant *Op1FpC, SmallVectorImpl<WithCache<const Value *>> &OpsKnown);
+      Constant *FpC, unsigned FpCOp,
+      SmallVectorImpl<WithCache<const Value *>> &OpsKnown);
   Instruction *foldBinopOfSextBoolToSelect(BinaryOperator &I);
   Instruction *narrowBinOp(TruncInst &Trunc);
   Instruction *narrowMaskedBinOp(BinaryOperator &And);
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 2663510efed35..3d7fbda32c6ab 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -1529,14 +1529,17 @@ Value *InstCombinerImpl::dyn_castNegVal(Value *V) const {
 //        -> ({s|u}itofp (int_binop x, y))
 //    2) (fp_binop ({s|u}itofp x), FpC)
 //        -> ({s|u}itofp (int_binop x, (fpto{s|u}i FpC)))
+//    3) (fsub FpC, ({s|u}itofp x))
+//        -> ({s|u}itofp (int_sub (fpto{s|u}i FpC), x))
 //
 // Assuming the sign of the cast for x/y is `OpsFromSigned`.
 Instruction *InstCombinerImpl::foldFBinOpOfIntCastsFromSign(
     BinaryOperator &BO, bool OpsFromSigned, std::array<Value *, 2> IntOps,
-    Constant *Op1FpC, SmallVectorImpl<WithCache<const Value *>> &OpsKnown) {
+    Constant *FpC, unsigned FpCOp,
+    SmallVectorImpl<WithCache<const Value *>> &OpsKnown) {
 
   Type *FPTy = BO.getType();
-  Type *IntTy = IntOps[0]->getType();
+  Type *IntTy = IntOps[FpC ? 1 - FpCOp : 0]->getType();
 
   unsigned IntSz = IntTy->getScalarSizeInBits();
   // This is the maximum number of inuse bits by the integer where the int -> fp
@@ -1602,37 +1605,34 @@ Instruction *InstCombinerImpl::foldFBinOpOfIntCastsFromSign(
            IsNonZero(OpNo);
   };
 
-  // If we have a constant rhs, see if we can losslessly convert it to an int.
-  if (Op1FpC != nullptr) {
+  // If we have a constant operand, see if we can losslessly convert it to an
+  // int.
+  if (FpC != nullptr) {
     // Signed + Mul req non-zero
     if (OpsFromSigned && BO.getOpcode() == Instruction::FMul &&
-        !match(Op1FpC, m_NonZeroFP()))
+        !match(FpC, m_NonZeroFP()))
       return nullptr;
 
-    Constant *Op1IntC = ConstantFoldCastOperand(
-        OpsFromSigned ? Instruction::FPToSI : Instruction::FPToUI, Op1FpC,
-        IntTy, DL);
-    if (Op1IntC == nullptr)
+    Constant *IntC = ConstantFoldCastOperand(
+        OpsFromSigned ? Instruction::FPToSI : Instruction::FPToUI, FpC, IntTy,
+        DL);
+    if (IntC == nullptr)
       return nullptr;
     if (ConstantFoldCastOperand(OpsFromSigned ? Instruction::SIToFP
                                               : Instruction::UIToFP,
-                                Op1IntC, FPTy, DL) != Op1FpC)
+                                IntC, FPTy, DL) != FpC)
       return nullptr;
 
-    // First try to keep sign of cast the same.
-    IntOps[1] = Op1IntC;
+    IntOps[FpCOp] = IntC;
   }
 
   // Ensure lhs/rhs integer types match.
   if (IntTy != IntOps[1]->getType())
     return nullptr;
 
-  if (Op1FpC == nullptr) {
-    if (!IsValidPromotion(1))
+  for (unsigned OpNo = 0; OpNo != 2; ++OpNo)
+    if ((!FpC || OpNo != FpCOp) && !IsValidPromotion(OpNo))
       return nullptr;
-  }
-  if (!IsValidPromotion(0))
-    return nullptr;
 
   // Final we check if the integer version of the binop will not overflow.
   BinaryOperator::BinaryOps IntOpc;
@@ -1691,6 +1691,8 @@ Instruction *InstCombinerImpl::foldFBinOpOfIntCastsFromSign(
 //        -> ({s|u}itofp (int_binop x, y))
 //    2) (fp_binop ({s|u}itofp x), FpC)
 //        -> ({s|u}itofp (int_binop x, (fpto{s|u}i FpC)))
+//    3) (fsub FpC, ({s|u}itofp x))
+//        -> ({s|u}itofp (int_sub (fpto{s|u}i FpC), x))
 Instruction *InstCombinerImpl::foldFBinOpOfIntCasts(BinaryOperator &BO) {
   // Don't perform the fold on vectors, as the integer operation may be much
   // more expensive than the float operation in that case.
@@ -1698,16 +1700,23 @@ Instruction *InstCombinerImpl::foldFBinOpOfIntCasts(BinaryOperator &BO) {
     return nullptr;
 
   std::array<Value *, 2> IntOps = {nullptr, nullptr};
-  Constant *Op1FpC = nullptr;
+  Constant *FpC = nullptr;
+  unsigned FpCOp = 1;
   // Check for:
   //    1) (binop ({s|u}itofp x), ({s|u}itofp y))
   //    2) (binop ({s|u}itofp x), FpC)
-  if (!match(BO.getOperand(0), m_IToFP(m_Value(IntOps[0]))))
-    return nullptr;
-
-  if (!match(BO.getOperand(1), m_Constant(Op1FpC)) &&
-      !match(BO.getOperand(1), m_IToFP(m_Value(IntOps[1]))))
+  //    3) (fsub FpC, ({s|u}itofp y))
+  if (match(BO.getOperand(0), m_IToFP(m_Value(IntOps[0])))) {
+    if (!match(BO.getOperand(1), m_Constant(FpC)) &&
+        !match(BO.getOperand(1), m_IToFP(m_Value(IntOps[1]))))
+      return nullptr;
+  } else if (BO.getOpcode() == Instruction::FSub &&
+             match(BO.getOperand(0), m_Constant(FpC)) &&
+             match(BO.getOperand(1), m_IToFP(m_Value(IntOps[1])))) {
+    FpCOp = 0;
+  } else {
     return nullptr;
+  }
 
   // Cache KnownBits a bit to potentially save some analysis.
   SmallVector<WithCache<const Value *>, 2> OpsKnown = {IntOps[0], IntOps[1]};
@@ -1715,11 +1724,11 @@ Instruction *InstCombinerImpl::foldFBinOpOfIntCasts(BinaryOperator &BO) {
   // Try treating x/y as coming from both `uitofp` and `sitofp`. There are
   // different constraints depending on the sign of the cast.
   // NB: `(uitofp nneg X)` == `(sitofp nneg X)`.
-  if (Instruction *R = foldFBinOpOfIntCastsFromSign(BO, /*OpsFromSigned=*/false,
-                                                    IntOps, Op1FpC, OpsKnown))
+  if (Instruction *R = foldFBinOpOfIntCastsFromSign(
+          BO, /*OpsFromSigned=*/false, IntOps, FpC, FpCOp, OpsKnown))
     return R;
-  return foldFBinOpOfIntCastsFromSign(BO, /*OpsFromSigned=*/true, IntOps,
-                                      Op1FpC, OpsKnown);
+  return foldFBinOpOfIntCastsFromSign(BO, /*OpsFromSigned=*/true, IntOps, FpC,
+                                      FpCOp, OpsKnown);
 }
 
 /// A binop with a constant operand and a sign-extended boolean operand may be
diff --git a/llvm/test/Transforms/InstCombine/binop-itofp.ll b/llvm/test/Transforms/InstCombine/binop-itofp.ll
index d4f57026174b6..645afb212c92b 100644
--- a/llvm/test/Transforms/InstCombine/binop-itofp.ll
+++ b/llvm/test/Transforms/InstCombine/binop-itofp.ll
@@ -161,6 +161,44 @@ define half @test_ui_ui_i8_sub_C_fail_overflow(i8 noundef %x_in) {
   ret half %r
 }
 
+; This is based on code from FFmpeg's AMR-WB decoder.
+define float @test_C_sub_ui_i32(i32 %x_in) {
+; CHECK-LABEL: @test_C_sub_ui_i32(
+; CHECK-NEXT:    [[X:%.*]] = and i32 [[X_IN:%.*]], 65535
+; CHECK-NEXT:    [[TMP1:%.*]] = sub nsw i32 32768, [[X]]
+; CHECK-NEXT:    [[R:%.*]] = sitofp i32 [[TMP1]] to float
+; CHECK-NEXT:    ret float [[R]]
+;
+  %x = and i32 %x_in, 65535
+  %xf = uitofp i32 %x to float
+  %r = fsub nsz float 32768.0, %xf
+  ret float %r
+}
+
+define float @test_C_sub_ui_i32_fail_no_repr(i32 %x_in) {
+; CHECK-LABEL: @test_C_sub_ui_i32_fail_no_repr(
+; CHECK-NEXT:    [[X:%.*]] = and i32 [[X_IN:%.*]], 65535
+; CHECK-NEXT:    [[XF:%.*]] = uitofp nneg i32 [[X]] to float
+; CHECK-NEXT:    [[R:%.*]] = fsub float 3.276850e+04, [[XF]]
+; CHECK-NEXT:    ret float [[R]]
+;
+  %x = and i32 %x_in, 65535
+  %xf = uitofp i32 %x to float
+  %r = fsub float 32768.5, %xf
+  ret float %r
+}
+
+define half @test_C_sub_si_i8_fail_overflow(i8 %x) {
+; CHECK-LABEL: @test_C_sub_si_i8_fail_overflow(
+; CHECK-NEXT:    [[XF:%.*]] = sitofp i8 [[X:%.*]] to half
+; CHECK-NEXT:    [[R:%.*]] = fsub half 1.270000e+02, [[XF]]
+; CHECK-NEXT:    ret half [[R]]
+;
+  %xf = sitofp i8 %x to half
+  %r = fsub half 127.0, %xf
+  ret half %r
+}
+
 define half @test_si_si_i8_sub(i8 noundef %x_in, i8 noundef %y_in) {
 ; CHECK-LABEL: @test_si_si_i8_sub(
 ; CHECK-NEXT:    [[X:%.*]] = and i8 [[X_IN:%.*]], 63



More information about the llvm-commits mailing list