[llvm] [InstCombine] Fold constant-mask bitfield clear pattern (PR #194734)

via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 28 15:03:35 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: glaziermag

<details>
<summary>Changes</summary>

## Summary

Fold the scalar integer constant-mask bitfield clear pattern:

```llvm
(X & ~M) | ((X & M) & ~(Y & M))  ->  X & ~(Y & M)
```

The matcher is intentionally narrow: it only handles scalar integer `or`
patterns where `M` is an exact compile-time `APInt` mask reused by both
`X & M` and `Y & M`, and it reuses the existing `~(Y & M)` value.

## Alive2

The original issue's Alive2 proof covers the mask-1 form and the forward
symbolic-mask scalar transform:

https://alive2.llvm.org/ce/z/GQeQTS

I also ran local Alive2 files covering a concrete `0x55` mask, a symbolic
scalar mask, and representative scalar widths:

```console
alive-tv ../alive2-83699.ll
alive-tv --smt-to=60000 ../alive2-83699-widths.ll
```

Results: `5 correct transformations`, `0 incorrect transformations`,
`0 failed-to-prove transformations`.

Vector splat matching was tried during development, but is intentionally not
included because the local Alive2 vector proof timed out rather than proving
cleanly.

## Before / after

I rebuilt `opt` once with this source change temporarily reversed and once with
the patch applied. For the issue pattern, pre-patch InstCombine left the masked
arms and root `or disjoint` intact:

```llvm
%x.mask = and i32 %x, 1
%y.mask = and i32 %y, 1
%not.ym = xor i32 %y.mask, -1
%new.masked = and i32 %x.mask, %not.ym
%old.bits = and i32 %x, -2
%r = or disjoint i32 %old.bits, %new.masked
```

With this patch, the same input folds to:

```llvm
%y.mask = and i32 %y, 1
%not.ym = xor i32 %y.mask, -1
%r = and i32 %x, %not.ym
```

The negative case where `X & ~M` is not the exact complement of `X & M` remains
unchanged after the patch.

## Tests

Added `llvm/test/Transforms/InstCombine/or-bitfield-mask.ll` with coverage for:

- the exact issue pattern with mask `1`
- explicit `or disjoint` input with mask `1`
- a wider nontrivial constant mask, `0x55`
- a commuted `0x0f` pattern
- negative mismatched-mask cases for `Y & M` and `X & ~M`
- multi-use masked arms where the root `or` is still replaced without
  duplicating operands

## Validation

```console
ninja -C build opt
python3 llvm/utils/update_test_checks.py --tool-binary=build/bin/opt llvm/test/Transforms/InstCombine/or-bitfield-mask.ll
build/bin/llvm-lit -v llvm/test/Transforms/InstCombine/or-bitfield-mask.ll
build/bin/opt < llvm/test/Transforms/InstCombine/or-bitfield-mask.ll -passes=instcombine -S
ninja -C build check-llvm-transforms
alive-tv ../alive2-83699.ll
alive-tv --smt-to=60000 ../alive2-83699-widths.ll
```

Fixes #<!-- -->83699

Assisted-by: OpenAI Codex

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


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+38) 
- (added) llvm/test/Transforms/InstCombine/or-bitfield-mask.ll (+136) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index d81ed56290476..0869a297f866e 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -1977,6 +1977,41 @@ static Instruction *foldOrToXor(BinaryOperator &I,
   return nullptr;
 }
 
+static bool matchConstantMaskBitfieldClear(Value *OldBits, Value *NewBits,
+                                           Value *&X, Value *&NotYAndM) {
+  const APInt *Mask, *NotMask, *YMask;
+
+  if (!match(OldBits, m_c_And(m_Value(X), m_APInt(NotMask))))
+    return false;
+
+  if (!match(NewBits,
+             m_c_And(
+                 m_c_And(m_Specific(X), m_APInt(Mask)),
+                 m_Value(NotYAndM, m_Not(m_c_And(m_Value(), m_APInt(YMask)))))))
+    return false;
+
+  return *Mask == *YMask && *NotMask == ~*Mask;
+}
+
+static Instruction *foldOrOfConstantMaskBitfieldClear(BinaryOperator &I) {
+  assert(I.getOpcode() == Instruction::Or);
+
+  if (!I.getType()->isIntegerTy())
+    return nullptr;
+
+  // (X & ~M) | ((X & M) & ~(Y & M)) --> X & ~(Y & M)
+  // Match only the exact constant-mask shape. The replacement reuses the
+  // existing complement of (Y & M), so multi-use operands are not duplicated.
+  Value *X, *NotYAndM;
+  if (matchConstantMaskBitfieldClear(I.getOperand(0), I.getOperand(1), X,
+                                     NotYAndM) ||
+      matchConstantMaskBitfieldClear(I.getOperand(1), I.getOperand(0), X,
+                                     NotYAndM))
+    return BinaryOperator::CreateAnd(X, NotYAndM);
+
+  return nullptr;
+}
+
 /// Return true if a constant shift amount is always less than the specified
 /// bit-width. If not, the shift could create poison in the narrower type.
 static bool canNarrowShiftAmt(Constant *C, unsigned BitWidth) {
@@ -4131,6 +4166,9 @@ Instruction *InstCombinerImpl::visitOr(BinaryOperator &I) {
   if (Value *V = foldOrOfInversions(I, Builder))
     return replaceInstUsesWith(I, V);
 
+  if (Instruction *R = foldOrOfConstantMaskBitfieldClear(I))
+    return R;
+
   // (A&B)|(A&C) -> A&(B|C) etc
   if (Value *V = foldUsingDistributiveLaws(I))
     return replaceInstUsesWith(I, V);
diff --git a/llvm/test/Transforms/InstCombine/or-bitfield-mask.ll b/llvm/test/Transforms/InstCombine/or-bitfield-mask.ll
new file mode 100644
index 0000000000000..e51121ccc74dd
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/or-bitfield-mask.ll
@@ -0,0 +1,136 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @use(i32)
+
+define i32 @bitfield_mask1(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @bitfield_mask1(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 1
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[X]], [[NOT_YM]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 1
+  %y.mask = and i32 %y, 1
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %x.mask, %not.ym
+  %old.bits = and i32 %x, -2
+  %r = or i32 %old.bits, %new.masked
+  ret i32 %r
+}
+
+define i32 @bitfield_mask1_disjoint(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @bitfield_mask1_disjoint(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 1
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[X]], [[NOT_YM]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 1
+  %y.mask = and i32 %y, 1
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %x.mask, %not.ym
+  %old.bits = and i32 %x, -2
+  %r = or disjoint i32 %old.bits, %new.masked
+  ret i32 %r
+}
+
+define i32 @bitfield_mask_0x55(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @bitfield_mask_0x55(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 85
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[X]], [[NOT_YM]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 85
+  %y.mask = and i32 %y, 85
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %x.mask, %not.ym
+  %old.bits = and i32 %x, -86
+  %r = or i32 %old.bits, %new.masked
+  ret i32 %r
+}
+
+define i32 @bitfield_mask_0x0f_commuted(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @bitfield_mask_0x0f_commuted(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 15
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[X]], [[NOT_YM]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 15
+  %y.mask = and i32 %y, 15
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %not.ym, %x.mask
+  %old.bits = and i32 %x, -16
+  %r = or i32 %new.masked, %old.bits
+  ret i32 %r
+}
+
+define i32 @negative_mismatched_y_mask(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @negative_mismatched_y_mask(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X_MASK:%.*]] = and i32 [[X]], 85
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 15
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[NEW_MASKED:%.*]] = and i32 [[X_MASK]], [[NOT_YM]]
+; CHECK-NEXT:    [[OLD_BITS:%.*]] = and i32 [[X]], -86
+; CHECK-NEXT:    [[R:%.*]] = or disjoint i32 [[OLD_BITS]], [[NEW_MASKED]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 85
+  %y.mask = and i32 %y, 15
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %x.mask, %not.ym
+  %old.bits = and i32 %x, -86
+  %r = or i32 %old.bits, %new.masked
+  ret i32 %r
+}
+
+define i32 @negative_mismatched_not_mask(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @negative_mismatched_not_mask(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X_MASK:%.*]] = and i32 [[X]], 85
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 85
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[NEW_MASKED:%.*]] = and i32 [[X_MASK]], [[NOT_YM]]
+; CHECK-NEXT:    [[OLD_BITS:%.*]] = and i32 [[X]], -87
+; CHECK-NEXT:    [[R:%.*]] = or i32 [[OLD_BITS]], [[NEW_MASKED]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 85
+  %y.mask = and i32 %y, 85
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %x.mask, %not.ym
+  %old.bits = and i32 %x, -87
+  %r = or i32 %old.bits, %new.masked
+  ret i32 %r
+}
+
+define i32 @multi_use_masked_arms(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @multi_use_masked_arms(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X_MASK:%.*]] = and i32 [[X]], 15
+; CHECK-NEXT:    [[Y_MASK:%.*]] = and i32 [[Y]], 15
+; CHECK-NEXT:    [[NOT_YM:%.*]] = xor i32 [[Y_MASK]], -1
+; CHECK-NEXT:    [[NEW_MASKED:%.*]] = and i32 [[X_MASK]], [[NOT_YM]]
+; CHECK-NEXT:    [[OLD_BITS:%.*]] = and i32 [[X]], -16
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[X]], [[NOT_YM]]
+; CHECK-NEXT:    call void @use(i32 [[OLD_BITS]])
+; CHECK-NEXT:    call void @use(i32 [[NEW_MASKED]])
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x.mask = and i32 %x, 15
+  %y.mask = and i32 %y, 15
+  %not.ym = xor i32 %y.mask, -1
+  %new.masked = and i32 %x.mask, %not.ym
+  %old.bits = and i32 %x, -16
+  %r = or i32 %old.bits, %new.masked
+  call void @use(i32 %old.bits)
+  call void @use(i32 %new.masked)
+  ret i32 %r
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/194734


More information about the llvm-commits mailing list