Skip to content

Commit 5752098

Browse files
[SPIR-V] Implement insertion of OpGenericCastToPtr using builtin functions (#95055)
This PR implements insertion of OpGenericCastToPtr using builtin functions (both opencl `to_global|local|private` and `__spirv_` wrappers), and improves type inference.
1 parent 6561460 commit 5752098

File tree

5 files changed

+268
-73
lines changed

5 files changed

+268
-73
lines changed

llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,72 @@ lookupBuiltin(StringRef DemangledCall,
300300
return nullptr;
301301
}
302302

303+
static MachineInstr *getBlockStructInstr(Register ParamReg,
304+
MachineRegisterInfo *MRI) {
305+
// We expect the following sequence of instructions:
306+
// %0:_(pN) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.alloca)
307+
// or = G_GLOBAL_VALUE @block_literal_global
308+
// %1:_(pN) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.bitcast), %0
309+
// %2:_(p4) = G_ADDRSPACE_CAST %1:_(pN)
310+
MachineInstr *MI = MRI->getUniqueVRegDef(ParamReg);
311+
assert(MI->getOpcode() == TargetOpcode::G_ADDRSPACE_CAST &&
312+
MI->getOperand(1).isReg());
313+
Register BitcastReg = MI->getOperand(1).getReg();
314+
MachineInstr *BitcastMI = MRI->getUniqueVRegDef(BitcastReg);
315+
assert(isSpvIntrinsic(*BitcastMI, Intrinsic::spv_bitcast) &&
316+
BitcastMI->getOperand(2).isReg());
317+
Register ValueReg = BitcastMI->getOperand(2).getReg();
318+
MachineInstr *ValueMI = MRI->getUniqueVRegDef(ValueReg);
319+
return ValueMI;
320+
}
321+
322+
// Return an integer constant corresponding to the given register and
323+
// defined in spv_track_constant.
324+
// TODO: maybe unify with prelegalizer pass.
325+
static unsigned getConstFromIntrinsic(Register Reg, MachineRegisterInfo *MRI) {
326+
MachineInstr *DefMI = MRI->getUniqueVRegDef(Reg);
327+
assert(isSpvIntrinsic(*DefMI, Intrinsic::spv_track_constant) &&
328+
DefMI->getOperand(2).isReg());
329+
MachineInstr *DefMI2 = MRI->getUniqueVRegDef(DefMI->getOperand(2).getReg());
330+
assert(DefMI2->getOpcode() == TargetOpcode::G_CONSTANT &&
331+
DefMI2->getOperand(1).isCImm());
332+
return DefMI2->getOperand(1).getCImm()->getValue().getZExtValue();
333+
}
334+
335+
// Return type of the instruction result from spv_assign_type intrinsic.
336+
// TODO: maybe unify with prelegalizer pass.
337+
static const Type *getMachineInstrType(MachineInstr *MI) {
338+
MachineInstr *NextMI = MI->getNextNode();
339+
if (!NextMI)
340+
return nullptr;
341+
if (isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_name))
342+
if ((NextMI = NextMI->getNextNode()) == nullptr)
343+
return nullptr;
344+
Register ValueReg = MI->getOperand(0).getReg();
345+
if ((!isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_type) &&
346+
!isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_ptr_type)) ||
347+
NextMI->getOperand(1).getReg() != ValueReg)
348+
return nullptr;
349+
Type *Ty = getMDOperandAsType(NextMI->getOperand(2).getMetadata(), 0);
350+
assert(Ty && "Type is expected");
351+
return Ty;
352+
}
353+
354+
static const Type *getBlockStructType(Register ParamReg,
355+
MachineRegisterInfo *MRI) {
356+
// In principle, this information should be passed to us from Clang via
357+
// an elementtype attribute. However, said attribute requires that
358+
// the function call be an intrinsic, which is not. Instead, we rely on being
359+
// able to trace this to the declaration of a variable: OpenCL C specification
360+
// section 6.12.5 should guarantee that we can do this.
361+
MachineInstr *MI = getBlockStructInstr(ParamReg, MRI);
362+
if (MI->getOpcode() == TargetOpcode::G_GLOBAL_VALUE)
363+
return MI->getOperand(1).getGlobal()->getType();
364+
assert(isSpvIntrinsic(*MI, Intrinsic::spv_alloca) &&
365+
"Blocks in OpenCL C must be traceable to allocation site");
366+
return getMachineInstrType(MI);
367+
}
368+
303369
//===----------------------------------------------------------------------===//
304370
// Helper functions for building misc instructions
305371
//===----------------------------------------------------------------------===//
@@ -1371,6 +1437,14 @@ static bool generateBarrierInst(const SPIRV::IncomingCall *Call,
13711437
return buildBarrierInst(Call, Opcode, MIRBuilder, GR);
13721438
}
13731439

1440+
static bool generateCastToPtrInst(const SPIRV::IncomingCall *Call,
1441+
MachineIRBuilder &MIRBuilder) {
1442+
MIRBuilder.buildInstr(TargetOpcode::G_ADDRSPACE_CAST)
1443+
.addDef(Call->ReturnRegister)
1444+
.addUse(Call->Arguments[0]);
1445+
return true;
1446+
}
1447+
13741448
static bool generateDotOrFMulInst(const SPIRV::IncomingCall *Call,
13751449
MachineIRBuilder &MIRBuilder,
13761450
SPIRVGlobalRegistry *GR) {
@@ -1847,68 +1921,6 @@ static bool buildNDRange(const SPIRV::IncomingCall *Call,
18471921
.addUse(TmpReg);
18481922
}
18491923

1850-
static MachineInstr *getBlockStructInstr(Register ParamReg,
1851-
MachineRegisterInfo *MRI) {
1852-
// We expect the following sequence of instructions:
1853-
// %0:_(pN) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.alloca)
1854-
// or = G_GLOBAL_VALUE @block_literal_global
1855-
// %1:_(pN) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.bitcast), %0
1856-
// %2:_(p4) = G_ADDRSPACE_CAST %1:_(pN)
1857-
MachineInstr *MI = MRI->getUniqueVRegDef(ParamReg);
1858-
assert(MI->getOpcode() == TargetOpcode::G_ADDRSPACE_CAST &&
1859-
MI->getOperand(1).isReg());
1860-
Register BitcastReg = MI->getOperand(1).getReg();
1861-
MachineInstr *BitcastMI = MRI->getUniqueVRegDef(BitcastReg);
1862-
assert(isSpvIntrinsic(*BitcastMI, Intrinsic::spv_bitcast) &&
1863-
BitcastMI->getOperand(2).isReg());
1864-
Register ValueReg = BitcastMI->getOperand(2).getReg();
1865-
MachineInstr *ValueMI = MRI->getUniqueVRegDef(ValueReg);
1866-
return ValueMI;
1867-
}
1868-
1869-
// Return an integer constant corresponding to the given register and
1870-
// defined in spv_track_constant.
1871-
// TODO: maybe unify with prelegalizer pass.
1872-
static unsigned getConstFromIntrinsic(Register Reg, MachineRegisterInfo *MRI) {
1873-
MachineInstr *DefMI = MRI->getUniqueVRegDef(Reg);
1874-
assert(isSpvIntrinsic(*DefMI, Intrinsic::spv_track_constant) &&
1875-
DefMI->getOperand(2).isReg());
1876-
MachineInstr *DefMI2 = MRI->getUniqueVRegDef(DefMI->getOperand(2).getReg());
1877-
assert(DefMI2->getOpcode() == TargetOpcode::G_CONSTANT &&
1878-
DefMI2->getOperand(1).isCImm());
1879-
return DefMI2->getOperand(1).getCImm()->getValue().getZExtValue();
1880-
}
1881-
1882-
// Return type of the instruction result from spv_assign_type intrinsic.
1883-
// TODO: maybe unify with prelegalizer pass.
1884-
static const Type *getMachineInstrType(MachineInstr *MI) {
1885-
MachineInstr *NextMI = MI->getNextNode();
1886-
if (isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_name))
1887-
NextMI = NextMI->getNextNode();
1888-
Register ValueReg = MI->getOperand(0).getReg();
1889-
if (!isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_type) ||
1890-
NextMI->getOperand(1).getReg() != ValueReg)
1891-
return nullptr;
1892-
Type *Ty = getMDOperandAsType(NextMI->getOperand(2).getMetadata(), 0);
1893-
assert(Ty && "Type is expected");
1894-
return Ty;
1895-
}
1896-
1897-
static const Type *getBlockStructType(Register ParamReg,
1898-
MachineRegisterInfo *MRI) {
1899-
// In principle, this information should be passed to us from Clang via
1900-
// an elementtype attribute. However, said attribute requires that
1901-
// the function call be an intrinsic, which is not. Instead, we rely on being
1902-
// able to trace this to the declaration of a variable: OpenCL C specification
1903-
// section 6.12.5 should guarantee that we can do this.
1904-
MachineInstr *MI = getBlockStructInstr(ParamReg, MRI);
1905-
if (MI->getOpcode() == TargetOpcode::G_GLOBAL_VALUE)
1906-
return MI->getOperand(1).getGlobal()->getType();
1907-
assert(isSpvIntrinsic(*MI, Intrinsic::spv_alloca) &&
1908-
"Blocks in OpenCL C must be traceable to allocation site");
1909-
return getMachineInstrType(MI);
1910-
}
1911-
19121924
// TODO: maybe move to the global register.
19131925
static SPIRVType *
19141926
getOrCreateSPIRVDeviceEventPointer(MachineIRBuilder &MIRBuilder,
@@ -2322,6 +2334,8 @@ std::optional<bool> lowerBuiltin(const StringRef DemangledCall,
23222334
return generateAtomicFloatingInst(Call.get(), MIRBuilder, GR);
23232335
case SPIRV::Barrier:
23242336
return generateBarrierInst(Call.get(), MIRBuilder, GR);
2337+
case SPIRV::CastToPtr:
2338+
return generateCastToPtrInst(Call.get(), MIRBuilder);
23252339
case SPIRV::Dot:
23262340
return generateDotOrFMulInst(Call.get(), MIRBuilder, GR);
23272341
case SPIRV::Wave:

llvm/lib/Target/SPIRV/SPIRVBuiltins.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def IntelSubgroups : BuiltinGroup;
5959
def AtomicFloating : BuiltinGroup;
6060
def GroupUniform : BuiltinGroup;
6161
def KernelClock : BuiltinGroup;
62+
def CastToPtr : BuiltinGroup;
6263

6364
//===----------------------------------------------------------------------===//
6465
// Class defining a demangled builtin record. The information in the record
@@ -595,6 +596,17 @@ defm : DemangledNativeBuiltin<"__spirv_GroupWaitEvents", OpenCL_std, AsyncCopy,
595596
defm : DemangledNativeBuiltin<"__spirv_Load", OpenCL_std, LoadStore, 1, 3, OpLoad>;
596597
defm : DemangledNativeBuiltin<"__spirv_Store", OpenCL_std, LoadStore, 2, 4, OpStore>;
597598

599+
// Address Space Qualifier Functions/Pointers Conversion Instructions:
600+
defm : DemangledNativeBuiltin<"to_global", OpenCL_std, CastToPtr, 1, 1, OpGenericCastToPtr>;
601+
defm : DemangledNativeBuiltin<"to_local", OpenCL_std, CastToPtr, 1, 1, OpGenericCastToPtr>;
602+
defm : DemangledNativeBuiltin<"to_private", OpenCL_std, CastToPtr, 1, 1, OpGenericCastToPtr>;
603+
defm : DemangledNativeBuiltin<"__spirv_GenericCastToPtr_ToGlobal", OpenCL_std, CastToPtr, 2, 2, OpGenericCastToPtr>;
604+
defm : DemangledNativeBuiltin<"__spirv_GenericCastToPtr_ToLocal", OpenCL_std, CastToPtr, 2, 2, OpGenericCastToPtr>;
605+
defm : DemangledNativeBuiltin<"__spirv_GenericCastToPtr_ToPrivate", OpenCL_std, CastToPtr, 2, 2, OpGenericCastToPtr>;
606+
defm : DemangledNativeBuiltin<"__spirv_OpGenericCastToPtrExplicit_ToGlobal", OpenCL_std, CastToPtr, 2, 2, OpGenericCastToPtr>;
607+
defm : DemangledNativeBuiltin<"__spirv_OpGenericCastToPtrExplicit_ToLocal", OpenCL_std, CastToPtr, 2, 2, OpGenericCastToPtr>;
608+
defm : DemangledNativeBuiltin<"__spirv_OpGenericCastToPtrExplicit_ToPrivate", OpenCL_std, CastToPtr, 2, 2, OpGenericCastToPtr>;
609+
598610
//===----------------------------------------------------------------------===//
599611
// Class defining a work/sub group builtin that should be translated into a
600612
// SPIR-V instruction using the defined properties.

llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class SPIRVEmitIntrinsics
6969
DenseSet<Instruction *> AggrStores;
7070

7171
// deduce element type of untyped pointers
72-
Type *deduceElementType(Value *I);
72+
Type *deduceElementType(Value *I, bool UnknownElemTypeI8);
7373
Type *deduceElementTypeHelper(Value *I);
7474
Type *deduceElementTypeHelper(Value *I, std::unordered_set<Value *> &Visited);
7575
Type *deduceElementTypeByValueDeep(Type *ValueTy, Value *Operand,
@@ -105,7 +105,8 @@ class SPIRVEmitIntrinsics
105105

106106
void replaceMemInstrUses(Instruction *Old, Instruction *New, IRBuilder<> &B);
107107
void processInstrAfterVisit(Instruction *I, IRBuilder<> &B);
108-
void insertAssignPtrTypeIntrs(Instruction *I, IRBuilder<> &B);
108+
bool insertAssignPtrTypeIntrs(Instruction *I, IRBuilder<> &B,
109+
bool UnknownElemTypeI8);
109110
void insertAssignTypeIntrs(Instruction *I, IRBuilder<> &B);
110111
void insertAssignPtrTypeTargetExt(TargetExtType *AssignedType, Value *V,
111112
IRBuilder<> &B);
@@ -367,6 +368,23 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
367368
if (Ty)
368369
break;
369370
}
371+
} else if (auto *CI = dyn_cast<CallInst>(I)) {
372+
static StringMap<unsigned> ResTypeByArg = {
373+
{"to_global", 0},
374+
{"to_local", 0},
375+
{"to_private", 0},
376+
{"__spirv_GenericCastToPtr_ToGlobal", 0},
377+
{"__spirv_GenericCastToPtr_ToLocal", 0},
378+
{"__spirv_GenericCastToPtr_ToPrivate", 0}};
379+
// TODO: maybe improve performance by caching demangled names
380+
if (Function *CalledF = CI->getCalledFunction()) {
381+
std::string DemangledName =
382+
getOclOrSpirvBuiltinDemangledName(CalledF->getName());
383+
auto AsArgIt = ResTypeByArg.find(DemangledName);
384+
if (AsArgIt != ResTypeByArg.end())
385+
Ty = deduceElementTypeHelper(CI->getArgOperand(AsArgIt->second),
386+
Visited);
387+
}
370388
}
371389

372390
// remember the found relationship
@@ -460,10 +478,10 @@ Type *SPIRVEmitIntrinsics::deduceNestedTypeHelper(
460478
return OrigTy;
461479
}
462480

463-
Type *SPIRVEmitIntrinsics::deduceElementType(Value *I) {
481+
Type *SPIRVEmitIntrinsics::deduceElementType(Value *I, bool UnknownElemTypeI8) {
464482
if (Type *Ty = deduceElementTypeHelper(I))
465483
return Ty;
466-
return IntegerType::getInt8Ty(I->getContext());
484+
return UnknownElemTypeI8 ? IntegerType::getInt8Ty(I->getContext()) : nullptr;
467485
}
468486

469487
// If the Instruction has Pointer operands with unresolved types, this function
@@ -1152,16 +1170,23 @@ void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
11521170
B.CreateIntrinsic(Intrinsic::spv_unref_global, GV.getType(), &GV);
11531171
}
11541172

1155-
void SPIRVEmitIntrinsics::insertAssignPtrTypeIntrs(Instruction *I,
1156-
IRBuilder<> &B) {
1173+
// Return true, if we can't decide what is the pointee type now and will get
1174+
// back to the question later. Return false is spv_assign_ptr_type is not needed
1175+
// or can be inserted immediately.
1176+
bool SPIRVEmitIntrinsics::insertAssignPtrTypeIntrs(Instruction *I,
1177+
IRBuilder<> &B,
1178+
bool UnknownElemTypeI8) {
11571179
reportFatalOnTokenType(I);
11581180
if (!isPointerTy(I->getType()) || !requireAssignType(I) ||
11591181
isa<BitCastInst>(I))
1160-
return;
1182+
return false;
11611183

11621184
setInsertPointAfterDef(B, I);
1163-
Type *ElemTy = deduceElementType(I);
1164-
buildAssignPtr(B, ElemTy, I);
1185+
if (Type *ElemTy = deduceElementType(I, UnknownElemTypeI8)) {
1186+
buildAssignPtr(B, ElemTy, I);
1187+
return false;
1188+
}
1189+
return true;
11651190
}
11661191

11671192
void SPIRVEmitIntrinsics::insertAssignTypeIntrs(Instruction *I,
@@ -1199,7 +1224,7 @@ void SPIRVEmitIntrinsics::insertAssignTypeIntrs(Instruction *I,
11991224
buildAssignPtr(B, PType->getElementType(), Op);
12001225
} else if (isPointerTy(OpTy)) {
12011226
Type *ElemTy = GR->findDeducedElementType(Op);
1202-
buildAssignPtr(B, ElemTy ? ElemTy : deduceElementType(Op), Op);
1227+
buildAssignPtr(B, ElemTy ? ElemTy : deduceElementType(Op, true), Op);
12031228
} else {
12041229
CallInst *AssignCI = buildIntrWithMD(Intrinsic::spv_assign_type,
12051230
{OpTy}, Op, Op, {}, B);
@@ -1395,10 +1420,15 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
13951420
if (isConvergenceIntrinsic(I))
13961421
continue;
13971422

1398-
insertAssignPtrTypeIntrs(I, B);
1423+
bool Postpone = insertAssignPtrTypeIntrs(I, B, false);
1424+
// if Postpone is true, we can't decide on pointee type yet
13991425
insertAssignTypeIntrs(I, B);
14001426
insertPtrCastOrAssignTypeInstr(I, B);
14011427
insertSpirvDecorations(I, B);
1428+
// if instruction requires a pointee type set, let's check if we know it
1429+
// already, and force it to be i8 if not
1430+
if (Postpone && !GR->findAssignPtrTypeInstr(I))
1431+
insertAssignPtrTypeIntrs(I, B, true);
14021432
}
14031433

14041434
for (auto &I : instructions(Func))

llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ void SPIRVTargetLowering::finalizeLowering(MachineFunction &MF) const {
339339
GR.getSPIRVTypeForVReg(MI.getOperand(1).getReg()));
340340
break;
341341
case SPIRV::OpPtrCastToGeneric:
342+
case SPIRV::OpGenericCastToPtr:
342343
validateAccessChain(STI, MRI, GR, MI);
343344
break;
344345
case SPIRV::OpInBoundsPtrAccessChain:

0 commit comments

Comments
 (0)