Skip to content

Conversation

@AlexVlx
Copy link
Contributor

@AlexVlx AlexVlx commented Nov 5, 2025

inttoptr usage in global initialisers is valid, and rather common when it comes to the machinery around vtables (construction vtables are particularly fond). This was not handled by the BE, even though SPIR-V allows forming SpecConstantOps with the OpConvertUToPtr opcode, which is what these would map to. We augment instruction selection to address this.

@llvmbot
Copy link
Member

llvmbot commented Nov 5, 2025

@llvm/pr-subscribers-backend-spir-v

Author: Alex Voicu (AlexVlx)

Changes

inttoptr usage in global initialisers is valid, and rather common when it comes to the machinery around vtables (construction vtables are particularly fond). This was not handled by the BE, even though SPIR-V allows forming SpecConstantOps with the OpConvertUToPtr opcode, which is what these would map to. We augment instruction selection to address this.


Full diff: https://github.com/llvm/llvm-project/pull/166494.diff

3 Files Affected:

  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+13-3)
  • (modified) llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp (+11)
  • (added) llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll (+49)
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 3f0424f436c72..b00b6bd164421 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -1210,8 +1210,17 @@ bool SPIRVInstructionSelector::selectUnOp(Register ResVReg,
     for (MachineRegisterInfo::def_instr_iterator DefIt =
              MRI->def_instr_begin(SrcReg);
          DefIt != MRI->def_instr_end(); DefIt = std::next(DefIt)) {
-      if ((*DefIt).getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
-          (*DefIt).getOpcode() == SPIRV::OpVariable) {
+      unsigned DefOpCode = (*DefIt).getOpcode();
+      if (DefOpCode == SPIRV::ASSIGN_TYPE) {
+        // We need special handling to look through the type assignment and see
+        // if this is a constant or a global
+        if (auto *VRD = getVRegDef(*MRI, (*DefIt).getOperand(1).getReg()))
+          DefOpCode = VRD->getOpcode();
+      }
+      if (DefOpCode == TargetOpcode::G_GLOBAL_VALUE ||
+          DefOpCode == TargetOpcode::G_CONSTANT ||
+          DefOpCode == SPIRV::OpVariable ||
+          DefOpCode == SPIRV::OpConstantI) {
         IsGV = true;
         break;
       }
@@ -3099,9 +3108,10 @@ bool SPIRVInstructionSelector::wrapIntoSpecConstantOp(
     SmallPtrSet<SPIRVType *, 4> Visited;
     if (!OpDefine || !OpType || isConstReg(MRI, OpDefine, Visited) ||
         OpDefine->getOpcode() == TargetOpcode::G_ADDRSPACE_CAST ||
+        OpDefine->getOpcode() == TargetOpcode::G_INTTOPTR ||
         GR.isAggregateType(OpType)) {
       // The case of G_ADDRSPACE_CAST inside spv_const_composite() is processed
-      // by selectAddrSpaceCast()
+      // by selectAddrSpaceCast(), and G_INTTOPTR is processed by selectUnOp()
       CompositeArgs.push_back(OpReg);
       continue;
     }
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index d538009f0ecbe..128403a646787 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -348,6 +348,16 @@ static SPIRVType *propagateSPIRVType(MachineInstr *MI, SPIRVGlobalRegistry *GR,
         SpvType = GR->getOrCreateSPIRVIntegerType(
             MRI.getType(Reg).getScalarSizeInBits(), MIB);
         break;
+      case TargetOpcode::G_INTTOPTR:
+        // With opaque pointers it doesn't appear as if we can convert to
+        // anything but a pointer to i8
+        SpvType = GR->getOrCreateSPIRVPointerType(
+            GR->getOrCreateSPIRVIntegerType(8, MIB),
+            MIB,
+            addressSpaceToStorageClass(
+                MRI.getType(Reg).getAddressSpace(),
+                MIB.getMF().getSubtarget<SPIRVSubtarget>()));
+        break;
       case TargetOpcode::G_TRUNC:
       case TargetOpcode::G_ADDRSPACE_CAST:
       case TargetOpcode::G_PTR_ADD:
@@ -684,6 +694,7 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       case TargetOpcode::G_SEXT:
       case TargetOpcode::G_ZEXT:
       case TargetOpcode::G_PTRTOINT:
+      case TargetOpcode::G_INTTOPTR:
       case TargetOpcode::COPY:
       case TargetOpcode::G_ADDRSPACE_CAST:
         propagateSPIRVType(&MI, GR, MRI, MIB);
diff --git a/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll b/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll
new file mode 100644
index 0000000000000..614c126e02656
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll
@@ -0,0 +1,49 @@
+; 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 %}
+
+; CHECK: %[[Int8Ty:[0-9]+]] = OpTypeInt 8 0
+; CHECK: %[[Int8PtrTy:[0-9]+]] = OpTypePointer Generic %[[Int8Ty]]
+; CHECK-DAG: %[[GlobInt8PtrTy:[0-9]+]] = OpTypePointer CrossWorkgroup %[[Int8Ty]]
+; CHECK: %[[GlobInt8PtrPtrTy:[0-9]+]] = OpTypePointer CrossWorkgroup %[[GlobInt8PtrTy]]
+; CHECK: %[[Int8PtrGlobPtrPtrTy:[0-9]+]] = OpTypePointer Generic %[[GlobInt8PtrPtrTy]]
+; CHECK: %[[Int32Ty:[0-9]+]] = OpTypeInt 32 0
+; CHECK: %[[Const5:[0-9]+]] = OpConstant %[[Int32Ty]] 5
+; CHECK: %[[ArrTy:[0-9]+]] = OpTypeArray %[[GlobInt8PtrTy]] %[[Const5]]
+; CHECK: %[[VtblTy:[0-9]+]] = OpTypeStruct %[[ArrTy]] %[[ArrTy]] %[[ArrTy]] %[[ArrTy]] %[[ArrTy]]
+; CHECK: %[[Int64Ty:[0-9]+]] = OpTypeInt 64 0
+; CHECK: %[[GlobVtblPtrTy:[0-9]+]] = OpTypePointer CrossWorkgroup %[[VtblTy]]
+; CHECK: %[[ConstMinus184:[0-9]+]] = OpConstant %[[Int64Ty]] 18446744073709551432
+; CHECK: %[[ConstMinus16:[0-9]+]] = OpConstant %[[Int64Ty]] 18446744073709551600
+; CHECK: %[[Const168:[0-9]+]] = OpConstant %[[Int64Ty]] 168
+; CHECK: %[[Const184:[0-9]+]] = OpConstant %[[Int64Ty]] 184
+; CHECK: %[[Nullptr:[0-9]+]] = OpConstantNull %[[GlobInt8PtrTy]]
+; CHECK: %[[Const184toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[Const184]]
+; CHECK: %[[Const168toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[Const168]]
+; CHECK: %[[ConstMinus16toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[ConstMinus16]]
+; CHECK: %[[ConstMinus184toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[ConstMinus184]]
+; CHECK: %[[Vtbl012:[0-9]+]] = OpConstantComposite %[[ArrTy]] %[[Const184toPtr]] %[[Nullptr]] %[[Nullptr]] %[[Nullptr]] %[[Nullptr]]
+; CHECK: %[[Vtbl3:[0-9]+]] = OpConstantComposite %[[ArrTy]] %[[Const168toPtr]] %[[ConstMinus16toPtr]] %[[Nullptr]] %[[Nullptr]] %[[Nullptr]]
+; CHECK: %[[Vtbl4:[0-9]+]] = OpConstantComposite %[[ArrTy]] %[[ConstMinus184toPtr]] %[[ConstMinus184toPtr]] %[[Nullptr]] %[[Nullptr]] %[[Nullptr]]
+; CHECK: %[[Vtbl:[0-9]+]] = OpConstantComposite %[[VtblTy]] %[[Vtbl012]] %[[Vtbl012]] %[[Vtbl012]] %[[Vtbl3]] %[[Vtbl4]]
+; CHECK: %[[#]] = OpVariable %[[GlobVtblPtrTy]] CrossWorkgroup %[[Vtbl]]
+
+@vtable = linkonce_odr unnamed_addr addrspace(1) constant { [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }
+                                                          { [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 184 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null],
+                                                            [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 184 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null],
+                                                            [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 184 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null],
+                                                            [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 168 to ptr addrspace(1)), ptr addrspace(1) inttoptr (i64 -16 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null],
+                                                            [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 -184 to ptr addrspace(1)), ptr addrspace(1) inttoptr (i64 -184 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null] }
+
+define linkonce_odr spir_func void @foo(ptr addrspace(4) %this) {
+entry:
+  %0 = getelementptr inbounds i8, ptr addrspace(4) %this, i64 184
+  store ptr addrspace(1) getelementptr inbounds inrange(-24, 16) ({ [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }, ptr addrspace(1) @vtable, i32 0, i32 0, i32 3), ptr addrspace(4) %this
+  store ptr addrspace(1) getelementptr inbounds inrange(-24, 16) ({ [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }, ptr addrspace(1) @vtable, i32 0, i32 1, i32 3), ptr addrspace(4) %this
+  store ptr addrspace(1) getelementptr inbounds inrange(-24, 16) ({ [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }, ptr addrspace(1) @vtable, i32 0, i32 2, i32 3), ptr addrspace(4) %this
+  %add.ptr = getelementptr inbounds i8, ptr addrspace(4) %this, i64 184
+  store ptr addrspace(1) getelementptr inbounds inrange(-24, 16) ({ [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }, ptr addrspace(1) @vtable, i32 0, i32 4, i32 3), ptr addrspace(4) %add.ptr
+  %add.ptr2 = getelementptr inbounds i8, ptr addrspace(4) %this, i64 16
+  store ptr addrspace(1) getelementptr inbounds inrange(-24, 16) ({ [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }, ptr addrspace(1) @vtable, i32 0, i32 3, i32 3), ptr addrspace(4) %add.ptr2
+
+  ret void
+}

@github-actions
Copy link

github-actions bot commented Nov 5, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@jmmartinez jmmartinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 2 nits.

if ((*DefIt).getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
(*DefIt).getOpcode() == SPIRV::OpVariable) {
unsigned DefOpCode = (*DefIt).getOpcode();
if (DefOpCode == SPIRV::ASSIGN_TYPE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this loop only iterating on register definitions and not uses?
I thought spv.assign.type as spv.assign.ptr.type were intrinsics registering as having a side-effect, but returning no values?

Copy link
Contributor Author

@AlexVlx AlexVlx Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intrinsics get lowered to a pseudo (ASSIGN_TYPE) which defines a destination vreg, which is what gets used by subsequent instructions, rather than the global whose type we assigned; hence the need to have a peek through the type assignment. The sequence looks something like so:

%60:iid(s64) = G_CONSTANT i64 184
%9:iid(s64) = ASSIGN_TYPE %60:iid(s64), %49:type(s64)
%8:_(p1) = G_INTTOPTR %9:iid(s64)

I will note that we cannot use the result of the type assignment, as that assigns the from integer's type, and not the to pointer's type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AH I see thanks! Was looking at the wrong level of IR.
Should you add an assert(DefOpCode != ASSIGN_TYPE) after this to make sure no ASSIGN_TYPE are ever chained? (Seems like this shouldn't happen)

Copy link
Contributor

@Keenuts Keenuts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM on my end

if ((*DefIt).getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
(*DefIt).getOpcode() == SPIRV::OpVariable) {
unsigned DefOpCode = (*DefIt).getOpcode();
if (DefOpCode == SPIRV::ASSIGN_TYPE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AH I see thanks! Was looking at the wrong level of IR.
Should you add an assert(DefOpCode != ASSIGN_TYPE) after this to make sure no ASSIGN_TYPE are ever chained? (Seems like this shouldn't happen)

Copy link
Contributor

@jmmartinez jmmartinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

@AlexVlx AlexVlx merged commit 995b0f1 into llvm:main Nov 6, 2025
11 checks passed
@AlexVlx AlexVlx deleted the spirv_staging_6 branch November 6, 2025 23:59
@llvm-ci
Copy link
Collaborator

llvm-ci commented Nov 7, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-mlir-rhel-clang running on ppc64le-mlir-rhel-test while building llvm at step 6 "test-build-check-mlir-build-only-check-mlir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/129/builds/32644

Here is the relevant piece of the build log for the reference
Step 6 (test-build-check-mlir-build-only-check-mlir) failure: 1200 seconds without output running [b'ninja', b'check-mlir'], attempting to kill
9.064 [0/1/0] Running the MLIR regression tests
command timed out: 1200 seconds without output running [b'ninja', b'check-mlir'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=1210.026098

vinay-deshmukh pushed a commit to vinay-deshmukh/llvm-project that referenced this pull request Nov 8, 2025
…llvm#166494)

`inttoptr` usage in global initialisers is valid, and rather common when it comes to the machinery around vtables (construction vtables are particularly fond). This was not handled by the BE, even though SPIR-V allows forming `SpecConstantOp`s with the `OpConvertUToPtr` opcode, which is what these would map to. We augment instruction selection to address this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants