Skip to content

[InstCombine] Generalize (A + 1) + ~B fold to any constant#188271

Merged
pfusik merged 17 commits intollvm:mainfrom
pfusik:add-c-add-not
Apr 10, 2026
Merged

[InstCombine] Generalize (A + 1) + ~B fold to any constant#188271
pfusik merged 17 commits intollvm:mainfrom
pfusik:add-c-add-not

Conversation

@pfusik
Copy link
Copy Markdown
Contributor

@pfusik pfusik commented Mar 24, 2026

Example:

int foo(int a, int b) { return a - 1 + ~b; }

Before, on AArch64:

mvn w8, w1
add w8, w0, w8
sub w0, w8, #1

After (matches gcc):

sub w0, w0, w1
sub w0, w0, #2

Proof: https://alive2.llvm.org/ce/z/g_bV01

pfusik added 2 commits March 24, 2026 14:38
Example:

    int foo(int a, int b) { return a - 1 + ~b; }

Before, on AArch64:

    mvn w8, w1
    add w8, w0, w8
    sub w0, w8, llvm#1

After (matches gcc):

    sub w0, w0, w1
    sub w0, w0, llvm#2
@pfusik pfusik requested a review from nikic as a code owner March 24, 2026 15:54
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Mar 24, 2026
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Mar 24, 2026

@llvm/pr-subscribers-llvm-transforms

Author: Piotr Fusik (pfusik)

Changes

Example:

int foo(int a, int b) { return a - 1 + ~b; }

Before, on AArch64:

mvn w8, w1
add w8, w0, w8
sub w0, w8, #<!-- -->1

After (matches gcc):

sub w0, w0, w1
sub w0, w0, #<!-- -->2

Full diff: https://github.com/llvm/llvm-project/pull/188271.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp (+14-7)
  • (modified) llvm/test/Transforms/InstCombine/add.ll (+61)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index c781c6978b275..ae9b66dd5a6f8 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1604,13 +1604,20 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
   if (Value *V = checkForNegativeOperand(I, Builder))
     return replaceInstUsesWith(I, V);
 
-  // (A + 1) + ~B --> A - B
-  // ~B + (A + 1) --> A - B
-  // (~B + A) + 1 --> A - B
-  // (A + ~B) + 1 --> A - B
-  if (match(&I, m_c_BinOp(m_Add(m_Value(A), m_One()), m_Not(m_Value(B)))) ||
-      match(&I, m_BinOp(m_c_Add(m_Not(m_Value(B)), m_Value(A)), m_One())))
-    return BinaryOperator::CreateSub(A, B);
+  {
+    // (A + C) + ~B --> A - B + (C-1)
+    // ~B + (A + C) --> A - B + (C-1)
+    // (~B + A) + C --> A - B + (C-1)
+    // (A + ~B) + C --> A - B + (C-1)
+    const APInt *C;
+    if (match(&I,
+              m_c_BinOp(m_Add(m_Value(A), m_APInt(C)), m_Not(m_Value(B)))) ||
+        match(&I,
+              m_BinOp(m_c_Add(m_Not(m_Value(B)), m_Value(A)), m_APInt(C)))) {
+      Value *Sub = Builder.CreateSub(A, B);
+      return BinaryOperator::CreateAdd(Sub, ConstantInt::get(Ty, *C - 1));
+    }
+  }
 
   // (A + RHS) + RHS --> A + (RHS << 1)
   if (match(LHS, m_OneUse(m_c_Add(m_Value(A), m_Specific(RHS)))))
diff --git a/llvm/test/Transforms/InstCombine/add.ll b/llvm/test/Transforms/InstCombine/add.ll
index aa68dfb540064..1006831fc1e0f 100644
--- a/llvm/test/Transforms/InstCombine/add.ll
+++ b/llvm/test/Transforms/InstCombine/add.ll
@@ -1194,6 +1194,67 @@ define i32 @add_to_sub2(i32 %A, i32 %M) {
   ret i32 %E
 }
 
+define i32 @add_not_add_m1(i32 %a, i32 %b) {
+; CHECK-LABEL: @add_not_add_m1(
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[TMP1]], -2
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %not = xor i32 %b, -1
+  %add = add i32 %a, -1
+  %r = add i32 %add, %not
+  ret i32 %r
+}
+
+define <2 x i32> @add_not_add_m1_vec(<2 x i32> %a, <2 x i32> %b) {
+; CHECK-LABEL: @add_not_add_m1_vec(
+; CHECK-NEXT:    [[TMP1:%.*]] = sub <2 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = add <2 x i32> [[TMP1]], splat (i32 -2)
+; CHECK-NEXT:    ret <2 x i32> [[R]]
+;
+  %not = xor <2 x i32> %b, <i32 -1, i32 -1>
+  %add = add <2 x i32> %a, <i32 -1, i32 -1>
+  %r = add <2 x i32> %add, %not
+  ret <2 x i32> %r
+}
+
+define i32 @add_not_add_m1_commuted(i32 %a, i32 %b) {
+; CHECK-LABEL: @add_not_add_m1_commuted(
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[TMP1]], -2
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %not = xor i32 %b, -1
+  %add = add i32 %a, -1
+  %r = add i32 %not, %add
+  ret i32 %r
+}
+
+define i32 @add_not_add_m1_assoc(i32 %a, i32 %b) {
+; CHECK-LABEL: @add_not_add_m1_assoc(
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[TMP1]], -2
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %not = xor i32 %b, -1
+  %add = add i32 %not, %a
+  %r = add i32 %add, -1
+  ret i32 %r
+}
+
+define i32 @add_not_add_intmin(i32 %a, i32 %b) {
+; CHECK-LABEL: @add_not_add_intmin(
+; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[B:%.*]], -1
+; CHECK-NEXT:    [[ADD:%.*]] = xor i32 [[A:%.*]], -2147483648
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[ADD]], [[NOT]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %not = xor i32 %b, -1
+  %add = add i32 %a, -2147483648
+  %r = add i32 %add, %not
+  ret i32 %r
+}
+
 ; (X | C1) + C2 --> (X | C1) ^ C1 iff (C1 == -C2)
 define i32 @test44(i32 %A) {
 ; CHECK-LABEL: @test44(

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

🐧 Linux x64 Test Results

  • 193400 tests passed
  • 5022 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

🪟 Windows x64 Test Results

  • 133326 tests passed
  • 3080 tests skipped

✅ The build succeeded and all tests passed.

@pfusik pfusik requested review from artagnon and rj-jesus April 8, 2026 13:16
Copy link
Copy Markdown
Contributor

@artagnon artagnon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to include an Alive2 proof?

@artagnon artagnon requested a review from dtcxzyw April 8, 2026 13:30
@pfusik pfusik changed the title [InstCombine] Combine A + C + ~B --> A - B + (C-1) [InstCombine] Generalize (A + 1) + ~B fold to any constant Apr 8, 2026
Copy link
Copy Markdown
Contributor

@artagnon artagnon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, with suggestion for improving clarity, thanks! Kindly wait 24h before landing, to give Yingwei a chance to take a look.

…with `C == 1`

For `C != 1` it would produce two adds with different constants
which is possibly a pessimization.
Copy link
Copy Markdown
Contributor

@rj-jesus rj-jesus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for changes, I've left a few more comments!

Copy link
Copy Markdown
Contributor

@rj-jesus rj-jesus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

There may be cases where C-1 isn't actually free, please keep an eye out for potential regressions.

@pfusik
Copy link
Copy Markdown
Contributor Author

pfusik commented Apr 10, 2026

There may be cases where C-1 isn't actually free

Do you mean add C-1 more costly than add C for certain values of C on certain targets?

@rj-jesus
Copy link
Copy Markdown
Contributor

rj-jesus commented Apr 10, 2026

There may be cases where C-1 isn't actually free

Do you mean add C-1 more costly than add C for certain values of C on certain targets?

Yes, you may not be able to encode or materialise C-1 as easily as C (https://godbolt.org/z/f8bP64xzn). But the opposite is also true, so I don't think this should be much of a concern.

It might also happen that C still needs to be materialised for something else, but I don't think that can be avoided easily.

@pfusik pfusik merged commit c0d4959 into llvm:main Apr 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants