[llvm] [IR] Only follow ptr/ptr-vector sources in stripAndAccumulateConstantOffsets bitcasts (PR #203356)

Anshil Gandhi via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 10:51:27 PDT 2026


https://github.com/gandhi56 updated https://github.com/llvm/llvm-project/pull/203356

>From 8a4fa070af1c667e99cfaa51be322a86f2bd9609 Mon Sep 17 00:00:00 2001
From: Anshil Gandhi <Anshil.Gandhi at amd.com>
Date: Thu, 11 Jun 2026 11:04:58 -0500
Subject: [PATCH 1/2] [GlobalISel][IRTranslator] Lower ptr/non-ptr bitcasts via
 G_INTTOPTR/G_PTRTOINT

IRTranslator::translateBitCast was emitting G_BITCAST across the pointer
boundary (e.g. <2 x s32> -> ptr on AMDGPU kernarg unpacking). MachineVerifier
requires G_BITCAST operands to be both pointers or both non-pointers, so
GlobalISel compilation aborted after IR translation on many AMDGPU tests.

For equal-sized ptr <-> non-ptr reinterprets, lower through G_INTTOPTR or
G_PTRTOINT instead. When the IR type is not already a pointer-sized integer
(or the vreg LLT already matches that integer), insert a G_BITCAST to/from
a pointer-sized scalar int first. Skip the intermediate G_BITCAST when the
source or destination is already that integer LLT to avoid invalid sN -> sN
bitcasts.

Update AMDGPU call-return-value checks and add Generic GlobalISel tests for
the two-step vector-byte <-> ptr lowering paths.
---
 llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp  | 77 ++++++++++++++---
 .../GlobalISel/irtranslator-byte-type.ll      | 84 +++++++++++++++++++
 2 files changed, 151 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index 28755da8327aa..221a5b58cc4f4 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -1585,6 +1585,8 @@ bool IRTranslator::translateBitCast(const User &U,
                                     MachineIRBuilder &MIRBuilder) {
   Type *SrcTy = U.getOperand(0)->getType();
   Type *DstTy = U.getType();
+  unsigned SrcTyBits = DL->getTypeSizeInBits(SrcTy);
+  unsigned DstTyBits = DL->getTypeSizeInBits(DstTy);
 
   // If we're bitcasting to the source type, we can reuse the source vreg.
   if (getLLTForType(*SrcTy, *DL) == getLLTForType(*DstTy, *DL)) {
@@ -1596,16 +1598,71 @@ bool IRTranslator::translateBitCast(const User &U,
     return translateCopy(U, *U.getOperand(0), MIRBuilder);
   }
 
-  // Only the scalar byte<->ptr crossing is redirected to G_INTTOPTR/G_PTRTOINT,
-  // which is the well-typed MIR shape for that boundary. Vector byte<->ptr
-  // (e.g. <N x b32> -> ptr produced by mixed-type load coalescing) and other
-  // legacy ptr/non-ptr IR bitcasts (AMDGPU iN<->p3 kernarg packing, etc.)
-  // keep their historical G_BITCAST lowering — G_INTTOPTR has no vector-src
-  // -> scalar-ptr form, and downstream passes already handle G_BITCAST.
-  if (DstTy->isPointerTy() && SrcTy->isByteTy())
-    return translateCast(TargetOpcode::G_INTTOPTR, U, MIRBuilder);
-  if (SrcTy->isPointerTy() && DstTy->isByteTy())
-    return translateCast(TargetOpcode::G_PTRTOINT, U, MIRBuilder);
+  // Emit G_INTTOPTR
+  if (!SrcTy->isPointerTy() && DstTy->isPointerTy()) {
+    if (DstTyBits == SrcTyBits) {
+      if (!mayTranslateUserTypes(U))
+        return false;
+
+      uint32_t Flags = 0;
+      if (const Instruction *I = dyn_cast<Instruction>(&U))
+        Flags = MachineInstr::copyFlagsFromInstruction(*I);
+
+      Register SrcReg = getOrCreateVReg(*U.getOperand(0));
+      Register DstReg = getOrCreateVReg(U);
+      MachineRegisterInfo &MRI = *MIRBuilder.getMRI();
+      LLT IntLLT = LLT::scalar(DstTyBits);
+      LLT SrcLLT = MRI.getType(SrcReg);
+
+      // Prefer IR integer check; also accept when the operand is already the
+      // pointer-sized scalar LLT (e.g. i64 value lowered to s64) so we never
+      // emit G_BITCAST sN -> sN, which MachineVerifier rejects.
+      if ((SrcTy->isIntegerTy() &&
+           cast<IntegerType>(SrcTy)->getBitWidth() == DstTyBits) ||
+          SrcLLT == IntLLT) {
+        MIRBuilder.buildInstr(TargetOpcode::G_INTTOPTR, {DstReg}, {SrcReg},
+                              Flags);
+        return true;
+      }
+
+      auto IntVal = MIRBuilder.buildBitcast(IntLLT, SrcReg);
+      MIRBuilder.buildInstr(TargetOpcode::G_INTTOPTR, {DstReg},
+                            {IntVal.getReg(0)}, Flags);
+      return true;
+    }
+  }
+
+  // Emit G_PTRTOINT for the Ptr -> Int case where the source
+  // and destination are equally sized. Otherwise, emit a G_BITCAST.
+  if (SrcTy->isPointerTy() && !DstTy->isPointerTy()) {
+    if (SrcTyBits == DstTyBits) {
+      if (!mayTranslateUserTypes(U))
+        return false;
+
+      uint32_t Flags = 0;
+      if (const Instruction *I = dyn_cast<Instruction>(&U))
+        Flags = MachineInstr::copyFlagsFromInstruction(*I);
+
+      Register SrcReg = getOrCreateVReg(*U.getOperand(0));
+      Register DstReg = getOrCreateVReg(U);
+      MachineRegisterInfo &MRI = *MIRBuilder.getMRI();
+      LLT IntLLT = LLT::scalar(SrcTyBits);
+      LLT DstLLT = MRI.getType(DstReg);
+
+      if ((DstTy->isIntegerTy() &&
+           cast<IntegerType>(DstTy)->getBitWidth() == SrcTyBits) ||
+          DstLLT == IntLLT) {
+        MIRBuilder.buildInstr(TargetOpcode::G_PTRTOINT, {DstReg}, {SrcReg},
+                              Flags);
+        return true;
+      }
+
+      auto IntVal = MIRBuilder.buildPtrToInt(IntLLT, SrcReg);
+      MIRBuilder.buildInstr(TargetOpcode::G_BITCAST, {DstReg},
+                            {IntVal.getReg(0)}, Flags);
+      return true;
+    }
+  }
 
   return translateCast(TargetOpcode::G_BITCAST, U, MIRBuilder);
 }
diff --git a/llvm/test/CodeGen/Generic/GlobalISel/irtranslator-byte-type.ll b/llvm/test/CodeGen/Generic/GlobalISel/irtranslator-byte-type.ll
index af6885a285240..01f8412cb7529 100644
--- a/llvm/test/CodeGen/Generic/GlobalISel/irtranslator-byte-type.ll
+++ b/llvm/test/CodeGen/Generic/GlobalISel/irtranslator-byte-type.ll
@@ -646,6 +646,90 @@ define ptr @bitcast_b64_to_p0(b64 %b) {
   ret ptr %r
 }
 
+; <2 x b32> is not an LLVM integer type, so lowering uses G_BITCAST to s64 then
+; G_INTTOPTR (case 2).
+define ptr @irtranslator_bitcast_v2b32_to_ptr(<2 x b32> %v) {
+  ; AARCH64-LABEL: name: irtranslator_bitcast_v2b32_to_ptr
+  ; AARCH64: bb.1 (%ir-block.0):
+  ; AARCH64-NEXT:   liveins: $d0
+  ; AARCH64-NEXT: {{  $}}
+  ; AARCH64-NEXT:   [[V:%[0-9]+]]:_(<2 x i32>) = COPY $d0
+  ; AARCH64-NEXT:   [[I:%[0-9]+]]:_(s64) = G_BITCAST [[V]](<2 x i32>)
+  ; AARCH64-NEXT:   [[P:%[0-9]+]]:_(p0) = G_INTTOPTR [[I]](s64)
+  ; AARCH64-NEXT:   $x0 = COPY [[P]](p0)
+  ; AARCH64-NEXT:   RET_ReallyLR implicit $x0
+  ;
+  ; AMDGPU-LABEL: name: irtranslator_bitcast_v2b32_to_ptr
+  ; AMDGPU: bb.1 (%ir-block.0):
+  ; AMDGPU-NEXT:   liveins: $vgpr0, $vgpr1
+  ; AMDGPU-NEXT: {{  $}}
+  ; AMDGPU-NEXT:   [[E0:%[0-9]+]]:_(s32) = COPY $vgpr0
+  ; AMDGPU-NEXT:   [[E1:%[0-9]+]]:_(s32) = COPY $vgpr1
+  ; AMDGPU-NEXT:   [[V:%[0-9]+]]:_(<2 x s32>) = G_BUILD_VECTOR [[E0]](s32), [[E1]](s32)
+  ; AMDGPU-NEXT:   [[I:%[0-9]+]]:_(s64) = G_BITCAST [[V]](<2 x s32>)
+  ; AMDGPU-NEXT:   [[P:%[0-9]+]]:_(p0) = G_INTTOPTR [[I]](s64)
+  ; AMDGPU-NEXT:   [[U0:%[0-9]+]]:_(s32), [[U1:%[0-9]+]]:_(s32) = G_UNMERGE_VALUES [[P]](p0)
+  ; AMDGPU-NEXT:   $vgpr0 = COPY [[U0]](s32)
+  ; AMDGPU-NEXT:   $vgpr1 = COPY [[U1]](s32)
+  ; AMDGPU-NEXT:   SI_RETURN implicit $vgpr0, implicit $vgpr1
+  ;
+  ; X86-LABEL: name: irtranslator_bitcast_v2b32_to_ptr
+  ; X86: bb.1 (%ir-block.0):
+  ; X86-NEXT:   liveins: $xmm0
+  ; X86-NEXT: {{  $}}
+  ; X86-NEXT:   [[WIDE:%[0-9]+]]:_(<4 x s32>) = COPY $xmm0
+  ; X86-NEXT:   [[V:%[0-9]+]]:_(<2 x s32>), {{%[0-9]+}}:_(<2 x s32>) = G_UNMERGE_VALUES [[WIDE]](<4 x s32>)
+  ; X86-NEXT:   [[I:%[0-9]+]]:_(s64) = G_BITCAST [[V]](<2 x s32>)
+  ; X86-NEXT:   [[P:%[0-9]+]]:_(p0) = G_INTTOPTR [[I]](s64)
+  ; X86-NEXT:   $rax = COPY [[P]](p0)
+  ; X86-NEXT:   RET 0, implicit $rax
+  %p = bitcast <2 x b32> %v to ptr
+  ret ptr %p
+}
+
+; Destination is a vector of bytes, not an LLVM integer type: G_PTRTOINT to
+; s64 then G_BITCAST.
+define <2 x b32> @irtranslator_bitcast_ptr_to_v2b32(ptr %p) {
+  ; AARCH64-LABEL: name: irtranslator_bitcast_ptr_to_v2b32
+  ; AARCH64: bb.1 (%ir-block.0):
+  ; AARCH64-NEXT:   liveins: $x0
+  ; AARCH64-NEXT: {{  $}}
+  ; AARCH64-NEXT:   [[P:%[0-9]+]]:_(p0) = COPY $x0
+  ; AARCH64-NEXT:   [[I:%[0-9]+]]:_(s64) = G_PTRTOINT [[P]](p0)
+  ; AARCH64-NEXT:   [[V:%[0-9]+]]:_(<2 x i32>) = G_BITCAST [[I]](s64)
+  ; AARCH64-NEXT:   $d0 = COPY [[V]](<2 x i32>)
+  ; AARCH64-NEXT:   RET_ReallyLR implicit $d0
+  ;
+  ; AMDGPU-LABEL: name: irtranslator_bitcast_ptr_to_v2b32
+  ; AMDGPU: bb.1 (%ir-block.0):
+  ; AMDGPU-NEXT:   liveins: $vgpr0, $vgpr1
+  ; AMDGPU-NEXT: {{  $}}
+  ; AMDGPU-NEXT:   [[E0:%[0-9]+]]:_(s32) = COPY $vgpr0
+  ; AMDGPU-NEXT:   [[E1:%[0-9]+]]:_(s32) = COPY $vgpr1
+  ; AMDGPU-NEXT:   [[P:%[0-9]+]]:_(p0) = G_MERGE_VALUES [[E0]](s32), [[E1]](s32)
+  ; AMDGPU-NEXT:   [[I:%[0-9]+]]:_(s64) = G_PTRTOINT [[P]](p0)
+  ; AMDGPU-NEXT:   [[V:%[0-9]+]]:_(<2 x s32>) = G_BITCAST [[I]](s64)
+  ; AMDGPU-NEXT:   [[U0:%[0-9]+]]:_(s32), [[U1:%[0-9]+]]:_(s32) = G_UNMERGE_VALUES [[V]](<2 x s32>)
+  ; AMDGPU-NEXT:   $vgpr0 = COPY [[U0]](s32)
+  ; AMDGPU-NEXT:   $vgpr1 = COPY [[U1]](s32)
+  ; AMDGPU-NEXT:   SI_RETURN implicit $vgpr0, implicit $vgpr1
+  ;
+  ; X86-LABEL: name: irtranslator_bitcast_ptr_to_v2b32
+  ; X86: bb.1 (%ir-block.0):
+  ; X86-NEXT:   liveins: $rdi
+  ; X86-NEXT: {{  $}}
+  ; X86-NEXT:   [[P:%[0-9]+]]:_(p0) = COPY $rdi
+  ; X86-NEXT:   [[I:%[0-9]+]]:_(s64) = G_PTRTOINT [[P]](p0)
+  ; X86-NEXT:   [[V:%[0-9]+]]:_(<2 x s32>) = G_BITCAST [[I]](s64)
+  ; X86-NEXT:   [[E0:%[0-9]+]]:_(s32), [[E1:%[0-9]+]]:_(s32) = G_UNMERGE_VALUES [[V]](<2 x s32>)
+  ; X86-NEXT:   [[Z:%[0-9]+]]:_(s32) = G_IMPLICIT_DEF
+  ; X86-NEXT:   [[WIDE:%[0-9]+]]:_(<4 x s32>) = G_BUILD_VECTOR [[E0]](s32), [[E1]](s32), [[Z]](s32), [[Z]](s32)
+  ; X86-NEXT:   $xmm0 = COPY [[WIDE]](<4 x s32>)
+  ; X86-NEXT:   RET 0, implicit $xmm0
+  %v = bitcast ptr %p to <2 x b32>
+  ret <2 x b32> %v
+}
+
 ;;
 ;; The "bytecast" surface: bitcast is the only IR cast that may produce or
 ;; consume a byte type. The cases below correspond to LangRef's

>From f4919fbc36fe3b91377eef323245b0f9bb63d84e Mon Sep 17 00:00:00 2001
From: Anshil Gandhi <Anshil.Gandhi at amd.com>
Date: Thu, 11 Jun 2026 11:59:14 -0500
Subject: [PATCH 2/2] [IR] Only follow ptr (or ptr-vector) sources in
 stripAndAccumulateConstantOffsets bitcasts

stripAndAccumulateConstantOffsets used to walk through every bitcast operand.
If the operand was not a pointer (e.g. a same-sized byte type such as b64
reinterpreted as ptr), the walk could continue and violate the invariant that
the stripped value remains ptr or ptr-vector.

Align with stripPointerCastsAndOffsets: only look through bitcasts whose
source type is a pointer or pointer vector; keep addrspacecast as its own case.

Add unit tests for ptr-to-ptr bitcast (GEP offset accumulated through) and
for non-pointer-to-ptr bitcast (stop at the bitcast while still applying the
GEP offset).

This commit is needed in PR #177908.
---
 llvm/lib/IR/Value.cpp                  | 20 +++++++----
 llvm/unittests/IR/InstructionsTest.cpp | 50 ++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 6f71a5093e4a3..6478c92599619 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -783,17 +783,25 @@ const Value *Value::stripAndAccumulateConstantOffsets(
         }
       }
       V = GEP->getPointerOperand();
-    } else if (Operator::getOpcode(V) == Instruction::BitCast ||
-               Operator::getOpcode(V) == Instruction::AddrSpaceCast) {
+    } else if (Operator::getOpcode(V) == Instruction::BitCast) {
+      // Match stripPointerCastsAndOffsets: only look through ptr->ptr (or
+      // ptr-vector) bitcasts. Do not follow bitcast from byte/int/etc. to ptr
+      // (e.g. lossless bN -> ptr reconstitution) — the operand is not a
+      // pointer and pointer offset stripping cannot continue.
+      const Value *Src = cast<Operator>(V)->getOperand(0);
+      if (!Src->getType()->isPtrOrPtrVectorTy())
+        return V;
+      V = Src;
+    } else if (Operator::getOpcode(V) == Instruction::AddrSpaceCast) {
       V = cast<Operator>(V)->getOperand(0);
     } else if (auto *GA = dyn_cast<GlobalAlias>(V)) {
       if (!GA->isInterposable())
         V = GA->getAliasee();
     } else if (const auto *Call = dyn_cast<CallBase>(V)) {
-        if (const Value *RV = Call->getReturnedArgOperand())
-          V = RV;
-        if (AllowInvariantGroup && Call->isLaunderOrStripInvariantGroup())
-          V = Call->getArgOperand(0);
+      if (const Value *RV = Call->getReturnedArgOperand())
+        V = RV;
+      if (AllowInvariantGroup && Call->isLaunderOrStripInvariantGroup())
+        V = Call->getArgOperand(0);
     } else if (auto *Int2Ptr = dyn_cast<Operator>(V)) {
       // Try to accumulate across (inttoptr (add (ptrtoint p), off)).
       if (!AllowNonInbounds || !LookThroughIntToPtr || !Int2Ptr ||
diff --git a/llvm/unittests/IR/InstructionsTest.cpp b/llvm/unittests/IR/InstructionsTest.cpp
index a1f22bbac02a1..4f2b9eaf9a2d9 100644
--- a/llvm/unittests/IR/InstructionsTest.cpp
+++ b/llvm/unittests/IR/InstructionsTest.cpp
@@ -2011,4 +2011,54 @@ TEST(InstructionsTest, StripAndAccumulateConstantOffset) {
   EXPECT_TRUE(Offset.isZero());
 }
 
+// stripAndAccumulateConstantOffsets must only look through ptr->ptr (or
+// ptr-vector) bitcasts, matching stripPointerCastsAndOffsets. Following a
+// bitcast from a non-pointer (e.g. lossless iN->ptr) would leave a non-pointer
+// V and violate the strip loop invariant.
+TEST(InstructionsTest, StripAndAccumulateConstantOffsets_ThroughPtrBitCast) {
+  LLVMContext C;
+  DataLayout DL("e-p:64:64:64-i64:64");
+  std::unique_ptr<Module> M = parseIR(C, R"(
+  define void @foo(ptr %p) {
+    %bc = bitcast ptr %p to ptr
+    %gep = getelementptr inbounds i8, ptr %bc, i64 2
+    ret void
+  })");
+  ASSERT_TRUE(M);
+  BasicBlock &BB = M->getFunction("foo")->getEntryBlock();
+  auto It = BB.begin();
+  Value *BC = &*It++;
+  Value *GEP = &*It++;
+  (void)BC;
+  APInt Offset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
+  Value *Stripped = GEP->stripAndAccumulateConstantOffsets(
+      DL, Offset, /*AllowNonInbounds=*/true);
+  EXPECT_EQ(Stripped, M->getFunction("foo")->getArg(0));
+  EXPECT_EQ(Offset, APInt(DL.getIndexTypeSizeInBits(GEP->getType()), 2));
+}
+
+TEST(InstructionsTest,
+     StripAndAccumulateConstantOffsets_NoLookThroughNonPtrToPtrBitCast) {
+  LLVMContext C;
+  DataLayout DL("e-p:64:64:64-i64:64");
+  // LLVM forbids bitcast iN -> ptr (use inttoptr). bN -> ptr is a same-sized
+  // bitcast whose source is not a pointer type; stripping must stop here.
+  std::unique_ptr<Module> M = parseIR(C, R"(
+  define void @foo(b64 %x) {
+    %bc = bitcast b64 %x to ptr
+    %gep = getelementptr inbounds i8, ptr %bc, i64 1
+    ret void
+  })");
+  ASSERT_TRUE(M);
+  BasicBlock &BB = M->getFunction("foo")->getEntryBlock();
+  auto It = BB.begin();
+  Value *BC = &*It++;
+  Value *GEP = &*It++;
+  APInt Offset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
+  Value *Stripped = GEP->stripAndAccumulateConstantOffsets(
+      DL, Offset, /*AllowNonInbounds=*/true);
+  EXPECT_EQ(Stripped, BC);
+  EXPECT_EQ(Offset, APInt(DL.getIndexTypeSizeInBits(GEP->getType()), 1));
+}
+
 } // end anonymous namespace



More information about the llvm-commits mailing list