[llvm] [SeparateConstOffsetFromGEP] Decompose xor constant operand when possible (PR #195830)

via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 04:02:44 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-amdgpu

Author: Antonio Frighetto (antoniofrighetto)

<details>
<summary>Changes</summary>

It may be desirable to fold constants directly into the addressing mode when computing an address. While lowering GEPs and looking for a constant to extract among the indexes, take into account constants which are xor expressions as well. When some bits of the constant operand of the xor are known-zero in the base operand, then, for those specific bits (disjoint bits), xor and additions behave alike. Such bits may be extracted from the xor, and are those that can contribute to the final GEP offset.

Co-authored-by: Sumanth Gundapaneni <sumanth.gundapaneni@<!-- -->amd.com>

---

Original patch: https://github.com/llvm/llvm-project/pull/150438

Miscompilation reported at: https://github.com/llvm/llvm-project/pull/175724, previously reverted: https://github.com/llvm/llvm-project/pull/179339.



The main change consists of pushing the original xor constant operand in UserChain, as this is the constant leaf (bottom of the chain) that `distributeCastsAndCloneChain` expects in order to clone the original chain. Later, `removeConstOffset` is responsible for replacing the xor constant operand with the non-disjoint bits.

These latter bits are separately stored in a private field (`NonDisjointXorBitsConstant`).

 Notably, this is possible per the following invariants:
 1) each ConstantOffsetExtractor is instantiated freshly for each GEP index,
 2) `removeConstOffset` always sees the latest NonDisjointXorBitsConstant set by `extractDisjointBitsFromXor`. If `findInEitherOperand` backtracks while exploring a node, leaving NonDisjointXorBitsConstant stale, `removeConstOffset` will never encounter a xor in the chain, since `resize(ChainLength)` will remove the previously explored xor constant and xor instruction (which are pushed together, the first by `extractDisjointBitsFromXor`, the other by its caller `find`).

---

Patch is 32.87 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/195830.diff


3 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp (+87) 
- (added) llvm/test/Transforms/SeparateConstOffsetFromGEP/AMDGPU/xor-decompose.ll (+420) 
- (added) llvm/test/Transforms/SeparateConstOffsetFromGEP/xor-decompose.ll (+83) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp b/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp
index bb470aff9bfc1..9df45841213af 100644
--- a/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp
+++ b/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp
@@ -161,6 +161,7 @@
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/KnownBits.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Transforms/Scalar.h"
 #include "llvm/Transforms/Utils/Local.h"
@@ -297,6 +298,33 @@ class ConstantOffsetExtractor {
   bool canTraceInto(bool SignExtended, bool ZeroExtended, BinaryOperator *BO,
                     GetElementPtrInst *GEP, Value *Idx);
 
+  /// Analyze a xor expression, and identify the bits in the constant operand
+  /// that are disjoint from the base operand's known set bits. For these
+  /// disjoint bits, a xor is equivalent to an addition, which allows us to
+  /// extract them as constant offsets that can be folded into the immediate
+  /// field of addressing operations. The transformation is the following one:
+  ///
+  ///   Base ^ Const  becomes  (Base ^ NonDisjointBits) + DisjointBits
+  ///
+  /// where DisjointBits = Const & KnownZeros(Base) and
+  ///       NonDisjointBits = Const & ~DisjointBits.
+  ///
+  /// Example with ptr having known-zero low bit:
+  ///   Original: `xor %ptr, 3`    ; 3 = 0b11
+  ///   Analysis: DisjointBits = 3 & KnownZeros(%ptr) = 0b11 & 0b01 = 0b01
+  ///   Result:   `(xor %ptr, 2) + 1` where 1 can be folded into address mode
+  ///
+  /// \param XorInst The XOR binary operator to analyze
+  /// \return Returns the disjoint bits (the extractable offset), or zero if
+  /// none exist. On success, stores NonDisjointBits in
+  /// NonDisjointXorConstantBits.
+  APInt extractDisjointBitsFromXor(BinaryOperator *XorInst);
+
+  /// The non-disjoint bits remaining after xor decomposition in
+  /// `extractDisjointBitsFromXor`, which are later used while replacing the
+  /// original xor constant operand.
+  ConstantInt *NonDisjointXorConstantBits = nullptr;
+
   /// The path from the constant offset to the old GEP index. e.g., if the GEP
   /// index is "a * b + (c + 5)". After running function find, UserChain[0] will
   /// be the constant 5, UserChain[1] will be the subexpression "c + 5", and
@@ -709,6 +737,8 @@ APInt ConstantOffsetExtractor::find(Value *V, GetElementPtrInst *GEP,
     // Trace into subexpressions for more hoisting opportunities.
     if (canTraceInto(SignExtended, ZeroExtended, BO, GEP, Idx))
       ConstantOffset = findInEitherOperand(BO, SignExtended, ZeroExtended);
+    else if (BO->getOpcode() == Instruction::Xor)
+      ConstantOffset = extractDisjointBitsFromXor(BO);
   } else if (isa<TruncInst>(V)) {
     ConstantOffset =
         find(U->getOperand(0), GEP, Idx, SignExtended, ZeroExtended)
@@ -827,6 +857,19 @@ Value *ConstantOffsetExtractor::removeConstOffset(unsigned ChainIndex) {
   Value *NextInChain = removeConstOffset(ChainIndex - 1);
   Value *TheOther = BO->getOperand(1 - OpNo);
 
+  // When rewriting xor(TheOther, NextInChain) expressions, the original
+  // constant operand is replaced with the non-disjoints bits, which are the
+  // non-extractable bits, i.e., those that must remain in the xor (the other
+  // bits have already compounded the GEP offset).
+  if (BO->getOpcode() == Instruction::Xor) {
+    // The non-disjoints bits are cached in NonDisjointXorBitsConstant, which is
+    // always up-to-date.
+    assert(NonDisjointXorConstantBits &&
+           "XOR in UserChain without recorded non-disjoint bits");
+    // Only casts can happen to be distributed among the xor operands.
+    NextInChain = applyCasts(NonDisjointXorConstantBits);
+  }
+
   // If NextInChain is 0 and not the LHS of a sub, we can simplify the
   // sub-expression to be just TheOther.
   if (ConstantInt *CI = dyn_cast<ConstantInt>(NextInChain)) {
@@ -862,6 +905,50 @@ Value *ConstantOffsetExtractor::removeConstOffset(unsigned ChainIndex) {
   return NewBO;
 }
 
+APInt ConstantOffsetExtractor::extractDisjointBitsFromXor(
+    BinaryOperator *XorInst) {
+  assert(XorInst && XorInst->getOpcode() == Instruction::Xor &&
+         "Expected XOR instruction");
+
+  unsigned BitWidth = XorInst->getType()->getScalarSizeInBits();
+  Value *BaseOp;
+  ConstantInt *XorConstantOp;
+
+  if (!match(XorInst, m_Xor(m_Value(BaseOp), m_ConstantInt(XorConstantOp))))
+    return APInt::getZero(BitWidth);
+
+  const SimplifyQuery SQ(DL);
+  const KnownBits BaseKnownBits = computeKnownBits(BaseOp, SQ);
+  const APInt &ConstantValue = XorConstantOp->getValue();
+
+  // Compute the disjoint bits, i.e., those bits of the constant operand that
+  // are known-zero in the base. These disjoint bits will contribute to the
+  // final GEP offset. If the are no disjoint bits, there isn't any offset to
+  // extract from the xor.
+  const APInt DisjointBits = ConstantValue & BaseKnownBits.Zero;
+  if (DisjointBits.isZero())
+    return DisjointBits;
+
+  // Avoid a pessimizing rewrite if the disjoint bits include the sign bit.
+  if (DisjointBits.isSignBitSet())
+    return APInt::getZero(BitWidth);
+
+  // Compute the remaining bits, i.e., the non-disjoint ones, which are those
+  // that must be preserved in the xor.
+  const APInt NonDisjointBits = ConstantValue & ~DisjointBits;
+  NonDisjointXorConstantBits =
+      ConstantInt::get(XorInst->getContext(), NonDisjointBits);
+
+  // UserChain maintains a path from the constant up to the GEP index. Push the
+  // xor constant operand, which is the constant leaf of the chain (which is
+  // also what `distributeCastsAndCloneChain` expects). Such a chained operand
+  // is the one to be replaced with the non-disjoint bits, while rebuilding the
+  // xor afterwards. The xor instruction itself is pushed upon returning.
+  UserChain.push_back(XorConstantOp);
+
+  return DisjointBits;
+}
+
 /// A helper function to check if reassociating through an entry in the user
 /// chain would invalidate the GEP's nuw flag.
 static bool allowsPreservingNUW(const User *U) {
diff --git a/llvm/test/Transforms/SeparateConstOffsetFromGEP/AMDGPU/xor-decompose.ll b/llvm/test/Transforms/SeparateConstOffsetFromGEP/AMDGPU/xor-decompose.ll
new file mode 100644
index 0000000000000..e8d09c518e6b9
--- /dev/null
+++ b/llvm/test/Transforms/SeparateConstOffsetFromGEP/AMDGPU/xor-decompose.ll
@@ -0,0 +1,420 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; Test XOR disjoint bits decomposition into GEP constant offsets.
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=separate-const-offset-from-gep \
+; RUN: -S < %s | FileCheck %s
+; Test GVN eliminates the redundant xor instructions from decomposition.
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=separate-const-offset-from-gep,gvn \
+; RUN: -S < %s | FileCheck --check-prefix=GVN %s
+
+; Check that disjoint constants are properly extracted and folded into GEP
+; addressing modes and GVN to eliminate redundant computations.
+define amdgpu_kernel void @test1(i1 %cond, ptr addrspace(3) %ptr) {
+; CHECK-LABEL: define amdgpu_kernel void @test1(
+; CHECK-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; CHECK-NEXT:    [[XOR0:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[GEP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR0]]
+; CHECK-NEXT:    [[XOR11:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR11]]
+; CHECK-NEXT:    [[GEP12:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 8192
+; CHECK-NEXT:    [[XOR23:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR23]]
+; CHECK-NEXT:    [[GEP24:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP1]], i32 16384
+; CHECK-NEXT:    [[XOR35:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR35]]
+; CHECK-NEXT:    [[GEP36:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP2]], i32 24576
+; CHECK-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP0]], align 16
+; CHECK-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP12]], align 16
+; CHECK-NEXT:    [[V2:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP24]], align 16
+; CHECK-NEXT:    [[V3:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP36]], align 16
+; CHECK-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; CHECK-NEXT:    [[ADD1:%.*]] = fadd <8 x half> [[V2]], [[V3]]
+; CHECK-NEXT:    [[ADD2:%.*]] = fadd <8 x half> [[ADD0]], [[ADD1]]
+; CHECK-NEXT:    store <8 x half> [[ADD2]], ptr addrspace(3) [[PTR]], align 16
+; CHECK-NEXT:    ret void
+;
+; GVN-LABEL: define amdgpu_kernel void @test1(
+; GVN-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; GVN-NEXT:  [[ENTRY:.*:]]
+; GVN-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; GVN-NEXT:    [[XOR0:%.*]] = xor i32 [[SEL]], 32
+; GVN-NEXT:    [[GEP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR0]]
+; GVN-NEXT:    [[GEP12:%.*]] = getelementptr i8, ptr addrspace(3) [[GEP0]], i32 8192
+; GVN-NEXT:    [[GEP24:%.*]] = getelementptr i8, ptr addrspace(3) [[GEP0]], i32 16384
+; GVN-NEXT:    [[GEP36:%.*]] = getelementptr i8, ptr addrspace(3) [[GEP0]], i32 24576
+; GVN-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP0]], align 16
+; GVN-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP12]], align 16
+; GVN-NEXT:    [[V2:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP24]], align 16
+; GVN-NEXT:    [[V3:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP36]], align 16
+; GVN-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; GVN-NEXT:    [[ADD1:%.*]] = fadd <8 x half> [[V2]], [[V3]]
+; GVN-NEXT:    [[ADD2:%.*]] = fadd <8 x half> [[ADD0]], [[ADD1]]
+; GVN-NEXT:    store <8 x half> [[ADD2]], ptr addrspace(3) [[PTR]], align 16
+; GVN-NEXT:    ret void
+;
+entry:
+  %sel = select i1 %cond, i32 0, i32 288
+  %xor0 = xor i32 %sel, 32
+  %xor1 = xor i32 %sel, 4128
+  %xor2 = xor i32 %sel, 8224
+  %xor3 = xor i32 %sel, 12320
+  %gep0 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor0
+  %gep1 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor1
+  %gep2 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor2
+  %gep3 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor3
+  %v0 = load <8 x half>, ptr addrspace(3) %gep0, align 16
+  %v1 = load <8 x half>, ptr addrspace(3) %gep1, align 16
+  %v2 = load <8 x half>, ptr addrspace(3) %gep2, align 16
+  %v3 = load <8 x half>, ptr addrspace(3) %gep3, align 16
+  %add0 = fadd <8 x half> %v0, %v1
+  %add1 = fadd <8 x half> %v2, %v3
+  %add2 = fadd <8 x half> %add0, %add1
+  store <8 x half> %add2, ptr addrspace(3) %ptr, align 16
+  ret void
+}
+
+; Check that disjoint constants are properly extracted and folded into GEP
+; addressing modes and GVN to eliminate redundant computation (reverse order).
+define amdgpu_kernel void @test2(i1 %cond, ptr addrspace(3) %ptr) {
+; CHECK-LABEL: define amdgpu_kernel void @test2(
+; CHECK-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; CHECK-NEXT:    [[XOR3:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[XOR01:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR01]]
+; CHECK-NEXT:    [[GEP02:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 24576
+; CHECK-NEXT:    [[XOR13:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR13]]
+; CHECK-NEXT:    [[GEP14:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP1]], i32 16384
+; CHECK-NEXT:    [[XOR25:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR25]]
+; CHECK-NEXT:    [[GEP26:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP2]], i32 8192
+; CHECK-NEXT:    [[GEP3:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR3]]
+; CHECK-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP02]], align 16
+; CHECK-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP14]], align 16
+; CHECK-NEXT:    [[V2:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP26]], align 16
+; CHECK-NEXT:    [[V3:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP3]], align 16
+; CHECK-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; CHECK-NEXT:    [[ADD1:%.*]] = fadd <8 x half> [[V2]], [[V3]]
+; CHECK-NEXT:    [[ADD2:%.*]] = fadd <8 x half> [[ADD0]], [[ADD1]]
+; CHECK-NEXT:    store <8 x half> [[ADD2]], ptr addrspace(3) [[PTR]], align 16
+; CHECK-NEXT:    ret void
+;
+; GVN-LABEL: define amdgpu_kernel void @test2(
+; GVN-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; GVN-NEXT:  [[ENTRY:.*:]]
+; GVN-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; GVN-NEXT:    [[XOR3:%.*]] = xor i32 [[SEL]], 32
+; GVN-NEXT:    [[TMP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR3]]
+; GVN-NEXT:    [[GEP02:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 24576
+; GVN-NEXT:    [[GEP14:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 16384
+; GVN-NEXT:    [[GEP26:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 8192
+; GVN-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP02]], align 16
+; GVN-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP14]], align 16
+; GVN-NEXT:    [[V2:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP26]], align 16
+; GVN-NEXT:    [[V3:%.*]] = load <8 x half>, ptr addrspace(3) [[TMP0]], align 16
+; GVN-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; GVN-NEXT:    [[ADD1:%.*]] = fadd <8 x half> [[V2]], [[V3]]
+; GVN-NEXT:    [[ADD2:%.*]] = fadd <8 x half> [[ADD0]], [[ADD1]]
+; GVN-NEXT:    store <8 x half> [[ADD2]], ptr addrspace(3) [[PTR]], align 16
+; GVN-NEXT:    ret void
+;
+entry:
+  %sel = select i1 %cond, i32 0, i32 288
+  %xor0 = xor i32 %sel, 12320
+  %xor1 = xor i32 %sel, 8224
+  %xor2 = xor i32 %sel, 4128
+  %xor3 = xor i32 %sel, 32
+  %gep0 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor0
+  %gep1 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor1
+  %gep2 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor2
+  %gep3 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor3
+  %v0 = load <8 x half>, ptr addrspace(3) %gep0, align 16
+  %v1 = load <8 x half>, ptr addrspace(3) %gep1, align 16
+  %v2 = load <8 x half>, ptr addrspace(3) %gep2, align 16
+  %v3 = load <8 x half>, ptr addrspace(3) %gep3, align 16
+  %add0 = fadd <8 x half> %v0, %v1
+  %add1 = fadd <8 x half> %v2, %v3
+  %add2 = fadd <8 x half> %add0, %add1
+  store <8 x half> %add2, ptr addrspace(3) %ptr, align 16
+  ret void
+}
+
+; Verify that xor instructions with different non-disjoint constants are optimized.
+define amdgpu_kernel void @test3(i1 %cond, ptr addrspace(3) %ptr) {
+; CHECK-LABEL: define amdgpu_kernel void @test3(
+; CHECK-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; CHECK-NEXT:    [[XOR0:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[GEP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR0]]
+; CHECK-NEXT:    [[XOR11:%.*]] = xor i32 [[SEL]], 288
+; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR11]]
+; CHECK-NEXT:    [[GEP12:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 4096
+; CHECK-NEXT:    [[XOR23:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR23]]
+; CHECK-NEXT:    [[GEP24:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP1]], i32 8192
+; CHECK-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP0]], align 16
+; CHECK-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP12]], align 16
+; CHECK-NEXT:    [[V2:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP24]], align 16
+; CHECK-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; CHECK-NEXT:    [[ADD1:%.*]] = fadd <8 x half> [[V2]], [[ADD0]]
+; CHECK-NEXT:    store <8 x half> [[ADD1]], ptr addrspace(3) [[PTR]], align 16
+; CHECK-NEXT:    ret void
+;
+; GVN-LABEL: define amdgpu_kernel void @test3(
+; GVN-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; GVN-NEXT:  [[ENTRY:.*:]]
+; GVN-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; GVN-NEXT:    [[XOR0:%.*]] = xor i32 [[SEL]], 32
+; GVN-NEXT:    [[GEP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR0]]
+; GVN-NEXT:    [[XOR11:%.*]] = xor i32 [[SEL]], 288
+; GVN-NEXT:    [[TMP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR11]]
+; GVN-NEXT:    [[GEP12:%.*]] = getelementptr i8, ptr addrspace(3) [[TMP0]], i32 4096
+; GVN-NEXT:    [[GEP24:%.*]] = getelementptr i8, ptr addrspace(3) [[GEP0]], i32 8192
+; GVN-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP0]], align 16
+; GVN-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP12]], align 16
+; GVN-NEXT:    [[V2:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP24]], align 16
+; GVN-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; GVN-NEXT:    [[ADD1:%.*]] = fadd <8 x half> [[V2]], [[ADD0]]
+; GVN-NEXT:    store <8 x half> [[ADD1]], ptr addrspace(3) [[PTR]], align 16
+; GVN-NEXT:    ret void
+;
+entry:
+  %sel = select i1 %cond, i32 0, i32 288
+  %xor0 = xor i32 %sel, 32
+  %xor1 = xor i32 %sel, 2336
+  %xor2 = xor i32 %sel, 4128
+  %gep0 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor0
+  %gep1 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor1
+  %gep2 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor2
+  %v0 = load <8 x half>, ptr addrspace(3) %gep0, align 16
+  %v1 = load <8 x half>, ptr addrspace(3) %gep1, align 16
+  %v2 = load <8 x half>, ptr addrspace(3) %gep2, align 16
+  %add0 = fadd <8 x half> %v0, %v1
+  %add1 = fadd <8 x half> %v2, %add0
+  store <8 x half> %add1, ptr addrspace(3) %ptr, align 16
+  ret void
+}
+
+; Verify that no optimization occurs when disjoint constants are absent.
+define amdgpu_kernel void @test4(i1 %cond, ptr addrspace(3) %ptr) {
+; CHECK-LABEL: define amdgpu_kernel void @test4(
+; CHECK-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; CHECK-NEXT:    [[XOR0:%.*]] = xor i32 [[SEL]], 32
+; CHECK-NEXT:    [[XOR1:%.*]] = xor i32 [[SEL]], 288
+; CHECK-NEXT:    [[GEP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR0]]
+; CHECK-NEXT:    [[GEP1:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR1]]
+; CHECK-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP0]], align 16
+; CHECK-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP1]], align 16
+; CHECK-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; CHECK-NEXT:    store <8 x half> [[ADD0]], ptr addrspace(3) [[PTR]], align 16
+; CHECK-NEXT:    ret void
+;
+; GVN-LABEL: define amdgpu_kernel void @test4(
+; GVN-SAME: i1 [[COND:%.*]], ptr addrspace(3) [[PTR:%.*]]) {
+; GVN-NEXT:  [[ENTRY:.*:]]
+; GVN-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 0, i32 288
+; GVN-NEXT:    [[XOR0:%.*]] = xor i32 [[SEL]], 32
+; GVN-NEXT:    [[XOR1:%.*]] = xor i32 [[SEL]], 288
+; GVN-NEXT:    [[GEP0:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR0]]
+; GVN-NEXT:    [[GEP1:%.*]] = getelementptr half, ptr addrspace(3) [[PTR]], i32 [[XOR1]]
+; GVN-NEXT:    [[V0:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP0]], align 16
+; GVN-NEXT:    [[V1:%.*]] = load <8 x half>, ptr addrspace(3) [[GEP1]], align 16
+; GVN-NEXT:    [[ADD0:%.*]] = fadd <8 x half> [[V0]], [[V1]]
+; GVN-NEXT:    store <8 x half> [[ADD0]], ptr addrspace(3) [[PTR]], align 16
+; GVN-NEXT:    ret void
+;
+entry:
+  %sel = select i1 %cond, i32 0, i32 288
+  %xor0 = xor i32 %sel, 32
+  %xor1 = xor i32 %sel, 288
+  %gep0 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor0
+  %gep1 = getelementptr half, ptr addrspace(3) %ptr, i32 %xor1
+  %v0 = load <8 x half>, ptr addrspace(3) %gep0, align 16
+  %v1 = load <8 x half>, ptr addrspace(3) %gep1, align 16
+  %add0 = fadd <8 x half> %v0, %v1
+  store <8 x half> ...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list