[llvm] [X86][Atomic] Fix bts/btr/btc regression caused by InstCombine trunc to i1 (PR #200667)

Jiří Filek via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 2 12:01:55 PDT 2026


https://github.com/fileho updated https://github.com/llvm/llvm-project/pull/200667

>From 456d41cd0fc45b963c870000224a3964aaf39542 Mon Sep 17 00:00:00 2001
From: Jiri Filek <jiri.filek at gmail.com>
Date: Sun, 31 May 2026 17:26:28 +0200
Subject: [PATCH 1/2] [X86][Atomic] Fix bts/btr/btc regression caused by
 InstCombine trunc to i1 fold

---
 llvm/lib/Target/X86/X86ISelLowering.cpp       | 35 +++++++----
 .../X86/atomic-rm-bit-test-pipeline.ll        | 42 ++++++++++++++
 llvm/test/CodeGen/X86/atomic-rm-bit-test.ll   | 58 +++++++++++++++++++
 3 files changed, 125 insertions(+), 10 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 087077216f747..93503ab424073 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -32730,21 +32730,30 @@ X86TargetLowering::shouldExpandLogicAtomicRMWInIR(
   const Instruction *I = AI->user_back();
   auto BitChange = FindSingleBitChange(AI->getValOperand());
   if (BitChange.second == UndefBit || !AI->hasOneUse() ||
-      I->getOpcode() != Instruction::And ||
       AI->getType()->getPrimitiveSizeInBits() == 8 ||
       AI->getParent() != I->getParent())
     return AtomicExpansionKind::CmpXChg;
 
-  unsigned OtherIdx = I->getOperand(0) == AI ? 1 : 0;
-
-  // This is a redundant AND, it should get cleaned up elsewhere.
-  if (AI == I->getOperand(OtherIdx))
+  // InstCombine folds (atomicrmw & 1) == 1 into trunc atomicrmw to i1
+  const bool IsTruncToI1 = isa<TruncInst>(I) && I->getType()->isIntegerTy(1);
+  if (!IsTruncToI1 && I->getOpcode() != Instruction::And)
     return AtomicExpansionKind::CmpXChg;
 
+  const Value *Mask;
+  if (IsTruncToI1) {
+    Mask = ConstantInt::get(AI->getType(), 1);
+  } else {
+    unsigned OtherIdx = I->getOperand(0) == AI ? 1 : 0;
+    // This is a redundant AND, it should get cleaned up elsewhere.
+    if (AI == I->getOperand(OtherIdx))
+      return AtomicExpansionKind::CmpXChg;
+    Mask = I->getOperand(OtherIdx);
+  }
+
   // The following instruction must be a AND single bit.
   if (BitChange.second == ConstantBit || BitChange.second == NotConstantBit) {
     auto *C1 = cast<ConstantInt>(AI->getValOperand());
-    auto *C2 = dyn_cast<ConstantInt>(I->getOperand(OtherIdx));
+    auto *C2 = dyn_cast<ConstantInt>(Mask);
     if (!C2 || !isPowerOf2_64(C2->getZExtValue())) {
       return AtomicExpansionKind::CmpXChg;
     }
@@ -32759,7 +32768,7 @@ X86TargetLowering::shouldExpandLogicAtomicRMWInIR(
 
   assert(BitChange.second == ShiftBit || BitChange.second == NotShiftBit);
 
-  auto BitTested = FindSingleBitChange(I->getOperand(OtherIdx));
+  auto BitTested = FindSingleBitChange(Mask);
   if (BitTested.second != ShiftBit && BitTested.second != NotShiftBit)
     return AtomicExpansionKind::CmpXChg;
 
@@ -32812,11 +32821,17 @@ void X86TargetLowering::emitBitTestAtomicRMWIntrinsic(AtomicRMWInst *AI) const {
   assert(BitTested.first != nullptr);
 
   if (BitTested.second == ConstantBit || BitTested.second == NotConstantBit) {
-    auto *C = cast<ConstantInt>(I->getOperand(I->getOperand(0) == AI ? 1 : 0));
-
-    unsigned Imm = llvm::countr_zero(C->getZExtValue());
+    const bool IsTruncToI1 = isa<TruncInst>(I) && I->getType()->isIntegerTy(1);
+    unsigned Imm = 0;
+    if (!IsTruncToI1) {
+      auto *C =
+          cast<ConstantInt>(I->getOperand(I->getOperand(0) == AI ? 1 : 0));
+      Imm = llvm::countr_zero(C->getZExtValue());
+    }
     Result = Builder.CreateIntrinsic(IID_C, AI->getType(),
                                      {Addr, Builder.getInt8(Imm)});
+    if (IsTruncToI1)
+      Result = Builder.CreateTrunc(Result, I->getType());
   } else {
     assert(BitTested.second == ShiftBit || BitTested.second == NotShiftBit);
 
diff --git a/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll b/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll
new file mode 100644
index 0000000000000..0410167a9fbc9
--- /dev/null
+++ b/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll
@@ -0,0 +1,42 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: opt < %s -O2 | llc -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
+
+; Regression tests for single bit atomics instruction codegen
+define i1 @or_check_bit0(ptr %v) nounwind {
+; CHECK-LABEL: or_check_bit0:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    lock btsl $0, (%rdi)
+; CHECK-NEXT:    setb %al
+; CHECK-NEXT:    retq
+entry:
+  %call = atomicrmw or ptr %v, i32 1 seq_cst, align 4
+  %and = and i32 %call, 1
+  %cmp = icmp eq i32 %and, 1
+  ret i1 %cmp
+}
+
+define i1 @xor_check_bit0(ptr %v) nounwind {
+; CHECK-LABEL: xor_check_bit0:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    lock btcl $0, (%rdi)
+; CHECK-NEXT:    setb %al
+; CHECK-NEXT:    retq
+entry:
+  %call = atomicrmw xor ptr %v, i32 1 seq_cst, align 4
+  %and = and i32 %call, 1
+  %cmp = icmp eq i32 %and, 1
+  ret i1 %cmp
+}
+
+define i1 @and_check_bit0(ptr %v) nounwind {
+; CHECK-LABEL: and_check_bit0:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    lock btrl $0, (%rdi)
+; CHECK-NEXT:    setb %al
+; CHECK-NEXT:    retq
+entry:
+  %call = atomicrmw and ptr %v, i32 -2 seq_cst, align 4
+  %and = and i32 %call, 1
+  %cmp = icmp eq i32 %and, 1
+  ret i1 %cmp
+}
diff --git a/llvm/test/CodeGen/X86/atomic-rm-bit-test.ll b/llvm/test/CodeGen/X86/atomic-rm-bit-test.ll
index 71887e369bd18..18b325cc797d6 100644
--- a/llvm/test/CodeGen/X86/atomic-rm-bit-test.ll
+++ b/llvm/test/CodeGen/X86/atomic-rm-bit-test.ll
@@ -7014,3 +7014,61 @@ cont11:  ; No predecessors!
 if.end19:  ; preds = %cont11, %entry
   ret void
 }
+
+; Regression tests that bts recognizes trunc to i1
+define i1 @atomic_or_1_trunc_i1(ptr %v) nounwind {
+; X86-LABEL: atomic_or_1_trunc_i1:
+; X86:       # %bb.0: # %entry
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    lock btsl $0, (%eax)
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: atomic_or_1_trunc_i1:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    lock btsl $0, (%rdi)
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+entry:
+  %0 = atomicrmw or ptr %v, i32 1 monotonic, align 4
+  %r = trunc i32 %0 to i1
+  ret i1 %r
+}
+
+define i1 @atomic_xor_1_trunc_i1(ptr %v) nounwind {
+; X86-LABEL: atomic_xor_1_trunc_i1:
+; X86:       # %bb.0: # %entry
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    lock btcl $0, (%eax)
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: atomic_xor_1_trunc_i1:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    lock btcl $0, (%rdi)
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+entry:
+  %0 = atomicrmw xor ptr %v, i32 1 monotonic, align 4
+  %r = trunc i32 %0 to i1
+  ret i1 %r
+}
+
+define i1 @atomic_and_not1_trunc_i1(ptr %v) nounwind {
+; X86-LABEL: atomic_and_not1_trunc_i1:
+; X86:       # %bb.0: # %entry
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    lock btrl $0, (%eax)
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: atomic_and_not1_trunc_i1:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    lock btrl $0, (%rdi)
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+entry:
+  %0 = atomicrmw and ptr %v, i32 -2 monotonic, align 4
+  %r = trunc i32 %0 to i1
+  ret i1 %r
+}

>From 5977b467a87d015024862420cda0b709b8967b2f Mon Sep 17 00:00:00 2001
From: Jiri Filek <jiri.filek at gmail.com>
Date: Tue, 2 Jun 2026 21:01:22 +0200
Subject: [PATCH 2/2] Add 32-bit test coverage

Co-authored-by: Copilot <copilot at github.com>
---
 .../X86/atomic-rm-bit-test-pipeline.ll        | 56 +++++++++++++------
 1 file changed, 40 insertions(+), 16 deletions(-)

diff --git a/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll b/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll
index 0410167a9fbc9..ecf72a8b67000 100644
--- a/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll
+++ b/llvm/test/CodeGen/X86/atomic-rm-bit-test-pipeline.ll
@@ -1,13 +1,21 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: opt < %s -O2 | llc -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
+; RUN: opt < %s -O2 | llc -mtriple=x86_64-unknown-linux-gnu | FileCheck %s --check-prefixes=CHECK,X64
+; RUN: opt < %s -O2 | llc -mtriple=i686-unknown-linux-gnu | FileCheck %s --check-prefixes=CHECK,X86
 
 ; Regression tests for single bit atomics instruction codegen
 define i1 @or_check_bit0(ptr %v) nounwind {
-; CHECK-LABEL: or_check_bit0:
-; CHECK:       # %bb.0: # %entry
-; CHECK-NEXT:    lock btsl $0, (%rdi)
-; CHECK-NEXT:    setb %al
-; CHECK-NEXT:    retq
+; X64-LABEL: or_check_bit0:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    lock btsl $0, (%rdi)
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+;
+; X86-LABEL: or_check_bit0:
+; X86:       # %bb.0: # %entry
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    lock btsl $0, (%eax)
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
 entry:
   %call = atomicrmw or ptr %v, i32 1 seq_cst, align 4
   %and = and i32 %call, 1
@@ -16,11 +24,18 @@ entry:
 }
 
 define i1 @xor_check_bit0(ptr %v) nounwind {
-; CHECK-LABEL: xor_check_bit0:
-; CHECK:       # %bb.0: # %entry
-; CHECK-NEXT:    lock btcl $0, (%rdi)
-; CHECK-NEXT:    setb %al
-; CHECK-NEXT:    retq
+; X64-LABEL: xor_check_bit0:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    lock btcl $0, (%rdi)
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+;
+; X86-LABEL: xor_check_bit0:
+; X86:       # %bb.0: # %entry
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    lock btcl $0, (%eax)
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
 entry:
   %call = atomicrmw xor ptr %v, i32 1 seq_cst, align 4
   %and = and i32 %call, 1
@@ -29,14 +44,23 @@ entry:
 }
 
 define i1 @and_check_bit0(ptr %v) nounwind {
-; CHECK-LABEL: and_check_bit0:
-; CHECK:       # %bb.0: # %entry
-; CHECK-NEXT:    lock btrl $0, (%rdi)
-; CHECK-NEXT:    setb %al
-; CHECK-NEXT:    retq
+; X64-LABEL: and_check_bit0:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    lock btrl $0, (%rdi)
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+;
+; X86-LABEL: and_check_bit0:
+; X86:       # %bb.0: # %entry
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    lock btrl $0, (%eax)
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
 entry:
   %call = atomicrmw and ptr %v, i32 -2 seq_cst, align 4
   %and = and i32 %call, 1
   %cmp = icmp eq i32 %and, 1
   ret i1 %cmp
 }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; CHECK: {{.*}}



More information about the llvm-commits mailing list