Skip to content

[SPIR-V] Implement OpSpecConstantOp with ptr-cast operation #109979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,11 @@ SPIRVGlobalRegistry::getPointerStorageClass(Register VReg) const {
SPIRVType *Type = getSPIRVTypeForVReg(VReg);
assert(Type && Type->getOpcode() == SPIRV::OpTypePointer &&
Type->getOperand(1).isImm() && "Pointer type is expected");
return getPointerStorageClass(Type);
}

SPIRV::StorageClass::StorageClass
SPIRVGlobalRegistry::getPointerStorageClass(const SPIRVType *Type) const {
return static_cast<SPIRV::StorageClass::StorageClass>(
Type->getOperand(1).getImm());
}
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ class SPIRVGlobalRegistry {

// Gets the storage class of the pointer type assigned to this vreg.
SPIRV::StorageClass::StorageClass getPointerStorageClass(Register VReg) const;
SPIRV::StorageClass::StorageClass
getPointerStorageClass(const SPIRVType *Type) const;

// Return the number of bits SPIR-V pointers and size_t variables require.
unsigned getPointerSize() const { return PointerSize; }
Expand Down
134 changes: 107 additions & 27 deletions llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ class SPIRVInstructionSelector : public InstructionSelector {

bool selectUnmergeValues(MachineInstr &I) const;

// Utilities
Register buildI32Constant(uint32_t Val, MachineInstr &I,
const SPIRVType *ResType = nullptr) const;

Expand All @@ -260,6 +261,14 @@ class SPIRVInstructionSelector : public InstructionSelector {

bool wrapIntoSpecConstantOp(MachineInstr &I,
SmallVector<Register> &CompositeArgs) const;

Register getUcharPtrTypeReg(MachineInstr &I,
SPIRV::StorageClass::StorageClass SC) const;
MachineInstrBuilder buildSpecConstantOp(MachineInstr &I, Register Dest,
Register Src, Register DestType,
uint32_t Opcode) const;
MachineInstrBuilder buildConstGenericPtr(MachineInstr &I, Register SrcPtr,
SPIRVType *SrcPtrTy) const;
};

} // end anonymous namespace
Expand Down Expand Up @@ -1242,6 +1251,58 @@ static bool isUSMStorageClass(SPIRV::StorageClass::StorageClass SC) {
}
}

// Returns true ResVReg is referred only from global vars and OpName's.
static bool isASCastInGVar(MachineRegisterInfo *MRI, Register ResVReg) {
bool IsGRef = false;
bool IsAllowedRefs =
std::all_of(MRI->use_instr_begin(ResVReg), MRI->use_instr_end(),
[&IsGRef](auto const &It) {
unsigned Opcode = It.getOpcode();
if (Opcode == SPIRV::OpConstantComposite ||
Opcode == SPIRV::OpVariable ||
isSpvIntrinsic(It, Intrinsic::spv_init_global))
return IsGRef = true;
return Opcode == SPIRV::OpName;
});
return IsAllowedRefs && IsGRef;
}

Register SPIRVInstructionSelector::getUcharPtrTypeReg(
MachineInstr &I, SPIRV::StorageClass::StorageClass SC) const {
return GR.getSPIRVTypeID(GR.getOrCreateSPIRVPointerType(
GR.getOrCreateSPIRVIntegerType(8, I, TII), I, TII, SC));
}

MachineInstrBuilder
SPIRVInstructionSelector::buildSpecConstantOp(MachineInstr &I, Register Dest,
Register Src, Register DestType,
uint32_t Opcode) const {
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
TII.get(SPIRV::OpSpecConstantOp))
.addDef(Dest)
.addUse(DestType)
.addImm(Opcode)
.addUse(Src);
}

MachineInstrBuilder
SPIRVInstructionSelector::buildConstGenericPtr(MachineInstr &I, Register SrcPtr,
SPIRVType *SrcPtrTy) const {
SPIRVType *GenericPtrTy = GR.getOrCreateSPIRVPointerType(
GR.getPointeeType(SrcPtrTy), I, TII, SPIRV::StorageClass::Generic);
Register Tmp = MRI->createVirtualRegister(&SPIRV::pIDRegClass);
MRI->setType(Tmp, LLT::pointer(storageClassToAddressSpace(
SPIRV::StorageClass::Generic),
GR.getPointerSize()));
MachineFunction *MF = I.getParent()->getParent();
GR.assignSPIRVTypeToVReg(GenericPtrTy, Tmp, *MF);
MachineInstrBuilder MIB = buildSpecConstantOp(
I, Tmp, SrcPtr, GR.getSPIRVTypeID(GenericPtrTy),
static_cast<uint32_t>(SPIRV::Opcode::PtrCastToGeneric));
GR.add(MIB.getInstr(), MF, Tmp);
return MIB;
}

// In SPIR-V address space casting can only happen to and from the Generic
// storage class. We can also only cast Workgroup, CrossWorkgroup, or Function
// pointers to and from Generic pointers. As such, we can convert e.g. from
Expand All @@ -1250,36 +1311,57 @@ static bool isUSMStorageClass(SPIRV::StorageClass::StorageClass SC) {
bool SPIRVInstructionSelector::selectAddrSpaceCast(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
// If the AddrSpaceCast user is single and in OpConstantComposite or
// OpVariable, we should select OpSpecConstantOp.
auto UIs = MRI->use_instructions(ResVReg);
if (!UIs.empty() && ++UIs.begin() == UIs.end() &&
(UIs.begin()->getOpcode() == SPIRV::OpConstantComposite ||
UIs.begin()->getOpcode() == SPIRV::OpVariable ||
isSpvIntrinsic(*UIs.begin(), Intrinsic::spv_init_global))) {
Register NewReg = I.getOperand(1).getReg();
MachineBasicBlock &BB = *I.getParent();
SPIRVType *SpvBaseTy = GR.getOrCreateSPIRVIntegerType(8, I, TII);
ResType = GR.getOrCreateSPIRVPointerType(SpvBaseTy, I, TII,
SPIRV::StorageClass::Generic);
bool Result =
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpSpecConstantOp))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addImm(static_cast<uint32_t>(SPIRV::Opcode::PtrCastToGeneric))
.addUse(NewReg)
.constrainAllUses(TII, TRI, RBI);
return Result;
}
MachineBasicBlock &BB = *I.getParent();
const DebugLoc &DL = I.getDebugLoc();

Register SrcPtr = I.getOperand(1).getReg();
SPIRVType *SrcPtrTy = GR.getSPIRVTypeForVReg(SrcPtr);
SPIRV::StorageClass::StorageClass SrcSC = GR.getPointerStorageClass(SrcPtr);
SPIRV::StorageClass::StorageClass DstSC = GR.getPointerStorageClass(ResVReg);

// don't generate a cast for a null that may be represented by OpTypeInt
if (SrcPtrTy->getOpcode() != SPIRV::OpTypePointer ||
ResType->getOpcode() != SPIRV::OpTypePointer)
return BuildMI(BB, I, DL, TII.get(TargetOpcode::COPY))
.addDef(ResVReg)
.addUse(SrcPtr)
.constrainAllUses(TII, TRI, RBI);

SPIRV::StorageClass::StorageClass SrcSC = GR.getPointerStorageClass(SrcPtrTy);
SPIRV::StorageClass::StorageClass DstSC = GR.getPointerStorageClass(ResType);

if (isASCastInGVar(MRI, ResVReg)) {
// AddrSpaceCast uses within OpVariable and OpConstantComposite instructions
// are expressed by OpSpecConstantOp with an Opcode.
// TODO: maybe insert a check whether the Kernel capability was declared and
// so PtrCastToGeneric/GenericCastToPtr are available.
unsigned SpecOpcode =
DstSC == SPIRV::StorageClass::Generic && isGenericCastablePtr(SrcSC)
? static_cast<uint32_t>(SPIRV::Opcode::PtrCastToGeneric)
: (SrcSC == SPIRV::StorageClass::Generic &&
isGenericCastablePtr(DstSC)
? static_cast<uint32_t>(SPIRV::Opcode::GenericCastToPtr)
: 0);
// TODO: OpConstantComposite expects i8*, so we are forced to forget a
// correct value of ResType and use general i8* instead. Maybe this should
// be addressed in the emit-intrinsic step to infer a correct
// OpConstantComposite type.
if (SpecOpcode) {
return buildSpecConstantOp(I, ResVReg, SrcPtr,
getUcharPtrTypeReg(I, DstSC), SpecOpcode)
.constrainAllUses(TII, TRI, RBI);
} else if (isGenericCastablePtr(SrcSC) && isGenericCastablePtr(DstSC)) {
MachineInstrBuilder MIB = buildConstGenericPtr(I, SrcPtr, SrcPtrTy);
return MIB.constrainAllUses(TII, TRI, RBI) &&
buildSpecConstantOp(
I, ResVReg, MIB->getOperand(0).getReg(),
getUcharPtrTypeReg(I, DstSC),
static_cast<uint32_t>(SPIRV::Opcode::GenericCastToPtr))
.constrainAllUses(TII, TRI, RBI);
}
}

// don't generate a cast between identical storage classes
if (SrcSC == DstSC)
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
TII.get(TargetOpcode::COPY))
return BuildMI(BB, I, DL, TII.get(TargetOpcode::COPY))
.addDef(ResVReg)
.addUse(SrcPtr)
.constrainAllUses(TII, TRI, RBI);
Expand All @@ -1295,8 +1377,6 @@ bool SPIRVInstructionSelector::selectAddrSpaceCast(Register ResVReg,
Register Tmp = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
SPIRVType *GenericPtrTy = GR.getOrCreateSPIRVPointerType(
GR.getPointeeType(SrcPtrTy), I, TII, SPIRV::StorageClass::Generic);
MachineBasicBlock &BB = *I.getParent();
const DebugLoc &DL = I.getDebugLoc();
bool Success = BuildMI(BB, I, DL, TII.get(SPIRV::OpPtrCastToGeneric))
.addDef(Tmp)
.addUse(GR.getSPIRVTypeID(GenericPtrTy))
Expand Down
23 changes: 22 additions & 1 deletion llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,21 @@ static SPIRVType *propagateSPIRVType(MachineInstr *MI, SPIRVGlobalRegistry *GR,
default:
break;
}
if (SpvType)
if (SpvType) {
// check if the address space needs correction
LLT RegType = MRI.getType(Reg);
if (SpvType->getOpcode() == SPIRV::OpTypePointer &&
RegType.isPointer() &&
storageClassToAddressSpace(GR->getPointerStorageClass(SpvType)) !=
RegType.getAddressSpace()) {
const SPIRVSubtarget &ST =
MI->getParent()->getParent()->getSubtarget<SPIRVSubtarget>();
SpvType = GR->getOrCreateSPIRVPointerType(
GR->getPointeeType(SpvType), *MI, *ST.getInstrInfo(),
addressSpaceToStorageClass(RegType.getAddressSpace(), ST));
}
GR->assignSPIRVTypeToVReg(SpvType, Reg, MIB.getMF());
}
if (!MRI.getRegClassOrNull(Reg))
MRI.setRegClass(Reg, SpvType ? GR->getRegClass(SpvType)
: &SPIRV::iIDRegClass);
Expand Down Expand Up @@ -510,6 +523,14 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
? MI.getOperand(1).getCImm()->getType()
: TargetExtIt->second;
const ConstantInt *OpCI = MI.getOperand(1).getCImm();
// TODO: we may wish to analyze here if OpCI is zero and LLT RegType =
// MRI.getType(Reg); RegType.isPointer() is true, so that we observe
// at this point not i64/i32 constant but null pointer in the
// corresponding address space of RegType.getAddressSpace(). This may
// help to successfully validate the case when a OpConstantComposite's
// constituent has type that does not match Result Type of
// OpConstantComposite (see, for example,
// pointers/PtrCast-null-in-OpSpecConstantOp.ll).
Register PrimaryReg = GR->find(OpCI, &MF);
if (!PrimaryReg.isValid()) {
GR->add(OpCI, &MF, Reg);
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,7 @@ multiclass OpcodeOperand<bits<32> value> {
defm InBoundsAccessChain : OpcodeOperand<66>;
defm InBoundsPtrAccessChain : OpcodeOperand<70>;
defm PtrCastToGeneric : OpcodeOperand<121>;
defm GenericCastToPtr : OpcodeOperand<122>;
defm Bitcast : OpcodeOperand<124>;
defm ConvertPtrToU : OpcodeOperand<117>;
defm ConvertUToPtr : OpcodeOperand<120>;
29 changes: 3 additions & 26 deletions llvm/lib/Target/SPIRV/SPIRVUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ static uint32_t convertCharsToWord(const StringRef &Str, unsigned i) {
}

// Get length including padding and null terminator.
static size_t getPaddedLen(const StringRef &Str) { return Str.size() + 4 & ~3; }
static size_t getPaddedLen(const StringRef &Str) {
return (Str.size() + 4) & ~3;
}

void addStringImm(const StringRef &Str, MCInst &Inst) {
const size_t PaddedLen = getPaddedLen(Str);
Expand Down Expand Up @@ -160,31 +162,6 @@ void buildOpSpirvDecorations(Register Reg, MachineIRBuilder &MIRBuilder,
}
}

// TODO: maybe the following two functions should be handled in the subtarget
// to allow for different OpenCL vs Vulkan handling.
unsigned storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) {
switch (SC) {
case SPIRV::StorageClass::Function:
return 0;
case SPIRV::StorageClass::CrossWorkgroup:
return 1;
case SPIRV::StorageClass::UniformConstant:
return 2;
case SPIRV::StorageClass::Workgroup:
return 3;
case SPIRV::StorageClass::Generic:
return 4;
case SPIRV::StorageClass::DeviceOnlyINTEL:
return 5;
case SPIRV::StorageClass::HostOnlyINTEL:
return 6;
case SPIRV::StorageClass::Input:
return 7;
default:
report_fatal_error("Unable to get address space id");
}
}

SPIRV::StorageClass::StorageClass
addressSpaceToStorageClass(unsigned AddrSpace, const SPIRVSubtarget &STI) {
switch (AddrSpace) {
Expand Down
26 changes: 25 additions & 1 deletion llvm/lib/Target/SPIRV/SPIRVUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,31 @@ void buildOpSpirvDecorations(Register Reg, MachineIRBuilder &MIRBuilder,
const MDNode *GVarMD);

// Convert a SPIR-V storage class to the corresponding LLVM IR address space.
unsigned storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC);
// TODO: maybe the following two functions should be handled in the subtarget
// to allow for different OpenCL vs Vulkan handling.
constexpr unsigned
storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) {
switch (SC) {
case SPIRV::StorageClass::Function:
return 0;
case SPIRV::StorageClass::CrossWorkgroup:
return 1;
case SPIRV::StorageClass::UniformConstant:
return 2;
case SPIRV::StorageClass::Workgroup:
return 3;
case SPIRV::StorageClass::Generic:
return 4;
case SPIRV::StorageClass::DeviceOnlyINTEL:
return 5;
case SPIRV::StorageClass::HostOnlyINTEL:
return 6;
case SPIRV::StorageClass::Input:
return 7;
default:
report_fatal_error("Unable to get address space id");
}
}

// Convert an LLVM IR address space to a SPIR-V storage class.
SPIRV::StorageClass::StorageClass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
; The goal of this test case is to check that cases covered by pointers/PtrCast-in-OpSpecConstantOp.ll and
; pointers/PtrCast-null-in-OpSpecConstantOp.ll (that is OpSpecConstantOp with ptr-cast operation) correctly
; work also for function pointers.

; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - --spirv-ext=+SPV_INTEL_function_pointers | FileCheck %s
; TODO: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}

; Running with -verify-machineinstrs would lead to "Reading virtual register without a def"
; error, because OpConstantFunctionPointerINTEL forward-refers to a function definition.

; CHECK-COUNT-3: %[[#]] = OpSpecConstantOp %[[#]] 121 %[[#]]
; CHECK-COUNT-3: OpPtrCastToGeneric

@G1 = addrspace(1) constant { [3 x ptr addrspace(4)] } { [3 x ptr addrspace(4)] [ptr addrspace(4) null, ptr addrspace(4) addrspacecast (ptr @foo to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @bar to ptr addrspace(4))] }
@G2 = addrspace(1) constant { [3 x ptr addrspace(4)] } { [3 x ptr addrspace(4)] [ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @bar to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @foo to ptr addrspace(4))] }

define void @foo(ptr addrspace(4) %p) {
entry:
%r1 = addrspacecast ptr @foo to ptr addrspace(4)
%r2 = addrspacecast ptr null to ptr addrspace(4)
ret void
}

define void @bar(ptr addrspace(4) %p) {
entry:
%r1 = addrspacecast ptr @bar to ptr addrspace(4)
ret void
}
Loading
Loading