[llvm] 1075a2f - [Instcombine] Write Instcombine pass to strength reduce lock xadd to lock sub (#184715)

via llvm-commits llvm-commits at lists.llvm.org
Sat Mar 21 13:45:35 PDT 2026


Author: Takashi Idobe
Date: 2026-03-21T20:45:29Z
New Revision: 1075a2fa7ed0a576e5aeb4af03b570eab1aaae04

URL: https://github.com/llvm/llvm-project/commit/1075a2fa7ed0a576e5aeb4af03b570eab1aaae04
DIFF: https://github.com/llvm/llvm-project/commit/1075a2fa7ed0a576e5aeb4af03b570eab1aaae04.diff

LOG: [Instcombine] Write Instcombine pass to strength reduce lock xadd to lock sub (#184715)

Resolves: https://github.com/llvm/llvm-project/issues/174933

The issue goes into a case where fetch_sub(n) is properly optimized but
fetch_add(neg(n)) is not optimized to the same code.

Although the issue is tagged for x86 I assumed this be best handled
outside of the backends so I put this in InstCombine.

Added: 
    llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll

Modified: 
    llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
index a2e8c695331a6..4d6b0684abe4d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
@@ -14,6 +14,7 @@
 #include "llvm/IR/Instructions.h"
 
 using namespace llvm;
+using namespace PatternMatch;
 
 /// Return true if and only if the given instruction does not modify the memory
 /// location referenced.  Note that an idemptent atomicrmw may still have
@@ -118,6 +119,23 @@ Instruction *InstCombinerImpl::visitAtomicRMWInst(AtomicRMWInst &RMWI) {
          RMWI.getOrdering() != AtomicOrdering::Unordered &&
          "AtomicRMWs don't make sense with Unordered or NotAtomic");
 
+  // Canonicalize atomicrmw add(ptr, neg(X)) -> atomicrmw sub(ptr, X)
+  //              atomicrmw sub(ptr, neg(X)) -> atomicrmw add(ptr, X)
+  // old + (-X) == old - X and old - (-X) == old + X; the returned old value
+  // is identical in both cases. We match strictly on `sub 0, X` (negation) to
+  // avoid infinite loops: a general negation of `sub A, B` yields `sub B, A`,
+  // which would infinitely be negated back on the next iteration.
+  auto Op = RMWI.getOperation();
+  if (Op == AtomicRMWInst::Add || Op == AtomicRMWInst::Sub) {
+    Value *Val = RMWI.getValOperand();
+    Value *X;
+    if (match(Val, m_Neg(m_Value(X)))) {
+      RMWI.setOperation(Op == AtomicRMWInst::Add ? AtomicRMWInst::Sub
+                                                 : AtomicRMWInst::Add);
+      return replaceOperand(RMWI, 1, X);
+    }
+  }
+
   if (!isIdempotentRMW(RMWI))
     return nullptr;
 

diff  --git a/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll b/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
new file mode 100644
index 0000000000000..9269b0d2465ba
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
@@ -0,0 +1,95 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=instcombine -S %s | FileCheck %s
+
+; Regression case: atomicrmw sub with variable is left unchanged
+define i64 @fn_sub(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_sub(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[TMP1]]
+;
+  %1 = atomicrmw sub ptr %a, i64 %n monotonic
+  ret i64 %1
+}
+
+; Canonicalize atomicrmw add(ptr, neg(n)) -> atomicrmw sub(ptr, n)
+define i64 @fn_add(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_add(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[TMP1]]
+;
+  %sub = sub i64 0, %n
+  %1 = atomicrmw add ptr %a, i64 %sub monotonic
+  ret i64 %1
+}
+
+; Canonicalize atomicrmw add(ptr, neg(n)) -> atomicrmw sub(ptr, n)
+; even when the negation is nsw
+define i64 @fn_adds(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_adds(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[OLD:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[OLD]]
+;
+  %neg = sub nsw i64 0, %n
+  %old = atomicrmw add ptr %a, i64 %neg monotonic
+  ret i64 %old
+}
+
+; Canonicalize atomicrmw sub(ptr, neg(n)) -> atomicrmw add(ptr, n)
+define i64 @fn_sub_neg(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_sub_neg(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = atomicrmw add ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[TMP1]]
+;
+  %neg = sub i64 0, %n
+  %1 = atomicrmw sub ptr %a, i64 %neg monotonic
+  ret i64 %1
+}
+
+; Don't canonicalize if the negated value has multiple uses -- as that would add an extra instruction.
+define i64 @fn_add_1_with_use(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_add_1_with_use(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[N]], 1
+; CHECK-NEXT:    call void @use(i64 [[ADD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = atomicrmw add ptr [[A]], i64 [[ADD]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[TMP1]]
+;
+  %add = add i64 %n, 1
+  call void @use(i64 %add)
+  %1 = atomicrmw add ptr %a, i64 %add monotonic
+  ret i64 %1
+}
+
+; Don't canonicalize atomicrmw sub(ptr, sub(a, b)) -> atomicrmw add(ptr, sub(b, a)),
+; as that would create an infinite loop by continuously swapping the sub operands.
+define i64 @fn_sub_of_two_vars(ptr %p, i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @fn_sub_of_two_vars(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[TMP1:%.*]] = atomicrmw sub ptr [[P]], i64 [[SUB]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[TMP1]]
+;
+  %sub = sub i64 %a, %b
+  %1 = atomicrmw sub ptr %p, i64 %sub monotonic
+  ret i64 %1
+}
+
+; Don't canonicalize atomicrmw add(ptr, sub(a, b)) -> atomicrmw sub(ptr, sub(b, a)),
+; as that would create an infinite loop by continuously swapping the sub operands.
+define i64 @fn_add_of_two_vars(ptr %p, i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @fn_add_of_two_vars(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[TMP1:%.*]] = atomicrmw add ptr [[P]], i64 [[SUB]] monotonic, align 8
+; CHECK-NEXT:    ret i64 [[TMP1]]
+;
+  %sub = sub i64 %a, %b
+  %1 = atomicrmw add ptr %p, i64 %sub monotonic
+  ret i64 %1
+}
+
+declare void @use(i64)


        


More information about the llvm-commits mailing list