[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