[llvm] [SPIR-V] initial support for @llvm.structured.gep (PR #178668)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 29 06:53:37 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Nathan Gauër (Keenuts)

<details>
<summary>Changes</summary>

This commit adds initial support to lower the intrinsinc `@<!-- -->llvm.structured.gep` into proper SPIR-V.
For now, the backend continues to support both GEP formats. We might want to revisit this at some point for the logical part.

---

Patch is 28.86 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/178668.diff


9 Files Affected:

- (modified) llvm/include/llvm/IR/IntrinsicInst.h (+6) 
- (modified) llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp (+56-17) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-array.ll (+141) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-class.ll (+39) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-dynamic-index.ll (+47) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-nested-struct-array.ll (+35) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-runtime-array.ll (+32) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-struct.ll (+36) 
- (added) llvm/test/CodeGen/SPIRV/pointers/sgep-vector.ll (+86) 


``````````diff
diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h
index ba42601085448..784e599c53f44 100644
--- a/llvm/include/llvm/IR/IntrinsicInst.h
+++ b/llvm/include/llvm/IR/IntrinsicInst.h
@@ -1814,6 +1814,12 @@ class StructuredGEPInst : public IntrinsicInst {
     return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
   }
 
+  static unsigned getPointerOperandIndex() { return 0; }
+
+  Value *getPointerOperand() const {
+    return getOperand(getPointerOperandIndex());
+  }
+
   Type *getBaseType() const {
     return getParamAttr(0, Attribute::ElementType).getValueAsType();
   }
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 0ae30a2cdf1ac..a562f86837443 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -63,6 +63,10 @@ namespace llvm::SPIRV {
 
 namespace {
 
+static bool isaGEP(const Value *V) {
+  return isa<StructuredGEPInst>(V) || isa<GetElementPtrInst>(V);
+}
+
 class SPIRVEmitIntrinsics
     : public ModulePass,
       public InstVisitor<SPIRVEmitIntrinsics, Instruction *> {
@@ -85,7 +89,7 @@ class SPIRVEmitIntrinsics
   DenseMap<Value *, bool> TodoType;
   void insertTodoType(Value *Op) {
     // TODO: add isa<CallInst>(Op) to no-insert
-    if (CanTodoType && !isa<GetElementPtrInst>(Op)) {
+    if (CanTodoType && !isaGEP(Op)) {
       auto It = TodoType.try_emplace(Op, true);
       if (It.second)
         ++TodoTypeSz;
@@ -99,7 +103,7 @@ class SPIRVEmitIntrinsics
     }
   }
   bool isTodoType(Value *Op) {
-    if (isa<GetElementPtrInst>(Op))
+    if (isaGEP(Op))
       return false;
     auto It = TodoType.find(Op);
     return It != TodoType.end() && It->second;
@@ -250,6 +254,7 @@ class SPIRVEmitIntrinsics
   Instruction *visitInstruction(Instruction &I) { return &I; }
   Instruction *visitSwitchInst(SwitchInst &I);
   Instruction *visitGetElementPtrInst(GetElementPtrInst &I);
+  Instruction *visitIntrinsicInst(IntrinsicInst &I);
   Instruction *visitBitCastInst(BitCastInst &I);
   Instruction *visitInsertElementInst(InsertElementInst &I);
   Instruction *visitExtractElementInst(ExtractElementInst &I);
@@ -513,8 +518,7 @@ void SPIRVEmitIntrinsics::propagateElemType(
     Instruction *UI = dyn_cast<Instruction>(U);
     // If the instruction was validated already, we need to keep it valid by
     // keeping current Op type.
-    if (isa<GetElementPtrInst>(UI) ||
-        TypeValidated.find(UI) != TypeValidated.end())
+    if (isaGEP(UI) || TypeValidated.find(UI) != TypeValidated.end())
       replaceUsesOfWithSpvPtrcast(Op, ElemTy, UI, Ptrcasts);
   }
 }
@@ -544,8 +548,7 @@ void SPIRVEmitIntrinsics::propagateElemTypeRec(
     Instruction *UI = dyn_cast<Instruction>(U);
     // If the instruction was validated already, we need to keep it valid by
     // keeping current Op type.
-    if (isa<GetElementPtrInst>(UI) ||
-        TypeValidated.find(UI) != TypeValidated.end())
+    if (isaGEP(UI) || TypeValidated.find(UI) != TypeValidated.end())
       replaceUsesOfWithSpvPtrcast(Op, CastElemTy, UI, Ptrcasts);
   }
 }
@@ -785,6 +788,8 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
     maybeAssignPtrType(Ty, I, Ref->getAllocatedType(), UnknownElemTypeI8);
   } else if (auto *Ref = dyn_cast<GetElementPtrInst>(I)) {
     Ty = getGEPType(Ref);
+  } else if (auto *SGEP = dyn_cast<StructuredGEPInst>(I)) {
+    Ty = SGEP->getResultElementType();
   } else if (auto *Ref = dyn_cast<LoadInst>(I)) {
     Value *Op = Ref->getPointerOperand();
     Type *KnownTy = GR->findDeducedElementType(Op);
@@ -1223,6 +1228,12 @@ void SPIRVEmitIntrinsics::deduceOperandElementType(
     KnownElemTy = Ref->getSourceElementType();
     Ops.push_back(std::make_pair(Ref->getPointerOperand(),
                                  GetElementPtrInst::getPointerOperandIndex()));
+  } else if (auto *Ref = dyn_cast<StructuredGEPInst>(I)) {
+    if (GR->findDeducedElementType(Ref->getPointerOperand()))
+      return;
+    KnownElemTy = Ref->getBaseType();
+    Ops.push_back(std::make_pair(Ref->getPointerOperand(),
+                                 StructuredGEPInst::getPointerOperandIndex()));
   } else if (auto *Ref = dyn_cast<LoadInst>(I)) {
     KnownElemTy = I->getType();
     if (isUntypedPointerTy(KnownElemTy))
@@ -1619,6 +1630,26 @@ static bool isFirstIndexZero(const GetElementPtrInst *GEP) {
   return false;
 }
 
+Instruction *SPIRVEmitIntrinsics::visitIntrinsicInst(IntrinsicInst &I) {
+  auto *SGEP = dyn_cast<StructuredGEPInst>(&I);
+  if (!SGEP)
+    return &I;
+
+  IRBuilder<> B(I.getParent());
+  B.SetInsertPoint(&I);
+  SmallVector<Type *, 2> Types = {I.getType(), I.getOperand(0)->getType()};
+  SmallVector<Value *, 4> Args;
+  Args.push_back(/* inBounds= */ B.getInt1(true));
+  Args.push_back(I.getOperand(0));
+  Args.push_back(/* zero index */ B.getInt32(0));
+  for (unsigned I = 0; I < SGEP->getNumIndices(); ++I)
+    Args.push_back(SGEP->getIndexOperand(I));
+
+  auto *NewI = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
+  replaceAllUsesWithAndErase(B, &I, NewI);
+  return NewI;
+}
+
 Instruction *SPIRVEmitIntrinsics::visitGetElementPtrInst(GetElementPtrInst &I) {
   IRBuilder<> B(I.getParent());
   B.SetInsertPoint(&I);
@@ -1787,7 +1818,7 @@ void SPIRVEmitIntrinsics::replacePointerOperandWithPtrCast(
       return;
     } else if (isTodoType(Pointer)) {
       eraseTodoType(Pointer);
-      if (!isa<CallInst>(Pointer) && !isa<GetElementPtrInst>(Pointer)) {
+      if (!isa<CallInst>(Pointer) && !isaGEP(Pointer)) {
         //  If this wouldn't be the first spv_ptrcast but existing type info is
         //  uncomplete, update spv_assign_ptr_type arguments.
         if (CallInst *AssignCI = GR->findAssignPtrTypeInstr(Pointer)) {
@@ -2826,7 +2857,7 @@ void SPIRVEmitIntrinsics::applyDemangledPtrArgTypes(IRBuilder<> &B) {
             B.SetCurrentDebugLocation(DebugLoc());
             GR->buildAssignPtr(B, ElemTy, Arg);
           }
-        } else if (isa<GetElementPtrInst>(Param)) {
+        } else if (isaGEP(Param)) {
           replaceUsesOfWithSpvPtrcast(Param, normalizeType(ElemTy), CI,
                                       Ptrcasts);
         } else if (isa<Instruction>(Param)) {
@@ -2899,18 +2930,26 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
   // Data structure for dead instructions that were simplified and replaced.
   SmallPtrSet<Instruction *, 4> DeadInsts;
   for (auto &I : instructions(Func)) {
-    auto *Ref = dyn_cast<GetElementPtrInst>(&I);
-    if (!Ref || GR->findDeducedElementType(Ref))
+    auto *GEP = dyn_cast<GetElementPtrInst>(&I);
+    auto *SGEP = dyn_cast<StructuredGEPInst>(&I);
+
+    if ((!GEP && !SGEP) || GR->findDeducedElementType(&I))
       continue;
 
-    GetElementPtrInst *NewGEP = simplifyZeroLengthArrayGepInst(Ref);
+    if (SGEP) {
+      GR->addDeducedElementType(SGEP,
+                                normalizeType(SGEP->getResultElementType()));
+      continue;
+    }
+
+    GetElementPtrInst *NewGEP = simplifyZeroLengthArrayGepInst(GEP);
     if (NewGEP) {
-      Ref->replaceAllUsesWith(NewGEP);
-      DeadInsts.insert(Ref);
-      Ref = NewGEP;
+      GEP->replaceAllUsesWith(NewGEP);
+      DeadInsts.insert(GEP);
+      GEP = NewGEP;
     }
-    if (Type *GepTy = getGEPType(Ref))
-      GR->addDeducedElementType(Ref, normalizeType(GepTy));
+    if (Type *GepTy = getGEPType(GEP))
+      GR->addDeducedElementType(GEP, normalizeType(GepTy));
   }
   // Remove dead instructions that were simplified and replaced.
   for (auto *I : DeadInsts) {
@@ -3006,7 +3045,7 @@ bool SPIRVEmitIntrinsics::postprocessTypes(Module &M) {
   DenseMap<Value *, SmallPtrSet<Value *, 4>> ToProcess;
   for (auto [Op, Enabled] : TodoType) {
     // TODO: add isa<CallInst>(Op) to continue
-    if (!Enabled || isa<GetElementPtrInst>(Op))
+    if (!Enabled || isaGEP(Op))
       continue;
     CallInst *AssignCI = GR->findAssignPtrTypeInstr(Op);
     Type *KnownTy = GR->findDeducedElementType(Op);
diff --git a/llvm/test/CodeGen/SPIRV/pointers/sgep-array.ll b/llvm/test/CodeGen/SPIRV/pointers/sgep-array.ll
new file mode 100644
index 0000000000000..1582e2404d3fa
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/sgep-array.ll
@@ -0,0 +1,141 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+%struct.S = type { i32, i32 }
+%struct.S2 = type { i32, %struct.S, i32 }
+
+; CHECK-DAG:           %[[#uint:]] = OpTypeInt 32 0
+; CHECK-DAG:          %[[#ulong:]] = OpTypeInt 64 0
+; CHECK-DAG:        %[[#ulong_0:]] = OpConstant %[[#ulong]] 0
+; CHECK-DAG:        %[[#ulong_1:]] = OpConstant %[[#ulong]] 1
+; CHECK-DAG:        %[[#ulong_2:]] = OpConstant %[[#ulong]] 2
+; CHECK-DAG:        %[[#ulong_3:]] = OpConstant %[[#ulong]] 3
+; CHECK-DAG:         %[[#uint_0:]] = OpConstant %[[#uint]] 0
+; CHECK-DAG:         %[[#uint_1:]] = OpConstant %[[#uint]] 1
+; CHECK-DAG:         %[[#uint_5:]] = OpConstant %[[#uint]] 5
+; CHECK-DAG:       %[[#ptr_uint:]] = OpTypePointer Function %[[#uint]]
+; CHECK-DAG:     %[[#arr_uint_5:]] = OpTypeArray %[[#uint]] %[[#uint_5]]
+; CHECK-DAG: %[[#ptr_arr_uint_5:]] = OpTypePointer Function %[[#arr_uint_5]]
+; CHECK-DAG:              %[[#S:]] = OpTypeStruct %[[#uint]] %[[#uint]]
+; CHECK-DAG:          %[[#ptr_S:]] = OpTypePointer Function %[[#S]]
+; CHECK-DAG:        %[[#arr_S_5:]] = OpTypeArray %[[#S]] %[[#uint_5]]
+; CHECK-DAG:    %[[#ptr_arr_S_5:]] = OpTypePointer Function %[[#arr_S_5]]
+; CHECK-DAG:             %[[#S2:]] = OpTypeStruct %[[#uint]] %[[#S]] %[[#uint]]
+; CHECK-DAG:         %[[#ptr_S2:]] = OpTypePointer Function %[[#S2]]
+; CHECK-DAG:       %[[#arr_S2_5:]] = OpTypeArray %[[#S2]] %[[#uint_5]]
+; CHECK-DAG:   %[[#ptr_arr_S2_5:]] = OpTypePointer Function %[[#arr_S2_5]]
+
+define spir_func void @array_load_store(ptr %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  %tmp = alloca [5 x i32], align 4
+; CHECK:	%[[#tmp:]] = OpVariable %[[#ptr_arr_uint_5]] Function
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %a, i64 2)
+  %2 = load i32, ptr %1, align 4
+; CHECK:	  %[[#A:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#]] %[[#ulong_2]]
+; CHECK:	  %[[#B:]] = OpLoad %[[#uint]] %[[#A]] Aligned 4
+
+  %3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x i32]) %tmp, i64 3)
+  store i32 %2, ptr %3, align 4
+; CHECK:	  %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#tmp]] %[[#ulong_3]]
+; CHECK:	             OpStore %[[#C]] %[[#B]] Aligned 4
+
+  ret void
+}
+
+define spir_func void @array_struct_load_store(ptr %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  %tmp = alloca [5 x %struct.S], align 4
+; CHECK:	%[[#tmp:]] = OpVariable %[[#ptr_arr_S_5]] Function
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %a, i64 2)
+  %2 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %1, i64 1)
+  %3 = load i32, ptr %2, align 4
+; CHECK:	  %[[#A:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#]] %[[#ulong_2]]
+; CHECK:	  %[[#B:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#A]] %[[#ulong_1]]
+; CHECK:	  %[[#C:]] = OpLoad %[[#uint]] %[[#B]] Aligned 4
+
+  %4 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S]) %tmp, i64 3)
+  %5 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %4, i32 0)
+  store i32 %3, ptr %5, align 4
+; CHECK:	  %[[#D:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#tmp]] %[[#ulong_3]]
+; CHECK:	  %[[#E:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#D]] %[[#uint_0]]
+; CHECK:	             OpStore %[[#E]] %[[#C]] Aligned 4
+
+  ret void
+}
+
+define spir_func void @array_struct_load_store_combined(ptr %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  %tmp = alloca [5 x %struct.S], align 4
+; CHECK:	%[[#tmp:]] = OpVariable %[[#ptr_arr_S_5]] Function
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %a, i64 2, i64 1)
+  %2 = load i32, ptr %1, align 4
+; CHECK:	  %[[#A:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#]] %[[#ulong_2]] %[[#ulong_1]]
+; CHECK:	  %[[#B:]] = OpLoad %[[#uint]] %[[#A]] Aligned 4
+
+  %3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S]) %tmp, i64 3, i32 0)
+  store i32 %2, ptr %3, align 4
+; CHECK:	  %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#tmp]] %[[#ulong_3]] %[[#uint_0]]
+; CHECK:	             OpStore %[[#C]] %[[#B]] Aligned 4
+
+  ret void
+}
+
+define spir_func void @array_nested_struct_load_store(ptr %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  %tmp = alloca [5 x %struct.S2], align 4
+; CHECK:	%[[#tmp:]] = OpVariable %[[#ptr_arr_S2_5]] Function
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S2]) %a, i64 1)
+  %2 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %1, i64 1)
+  %3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %2, i64 0)
+  %4 = load i32, ptr %3, align 4
+; CHECK:	  %[[#A:]] = OpInBoundsAccessChain %[[#ptr_S2]] %[[#]] %[[#ulong_1]]
+; CHECK:	  %[[#B:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#A]] %[[#ulong_1]]
+; CHECK:	  %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#B]] %[[#ulong_0]]
+; CHECK:	  %[[#D:]] = OpLoad %[[#uint]] %[[#C]] Aligned 4
+
+  %5 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S2]) %tmp, i64 3)
+  %6 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %5, i32 1)
+  %7 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %6, i32 0)
+  store i32 %4, ptr %7, align 4
+; CHECK:	  %[[#E:]] = OpInBoundsAccessChain %[[#ptr_S2]] %[[#tmp]] %[[#ulong_3]]
+; CHECK:	  %[[#F:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#E]] %[[#uint_1]]
+; CHECK:	  %[[#G:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#F]] %[[#uint_0]]
+; CHECK:	             OpStore %[[#G]] %[[#D]] Aligned 4
+
+  ret void
+}
+
+define spir_func void @array_nested_struct_load_store_combined(ptr %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  %tmp = alloca [5 x %struct.S2], align 4
+; CHECK:	%[[#tmp:]] = OpVariable %[[#ptr_arr_S2_5]] Function
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S2]) %a, i64 1, i64 1, i64 0)
+  %2 = load i32, ptr %1, align 4
+; CHECK:	  %[[#A:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#]] %[[#ulong_1]] %[[#ulong_1]] %[[#ulong_0]]
+; CHECK:	  %[[#B:]] = OpLoad %[[#uint]] %[[#A]] Aligned 4
+
+  %3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S2]) %tmp, i64 3, i32 1, i32 0)
+  store i32 %2, ptr %3, align 4
+; CHECK:	  %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#tmp]] %[[#ulong_3]] %[[#uint_1]] %[[#uint_0]]
+; CHECK:	             OpStore %[[#C]] %[[#B]] Aligned 4
+
+  ret void
+}
+
+declare token @llvm.experimental.convergence.entry() #1
+
+declare ptr @llvm.structured.gep.p0(ptr, ...) #3
+
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
+attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
diff --git a/llvm/test/CodeGen/SPIRV/pointers/sgep-class.ll b/llvm/test/CodeGen/SPIRV/pointers/sgep-class.ll
new file mode 100644
index 0000000000000..6f85bbd2e6bad
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/sgep-class.ll
@@ -0,0 +1,39 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+%class.Base = type { i32 }
+%class.Derived = type { %class.Base, float }
+
+; CHECK-DAG: %[[#int:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#Base:]] = OpTypeStruct %[[#int]]
+; CHECK-DAG: %[[#Derived:]] = OpTypeStruct %[[#Base]] %[[#float]]
+; CHECK-DAG: %[[#ptr_Derived:]] = OpTypePointer Function %[[#Derived]]
+; CHECK-DAG: %[[#ptr_Base:]] = OpTypePointer Function %[[#Base]]
+; CHECK-DAG: %[[#ptr_int:]] = OpTypePointer Function %[[#int]]
+; CHECK-DAG: %[[#idx_0:]] = OpConstant %[[#int]] 0
+
+define spir_func void @class_access(ptr %d) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  ; CHECK: %[[#d_var:]] = OpFunctionParameter %[[#ptr_Derived]]
+
+  ; Access Base part
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%class.Derived) %d, i32 0)
+  ; CHECK: %[[#ptr_base:]] = OpInBoundsAccessChain %[[#ptr_Base]] %[[#d_var]] %[[#idx_0]]
+
+  ; Access field in Base
+  %2 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%class.Base) %1, i32 0)
+  ; CHECK: %[[#ptr_field:]] = OpInBoundsAccessChain %[[#ptr_int]] %[[#ptr_base]] %[[#idx_0]]
+
+  store i32 42, ptr %2, align 4
+  ; CHECK: OpStore %[[#ptr_field]]
+
+  ret void
+}
+
+declare token @llvm.experimental.convergence.entry() #1
+declare ptr @llvm.structured.gep.p0(ptr, ...) #3
+
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
diff --git a/llvm/test/CodeGen/SPIRV/pointers/sgep-dynamic-index.ll b/llvm/test/CodeGen/SPIRV/pointers/sgep-dynamic-index.ll
new file mode 100644
index 0000000000000..5df35e8c86738
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/sgep-dynamic-index.ll
@@ -0,0 +1,47 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[#int:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#long:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#ptr_int:]] = OpTypePointer Function %[[#int]]
+; CHECK-DAG: %[[#idx_10:]] = OpConstant %[[#int]] 10
+; CHECK-DAG: %[[#array:]] = OpTypeArray %[[#int]] %[[#idx_10]]
+; CHECK-DAG: %[[#ptr_array:]] = OpTypePointer Function %[[#array]]
+
+define spir_func i32 @dynamic_access(ptr %arr, i32 %idx) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  ; CHECK: %[[#arr_var:]] = OpFunctionParameter %[[#ptr_array]]
+  ; CHECK: %[[#idx_var:]] = OpFunctionParameter %[[#int]]
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %arr, i32 %idx)
+  ; CHECK: %[[#ptr_elem:]] = OpInBoundsAccessChain %[[#ptr_int]] %[[#arr_var]] %[[#idx_var]]
+
+  %2 = load i32, ptr %1, align 4
+  ; CHECK: %[[#val:]] = OpLoad %[[#int]] %[[#ptr_elem]] Aligned 4
+
+  ret i32 %2
+  ; CHECK: OpReturnValue %[[#val]]
+}
+
+define spir_func i32 @dynamic_access_i64(ptr %arr, i64 %idx) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  ; CHECK: %[[#arr_var2:]] = OpFunctionParameter %[[#ptr_array]]
+  ; CHECK: %[[#idx_var2:]] = OpFunctionParameter %[[#long]]
+
+  %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %arr, i64 %idx)
+  ; CHECK: %[[#ptr_elem2:]] = OpInBoundsAccessChain %[[#ptr_int]] %[[#arr_var2]] %[[#idx_var2]]
+
+  %2 = load i32, ptr %1, align 4
+  ; CHECK: %[[#val2:]] = OpLoad %[[#int]] %[[#ptr_elem2]] Aligned 4
+
+  ret i32 %2
+  ; CHECK: OpReturnValue %[[#val2]]
+}
+
+declare token @llvm.experimental.convergence.entry() #1
+declare ptr @llvm.structured.gep.p0(ptr, ...) #3
+
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
diff --git a/llvm/test/CodeGen/SPIRV/pointers/sgep-nested-struct-array.ll b/llvm/test/CodeGen/SPIRV/pointers/sgep-nested-struct-array.ll
new file mode 100644
index 0000000000000..785fdd8fac242
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/sgep-nested-struct-array.ll
@@ -0,0 +1,35 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+%struct.Inner = type { i32, <4 x float> }
+%struct.Outer = type { [5 x %struct.Inner], i32 }
+
+; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#vec4:]] =...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/178668


More information about the llvm-commits mailing list