[llvm] [InstCombine] Fold (A & ~B) | (A ^ B) -> A ^ B (PR #171047)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Dec 11 08:11:08 PST 2025
https://github.com/wentywenty updated https://github.com/llvm/llvm-project/pull/171047
>From d729962bd954c3ac45093187f1f9d53e24fe71c1 Mon Sep 17 00:00:00 2001
From: wentywenty <2321901849 at qq.com>
Date: Mon, 8 Dec 2025 01:30:18 +0800
Subject: [PATCH 1/2] [InstCombine] Drop one-use check for (A & ~B) | (A ^ B)
-> A ^ B
This patch removes the m_OneUse constraint for the fold:
(A & ~B) | (A ^ B) -> A ^ B
Previously, this optimization was limited to cases where the operands had
a single use. However, replacing the 'or' with the existing 'xor' operand
is always profitable because it strictly removes one instruction (the 'or'),
regardless of whether the intermediate 'and' or 'xor' are used elsewhere.
Tests have been updated to reflect this multi-use optimization.
Signed-off-by: wentywenty <2321901849 at qq.com>
---
.../InstCombine/InstCombineAndOrXor.cpp | 5 ++
.../Transforms/InstCombine/or-xor-fold.ll | 58 +++++++++++++++++++
2 files changed, 63 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/or-xor-fold.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index ba5568b00441b..821086879339c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -1982,6 +1982,11 @@ static Instruction *foldOrToXor(BinaryOperator &I,
match(Op1, m_c_And(m_Not(m_Specific(A)), m_Specific(B))))
return BinaryOperator::CreateXor(A, B);
+ // (A & ~B) | (A ^ B) -> A ^ B
+ if (match(Op0, m_c_And(m_Value(A), m_Not(m_Value(B)))) &&
+ match(Op1, m_c_Xor(m_Specific(A), m_Specific(B))))
+ return replaceInstUsesWith(I, Op1);
+
return nullptr;
}
diff --git a/llvm/test/Transforms/InstCombine/or-xor-fold.ll b/llvm/test/Transforms/InstCombine/or-xor-fold.ll
new file mode 100644
index 0000000000000..57797a33335b8
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/or-xor-fold.ll
@@ -0,0 +1,58 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @use(i32)
+
+; (A & ~B) | (A ^ B) -> A ^ B
+
+define i32 @test_basic(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_basic(
+; CHECK-NEXT: [[RES:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %not_b = xor i32 %b, -1
+ %and = and i32 %a, %not_b
+ %xor = xor i32 %a, %b
+ %res = or i32 %and, %xor
+ ret i32 %res
+}
+
+define <2 x i32> @test_vector(<2 x i32> %a, <2 x i32> %b) {
+; CHECK-LABEL: @test_vector(
+; CHECK-NEXT: [[RES:%.*]] = xor <2 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT: ret <2 x i32> [[RES]]
+;
+ %not_b = xor <2 x i32> %b, <i32 -1, i32 -1>
+ %and = and <2 x i32> %a, %not_b
+ %xor = xor <2 x i32> %a, %b
+ %res = or <2 x i32> %and, %xor
+ ret <2 x i32> %res
+}
+
+define i32 @test_commute(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_commute(
+; CHECK-NEXT: [[RES:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %not_b = xor i32 %b, -1
+ %and = and i32 %a, %not_b
+ %xor = xor i32 %a, %b
+ %res = or i32 %xor, %and ;
+ ret i32 %res
+}
+
+define i32 @test_multiuse(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_multiuse(
+; CHECK-NEXT: [[NOT_B:%.*]] = xor i32 [[B:%.*]], -1
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], [[NOT_B]]
+; CHECK-NEXT: call void @use(i32 [[AND]])
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[XOR]]
+;
+ %not_b = xor i32 %b, -1
+ %and = and i32 %a, %not_b
+ call void @use(i32 %and)
+ %xor = xor i32 %a, %b
+ %res = or i32 %and, %xor
+ ret i32 %res
+}
>From 5819cc6d58acda1e3edb8d41608ed70ecc4c58d2 Mon Sep 17 00:00:00 2001
From: wentywenty <2321901849 at qq.com>
Date: Fri, 12 Dec 2025 00:10:38 +0800
Subject: [PATCH 2/2] [InstCombine] Add constant folding for hypot library
calls
This patch adds constant folding optimization for hypot(x, y) when both
arguments are constant values.
Implementation details:
- Only supports float and double precision, as std::hypot lacks support
for extended precision types (fp128, x86_fp80, ppc_fp128, fp16)
- Respects errno semantics: folds when the function is marked as not
accessing memory (intrinsic), fast-math is enabled, or when the result
is finite / inputs are non-finite (hypot only sets errno on overflow
with finite inputs)
- Follows the same pattern as other LibCall optimizations like remquo
The optimization handles:
- Basic cases: hypot(3.0, 4.0) -> 5.0
- Special values: Inf and NaN propagation
- Overflow: preserves calls in strict mode, folds in fast-math mode
- Strict FP mode: preserves calls to maintain FP exception semantics
Tests cover all major scenarios including precision handling, special
values, overflow behavior, and strictfp mode.
Signed-off-by: wentywenty <2321901849 at qq.com>
---
.../llvm/Transforms/Utils/SimplifyLibCalls.h | 1 +
.../lib/Transforms/Utils/SimplifyLibCalls.cpp | 38 ++++++++
llvm/test/Transforms/InstCombine/hypot.ll | 89 +++++++++++++++++++
3 files changed, 128 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/hypot.ll
diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
index 64d2512308935..7de7ff52c6ac2 100644
--- a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
+++ b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
@@ -215,6 +215,7 @@ class LibCallSimplifier {
Value *optimizeSymmetric(CallInst *CI, LibFunc Func, IRBuilderBase &B);
Value *optimizeRemquo(CallInst *CI, IRBuilderBase &B);
Value *optimizeFdim(CallInst *CI, IRBuilderBase &B);
+ Value *optimizeHypot(CallInst *CI, IRBuilderBase &B);
// Wrapper for all floating point library call optimizations
Value *optimizeFloatingPointLibCall(CallInst *CI, LibFunc Func,
IRBuilderBase &B);
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index c3537f544c432..ee55296863a95 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -3215,6 +3215,40 @@ Value *LibCallSimplifier::optimizeFdim(CallInst *CI, IRBuilderBase &B) {
return ConstantFP::get(CI->getType(), MaxVal);
}
+/// Constant folds hypot
+Value *LibCallSimplifier::optimizeHypot(CallInst *CI, IRBuilderBase &B) {
+
+ const APFloat *X, *Y;
+ if (!match(CI->getArgOperand(0), m_APFloat(X)) ||
+ !match(CI->getArgOperand(1), m_APFloat(Y))) {
+ return nullptr;
+ }
+ Type *Ty = CI->getType();
+ APFloat Res(X->getSemantics());
+
+ // Only support float and double, as std::hypot doesn't support extended precision types.
+ if (Ty->isFloatTy())
+ Res = APFloat(std::hypot(X->convertToFloat(), Y->convertToFloat()));
+ else if(Ty->isDoubleTy())
+ Res = APFloat(std::hypot(X->convertToDouble(), Y->convertToDouble()));
+ else {
+ return nullptr;
+ }
+
+ // We can fold in the following cases:
+ // 1. Function doesn't access memory (intrinsic, no errno).
+ // 2. Fast-math is enabled (ignore errno).
+ if (CI->doesNotAccessMemory() || CI->isFast())
+ return ConstantFP::get(Ty, Res);
+
+ // 3. Result is finite OR inputs are non-finite (won't set errno).
+ // hypot only sets errno on overflow with finite inputs.
+ if (Res.isFinite() || !X->isFinite() || !Y->isFinite())
+ return ConstantFP::get(Ty, Res);
+
+ return nullptr;
+}
+
//===----------------------------------------------------------------------===//
// Integer Library Call Optimizations
//===----------------------------------------------------------------------===//
@@ -4140,6 +4174,10 @@ Value *LibCallSimplifier::optimizeFloatingPointLibCall(CallInst *CI,
case LibFunc_fdimf:
case LibFunc_fdiml:
return optimizeFdim(CI, Builder);
+ case LibFunc_hypot:
+ case LibFunc_hypotf:
+ case LibFunc_hypotl:
+ return optimizeHypot(CI, Builder);
case LibFunc_fminf:
case LibFunc_fmin:
case LibFunc_fminl:
diff --git a/llvm/test/Transforms/InstCombine/hypot.ll b/llvm/test/Transforms/InstCombine/hypot.ll
new file mode 100644
index 0000000000000..4c0c5c82d89c0
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/hypot.ll
@@ -0,0 +1,89 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare double @hypot(double, double)
+declare float @hypotf(float, float)
+
+; Basic folding: Double precision
+define double @test_hypot_double_basic() {
+; CHECK-LABEL: @test_hypot_double_basic(
+; CHECK-NEXT: ret double 5.000000e+00
+;
+ %call = call double @hypot(double 3.000000e+00, double 4.000000e+00)
+ ret double %call
+}
+
+; Basic folding: Float precision
+define float @test_hypot_float_basic() {
+; CHECK-LABEL: @test_hypot_float_basic(
+; CHECK-NEXT: ret float 5.000000e+00
+;
+ %call = call float @hypotf(float 3.000000e+00, float 4.000000e+00)
+ ret float %call
+}
+
+; Special value: Infinity (doesn't set errno, should fold)
+define double @test_hypot_inf() {
+; CHECK-LABEL: @test_hypot_inf(
+; CHECK-NEXT: ret double 0x7FF0000000000000
+;
+ %call = call double @hypot(double 0x7FF0000000000000, double 3.000000e+00)
+ ret double %call
+}
+
+; Special value: NaN (should propagate NaN)
+define double @test_hypot_nan() {
+; CHECK-LABEL: @test_hypot_nan(
+; CHECK-NEXT: ret double 0x7FF8000000000000
+;
+ %call = call double @hypot(double 0x7FF8000000000000, double 3.000000e+00)
+ ret double %call
+}
+
+; Overflow: Strict mode (errno-sensitive, should not fold)
+define double @test_hypot_overflow_strict() {
+; CHECK-LABEL: @test_hypot_overflow_strict(
+; CHECK-NEXT: [[CALL:%.*]] = call double @hypot(double 0x7FEFFFFFFFFFFFFF, double 0x7FEFFFFFFFFFFFFF)
+; CHECK-NEXT: ret double [[CALL]]
+;
+ %call = call double @hypot(double 0x7FEFFFFFFFFFFFFF, double 0x7FEFFFFFFFFFFFFF)
+ ret double %call
+}
+
+; Overflow: Fast-math mode (ignore errno, force folding)
+define double @test_hypot_overflow_fast() {
+; CHECK-LABEL: @test_hypot_overflow_fast(
+; CHECK-NEXT: ret double 0x7FF0000000000000
+;
+ %call = call fast double @hypot(double 0x7FEFFFFFFFFFFFFF, double 0x7FEFFFFFFFFFFFFF)
+ ret double %call
+}
+
+; Non-constant argument (should not fold)
+define double @test_hypot_non_constant(double %x) {
+; CHECK-LABEL: @test_hypot_non_constant(
+; CHECK-NEXT: [[CALL:%.*]] = call double @hypot(double [[X:%.*]], double 4.000000e+00)
+; CHECK-NEXT: ret double [[CALL]]
+;
+ %call = call double @hypot(double %x, double 4.000000e+00)
+ ret double %call
+}
+
+; Strict FP mode (should not fold even for valid constants)
+define double @test_hypot_strictfp() strictfp {
+; CHECK-LABEL: @test_hypot_strictfp(
+; CHECK-NEXT: [[CALL:%.*]] = call double @hypot(double 3.000000e+00, double 4.000000e+00) #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: ret double [[CALL]]
+;
+ %call = call double @hypot(double 3.000000e+00, double 4.000000e+00) strictfp
+ ret double %call
+}
+
+; Zero values (should fold: hypot(0,0) = 0)
+define double @test_hypot_zero() {
+; CHECK-LABEL: @test_hypot_zero(
+; CHECK-NEXT: ret double 0.000000e+00
+;
+ %call = call double @hypot(double 0.000000e+00, double 0.000000e+00)
+ ret double %call
+}
\ No newline at end of file
More information about the llvm-commits
mailing list