[llvm] [Instcombine] Write Instcombine pass to strength reduce lock xadd to lock sub (PR #184715)
Takashi Idobe via llvm-commits
llvm-commits at lists.llvm.org
Thu Mar 5 07:45:25 PST 2026
https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/184715
>From 1b6356b8b354e02d6561a5f8e58ba09b6cb0cffa Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Tue, 3 Mar 2026 22:34:19 -0500
Subject: [PATCH 1/2] write Instcombine to strength reduce lock xadd to lock
add
---
.../InstCombine/InstCombineAtomicRMW.cpp | 12 +++++
.../InstCombine/atomicrmw-add-neg.ll | 49 +++++++++++++++++++
2 files changed, 61 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
index a2e8c695331a6..a3ba3312138bd 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 llvm::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,17 @@ 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).
+ // old + (-X) == old - X; the returned old value is identical.
+ // This allows strength reduction on targets where atomic sub is cheaper,
+ // e.g. lock sub instead of lock xadd on x86.
+ Value *X;
+ if (RMWI.getOperation() == AtomicRMWInst::Add &&
+ match(RMWI.getValOperand(), m_Neg(m_Value(X)))) {
+ RMWI.setOperation(AtomicRMWInst::Sub);
+ 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..4c1bb20b6a9d4
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
@@ -0,0 +1,49 @@
+; 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 is unchanged by this transform.
+define i1 @fn_sub(ptr noundef nonnull align 8 captures(none) dereferenceable(8) %a, i64 noundef %n) {
+; CHECK-LABEL: define i1 @fn_sub(
+; CHECK-SAME: ptr noundef nonnull align 8 captures(none) dereferenceable(8) [[A:%.*]], i64 noundef [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[TMP0]], [[N]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+entry:
+ %0 = atomicrmw sub ptr %a, i64 %n monotonic, align 8
+ %cmp = icmp eq i64 %0, %n
+ ret i1 %cmp
+}
+
+; Canonicalize atomicrmw add(ptr, neg(n)) -> atomicrmw sub(ptr, n)
+define i1 @fn_add(ptr noundef nonnull align 8 captures(none) dereferenceable(8) %a, i64 noundef %n) {
+; CHECK-LABEL: define i1 @fn_add(
+; CHECK-SAME: ptr noundef nonnull align 8 captures(none) dereferenceable(8) [[A:%.*]], i64 noundef [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[TMP0]], [[N]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+entry:
+ %sub = sub i64 0, %n
+ %0 = atomicrmw add ptr %a, i64 %sub monotonic, align 8
+ %cmp = icmp eq i64 %0, %n
+ ret i1 %cmp
+}
+
+; nsw neg (signed -n) is also handled; the transform replaces the poison case on neg(INT_MIN) with wrapping sub
+define i1 @fn_adds(ptr noundef nonnull align 8 captures(none) dereferenceable(8) %a, i64 noundef %n) {
+; CHECK-LABEL: define i1 @fn_adds(
+; CHECK-SAME: ptr noundef nonnull align 8 captures(none) dereferenceable(8) [[A:%.*]], i64 noundef [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[OLD:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[OLD]], [[N]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+entry:
+ %neg = sub nsw i64 0, %n
+ %old = atomicrmw add ptr %a, i64 %neg monotonic, align 8
+ %cmp = icmp eq i64 %old, %n
+ ret i1 %cmp
+}
>From e80053fe2f1fdf2818dcc86ecc108c9eeb02069e Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Thu, 5 Mar 2026 10:45:13 -0500
Subject: [PATCH 2/2] code review feedback: remove unnecessary attributes in
tests, shrink IR tests, and use Negator::negate instead of handrolling it
with match
---
.../InstCombine/InstCombineAtomicRMW.cpp | 14 +++---
.../InstCombine/atomicrmw-add-neg.ll | 50 +++++++------------
2 files changed, 25 insertions(+), 39 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
index a3ba3312138bd..642a794f97927 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAtomicRMW.cpp
@@ -14,7 +14,6 @@
#include "llvm/IR/Instructions.h"
using namespace llvm;
-using namespace llvm::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
@@ -121,13 +120,12 @@ Instruction *InstCombinerImpl::visitAtomicRMWInst(AtomicRMWInst &RMWI) {
// Canonicalize atomicrmw add(ptr, neg(X)) -> atomicrmw sub(ptr, X).
// old + (-X) == old - X; the returned old value is identical.
- // This allows strength reduction on targets where atomic sub is cheaper,
- // e.g. lock sub instead of lock xadd on x86.
- Value *X;
- if (RMWI.getOperation() == AtomicRMWInst::Add &&
- match(RMWI.getValOperand(), m_Neg(m_Value(X)))) {
- RMWI.setOperation(AtomicRMWInst::Sub);
- return replaceOperand(RMWI, 1, X);
+ if (RMWI.getOperation() == AtomicRMWInst::Add) {
+ if (Value *X = Negator::Negate(/*LHSIsZero=*/true, /*IsNSW=*/false,
+ RMWI.getValOperand(), *this)) {
+ RMWI.setOperation(AtomicRMWInst::Sub);
+ return replaceOperand(RMWI, 1, X);
+ }
}
if (!isIdempotentRMW(RMWI))
diff --git a/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll b/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
index 4c1bb20b6a9d4..9010bdb166bfc 100644
--- a/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
+++ b/llvm/test/Transforms/InstCombine/atomicrmw-add-neg.ll
@@ -1,49 +1,37 @@
; 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 is unchanged by this transform.
-define i1 @fn_sub(ptr noundef nonnull align 8 captures(none) dereferenceable(8) %a, i64 noundef %n) {
-; CHECK-LABEL: define i1 @fn_sub(
-; CHECK-SAME: ptr noundef nonnull align 8 captures(none) dereferenceable(8) [[A:%.*]], i64 noundef [[N:%.*]]) {
-; CHECK-NEXT: [[ENTRY:.*:]]
+; Regression case: atomicrmw sub is unchanged by this transform.
+define i64 @fn_sub(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_sub(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: [[TMP0:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
-; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[TMP0]], [[N]]
-; CHECK-NEXT: ret i1 [[CMP]]
+; CHECK-NEXT: ret i64 [[TMP0]]
;
-entry:
- %0 = atomicrmw sub ptr %a, i64 %n monotonic, align 8
- %cmp = icmp eq i64 %0, %n
- ret i1 %cmp
+ %1 = atomicrmw sub ptr %a, i64 %n monotonic
+ ret i64 %1
}
; Canonicalize atomicrmw add(ptr, neg(n)) -> atomicrmw sub(ptr, n)
-define i1 @fn_add(ptr noundef nonnull align 8 captures(none) dereferenceable(8) %a, i64 noundef %n) {
-; CHECK-LABEL: define i1 @fn_add(
-; CHECK-SAME: ptr noundef nonnull align 8 captures(none) dereferenceable(8) [[A:%.*]], i64 noundef [[N:%.*]]) {
-; CHECK-NEXT: [[ENTRY:.*:]]
+define i64 @fn_add(ptr %a, i64 %n) {
+; CHECK-LABEL: define i64 @fn_add(
+; CHECK-SAME: ptr [[A:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: [[TMP0:%.*]] = atomicrmw sub ptr [[A]], i64 [[N]] monotonic, align 8
-; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[TMP0]], [[N]]
-; CHECK-NEXT: ret i1 [[CMP]]
+; CHECK-NEXT: ret i64 [[TMP0]]
;
-entry:
%sub = sub i64 0, %n
- %0 = atomicrmw add ptr %a, i64 %sub monotonic, align 8
- %cmp = icmp eq i64 %0, %n
- ret i1 %cmp
+ %1 = atomicrmw add ptr %a, i64 %sub monotonic
+ ret i64 %1
}
; nsw neg (signed -n) is also handled; the transform replaces the poison case on neg(INT_MIN) with wrapping sub
-define i1 @fn_adds(ptr noundef nonnull align 8 captures(none) dereferenceable(8) %a, i64 noundef %n) {
-; CHECK-LABEL: define i1 @fn_adds(
-; CHECK-SAME: ptr noundef nonnull align 8 captures(none) dereferenceable(8) [[A:%.*]], i64 noundef [[N:%.*]]) {
-; CHECK-NEXT: [[ENTRY:.*:]]
+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: [[CMP:%.*]] = icmp eq i64 [[OLD]], [[N]]
-; CHECK-NEXT: ret i1 [[CMP]]
+; CHECK-NEXT: ret i64 [[OLD]]
;
-entry:
%neg = sub nsw i64 0, %n
- %old = atomicrmw add ptr %a, i64 %neg monotonic, align 8
- %cmp = icmp eq i64 %old, %n
- ret i1 %cmp
+ %old = atomicrmw add ptr %a, i64 %neg monotonic
+ ret i64 %old
}
More information about the llvm-commits
mailing list