Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 091972c

Browse files
authored
[SPIR-V] initial support for @llvm.structured.gep (llvm#178668)
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.
1 parent ab2e10d commit 091972c

9 files changed

Lines changed: 478 additions & 17 deletions

File tree

llvm/include/llvm/IR/IntrinsicInst.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,12 @@ class StructuredGEPInst : public IntrinsicInst {
18141814
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
18151815
}
18161816

1817+
static unsigned getPointerOperandIndex() { return 0; }
1818+
1819+
Value *getPointerOperand() const {
1820+
return getOperand(getPointerOperandIndex());
1821+
}
1822+
18171823
Type *getBaseType() const {
18181824
return getParamAttr(0, Attribute::ElementType).getValueAsType();
18191825
}

llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ namespace llvm::SPIRV {
6363

6464
namespace {
6565

66+
static bool isaGEP(const Value *V) {
67+
return isa<StructuredGEPInst>(V) || isa<GetElementPtrInst>(V);
68+
}
69+
6670
class SPIRVEmitIntrinsics
6771
: public ModulePass,
6872
public InstVisitor<SPIRVEmitIntrinsics, Instruction *> {
@@ -85,7 +89,7 @@ class SPIRVEmitIntrinsics
8589
DenseMap<Value *, bool> TodoType;
8690
void insertTodoType(Value *Op) {
8791
// TODO: add isa<CallInst>(Op) to no-insert
88-
if (CanTodoType && !isa<GetElementPtrInst>(Op)) {
92+
if (CanTodoType && !isaGEP(Op)) {
8993
auto It = TodoType.try_emplace(Op, true);
9094
if (It.second)
9195
++TodoTypeSz;
@@ -99,7 +103,7 @@ class SPIRVEmitIntrinsics
99103
}
100104
}
101105
bool isTodoType(Value *Op) {
102-
if (isa<GetElementPtrInst>(Op))
106+
if (isaGEP(Op))
103107
return false;
104108
auto It = TodoType.find(Op);
105109
return It != TodoType.end() && It->second;
@@ -252,6 +256,7 @@ class SPIRVEmitIntrinsics
252256
Instruction *visitInstruction(Instruction &I) { return &I; }
253257
Instruction *visitSwitchInst(SwitchInst &I);
254258
Instruction *visitGetElementPtrInst(GetElementPtrInst &I);
259+
Instruction *visitIntrinsicInst(IntrinsicInst &I);
255260
Instruction *visitBitCastInst(BitCastInst &I);
256261
Instruction *visitInsertElementInst(InsertElementInst &I);
257262
Instruction *visitExtractElementInst(ExtractElementInst &I);
@@ -515,8 +520,7 @@ void SPIRVEmitIntrinsics::propagateElemType(
515520
Instruction *UI = dyn_cast<Instruction>(U);
516521
// If the instruction was validated already, we need to keep it valid by
517522
// keeping current Op type.
518-
if (isa<GetElementPtrInst>(UI) ||
519-
TypeValidated.find(UI) != TypeValidated.end())
523+
if (isaGEP(UI) || TypeValidated.find(UI) != TypeValidated.end())
520524
replaceUsesOfWithSpvPtrcast(Op, ElemTy, UI, Ptrcasts);
521525
}
522526
}
@@ -546,8 +550,7 @@ void SPIRVEmitIntrinsics::propagateElemTypeRec(
546550
Instruction *UI = dyn_cast<Instruction>(U);
547551
// If the instruction was validated already, we need to keep it valid by
548552
// keeping current Op type.
549-
if (isa<GetElementPtrInst>(UI) ||
550-
TypeValidated.find(UI) != TypeValidated.end())
553+
if (isaGEP(UI) || TypeValidated.find(UI) != TypeValidated.end())
551554
replaceUsesOfWithSpvPtrcast(Op, CastElemTy, UI, Ptrcasts);
552555
}
553556
}
@@ -787,6 +790,8 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
787790
maybeAssignPtrType(Ty, I, Ref->getAllocatedType(), UnknownElemTypeI8);
788791
} else if (auto *Ref = dyn_cast<GetElementPtrInst>(I)) {
789792
Ty = getGEPType(Ref);
793+
} else if (auto *SGEP = dyn_cast<StructuredGEPInst>(I)) {
794+
Ty = SGEP->getResultElementType();
790795
} else if (auto *Ref = dyn_cast<LoadInst>(I)) {
791796
Value *Op = Ref->getPointerOperand();
792797
Type *KnownTy = GR->findDeducedElementType(Op);
@@ -1225,6 +1230,12 @@ void SPIRVEmitIntrinsics::deduceOperandElementType(
12251230
KnownElemTy = Ref->getSourceElementType();
12261231
Ops.push_back(std::make_pair(Ref->getPointerOperand(),
12271232
GetElementPtrInst::getPointerOperandIndex()));
1233+
} else if (auto *Ref = dyn_cast<StructuredGEPInst>(I)) {
1234+
if (GR->findDeducedElementType(Ref->getPointerOperand()))
1235+
return;
1236+
KnownElemTy = Ref->getBaseType();
1237+
Ops.push_back(std::make_pair(Ref->getPointerOperand(),
1238+
StructuredGEPInst::getPointerOperandIndex()));
12281239
} else if (auto *Ref = dyn_cast<LoadInst>(I)) {
12291240
KnownElemTy = I->getType();
12301241
if (isUntypedPointerTy(KnownElemTy))
@@ -1621,6 +1632,26 @@ static bool isFirstIndexZero(const GetElementPtrInst *GEP) {
16211632
return false;
16221633
}
16231634

1635+
Instruction *SPIRVEmitIntrinsics::visitIntrinsicInst(IntrinsicInst &I) {
1636+
auto *SGEP = dyn_cast<StructuredGEPInst>(&I);
1637+
if (!SGEP)
1638+
return &I;
1639+
1640+
IRBuilder<> B(I.getParent());
1641+
B.SetInsertPoint(&I);
1642+
SmallVector<Type *, 2> Types = {I.getType(), I.getOperand(0)->getType()};
1643+
SmallVector<Value *, 4> Args;
1644+
Args.push_back(/* inBounds= */ B.getInt1(true));
1645+
Args.push_back(I.getOperand(0));
1646+
Args.push_back(/* zero index */ B.getInt32(0));
1647+
for (unsigned J = 0; J < SGEP->getNumIndices(); ++J)
1648+
Args.push_back(SGEP->getIndexOperand(J));
1649+
1650+
auto *NewI = B.CreateIntrinsic(Intrinsic::spv_gep, Types, Args);
1651+
replaceAllUsesWithAndErase(B, &I, NewI);
1652+
return NewI;
1653+
}
1654+
16241655
Instruction *SPIRVEmitIntrinsics::visitGetElementPtrInst(GetElementPtrInst &I) {
16251656
IRBuilder<> B(I.getParent());
16261657
B.SetInsertPoint(&I);
@@ -1789,7 +1820,7 @@ void SPIRVEmitIntrinsics::replacePointerOperandWithPtrCast(
17891820
return;
17901821
} else if (isTodoType(Pointer)) {
17911822
eraseTodoType(Pointer);
1792-
if (!isa<CallInst>(Pointer) && !isa<GetElementPtrInst>(Pointer)) {
1823+
if (!isa<CallInst>(Pointer) && !isaGEP(Pointer)) {
17931824
// If this wouldn't be the first spv_ptrcast but existing type info is
17941825
// uncomplete, update spv_assign_ptr_type arguments.
17951826
if (CallInst *AssignCI = GR->findAssignPtrTypeInstr(Pointer)) {
@@ -2828,7 +2859,7 @@ void SPIRVEmitIntrinsics::applyDemangledPtrArgTypes(IRBuilder<> &B) {
28282859
B.SetCurrentDebugLocation(DebugLoc());
28292860
GR->buildAssignPtr(B, ElemTy, Arg);
28302861
}
2831-
} else if (isa<GetElementPtrInst>(Param)) {
2862+
} else if (isaGEP(Param)) {
28322863
replaceUsesOfWithSpvPtrcast(Param, normalizeType(ElemTy), CI,
28332864
Ptrcasts);
28342865
} else if (isa<Instruction>(Param)) {
@@ -2933,18 +2964,26 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
29332964
// Data structure for dead instructions that were simplified and replaced.
29342965
SmallPtrSet<Instruction *, 4> DeadInsts;
29352966
for (auto &I : instructions(Func)) {
2936-
auto *Ref = dyn_cast<GetElementPtrInst>(&I);
2937-
if (!Ref || GR->findDeducedElementType(Ref))
2967+
auto *GEP = dyn_cast<GetElementPtrInst>(&I);
2968+
auto *SGEP = dyn_cast<StructuredGEPInst>(&I);
2969+
2970+
if ((!GEP && !SGEP) || GR->findDeducedElementType(&I))
29382971
continue;
29392972

2940-
GetElementPtrInst *NewGEP = simplifyZeroLengthArrayGepInst(Ref);
2973+
if (SGEP) {
2974+
GR->addDeducedElementType(SGEP,
2975+
normalizeType(SGEP->getResultElementType()));
2976+
continue;
2977+
}
2978+
2979+
GetElementPtrInst *NewGEP = simplifyZeroLengthArrayGepInst(GEP);
29412980
if (NewGEP) {
2942-
Ref->replaceAllUsesWith(NewGEP);
2943-
DeadInsts.insert(Ref);
2944-
Ref = NewGEP;
2981+
GEP->replaceAllUsesWith(NewGEP);
2982+
DeadInsts.insert(GEP);
2983+
GEP = NewGEP;
29452984
}
2946-
if (Type *GepTy = getGEPType(Ref))
2947-
GR->addDeducedElementType(Ref, normalizeType(GepTy));
2985+
if (Type *GepTy = getGEPType(GEP))
2986+
GR->addDeducedElementType(GEP, normalizeType(GepTy));
29482987
}
29492988
// Remove dead instructions that were simplified and replaced.
29502989
for (auto *I : DeadInsts) {
@@ -3042,7 +3081,7 @@ bool SPIRVEmitIntrinsics::postprocessTypes(Module &M) {
30423081
DenseMap<Value *, SmallPtrSet<Value *, 4>> ToProcess;
30433082
for (auto [Op, Enabled] : TodoType) {
30443083
// TODO: add isa<CallInst>(Op) to continue
3045-
if (!Enabled || isa<GetElementPtrInst>(Op))
3084+
if (!Enabled || isaGEP(Op))
30463085
continue;
30473086
CallInst *AssignCI = GR->findAssignPtrTypeInstr(Op);
30483087
Type *KnownTy = GR->findDeducedElementType(Op);
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
3+
4+
%struct.S = type { i32, i32 }
5+
%struct.S2 = type { i32, %struct.S, i32 }
6+
7+
; CHECK-DAG: %[[#uint:]] = OpTypeInt 32 0
8+
; CHECK-DAG: %[[#ulong:]] = OpTypeInt 64 0
9+
; CHECK-DAG: %[[#ulong_0:]] = OpConstant %[[#ulong]] 0
10+
; CHECK-DAG: %[[#ulong_1:]] = OpConstant %[[#ulong]] 1
11+
; CHECK-DAG: %[[#ulong_2:]] = OpConstant %[[#ulong]] 2
12+
; CHECK-DAG: %[[#ulong_3:]] = OpConstant %[[#ulong]] 3
13+
; CHECK-DAG: %[[#uint_0:]] = OpConstant %[[#uint]] 0
14+
; CHECK-DAG: %[[#uint_1:]] = OpConstant %[[#uint]] 1
15+
; CHECK-DAG: %[[#uint_5:]] = OpConstant %[[#uint]] 5
16+
; CHECK-DAG: %[[#ptr_uint:]] = OpTypePointer Function %[[#uint]]
17+
; CHECK-DAG: %[[#arr_uint_5:]] = OpTypeArray %[[#uint]] %[[#uint_5]]
18+
; CHECK-DAG: %[[#ptr_arr_uint_5:]] = OpTypePointer Function %[[#arr_uint_5]]
19+
; CHECK-DAG: %[[#S:]] = OpTypeStruct %[[#uint]] %[[#uint]]
20+
; CHECK-DAG: %[[#ptr_S:]] = OpTypePointer Function %[[#S]]
21+
; CHECK-DAG: %[[#arr_S_5:]] = OpTypeArray %[[#S]] %[[#uint_5]]
22+
; CHECK-DAG: %[[#ptr_arr_S_5:]] = OpTypePointer Function %[[#arr_S_5]]
23+
; CHECK-DAG: %[[#S2:]] = OpTypeStruct %[[#uint]] %[[#S]] %[[#uint]]
24+
; CHECK-DAG: %[[#ptr_S2:]] = OpTypePointer Function %[[#S2]]
25+
; CHECK-DAG: %[[#arr_S2_5:]] = OpTypeArray %[[#S2]] %[[#uint_5]]
26+
; CHECK-DAG: %[[#ptr_arr_S2_5:]] = OpTypePointer Function %[[#arr_S2_5]]
27+
28+
define spir_func void @array_load_store(ptr %a) convergent {
29+
entry:
30+
%0 = call token @llvm.experimental.convergence.entry()
31+
%tmp = alloca [5 x i32], align 4
32+
; CHECK: %[[#tmp:]] = OpVariable %[[#ptr_arr_uint_5]] Function
33+
34+
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %a, i64 2)
35+
%2 = load i32, ptr %1, align 4
36+
; CHECK: %[[#A:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#]] %[[#ulong_2]]
37+
; CHECK: %[[#B:]] = OpLoad %[[#uint]] %[[#A]] Aligned 4
38+
39+
%3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x i32]) %tmp, i64 3)
40+
store i32 %2, ptr %3, align 4
41+
; CHECK: %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#tmp]] %[[#ulong_3]]
42+
; CHECK: OpStore %[[#C]] %[[#B]] Aligned 4
43+
44+
ret void
45+
}
46+
47+
define spir_func void @array_struct_load_store(ptr %a) convergent {
48+
entry:
49+
%0 = call token @llvm.experimental.convergence.entry()
50+
%tmp = alloca [5 x %struct.S], align 4
51+
; CHECK: %[[#tmp:]] = OpVariable %[[#ptr_arr_S_5]] Function
52+
53+
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %a, i64 2)
54+
%2 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %1, i64 1)
55+
%3 = load i32, ptr %2, align 4
56+
; CHECK: %[[#A:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#]] %[[#ulong_2]]
57+
; CHECK: %[[#B:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#A]] %[[#ulong_1]]
58+
; CHECK: %[[#C:]] = OpLoad %[[#uint]] %[[#B]] Aligned 4
59+
60+
%4 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S]) %tmp, i64 3)
61+
%5 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %4, i32 0)
62+
store i32 %3, ptr %5, align 4
63+
; CHECK: %[[#D:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#tmp]] %[[#ulong_3]]
64+
; CHECK: %[[#E:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#D]] %[[#uint_0]]
65+
; CHECK: OpStore %[[#E]] %[[#C]] Aligned 4
66+
67+
ret void
68+
}
69+
70+
define spir_func void @array_struct_load_store_combined(ptr %a) convergent {
71+
entry:
72+
%0 = call token @llvm.experimental.convergence.entry()
73+
%tmp = alloca [5 x %struct.S], align 4
74+
; CHECK: %[[#tmp:]] = OpVariable %[[#ptr_arr_S_5]] Function
75+
76+
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %a, i64 2, i64 1)
77+
%2 = load i32, ptr %1, align 4
78+
; CHECK: %[[#A:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#]] %[[#ulong_2]] %[[#ulong_1]]
79+
; CHECK: %[[#B:]] = OpLoad %[[#uint]] %[[#A]] Aligned 4
80+
81+
%3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S]) %tmp, i64 3, i32 0)
82+
store i32 %2, ptr %3, align 4
83+
; CHECK: %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#tmp]] %[[#ulong_3]] %[[#uint_0]]
84+
; CHECK: OpStore %[[#C]] %[[#B]] Aligned 4
85+
86+
ret void
87+
}
88+
89+
define spir_func void @array_nested_struct_load_store(ptr %a) convergent {
90+
entry:
91+
%0 = call token @llvm.experimental.convergence.entry()
92+
%tmp = alloca [5 x %struct.S2], align 4
93+
; CHECK: %[[#tmp:]] = OpVariable %[[#ptr_arr_S2_5]] Function
94+
95+
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S2]) %a, i64 1)
96+
%2 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %1, i64 1)
97+
%3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %2, i64 0)
98+
%4 = load i32, ptr %3, align 4
99+
; CHECK: %[[#A:]] = OpInBoundsAccessChain %[[#ptr_S2]] %[[#]] %[[#ulong_1]]
100+
; CHECK: %[[#B:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#A]] %[[#ulong_1]]
101+
; CHECK: %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#B]] %[[#ulong_0]]
102+
; CHECK: %[[#D:]] = OpLoad %[[#uint]] %[[#C]] Aligned 4
103+
104+
%5 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S2]) %tmp, i64 3)
105+
%6 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %5, i32 1)
106+
%7 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %6, i32 0)
107+
store i32 %4, ptr %7, align 4
108+
; CHECK: %[[#E:]] = OpInBoundsAccessChain %[[#ptr_S2]] %[[#tmp]] %[[#ulong_3]]
109+
; CHECK: %[[#F:]] = OpInBoundsAccessChain %[[#ptr_S]] %[[#E]] %[[#uint_1]]
110+
; CHECK: %[[#G:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#F]] %[[#uint_0]]
111+
; CHECK: OpStore %[[#G]] %[[#D]] Aligned 4
112+
113+
ret void
114+
}
115+
116+
define spir_func void @array_nested_struct_load_store_combined(ptr %a) convergent {
117+
entry:
118+
%0 = call token @llvm.experimental.convergence.entry()
119+
%tmp = alloca [5 x %struct.S2], align 4
120+
; CHECK: %[[#tmp:]] = OpVariable %[[#ptr_arr_S2_5]] Function
121+
122+
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S2]) %a, i64 1, i64 1, i64 0)
123+
%2 = load i32, ptr %1, align 4
124+
; CHECK: %[[#A:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#]] %[[#ulong_1]] %[[#ulong_1]] %[[#ulong_0]]
125+
; CHECK: %[[#B:]] = OpLoad %[[#uint]] %[[#A]] Aligned 4
126+
127+
%3 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([5 x %struct.S2]) %tmp, i64 3, i32 1, i32 0)
128+
store i32 %2, ptr %3, align 4
129+
; CHECK: %[[#C:]] = OpInBoundsAccessChain %[[#ptr_uint]] %[[#tmp]] %[[#ulong_3]] %[[#uint_1]] %[[#uint_0]]
130+
; CHECK: OpStore %[[#C]] %[[#B]] Aligned 4
131+
132+
ret void
133+
}
134+
135+
declare token @llvm.experimental.convergence.entry() #1
136+
137+
declare ptr @llvm.structured.gep.p0(ptr, ...) #3
138+
139+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
140+
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
141+
attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-pc-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
3+
4+
%class.Base = type { i32 }
5+
%class.Derived = type { %class.Base, float }
6+
7+
; CHECK-DAG: %[[#int:]] = OpTypeInt 32 0
8+
; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
9+
; CHECK-DAG: %[[#Base:]] = OpTypeStruct %[[#int]]
10+
; CHECK-DAG: %[[#Derived:]] = OpTypeStruct %[[#Base]] %[[#float]]
11+
; CHECK-DAG: %[[#ptr_Derived:]] = OpTypePointer Function %[[#Derived]]
12+
; CHECK-DAG: %[[#ptr_Base:]] = OpTypePointer Function %[[#Base]]
13+
; CHECK-DAG: %[[#ptr_int:]] = OpTypePointer Function %[[#int]]
14+
; CHECK-DAG: %[[#idx_0:]] = OpConstant %[[#int]] 0
15+
16+
define spir_func void @class_access(ptr %d) convergent {
17+
entry:
18+
%0 = call token @llvm.experimental.convergence.entry()
19+
; CHECK: %[[#d_var:]] = OpFunctionParameter %[[#ptr_Derived]]
20+
21+
; Access Base part
22+
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%class.Derived) %d, i32 0)
23+
; CHECK: %[[#ptr_base:]] = OpInBoundsAccessChain %[[#ptr_Base]] %[[#d_var]] %[[#idx_0]]
24+
25+
; Access field in Base
26+
%2 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%class.Base) %1, i32 0)
27+
; CHECK: %[[#ptr_field:]] = OpInBoundsAccessChain %[[#ptr_int]] %[[#ptr_base]] %[[#idx_0]]
28+
29+
store i32 42, ptr %2, align 4
30+
; CHECK: OpStore %[[#ptr_field]]
31+
32+
ret void
33+
}
34+
35+
declare token @llvm.experimental.convergence.entry() #1
36+
declare ptr @llvm.structured.gep.p0(ptr, ...) #3
37+
38+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
39+
attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

0 commit comments

Comments
 (0)