[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