Skip to content

Conversation

Aethezz
Copy link
Contributor

@Aethezz Aethezz commented Sep 9, 2025

This adds the optimizations to combine adds through zext nneg when adds cancel.

Updated proof: https://alive2.llvm.org/ce/z/T2BmZj
This fixes #157111

@Aethezz Aethezz requested a review from nikic as a code owner September 9, 2025 17:36
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Sep 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 9, 2025

@llvm/pr-subscribers-llvm-transforms

Author: None (Aethezz)

Changes

This adds the optimizations to combine adds through zext nneg when adds cancel.

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

This fixes #157111


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp (+17)
  • (modified) llvm/test/Transforms/InstCombine/zext.ll (+11)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index d934638c15e75..1896bc3d577f3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1910,6 +1910,23 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
   if (Instruction *Res = foldBinOpOfSelectAndCastOfSelectCondition(I))
     return Res;
 
+  // Combine adds through zext nneg:
+  // (zext nneg (X + C1)) + C2 --> zext X if C1 + C2 == 0
+  {
+    Value *X;
+    const APInt *C1, *C2;
+    if (match(&I, m_c_Add(m_NNegZExt(m_Add(m_Value(X), m_APInt(C1))), 
+                          m_APInt(C2)))) {
+      // Check if the constants cancel out (C1 + C2 == 0)
+      APInt Sum = C1->sext(C2->getBitWidth()) + *C2;
+      if (Sum.isZero()) {
+        // The add inside the zext and the outer add cancel out
+        Value *NewZExt = Builder.CreateZExt(X, I.getType());
+        return replaceInstUsesWith(I, NewZExt);
+      }
+    }
+  }
+
   // Re-enqueue users of the induction variable of add recurrence if we infer
   // new nuw/nsw flags.
   if (Changed) {
diff --git a/llvm/test/Transforms/InstCombine/zext.ll b/llvm/test/Transforms/InstCombine/zext.ll
index e4d18e9395219..15aa2aaec430a 100644
--- a/llvm/test/Transforms/InstCombine/zext.ll
+++ b/llvm/test/Transforms/InstCombine/zext.ll
@@ -976,3 +976,14 @@ entry:
   %res = zext nneg i2 %x to i32
   ret i32 %res
 }
+
+define i32 @zext_nneg_add_cancel(i8 %arg) {
+; CHECK-LABEL: @zext_nneg_add_cancel(
+; CHECK-NEXT:    [[ADD2:%.*]] = zext i8 [[ARG:%.*]] to i32
+; CHECK-NEXT:    ret i32 [[ADD2]]
+;
+  %add = add i8 %arg, -2
+  %zext = zext nneg i8 %add to i32
+  %add2 = add i32 %zext, 2
+  ret i32 %add2
+}
\ No newline at end of file

Copy link

github-actions bot commented Sep 9, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
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.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

This is missing the precondition that the inner constant must be negative.

Can you please add a generalized proof? See https://llvm.org/docs/InstCombineContributorGuide.html#use-generic-values-in-proofs.

Also we don't need the constants to exactly cancel, something like this is also profitable: https://alive2.llvm.org/ce/z/pT89U9 (Though this one would need a one-use check.)

@nikic
Copy link
Contributor

nikic commented Sep 17, 2025

Updated proof: https://alive2.llvm.org/ce/z/T2BmZj

This proof is not correct? src and tgt are the same here.

I believe this is a translation of your preconditions: https://alive2.llvm.org/ce/z/nrK53A It shows that they are not correct. Even if the addition result is a signed i8, the fact that the original constant is larger is a problem.

This variant works: https://alive2.llvm.org/ce/z/5geVhA

@Aethezz
Copy link
Contributor Author

Aethezz commented Sep 19, 2025

Updated proof: https://alive2.llvm.org/ce/z/T2BmZj

This proof is not correct? src and tgt are the same here.

I believe this is a translation of your preconditions: https://alive2.llvm.org/ce/z/nrK53A It shows that they are not correct. Even if the addition result is a signed i8, the fact that the original constant is larger is a problem.

This variant works: https://alive2.llvm.org/ce/z/5geVhA

Oh thanks, I didn't export the updated link after editing the proof.

@dtcxzyw
Copy link
Member

dtcxzyw commented Sep 19, 2025

@zyw-bot mfuzz

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

I believe this is a translation of your preconditions: https://alive2.llvm.org/ce/z/nrK53A It shows that they are not correct. Even if the addition result is a signed i8, the fact that the original constant is larger is a problem.

This variant works: https://alive2.llvm.org/ce/z/5geVhA

This issue is still open. To give a specific example, you are currently miscompiling this test case: https://alive2.llvm.org/ce/z/66TJWX

; CHECK-NEXT: [[ZEXT:%.*]] = zext nneg i8 [[ADD]] to i32
; CHECK-NEXT: call void @use32(i32 [[ZEXT]])
; CHECK-NEXT: [[TMP1:%.*]] = add i8 [[ARG]], -1
; CHECK-NEXT: [[ADD2:%.*]] = zext i8 [[TMP1]] to i32
Copy link
Contributor

Choose a reason for hiding this comment

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

This increases the number of instructions. The transform should not be performed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also we don't need the constants to exactly cancel, something like this is also profitable: https://alive2.llvm.org/ce/z/pT89U9 (Though this one would need a one-use check.)

In this case, the multiuse shows that number of instructions increases, so are we supporting the constants not exactly cancelling?

Copy link
Contributor

Choose a reason for hiding this comment

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

We should do this fold, but guarded by a hasOneUse check.

@Aethezz Aethezz requested a review from nikic October 1, 2025 15:09
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

This issue is still open. To give a specific example, you are currently miscompiling this test case: https://alive2.llvm.org/ce/z/66TJWX

Can you please also add a test for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

If you use early exits like this, the transform should be moved into a separate function. Otherwise there is a high risk of accidentally skipping transforms that get added after this point.

Copy link
Contributor

Choose a reason for hiding this comment

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

Per the alive proof in https://alive2.llvm.org/ce/z/pY63fh, you need to perform this check on C2, not on Sum. And if you do, then you don't need the additional check below.

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.

[InstCombine] Combine adds through zext nneg
5 participants