[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