[llvm] [SPIR-V] Scalarize vector of pointers for ptrtoint/inttoptr (PR #184817)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 12 08:41:50 PDT 2026


https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/184817

>From 42600273d1a6adf5350d0f539ab3d85ab36b4662 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 5 Mar 2026 16:38:46 +0100
Subject: [PATCH 1/3] [SPIR-V] Scalarize vector of pointers for
 ptrtoint/inttoptr

Scalarize ptrtoint and inttoptr instructions operating on vector-of-pointer types by converting them to extract/convert/insert sequences
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 55 +++++++++++++++++++
 llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp  |  6 +-
 llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp  |  1 +
 llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp   | 12 +++-
 .../pointers/vector-of-pointers-scalarized.ll | 29 ++++++++++
 .../CodeGen/SPIRV/pointers/vector-ptrtoint.ll | 27 +++++++++
 6 files changed, 125 insertions(+), 5 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 7a886de005b88..40262ee40340a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -305,6 +305,7 @@ class SPIRVEmitIntrinsics
   bool postprocessTypes(Module &M);
   bool processFunctionPointers(Module &M);
   void parseFunDeclarations(Module &M);
+
   void useRoundingMode(ConstrainedFPIntrinsic *FPI, IRBuilder<> &B);
   bool processMaskedMemIntrinsic(IntrinsicInst &I);
   bool convertMaskedMemIntrinsics(Module &M);
@@ -366,6 +367,8 @@ class SPIRVEmitIntrinsics
   Instruction *visitAtomicCmpXchgInst(AtomicCmpXchgInst &I);
   Instruction *visitUnreachableInst(UnreachableInst &I);
   Instruction *visitCallInst(CallInst &I);
+  Instruction *visitPtrToIntInst(PtrToIntInst &I);
+  Instruction *visitIntToPtrInst(IntToPtrInst &I);
 
   StringRef getPassName() const override { return "SPIRV emit intrinsics"; }
 
@@ -2124,6 +2127,58 @@ SPIRVEmitIntrinsics::visitExtractElementInst(ExtractElementInst &I) {
   return NewI;
 }
 
+Instruction *SPIRVEmitIntrinsics::visitPtrToIntInst(PtrToIntInst &I) {
+  // Scalarize ptrtoint on vectors of pointers, since SPIR-V does not support
+  // vectors of pointers.
+  Type *SrcTy = I.getOperand(0)->getType();
+  auto *VecTy = dyn_cast<FixedVectorType>(SrcTy);
+
+  if (!VecTy || !VecTy->getElementType()->isPointerTy())
+    return &I;
+
+  IRBuilder<> B(I.getParent());
+  B.SetInsertPoint(&I);
+
+  unsigned NumElems = VecTy->getNumElements();
+  Type *ElemIntTy = cast<VectorType>(I.getType())->getElementType();
+  Value *Result = PoisonValue::get(I.getType());
+
+  for (unsigned Idx = 0; Idx < NumElems; ++Idx) {
+    Value *Elem = B.CreateExtractElement(I.getOperand(0), B.getInt32(Idx));
+    Value *Conv = B.CreatePtrToInt(Elem, ElemIntTy);
+    Result = B.CreateInsertElement(Result, Conv, B.getInt32(Idx));
+  }
+
+  replaceAllUsesWithAndErase(B, &I, cast<Instruction>(Result));
+  return cast<Instruction>(Result);
+}
+
+Instruction *SPIRVEmitIntrinsics::visitIntToPtrInst(IntToPtrInst &I) {
+  // Scalarize inttoptr producing vectors of pointers, since SPIR-V does not
+  // support vectors of pointers.
+  Type *DstTy = I.getType();
+  auto *VecTy = dyn_cast<FixedVectorType>(DstTy);
+
+  if (!VecTy || !VecTy->getElementType()->isPointerTy())
+    return &I;
+
+  IRBuilder<> B(I.getParent());
+  B.SetInsertPoint(&I);
+
+  unsigned NumElems = VecTy->getNumElements();
+  Type *ElemPtrTy = VecTy->getElementType();
+  Value *Result = PoisonValue::get(I.getType());
+
+  for (unsigned Idx = 0; Idx < NumElems; ++Idx) {
+    Value *Elem = B.CreateExtractElement(I.getOperand(0), B.getInt32(Idx));
+    Value *Conv = B.CreateIntToPtr(Elem, ElemPtrTy);
+    Result = B.CreateInsertElement(Result, Conv, B.getInt32(Idx));
+  }
+
+  replaceAllUsesWithAndErase(B, &I, cast<Instruction>(Result));
+  return cast<Instruction>(Result);
+}
+
 Instruction *SPIRVEmitIntrinsics::visitInsertValueInst(InsertValueInst &I) {
   IRBuilder<> B(I.getParent());
   B.SetInsertPoint(&I);
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index 47ffecc4085ab..7acb632bacf2c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -376,7 +376,8 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
         return DstTy.isPointerVector() && SrcTy.isVector() &&
                !SrcTy.isPointer() &&
                DstTy.getNumElements() == SrcTy.getNumElements();
-      });
+      })
+      .scalarize(0);
   getActionDefinitionsBuilder(G_PTRTOINT)
       .legalForCartesianProduct(allIntScalars, allPtrs)
       .legalIf(
@@ -387,7 +388,8 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
         return SrcTy.isPointerVector() && DstTy.isVector() &&
                !DstTy.isPointer() &&
                DstTy.getNumElements() == SrcTy.getNumElements();
-      });
+      })
+      .scalarize(0);
   getActionDefinitionsBuilder(G_PTR_ADD)
       .legalForCartesianProduct(allPtrs, allIntScalars)
       .legalIf(
diff --git a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
index d9e134552a269..c467433adbcc1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
@@ -277,6 +277,7 @@ static SPIRVTypeInst deduceResultTypeFromOperands(MachineInstr *I,
   case TargetOpcode::G_ANYEXT:
   case TargetOpcode::G_SEXT:
   case TargetOpcode::G_ZEXT:
+  case TargetOpcode::G_PTRTOINT:
     return deduceIntTypeFromResult(ResVReg, MIB, GR);
   case TargetOpcode::G_BUILD_VECTOR:
     return deduceTypeFromOperandRange(I, MIB, GR, 1, I->getNumOperands());
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 9df5d18221a6d..c86cc076cc5da 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -349,10 +349,16 @@ static SPIRVTypeInst propagateSPIRVType(MachineInstr *MI,
         }
         break;
       }
-      case TargetOpcode::G_PTRTOINT:
-        SpvType = GR->getOrCreateSPIRVIntegerType(
-            MRI.getType(Reg).getScalarSizeInBits(), MIB);
+      case TargetOpcode::G_PTRTOINT: {
+        LLT RegType = MRI.getType(Reg);
+        SpvType =
+            GR->getOrCreateSPIRVIntegerType(RegType.getScalarSizeInBits(), MIB);
+        if (RegType.isVector()) {
+          SpvType = GR->getOrCreateSPIRVVectorType(
+              SpvType, RegType.getNumElements(), MIB, true);
+        }
         break;
+      }
       case TargetOpcode::G_TRUNC:
       case TargetOpcode::G_ADDRSPACE_CAST:
       case TargetOpcode::G_PTR_ADD:
diff --git a/llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll b/llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll
new file mode 100644
index 0000000000000..34befb07728a3
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll
@@ -0,0 +1,29 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+declare spir_func void @foo(<2 x i64>)
+
+; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#I8:]] = OpTypeInt 8 0
+; CHECK-DAG: %[[#PTR:]] = OpTypePointer CrossWorkgroup %[[#I8]]
+; CHECK-DAG: %[[#V2I64:]] = OpTypeVector %[[#I64]] 2
+; CHECK-DAG: OpName %[[#P0:]] "p.elem0"
+; CHECK-DAG: OpName %[[#P1:]] "p.elem1"
+
+; CHECK: %[[#ENTRY:]] = OpFunction
+; CHECK: %[[#P0]] = OpFunctionParameter %[[#PTR]]
+; CHECK: %[[#P1]] = OpFunctionParameter %[[#PTR]]
+; CHECK: OpFunctionParameter %[[#PTR]]
+; CHECK: OpFunctionParameter %[[#PTR]]
+
+; CHECK: %[[#CONV0:]] = OpConvertPtrToU %[[#I64]] %[[#P0]]
+; CHECK: OpCompositeInsert %[[#V2I64]] %[[#CONV0]]
+; CHECK: %[[#CONV1:]] = OpConvertPtrToU %[[#I64]] %[[#P1]]
+; CHECK: OpCompositeInsert %[[#V2I64]] %[[#CONV1]]
+
+define spir_kernel void @test_ptrtoaddr(<2 x ptr addrspace(1)> %p, <2 x ptr addrspace(1)> %res) {
+entry:
+  %addr = ptrtoint <2 x ptr addrspace(1)> %p to <2 x i64>
+  call void @foo(<2 x i64> %addr)
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll b/llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll
new file mode 100644
index 0000000000000..cc4a89380e316
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll
@@ -0,0 +1,27 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+declare spir_func void @foo(<2 x i64>)
+
+; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#V2I64:]] = OpTypeVector %[[#I64]] 2
+; CHECK-DAG: OpName %[[#FUNC:]] "test_scalar_ptrtoint_to_vector"
+
+; CHECK: %[[#FUNC]] = OpFunction
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V2I64]]
+; CHECK: OpCompositeInsert %[[#V2I64]]
+; CHECK: OpFunctionEnd
+
+define spir_kernel void @test_scalar_ptrtoint_to_vector(ptr addrspace(1) %p0, ptr addrspace(1) %p1) {
+entry:
+  ; Convert each pointer to integer separately
+  %addr0 = ptrtoint ptr addrspace(1) %p0 to i64
+  %addr1 = ptrtoint ptr addrspace(1) %p1 to i64
+  ; Combine into a vector
+  %vec0 = insertelement <2 x i64> poison, i64 %addr0, i32 0
+  %vec = insertelement <2 x i64> %vec0, i64 %addr1, i32 1
+  call void @foo(<2 x i64> %vec)
+  ret void
+}

>From 559cd5a82f0449a129d5475f5be56e53ed5152a1 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 21 Apr 2026 14:17:50 +0200
Subject: [PATCH 2/3] Update + address comments

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 139 ++++++++++++------
 .../pointers/scalarize-vector-inttoptr.ll     |  27 ++++
 .../pointers/scalarize-vector-ptrtoint.ll     |  27 ++++
 .../pointers/vector-of-pointers-scalarized.ll |  29 ----
 .../CodeGen/SPIRV/pointers/vector-ptrtoint.ll |  27 ----
 5 files changed, 151 insertions(+), 98 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll
 delete mode 100644 llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll
 delete mode 100644 llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 40262ee40340a..860b926512999 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -305,11 +305,11 @@ class SPIRVEmitIntrinsics
   bool postprocessTypes(Module &M);
   bool processFunctionPointers(Module &M);
   void parseFunDeclarations(Module &M);
-
   void useRoundingMode(ConstrainedFPIntrinsic *FPI, IRBuilder<> &B);
   bool processMaskedMemIntrinsic(IntrinsicInst &I);
   bool convertMaskedMemIntrinsics(Module &M);
   void preprocessBoolVectorBitcasts(Function &F);
+  void scalarizeVectorPtrCasts(Function &F);
 
   void emitUnstructuredLoopControls(Function &F, IRBuilder<> &B);
 
@@ -367,8 +367,6 @@ class SPIRVEmitIntrinsics
   Instruction *visitAtomicCmpXchgInst(AtomicCmpXchgInst &I);
   Instruction *visitUnreachableInst(UnreachableInst &I);
   Instruction *visitCallInst(CallInst &I);
-  Instruction *visitPtrToIntInst(PtrToIntInst &I);
-  Instruction *visitIntToPtrInst(IntToPtrInst &I);
 
   StringRef getPassName() const override { return "SPIRV emit intrinsics"; }
 
@@ -2127,56 +2125,112 @@ SPIRVEmitIntrinsics::visitExtractElementInst(ExtractElementInst &I) {
   return NewI;
 }
 
-Instruction *SPIRVEmitIntrinsics::visitPtrToIntInst(PtrToIntInst &I) {
-  // Scalarize ptrtoint on vectors of pointers, since SPIR-V does not support
-  // vectors of pointers.
-  Type *SrcTy = I.getOperand(0)->getType();
-  auto *VecTy = dyn_cast<FixedVectorType>(SrcTy);
+// Trace a vector Value to find which scalar value was inserted at a given
+// index. Walks backwards through insertelement chains. Returns nullptr if the
+// element cannot be determined statically.
+static Value *findInsertedElement(Value *Vec, unsigned TargetIdx) {
+  while (auto *IE = dyn_cast<InsertElementInst>(Vec)) {
+    auto *IdxC = dyn_cast<ConstantInt>(IE->getOperand(2));
+    if (!IdxC)
+      return nullptr;
+    if (IdxC->getZExtValue() == TargetIdx)
+      return IE->getOperand(1);
+    Vec = IE->getOperand(0);
+  }
+  return nullptr;
+}
 
-  if (!VecTy || !VecTy->getElementType()->isPointerTy())
-    return &I;
+// Scalarize ptrtoint and inttoptr instructions operating on vectors of
+// pointers, since SPIR-V does not support vectors of pointers without
+// extensions. This must run before the main visitor loop to ensure
+// insertelement/extractelement chains are still in their original IR form.
+void SPIRVEmitIntrinsics::scalarizeVectorPtrCasts(Function &F) {
+  // If the extension for vector-of-pointers is available, no scalarization
+  // is needed - the backend can handle them natively.
+  const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(F);
+  if (ST.canUseExtension(SPIRV::Extension::SPV_INTEL_masked_gather_scatter))
+    return;
 
-  IRBuilder<> B(I.getParent());
-  B.SetInsertPoint(&I);
+  SmallVector<Instruction *, 8> ToErase;
 
-  unsigned NumElems = VecTy->getNumElements();
-  Type *ElemIntTy = cast<VectorType>(I.getType())->getElementType();
-  Value *Result = PoisonValue::get(I.getType());
+  for (Instruction &I : make_early_inc_range(instructions(F))) {
+    if (auto *PTI = dyn_cast<PtrToIntInst>(&I)) {
+      auto *VecTy = dyn_cast<FixedVectorType>(PTI->getOperand(0)->getType());
+      if (!VecTy || !VecTy->getElementType()->isPointerTy())
+        continue;
 
-  for (unsigned Idx = 0; Idx < NumElems; ++Idx) {
-    Value *Elem = B.CreateExtractElement(I.getOperand(0), B.getInt32(Idx));
-    Value *Conv = B.CreatePtrToInt(Elem, ElemIntTy);
-    Result = B.CreateInsertElement(Result, Conv, B.getInt32(Idx));
-  }
+      IRBuilder<> B(PTI);
+      unsigned NumElems = VecTy->getNumElements();
+      Type *ElemIntTy = cast<VectorType>(PTI->getType())->getElementType();
+      Value *Result = PoisonValue::get(PTI->getType());
+
+      for (unsigned Idx = 0; Idx < NumElems; ++Idx) {
+        Value *ElemPtr = findInsertedElement(PTI->getOperand(0), Idx);
+        if (!ElemPtr)
+          ElemPtr = B.CreateExtractElement(PTI->getOperand(0), B.getInt32(Idx));
+        Value *Conv = B.CreatePtrToInt(ElemPtr, ElemIntTy);
+        Result = B.CreateInsertElement(Result, Conv, B.getInt32(Idx));
+      }
 
-  replaceAllUsesWithAndErase(B, &I, cast<Instruction>(Result));
-  return cast<Instruction>(Result);
-}
+      PTI->replaceAllUsesWith(Result);
+      ToErase.push_back(PTI);
+    } else if (auto *ITP = dyn_cast<IntToPtrInst>(&I)) {
+      auto *VecTy = dyn_cast<FixedVectorType>(ITP->getType());
+      if (!VecTy || !VecTy->getElementType()->isPointerTy())
+        continue;
 
-Instruction *SPIRVEmitIntrinsics::visitIntToPtrInst(IntToPtrInst &I) {
-  // Scalarize inttoptr producing vectors of pointers, since SPIR-V does not
-  // support vectors of pointers.
-  Type *DstTy = I.getType();
-  auto *VecTy = dyn_cast<FixedVectorType>(DstTy);
+      // Only scalarize if all users are extractelement with constant index.
+      // Other uses (e.g. masked.gather) need the vector-of-pointers and
+      // should be handled by extension-specific lowering.
+      bool AllUsersExtract = llvm::all_of(ITP->users(), [&](User *U) {
+        auto *EE = dyn_cast<ExtractElementInst>(U);
+        return EE && isa<ConstantInt>(EE->getIndexOperand());
+      });
+      if (!AllUsersExtract)
+        continue;
 
-  if (!VecTy || !VecTy->getElementType()->isPointerTy())
-    return &I;
+      IRBuilder<> B(ITP);
+      Type *ElemPtrTy = VecTy->getElementType();
+
+      // Replace extractelement users with scalar inttoptr.
+      for (User *U : make_early_inc_range(ITP->users())) {
+        auto *EE = cast<ExtractElementInst>(U);
+        unsigned Idx = cast<ConstantInt>(EE->getIndexOperand())->getZExtValue();
+        B.SetInsertPoint(EE);
+        Value *Elem =
+            B.CreateExtractElement(ITP->getOperand(0), B.getInt32(Idx));
+        Value *Conv = B.CreateIntToPtr(Elem, ElemPtrTy);
+        EE->replaceAllUsesWith(Conv);
+        ToErase.push_back(EE);
+      }
 
-  IRBuilder<> B(I.getParent());
-  B.SetInsertPoint(&I);
+      ToErase.push_back(ITP);
+    }
+  }
 
-  unsigned NumElems = VecTy->getNumElements();
-  Type *ElemPtrTy = VecTy->getElementType();
-  Value *Result = PoisonValue::get(I.getType());
+  for (Instruction *I : ToErase)
+    I->eraseFromParent();
 
-  for (unsigned Idx = 0; Idx < NumElems; ++Idx) {
-    Value *Elem = B.CreateExtractElement(I.getOperand(0), B.getInt32(Idx));
-    Value *Conv = B.CreateIntToPtr(Elem, ElemPtrTy);
-    Result = B.CreateInsertElement(Result, Conv, B.getInt32(Idx));
+  // Clean up dead insertelement chains that produced vectors of pointers
+  // (e.g. the chain feeding a now-erased ptrtoint). Use a worklist to
+  // handle chains where erasing one insert makes the previous one dead.
+  SmallVector<Instruction *, 8> DeadInsts;
+  for (Instruction &I : instructions(F)) {
+    if (!isa<InsertElementInst>(&I))
+      continue;
+    auto *VecTy = dyn_cast<FixedVectorType>(I.getType());
+    if (VecTy && VecTy->getElementType()->isPointerTy() && I.use_empty())
+      DeadInsts.push_back(&I);
+  }
+  while (!DeadInsts.empty()) {
+    Instruction *I = DeadInsts.pop_back_val();
+    SmallVector<Value *, 4> Ops(I->operand_values());
+    I->eraseFromParent();
+    for (Value *Op : Ops)
+      if (auto *OpI = dyn_cast<Instruction>(Op);
+          OpI && OpI->use_empty() && isa<InsertElementInst>(OpI))
+        DeadInsts.push_back(OpI);
   }
-
-  replaceAllUsesWithAndErase(B, &I, cast<Instruction>(Result));
-  return cast<Instruction>(Result);
 }
 
 Instruction *SPIRVEmitIntrinsics::visitInsertValueInst(InsertValueInst &I) {
@@ -3260,6 +3314,7 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
         Phi.mutateType(B.getInt32Ty());
       }
 
+  scalarizeVectorPtrCasts(Func);
   preprocessBoolVectorBitcasts(Func);
   SmallVector<Instruction *> Worklist(
       llvm::make_pointer_range(instructions(Func)));
diff --git a/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll
new file mode 100644
index 0000000000000..5a62737254ff8
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll
@@ -0,0 +1,27 @@
+; Test that inttoptr on a vector of integers to a vector of pointers is
+; scalarized into per-element inttoptr operations, since SPIR-V does not
+; support vectors of pointers without extensions.
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#I8:]] = OpTypeInt 8 0
+; CHECK-DAG: %[[#PTR:]] = OpTypePointer CrossWorkgroup %[[#I8]]
+
+; CHECK: OpFunction
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK: OpFunctionEnd
+
+define spir_kernel void @test_inttoptr_scalarized(<2 x i64> %addr) {
+entry:
+  %ptrs = inttoptr <2 x i64> %addr to <2 x ptr addrspace(1)>
+  %p0 = extractelement <2 x ptr addrspace(1)> %ptrs, i32 0
+  %p1 = extractelement <2 x ptr addrspace(1)> %ptrs, i32 1
+  %val0 = load i8, ptr addrspace(1) %p0
+  %val1 = load i8, ptr addrspace(1) %p1
+  %sum = add i8 %val0, %val1
+  store i8 %sum, ptr addrspace(1) %p0
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll
new file mode 100644
index 0000000000000..8892d0ec942cc
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll
@@ -0,0 +1,27 @@
+; Test that ptrtoint on a vector of pointers to a vector of integers is
+; scalarized into per-element ptrtoint operations, since SPIR-V does not
+; support vectors of pointers without extensions.
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+declare spir_func void @foo(<2 x i64>)
+
+; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#V2I64:]] = OpTypeVector %[[#I64]] 2
+
+; CHECK: OpFunction
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V2I64]]
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V2I64]]
+; CHECK: OpFunctionEnd
+
+define spir_kernel void @test_ptrtoint_scalarized(ptr addrspace(1) %p0, ptr addrspace(1) %p1) {
+entry:
+  %vec0 = insertelement <2 x ptr addrspace(1)> poison, ptr addrspace(1) %p0, i32 0
+  %vec = insertelement <2 x ptr addrspace(1)> %vec0, ptr addrspace(1) %p1, i32 1
+  %addr = ptrtoint <2 x ptr addrspace(1)> %vec to <2 x i64>
+  call void @foo(<2 x i64> %addr)
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll b/llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll
deleted file mode 100644
index 34befb07728a3..0000000000000
--- a/llvm/test/CodeGen/SPIRV/pointers/vector-of-pointers-scalarized.ll
+++ /dev/null
@@ -1,29 +0,0 @@
-; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
-; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-
-declare spir_func void @foo(<2 x i64>)
-
-; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
-; CHECK-DAG: %[[#I8:]] = OpTypeInt 8 0
-; CHECK-DAG: %[[#PTR:]] = OpTypePointer CrossWorkgroup %[[#I8]]
-; CHECK-DAG: %[[#V2I64:]] = OpTypeVector %[[#I64]] 2
-; CHECK-DAG: OpName %[[#P0:]] "p.elem0"
-; CHECK-DAG: OpName %[[#P1:]] "p.elem1"
-
-; CHECK: %[[#ENTRY:]] = OpFunction
-; CHECK: %[[#P0]] = OpFunctionParameter %[[#PTR]]
-; CHECK: %[[#P1]] = OpFunctionParameter %[[#PTR]]
-; CHECK: OpFunctionParameter %[[#PTR]]
-; CHECK: OpFunctionParameter %[[#PTR]]
-
-; CHECK: %[[#CONV0:]] = OpConvertPtrToU %[[#I64]] %[[#P0]]
-; CHECK: OpCompositeInsert %[[#V2I64]] %[[#CONV0]]
-; CHECK: %[[#CONV1:]] = OpConvertPtrToU %[[#I64]] %[[#P1]]
-; CHECK: OpCompositeInsert %[[#V2I64]] %[[#CONV1]]
-
-define spir_kernel void @test_ptrtoaddr(<2 x ptr addrspace(1)> %p, <2 x ptr addrspace(1)> %res) {
-entry:
-  %addr = ptrtoint <2 x ptr addrspace(1)> %p to <2 x i64>
-  call void @foo(<2 x i64> %addr)
-  ret void
-}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll b/llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll
deleted file mode 100644
index cc4a89380e316..0000000000000
--- a/llvm/test/CodeGen/SPIRV/pointers/vector-ptrtoint.ll
+++ /dev/null
@@ -1,27 +0,0 @@
-; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
-; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-
-declare spir_func void @foo(<2 x i64>)
-
-; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
-; CHECK-DAG: %[[#V2I64:]] = OpTypeVector %[[#I64]] 2
-; CHECK-DAG: OpName %[[#FUNC:]] "test_scalar_ptrtoint_to_vector"
-
-; CHECK: %[[#FUNC]] = OpFunction
-; CHECK: OpConvertPtrToU %[[#I64]]
-; CHECK: OpConvertPtrToU %[[#I64]]
-; CHECK: OpCompositeInsert %[[#V2I64]]
-; CHECK: OpCompositeInsert %[[#V2I64]]
-; CHECK: OpFunctionEnd
-
-define spir_kernel void @test_scalar_ptrtoint_to_vector(ptr addrspace(1) %p0, ptr addrspace(1) %p1) {
-entry:
-  ; Convert each pointer to integer separately
-  %addr0 = ptrtoint ptr addrspace(1) %p0 to i64
-  %addr1 = ptrtoint ptr addrspace(1) %p1 to i64
-  ; Combine into a vector
-  %vec0 = insertelement <2 x i64> poison, i64 %addr0, i32 0
-  %vec = insertelement <2 x i64> %vec0, i64 %addr1, i32 1
-  call void @foo(<2 x i64> %vec)
-  ret void
-}

>From 3fd0774d1f050e9a90945b1c883ee64400a81020 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 21 Apr 2026 15:12:37 +0200
Subject: [PATCH 3/3] fix empty uses

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  9 ++---
 .../masked-gather-no-extension.ll             | 17 +++++----
 .../masked-scatter-no-extension.ll            | 13 ++++---
 .../pointers/scalarize-vector-inttoptr.ll     | 35 +++++++++++++++++++
 .../pointers/scalarize-vector-ptrtoint.ll     | 25 +++++++++++++
 5 files changed, 84 insertions(+), 15 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 860b926512999..1e45d4a660f80 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2182,10 +2182,11 @@ void SPIRVEmitIntrinsics::scalarizeVectorPtrCasts(Function &F) {
       // Only scalarize if all users are extractelement with constant index.
       // Other uses (e.g. masked.gather) need the vector-of-pointers and
       // should be handled by extension-specific lowering.
-      bool AllUsersExtract = llvm::all_of(ITP->users(), [&](User *U) {
-        auto *EE = dyn_cast<ExtractElementInst>(U);
-        return EE && isa<ConstantInt>(EE->getIndexOperand());
-      });
+      bool AllUsersExtract =
+          !ITP->use_empty() && llvm::all_of(ITP->users(), [&](User *U) {
+            auto *EE = dyn_cast<ExtractElementInst>(U);
+            return EE && isa<ConstantInt>(EE->getIndexOperand());
+          });
       if (!AllUsersExtract)
         continue;
 
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-gather-no-extension.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-gather-no-extension.ll
index 2270d384834fb..3cbaf6fcd5dfd 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-gather-no-extension.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-gather-no-extension.ll
@@ -1,16 +1,21 @@
-; Test that llvm.masked.gather produces an error when the
-; SPV_INTEL_masked_gather_scatter extension is not enabled, since vector of
-; pointers is not supported in SPIR-V without this extension.
+; Test that llvm.masked.gather is scalarized into individual loads when the
+; SPV_INTEL_masked_gather_scatter extension is not enabled. The generic
+; ScalarizeMaskedMemIntrin pass handles this before SPIR-V-specific passes run.
 
-; RUN: not llc -O0 -mtriple=spirv64-unknown-unknown %s -o /dev/null 2>&1 | FileCheck %s
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
 
 declare <4 x i32> @llvm.masked.gather.v4i32.v4p1(<4 x ptr addrspace(1)>, i32, <4 x i1>, <4 x i32>)
 
-; CHECK: error: {{.*}}Vector of pointers requires SPV_INTEL_masked_gather_scatter extension
+; CHECK: OpFunction
+; CHECK: OpLoad
+; CHECK: OpLoad
+; CHECK: OpLoad
+; CHECK: OpLoad
 
-define spir_kernel void @test_gather_no_ext(<4 x i64> %addrs) {
+define spir_kernel void @test_gather_no_ext(<4 x i64> %addrs, ptr addrspace(1) %out) {
 entry:
   %ptrs = inttoptr <4 x i64> %addrs to <4 x ptr addrspace(1)>
   %data = call <4 x i32> @llvm.masked.gather.v4i32.v4p1(<4 x ptr addrspace(1)> %ptrs, i32 4, <4 x i1> <i1 true, i1 true, i1 true, i1 true>, <4 x i32> zeroinitializer)
+  store <4 x i32> %data, ptr addrspace(1) %out
   ret void
 }
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-scatter-no-extension.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-scatter-no-extension.ll
index b76b41dd5b62b..dd3a6e2d837dd 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-scatter-no-extension.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_masked_gather_scatter/masked-scatter-no-extension.ll
@@ -1,12 +1,15 @@
-; Test that llvm.masked.scatter produces an error when the
-; SPV_INTEL_masked_gather_scatter extension is not enabled, since vector of
-; pointers is not supported in SPIR-V without this extension.
+; Test that llvm.masked.scatter is scalarized into individual stores when the
+; SPV_INTEL_masked_gather_scatter extension is not enabled. The generic
+; ScalarizeMaskedMemIntrin pass handles this before SPIR-V-specific passes run.
 
-; RUN: not llc -O0 -mtriple=spirv64-unknown-unknown %s -o /dev/null 2>&1 | FileCheck %s
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
 
 declare void @llvm.masked.scatter.v4i32.v4p1(<4 x i32>, <4 x ptr addrspace(1)>, i32, <4 x i1>)
 
-; CHECK: error: {{.*}}Vector of pointers requires SPV_INTEL_masked_gather_scatter extension
+; CHECK: OpFunction
+; CHECK: OpStore
+; CHECK: OpStore
+; CHECK: OpReturn
 
 define spir_kernel void @test_scatter_no_ext(<4 x i32> %data, <4 x i64> %addrs) {
 entry:
diff --git a/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll
index 5a62737254ff8..7ea135fa8a0c9 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-inttoptr.ll
@@ -25,3 +25,38 @@ entry:
   store i8 %sum, ptr addrspace(1) %p0
   ret void
 }
+
+; Test scalarization with a 4-element vector.
+; CHECK: OpFunction
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK: OpFunctionEnd
+
+define spir_kernel void @test_inttoptr_v4(<4 x i64> %addr) {
+entry:
+  %ptrs = inttoptr <4 x i64> %addr to <4 x ptr addrspace(1)>
+  %p0 = extractelement <4 x ptr addrspace(1)> %ptrs, i32 0
+  %p1 = extractelement <4 x ptr addrspace(1)> %ptrs, i32 1
+  %p2 = extractelement <4 x ptr addrspace(1)> %ptrs, i32 2
+  %p3 = extractelement <4 x ptr addrspace(1)> %ptrs, i32 3
+  store i8 1, ptr addrspace(1) %p0
+  store i8 2, ptr addrspace(1) %p1
+  store i8 3, ptr addrspace(1) %p2
+  store i8 4, ptr addrspace(1) %p3
+  ret void
+}
+
+; CHECK: OpFunction
+; CHECK: OpConvertUToPtr %[[#PTR]]
+; CHECK-NOT: OpConvertUToPtr
+; CHECK: OpFunctionEnd
+
+define spir_kernel void @test_inttoptr_partial_use(<2 x i64> %addr) {
+entry:
+  %ptrs = inttoptr <2 x i64> %addr to <2 x ptr addrspace(1)>
+  %p0 = extractelement <2 x ptr addrspace(1)> %ptrs, i32 0
+  store i8 42, ptr addrspace(1) %p0
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll
index 8892d0ec942cc..1a538e08e7ee8 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/scalarize-vector-ptrtoint.ll
@@ -6,9 +6,11 @@
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
 declare spir_func void @foo(<2 x i64>)
+declare spir_func void @bar(<4 x i64>)
 
 ; CHECK-DAG: %[[#I64:]] = OpTypeInt 64 0
 ; CHECK-DAG: %[[#V2I64:]] = OpTypeVector %[[#I64]] 2
+; CHECK-DAG: %[[#V4I64:]] = OpTypeVector %[[#I64]] 4
 
 ; CHECK: OpFunction
 ; CHECK: OpConvertPtrToU %[[#I64]]
@@ -25,3 +27,26 @@ entry:
   call void @foo(<2 x i64> %addr)
   ret void
 }
+
+; Test ptrtoint scalarization with a 4-element vector.
+; CHECK: OpFunction
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V4I64]]
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V4I64]]
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V4I64]]
+; CHECK: OpConvertPtrToU %[[#I64]]
+; CHECK: OpCompositeInsert %[[#V4I64]]
+; CHECK: OpFunctionEnd
+
+define spir_kernel void @test_ptrtoint_v4(ptr addrspace(1) %p0, ptr addrspace(1) %p1, ptr addrspace(1) %p2, ptr addrspace(1) %p3) {
+entry:
+  %vec0 = insertelement <4 x ptr addrspace(1)> poison, ptr addrspace(1) %p0, i32 0
+  %vec1 = insertelement <4 x ptr addrspace(1)> %vec0, ptr addrspace(1) %p1, i32 1
+  %vec2 = insertelement <4 x ptr addrspace(1)> %vec1, ptr addrspace(1) %p2, i32 2
+  %vec3 = insertelement <4 x ptr addrspace(1)> %vec2, ptr addrspace(1) %p3, i32 3
+  %addr = ptrtoint <4 x ptr addrspace(1)> %vec3 to <4 x i64>
+  call void @bar(<4 x i64> %addr)
+  ret void
+}



More information about the llvm-commits mailing list