[llvm] [IRBuilder] Handle byte types in CreateBitPreservingCastChain (PR #209557)

Anshil Gandhi via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 18 20:18:58 PDT 2026


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

>From f4713044b8c87767a296fb7d90bdb48fd2411574 Mon Sep 17 00:00:00 2001
From: Anshil Gandhi <Anshil.Gandhi at amd.com>
Date: Tue, 14 Jul 2026 13:54:40 -0500
Subject: [PATCH] [IRBuilder] Handle byte types in CreateBitPreservingCastChain

CreateBitPreservingCastChain only routed conversions through an
inttoptr/ptrtoint step when the non-pointer side was an integer or
integer vector. Byte and byte-vector types are not integer types, so a
byte/byte-vector to pointer conversion fell through to a plain bitcast,
producing an invalid bitcast between a non-pointer aggregate and a
pointer (e.g. `bitcast <2 x b32> to ptr`). This manifested as a
MachineVerifier "bitcast cannot convert between pointers and other
types" failure once a pass fed byte element types into the cast chain
(see the preceding precommit test).

Treat byte and byte-vector types the same as integers here so the
conversion goes through a pointer-sized integer.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 llvm/include/llvm/IR/Type.h                   |  4 ++
 llvm/lib/IR/IRBuilder.cpp                     |  6 ++-
 llvm/lib/IR/Type.cpp                          |  4 ++
 .../AMDGPU/promote-alloca-byte-ptr-cast.ll    | 49 +++++++++++++++++++
 llvm/unittests/IR/IRBuilderTest.cpp           | 18 +++++++
 5 files changed, 79 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/promote-alloca-byte-ptr-cast.ll

diff --git a/llvm/include/llvm/IR/Type.h b/llvm/include/llvm/IR/Type.h
index b0e949c118ce4..7a9816b74f987 100644
--- a/llvm/include/llvm/IR/Type.h
+++ b/llvm/include/llvm/IR/Type.h
@@ -298,6 +298,10 @@ class Type {
   /// Determine if this type could be losslessly bitcast to Ty
   LLVM_ABI bool canLosslesslyBitCastTo(Type *Ty) const;
 
+  /// Return true if this type could be converted through an int/int-vector
+  /// or a ptr/ptr-vector.
+  LLVM_ABI bool isCastableThroughIntOrPtr() const;
+
   /// Return true if this type is empty, that is, it has no elements or all of
   /// its elements are empty.
   LLVM_ABI bool isEmptyTy() const;
diff --git a/llvm/lib/IR/IRBuilder.cpp b/llvm/lib/IR/IRBuilder.cpp
index df738faad7842..8656450c8d5b5 100644
--- a/llvm/lib/IR/IRBuilder.cpp
+++ b/llvm/lib/IR/IRBuilder.cpp
@@ -140,7 +140,7 @@ Value *IRBuilderBase::CreateBitPreservingCastChain(const DataLayout &DL,
   };
 
   // See if we need inttoptr for this type pair. May require additional bitcast.
-  if (OldTy->isIntOrIntVectorTy() && NewTy->isPtrOrPtrVectorTy()) {
+  if (OldTy->isCastableThroughIntOrPtr() && NewTy->isPtrOrPtrVectorTy()) {
     // Expand <2 x i32> to i8* --> <2 x i32> to i64 to i8*
     // Expand i128 to <2 x i8*> --> i128 to <2 x i64> to <2 x i8*>
     // Expand <4 x i32> to <2 x i8*> --> <4 x i32> to <2 x i64> to <2 x i8*>
@@ -149,7 +149,9 @@ Value *IRBuilderBase::CreateBitPreservingCastChain(const DataLayout &DL,
   }
 
   // See if we need ptrtoint for this type pair. May require additional bitcast.
-  if (OldTy->isPtrOrPtrVectorTy() && NewTy->isIntOrIntVectorTy()) {
+  bool NewIsIntLike =
+      NewTy->isIntOrIntVectorTy() || NewTy->isByteOrByteVectorTy();
+  if (OldTy->isPtrOrPtrVectorTy() && NewIsIntLike) {
     // Expand <2 x i8*> to i128 --> <2 x i8*> to <2 x i64> to i128
     // Expand i8* to <2 x i32> --> i8* to i64 to <2 x i32>
     // Expand <2 x i8*> to <4 x i32> --> <2 x i8*> to <2 x i64> to <4 x i32>
diff --git a/llvm/lib/IR/Type.cpp b/llvm/lib/IR/Type.cpp
index 890f523b69f55..597cecf830724 100644
--- a/llvm/lib/IR/Type.cpp
+++ b/llvm/lib/IR/Type.cpp
@@ -177,6 +177,10 @@ bool Type::canLosslesslyBitCastTo(Type *Ty) const {
   return false;
 }
 
+bool Type::isCastableThroughIntOrPtr() const {
+  return isIntOrIntVectorTy() || isByteOrByteVectorTy();
+}
+
 bool Type::isEmptyTy() const {
   if (auto *ATy = dyn_cast<ArrayType>(this)) {
     unsigned NumElements = ATy->getNumElements();
diff --git a/llvm/test/CodeGen/AMDGPU/promote-alloca-byte-ptr-cast.ll b/llvm/test/CodeGen/AMDGPU/promote-alloca-byte-ptr-cast.ll
new file mode 100644
index 0000000000000..76b70d0c7dafb
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/promote-alloca-byte-ptr-cast.ll
@@ -0,0 +1,49 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -passes=amdgpu-promote-alloca,verify < %s | FileCheck %s
+
+; Promoting these allocas to a byte vector forces a conversion between a
+; byte-vector and a pointer of the same size. Byte types are not integers, so
+; before CreateBitPreservingCastChain learned to treat them like integers this
+; produced a direct (and invalid) `bitcast <2 x b32> to ptr`, which later
+; tripped the MachineVerifier ("bitcast cannot convert between pointers and
+; other types"). The conversion must instead route through a pointer-sized
+; integer via inttoptr/ptrtoint.
+
+target datalayout = "e-p:64:64-p5:32:32-A5"
+
+define amdgpu_kernel void @ptr_from_byte_vec(ptr %out, ptr %inptr) {
+; CHECK-LABEL: @ptr_from_byte_vec(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[A:%.*]] = freeze <2 x b32> poison
+; CHECK-NEXT:    [[PBITS:%.*]] = load b64, ptr [[INPTR:%.*]], align 8
+; CHECK-NEXT:    [[TMP0:%.*]] = bitcast b64 [[PBITS]] to <2 x b32>
+; CHECK-NEXT:    [[TMP1:%.*]] = bitcast <2 x b32> [[TMP0]] to i64
+; CHECK-NEXT:    [[TMP2:%.*]] = inttoptr i64 [[TMP1]] to ptr
+; CHECK-NEXT:    store ptr [[TMP2]], ptr [[OUT:%.*]], align 8
+; CHECK-NEXT:    ret void
+;
+entry:
+  %a = alloca [2 x b32], align 8, addrspace(5)
+  %pbits = load b64, ptr %inptr, align 8
+  store b64 %pbits, ptr addrspace(5) %a, align 8
+  %p = load ptr, ptr addrspace(5) %a, align 8
+  store ptr %p, ptr %out, align 8
+  ret void
+}
+
+define amdgpu_kernel void @byte_vec_from_ptr(ptr %out, ptr %argp) {
+; CHECK-LABEL: @byte_vec_from_ptr(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[A:%.*]] = freeze <2 x b32> poison
+; CHECK-NEXT:    [[TMP0:%.*]] = ptrtoint ptr [[ARGP:%.*]] to i64
+; CHECK-NEXT:    [[TMP1:%.*]] = bitcast i64 [[TMP0]] to <2 x b32>
+; CHECK-NEXT:    store <2 x b32> [[TMP1]], ptr [[OUT:%.*]], align 8
+; CHECK-NEXT:    ret void
+;
+entry:
+  %a = alloca [2 x b32], align 8, addrspace(5)
+  store ptr %argp, ptr addrspace(5) %a, align 8
+  %v = load <2 x b32>, ptr addrspace(5) %a, align 8
+  store <2 x b32> %v, ptr %out, align 8
+  ret void
+}
diff --git a/llvm/unittests/IR/IRBuilderTest.cpp b/llvm/unittests/IR/IRBuilderTest.cpp
index 055687f9f0407..bea757cb0b1a2 100644
--- a/llvm/unittests/IR/IRBuilderTest.cpp
+++ b/llvm/unittests/IR/IRBuilderTest.cpp
@@ -606,6 +606,24 @@ TEST_F(IRBuilderTest, GetIntTy) {
   EXPECT_EQ(IntPtrTy, IntegerType::get(Ctx, IntPtrBitSize));
 }
 
+TEST_F(IRBuilderTest, CreateBitPreservingCastChainByteTypes) {
+  M->setDataLayout("e-p:64:64");
+  IRBuilder<> Builder(BB);
+  const DataLayout &DL = M->getDataLayout();
+  Type *PtrTy = PointerType::getUnqual(Ctx);
+  Type *ByteVecTy = VectorType::get(Type::getByteNTy(Ctx, 32), 2,
+                                    /*Scalable=*/false);
+  Value *ByteVec = Builder.CreateLoad(ByteVecTy, Constant::getNullValue(PtrTy));
+  Value *Ptr = Builder.CreateLoad(PtrTy, Constant::getNullValue(PtrTy));
+  Value *ToPtr = Builder.CreateBitPreservingCastChain(DL, ByteVec, PtrTy);
+  ASSERT_TRUE(isa<IntToPtrInst>(ToPtr));
+  EXPECT_TRUE(isa<BitCastInst>(cast<IntToPtrInst>(ToPtr)->getOperand(0)));
+  Value *ToByteVec = Builder.CreateBitPreservingCastChain(DL, Ptr, ByteVecTy);
+  ASSERT_EQ(ToByteVec->getType(), ByteVecTy);
+  ASSERT_TRUE(isa<BitCastInst>(ToByteVec));
+  EXPECT_TRUE(isa<PtrToIntInst>(cast<BitCastInst>(ToByteVec)->getOperand(0)));
+}
+
 TEST_F(IRBuilderTest, UnaryOperators) {
   IRBuilder<NoFolder> Builder(BB);
   Value *V = Builder.CreateLoad(GV->getValueType(), GV);



More information about the llvm-commits mailing list