[llvm] [SPIRV] Add support for G_PTRMASK (PR #201450)

Nick Sarnie via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 9 07:15:03 PDT 2026


https://github.com/sarnex updated https://github.com/llvm/llvm-project/pull/201450

>From 918b970cdf420e2574817919ca86ffc651342e20 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Wed, 3 Jun 2026 13:37:38 -0700
Subject: [PATCH 1/3] [SPIRV] Add support for G_PTRMASK

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply at anthropic.com>
---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 56 +++++++++++++++++++
 llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp  | 12 ++++
 llvm/test/CodeGen/SPIRV/ptrmask-vec.ll        | 31 ++++++++++
 llvm/test/CodeGen/SPIRV/ptrmask32.ll          | 24 ++++++++
 llvm/test/CodeGen/SPIRV/ptrmask64-32.ll       | 25 +++++++++
 llvm/test/CodeGen/SPIRV/ptrmask64.ll          | 24 ++++++++
 6 files changed, 172 insertions(+)
 create mode 100644 llvm/test/CodeGen/SPIRV/ptrmask-vec.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/ptrmask32.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/ptrmask64-32.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/ptrmask64.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index c3f21fe025bd5..0e1867591e023 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -185,6 +185,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
   bool selectAddrSpaceCast(Register ResVReg, SPIRVTypeInst ResType,
                            MachineInstr &I) const;
 
+  bool selectPtrMask(Register ResVReg, SPIRVTypeInst ResType,
+                     MachineInstr &I) const;
+
   bool selectAnyOrAll(Register ResVReg, SPIRVTypeInst ResType, MachineInstr &I,
                       unsigned OpType) const;
 
@@ -1259,6 +1262,8 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
     return selectBitcast(ResVReg, ResType, I);
   case TargetOpcode::G_ADDRSPACE_CAST:
     return selectAddrSpaceCast(ResVReg, ResType, I);
+  case TargetOpcode::G_PTRMASK:
+    return selectPtrMask(ResVReg, ResType, I);
   case TargetOpcode::G_PTR_ADD: {
     // Currently, we get G_PTR_ADD only applied to global variables.
     assert(I.getOperand(1).isReg() && I.getOperand(2).isReg());
@@ -2682,6 +2687,57 @@ bool SPIRVInstructionSelector::selectAddrSpaceCast(Register ResVReg,
   return false;
 }
 
+// G_PTRMASK - Apply a bitmask to a pointer value.
+// Result = Ptr & Mask
+// We need to convert the pointer to an integer, perform the AND operation,
+// and convert back to a pointer.
+bool SPIRVInstructionSelector::selectPtrMask(Register ResVReg,
+                                             SPIRVTypeInst ResType,
+                                             MachineInstr &I) const {
+  MachineBasicBlock &BB = *I.getParent();
+  MachineFunction &MF = *BB.getParent();
+  const DebugLoc &DL = I.getDebugLoc();
+
+  Register PtrReg = I.getOperand(1).getReg();
+  Register MaskReg = I.getOperand(2).getReg();
+
+  SPIRVTypeInst MaskType = GR.getSPIRVTypeForVReg(MaskReg);
+
+  // Convert pointer to integer.
+  Register PtrAsInt = MRI->createVirtualRegister(GR.getRegClass(MaskType));
+  GR.assignSPIRVTypeToVReg(MaskType, PtrAsInt, MF);
+
+  BuildMI(BB, I, DL, TII.get(SPIRV::OpConvertPtrToU))
+      .addDef(PtrAsInt)
+      .addUse(GR.getSPIRVTypeID(MaskType))
+      .addUse(PtrReg)
+      .constrainAllUses(TII, TRI, RBI);
+
+  // Perform bitwise AND.
+  Register MaskedInt = MRI->createVirtualRegister(GR.getRegClass(MaskType));
+  GR.assignSPIRVTypeToVReg(MaskType, MaskedInt, MF);
+
+  unsigned AndOpcode = GR.getScalarOrVectorComponentCount(MaskType) > 1
+                           ? SPIRV::OpBitwiseAndV
+                           : SPIRV::OpBitwiseAndS;
+
+  BuildMI(BB, I, DL, TII.get(AndOpcode))
+      .addDef(MaskedInt)
+      .addUse(GR.getSPIRVTypeID(MaskType))
+      .addUse(PtrAsInt)
+      .addUse(MaskReg)
+      .constrainAllUses(TII, TRI, RBI);
+
+  // Convert integer back to pointer.
+  BuildMI(BB, I, DL, TII.get(SPIRV::OpConvertUToPtr))
+      .addDef(ResVReg)
+      .addUse(GR.getSPIRVTypeID(ResType))
+      .addUse(MaskedInt)
+      .constrainAllUses(TII, TRI, RBI);
+
+  return true;
+}
+
 static unsigned getFCmpOpcode(unsigned PredNum) {
   auto Pred = static_cast<CmpInst::Predicate>(PredNum);
   switch (Pred) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index 099243acc8819..31bf0c1ff9c62 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -402,6 +402,18 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
       .legalIf(
           all(typeInSet(0, allPtrs), typeOfExtendedScalars(1, IsExtendedInts)));
 
+  getActionDefinitionsBuilder(G_PTRMASK)
+      .legalForCartesianProduct(allPtrs, allIntScalars)
+      .legalIf(
+          all(typeInSet(0, allPtrs), typeOfExtendedScalars(1, IsExtendedInts)))
+      .legalIf([](const LegalityQuery &Query) {
+        const LLT PtrTy = Query.Types[0];
+        const LLT MaskTy = Query.Types[1];
+        return PtrTy.isPointerVector() && MaskTy.isVector() &&
+               !MaskTy.isPointer() &&
+               PtrTy.getNumElements() == MaskTy.getNumElements();
+      });
+
   // ST.canDirectlyComparePointers() for pointer args is supported in
   // legalizeCustom().
   getActionDefinitionsBuilder(G_ICMP)
diff --git a/llvm/test/CodeGen/SPIRV/ptrmask-vec.ll b/llvm/test/CodeGen/SPIRV/ptrmask-vec.ll
new file mode 100644
index 0000000000000..37abf025f4cbb
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/ptrmask-vec.ll
@@ -0,0 +1,31 @@
+; RUN: llc -O0 --spirv-ext=+SPV_INTEL_masked_gather_scatter -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 --spirv-ext=+SPV_INTEL_masked_gather_scatter -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Test that G_PTRMASK works with vector of pointers.
+; This requires the SPV_INTEL_masked_gather_scatter extension.
+
+; CHECK-DAG: OpCapability MaskedGatherScatterINTEL
+; CHECK-DAG: OpExtension "SPV_INTEL_masked_gather_scatter"
+; CHECK-DAG: %[[#INT8_TY:]] = OpTypeInt 8 0
+; CHECK-DAG: %[[#PTR_TY:]] = OpTypePointer CrossWorkgroup %[[#INT8_TY]]
+; CHECK-DAG: %[[#VEC_PTR_TY:]] = OpTypeVector %[[#PTR_TY]] 2
+; CHECK-DAG: %[[#INT64_TY:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#VEC_INT64_TY:]] = OpTypeVector %[[#INT64_TY]] 2
+; CHECK: %[[#PTR_VEC_PARAM:]] = OpFunctionParameter %[[#VEC_PTR_TY]]
+; CHECK: %[[#MASK_VEC_PARAM:]] = OpFunctionParameter %[[#VEC_INT64_TY]]
+; CHECK: %[[#OUT_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#PTR_AS_INT:]] = OpConvertPtrToU %[[#VEC_INT64_TY]] %[[#PTR_VEC_PARAM]]
+; CHECK: %[[#MASKED_INT:]] = OpBitwiseAnd %[[#VEC_INT64_TY]] %[[#PTR_AS_INT]] %[[#MASK_VEC_PARAM]]
+; CHECK: %[[#MASKED_PTR:]] = OpConvertUToPtr %[[#VEC_PTR_TY]] %[[#MASKED_INT]]
+; CHECK: %[[#ELEM0:]] = OpCompositeExtract %[[#PTR_TY]] %[[#MASKED_PTR]] 0
+; CHECK: OpStore %[[#OUT_PARAM]] %[[#ELEM0]] Aligned 8
+
+define spir_kernel void @test_ptrmask_vec(<2 x ptr addrspace(1)> %ptr_vec, <2 x i64> %mask_vec, ptr addrspace(1) %out) {
+entry:
+  %masked_ptr_vec = call <2 x ptr addrspace(1)> @llvm.ptrmask.v2p1.v2i64(<2 x ptr addrspace(1)> %ptr_vec, <2 x i64> %mask_vec)
+  %elem0 = extractelement <2 x ptr addrspace(1)> %masked_ptr_vec, i32 0
+  store ptr addrspace(1) %elem0, ptr addrspace(1) %out, align 8
+  ret void
+}
+
+declare <2 x ptr addrspace(1)> @llvm.ptrmask.v2p1.v2i64(<2 x ptr addrspace(1)>, <2 x i64>)
diff --git a/llvm/test/CodeGen/SPIRV/ptrmask32.ll b/llvm/test/CodeGen/SPIRV/ptrmask32.ll
new file mode 100644
index 0000000000000..c2a830502d7ed
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/ptrmask32.ll
@@ -0,0 +1,24 @@
+; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Test that G_PTRMASK is correctly lowered to SPIR-V for 32-bit targets.
+; Result = Ptr & Mask
+; This should generate: OpConvertPtrToU -> OpBitwiseAnd -> OpConvertUToPtr
+
+; CHECK-LABEL: Begin function test_ptrmask_i32
+; CHECK: %[[#PTR_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#MASK_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#OUT_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#PTR_AS_INT:]] = OpConvertPtrToU %[[#]] %[[#PTR_PARAM]]
+; CHECK-NEXT: %[[#MASKED_INT:]] = OpBitwiseAnd %[[#]] %[[#PTR_AS_INT]] %[[#MASK_PARAM]]
+; CHECK-NEXT: %[[#MASKED_PTR:]] = OpConvertUToPtr %[[#]] %[[#MASKED_INT]]
+; CHECK-NEXT: OpStore %[[#OUT_PARAM]] %[[#MASKED_PTR]] Aligned 4
+
+define spir_kernel void @test_ptrmask_i32(ptr addrspace(1) %ptr, i32 %mask, ptr addrspace(1) %out) {
+entry:
+  %masked_ptr = call ptr addrspace(1) @llvm.ptrmask.p1.i32(ptr addrspace(1) %ptr, i32 %mask)
+  store ptr addrspace(1) %masked_ptr, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+declare ptr addrspace(1) @llvm.ptrmask.p1.i32(ptr addrspace(1), i32)
diff --git a/llvm/test/CodeGen/SPIRV/ptrmask64-32.ll b/llvm/test/CodeGen/SPIRV/ptrmask64-32.ll
new file mode 100644
index 0000000000000..87bf59a180a6f
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/ptrmask64-32.ll
@@ -0,0 +1,25 @@
+; RUN: llc -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 %}
+
+; Test G_PTRMASK with 32-bit mask on 64-bit SPIR-V target.
+; The 32-bit mask should be zero-extended to 64-bit before the AND operation.
+
+; CHECK-LABEL: Begin function test_ptrmask_i32_mask
+; CHECK: %[[#PTR_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#MASK_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#OUT_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#MASK_64:]] = OpUConvert %[[#]] %[[#MASK_PARAM]]
+; CHECK-NEXT: %[[#PTR_AS_INT:]] = OpConvertPtrToU %[[#]] %[[#PTR_PARAM]]
+; CHECK-NEXT: %[[#MASKED_INT:]] = OpBitwiseAnd %[[#]] %[[#PTR_AS_INT]] %[[#MASK_64]]
+; CHECK-NEXT: %[[#MASKED_PTR:]] = OpConvertUToPtr %[[#]] %[[#MASKED_INT]]
+; CHECK-NEXT: OpStore %[[#OUT_PARAM]] %[[#MASKED_PTR]] Aligned 8
+
+define spir_kernel void @test_ptrmask_i32_mask(ptr addrspace(1) %ptr, i32 %mask, ptr addrspace(1) %out) {
+entry:
+  %mask_64 = zext i32 %mask to i64
+  %masked_ptr = call ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1) %ptr, i64 %mask_64)
+  store ptr addrspace(1) %masked_ptr, ptr addrspace(1) %out, align 8
+  ret void
+}
+
+declare ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1), i64)
diff --git a/llvm/test/CodeGen/SPIRV/ptrmask64.ll b/llvm/test/CodeGen/SPIRV/ptrmask64.ll
new file mode 100644
index 0000000000000..77fd41de768a7
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/ptrmask64.ll
@@ -0,0 +1,24 @@
+; RUN: llc -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 %}
+
+; Test that G_PTRMASK is correctly lowered to SPIR-V for 64-bit targets.
+; Result = Ptr & Mask
+; This should generate: OpConvertPtrToU -> OpBitwiseAnd -> OpConvertUToPtr
+
+; CHECK-LABEL: Begin function test_ptrmask_i64
+; CHECK: %[[#PTR_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#MASK_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#OUT_PARAM:]] = OpFunctionParameter
+; CHECK: %[[#PTR_AS_INT:]] = OpConvertPtrToU %[[#]] %[[#PTR_PARAM]]
+; CHECK-NEXT: %[[#MASKED_INT:]] = OpBitwiseAnd %[[#]] %[[#PTR_AS_INT]] %[[#MASK_PARAM]]
+; CHECK-NEXT: %[[#MASKED_PTR:]] = OpConvertUToPtr %[[#]] %[[#MASKED_INT]]
+; CHECK-NEXT: OpStore %[[#OUT_PARAM]] %[[#MASKED_PTR]] Aligned 8
+
+define spir_kernel void @test_ptrmask_i64(ptr addrspace(1) %ptr, i64 %mask, ptr addrspace(1) %out) {
+entry:
+  %masked_ptr = call ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1) %ptr, i64 %mask)
+  store ptr addrspace(1) %masked_ptr, ptr addrspace(1) %out, align 8
+  ret void
+}
+
+declare ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1), i64)

>From c0c4f879f97d9e02066ca342ff11dc7e8558ffa8 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Mon, 8 Jun 2026 10:16:54 -0700
Subject: [PATCH 2/3] fix logical spirv

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp    |  3 +++
 llvm/test/CodeGen/SPIRV/ptrmask-logical.ll       | 16 ++++++++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 llvm/test/CodeGen/SPIRV/ptrmask-logical.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 0e1867591e023..f5267c7b34a74 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -2694,6 +2694,9 @@ bool SPIRVInstructionSelector::selectAddrSpaceCast(Register ResVReg,
 bool SPIRVInstructionSelector::selectPtrMask(Register ResVReg,
                                              SPIRVTypeInst ResType,
                                              MachineInstr &I) const {
+  if (STI.isLogicalSPIRV())
+    return diagnoseUnsupported(
+        I, "G_PTRMASK is not supported with logical SPIR-V");
   MachineBasicBlock &BB = *I.getParent();
   MachineFunction &MF = *BB.getParent();
   const DebugLoc &DL = I.getDebugLoc();
diff --git a/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll b/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll
new file mode 100644
index 0000000000000..ce9e9856a02fb
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll
@@ -0,0 +1,16 @@
+; RUN: not llc -O0 -mtriple=spirv-unknown-vulkan %s -o - 2>&1 | FileCheck %s
+
+; Test that G_PTRMASK is errors for logical SPIR-V.
+
+; CHECK: G_PTRMASK is not supported with logical SPIR-V
+
+define spir_kernel void @test_ptrmask_i64(ptr addrspace(1) %ptr, i64 %mask, ptr addrspace(1) %out) #1 {
+entry:
+  %masked_ptr = call ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1) %ptr, i64 %mask)
+  store ptr addrspace(1) %masked_ptr, ptr addrspace(1) %out, align 8
+  ret void
+}
+
+declare ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1), i64)
+
+attributes #1 = { "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" }

>From b60c6e405f98c60618e7ae7772c926a6a58ca56d Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Tue, 9 Jun 2026 07:14:43 -0700
Subject: [PATCH 3/3] address feedback

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
 llvm/test/CodeGen/SPIRV/ptrmask-logical.ll | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll b/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll
index ce9e9856a02fb..4a9bbb2e97a76 100644
--- a/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll
+++ b/llvm/test/CodeGen/SPIRV/ptrmask-logical.ll
@@ -1,10 +1,10 @@
-; RUN: not llc -O0 -mtriple=spirv-unknown-vulkan %s -o - 2>&1 | FileCheck %s
+; RUN: not llc -O0 -mtriple=spirv-unknown-vulkan %s -o /dev/null 2>&1 | FileCheck %s
 
 ; Test that G_PTRMASK is errors for logical SPIR-V.
 
 ; CHECK: G_PTRMASK is not supported with logical SPIR-V
 
-define spir_kernel void @test_ptrmask_i64(ptr addrspace(1) %ptr, i64 %mask, ptr addrspace(1) %out) #1 {
+define void @test_ptrmask_i64(ptr addrspace(1) %ptr, i64 %mask, ptr addrspace(1) %out) #1 {
 entry:
   %masked_ptr = call ptr addrspace(1) @llvm.ptrmask.p1.i64(ptr addrspace(1) %ptr, i64 %mask)
   store ptr addrspace(1) %masked_ptr, ptr addrspace(1) %out, align 8



More information about the llvm-commits mailing list