Skip to content
Open
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
44 changes: 37 additions & 7 deletions llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "SPIRVUtils.h"
#include "llvm/ADT/APInt.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IntrinsicsSPIRV.h"
Expand Down Expand Up @@ -223,14 +224,43 @@ SPIRVType *SPIRVGlobalRegistry::getOpTypeVoid(MachineIRBuilder &MIRBuilder) {
}

void SPIRVGlobalRegistry::invalidateMachineInstr(MachineInstr *MI) {
// TODO:
// - review other data structure wrt. possible issues related to removal
// of a machine instruction during instruction selection.
// Other maps that may hold MachineInstr*:
// - VRegToTypeMap: We cannot remove the definitions of `MI` from
// VRegToTypeMap because some calls to invalidateMachineInstr are replacing MI
// with another instruction defining the same register. We expect that if MI
// is a type instruction, and it is still referenced in VRegToTypeMap, then
// those registers are dead or the VRegToTypeMap is out-of-date. We do not
// expect passes to ask for the SPIR-V type of a dead register. If the
// VRegToTypeMap is out-of-date already, then there was an error before. We
// cannot add an assert to verify this because the VRegToTypeMap can be
// out-of-date.
// - FunctionToInstr & FunctionToInstrRev: At this point, we should not be
// deleting functions. No need to update.
// - AliasInstMDMap: Would require a linear search, and the Intel Alias
// instruction are not instructions instruction selection will be able to
// remove.

const SPIRVSubtarget &ST = MI->getMF()->getSubtarget<SPIRVSubtarget>();
const SPIRVInstrInfo *TII = ST.getInstrInfo();
assert(!TII->isAliasingInstr(*MI) &&
"Cannot invalidate aliasing instructions.");
assert(MI->getOpcode() != SPIRV::OpFunction &&
"Cannot invalidate OpFunction.");

if (MI->getOpcode() == SPIRV::OpFunctionCall) {
if (const auto *F = dyn_cast<Function>(MI->getOperand(2).getGlobal())) {
auto It = ForwardCalls.find(F);
if (It != ForwardCalls.end()) {
It->second.erase(MI);
if (It->second.empty())
ForwardCalls.erase(It);
}
}
}

const MachineFunction *MF = MI->getMF();
auto It = LastInsertedTypeMap.find(MF);
if (It == LastInsertedTypeMap.end())
return;
if (It->second == MI)
if (It != LastInsertedTypeMap.end() && It->second == MI)
LastInsertedTypeMap.erase(MF);
// remove from the duplicate tracker to avoid incorrect reuse
erase(MI);
Expand Down Expand Up @@ -313,7 +343,7 @@ Register SPIRVGlobalRegistry::createConstFP(const ConstantFP *CF,
LLT LLTy = LLT::scalar(BitWidth);
Register Res = CurMF->getRegInfo().createGenericVirtualRegister(LLTy);
CurMF->getRegInfo().setRegClass(Res, &SPIRV::fIDRegClass);
assignFloatTypeToVReg(BitWidth, Res, I, TII);
assignSPIRVTypeToVReg(SpvType, Res, *CurMF);

MachineInstr *DepMI = const_cast<MachineInstr *>(SpvType);
MachineIRBuilder MIRBuilder(*DepMI->getParent(), DepMI->getIterator());
Expand Down
203 changes: 195 additions & 8 deletions llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class SPIRVInstructionSelector : public InstructionSelector {

private:
void resetVRegsType(MachineFunction &MF);
void removeDeadInstruction(MachineInstr &MI) const;
void removeOpNamesForDeadMI(MachineInstr &MI) const;

// tblgen-erated 'select' implementation, used as the initial selector for
// the patterns that don't require complex C++.
Expand Down Expand Up @@ -509,22 +511,202 @@ static bool isConstReg(MachineRegisterInfo *MRI, Register OpReg) {
return false;
}

// TODO(168736): We should make this either a flag in tabelgen
// or reduce our dependence on the global registry, so we can remove this
// function. It can easily be missed when new intrinsics are added.

// Most SPIR-V instrinsics are considered to have side-effects in their tablegen
// definition because they are referenced in the global registry. This is a list
// of intrinsics that have no side effects other than their references in the
// global registry.
static bool intrinsicHasSideEffects(Intrinsic::ID ID) {
switch (ID) {
// This is not an exhaustive list and may need to be updated.
case Intrinsic::spv_all:
case Intrinsic::spv_alloca:
case Intrinsic::spv_any:
case Intrinsic::spv_bitcast:
case Intrinsic::spv_const_composite:
case Intrinsic::spv_cross:
case Intrinsic::spv_degrees:
case Intrinsic::spv_distance:
case Intrinsic::spv_extractelt:
case Intrinsic::spv_extractv:
case Intrinsic::spv_faceforward:
case Intrinsic::spv_fdot:
case Intrinsic::spv_firstbitlow:
case Intrinsic::spv_firstbitshigh:
case Intrinsic::spv_firstbituhigh:
case Intrinsic::spv_frac:
case Intrinsic::spv_gep:
case Intrinsic::spv_global_offset:
case Intrinsic::spv_global_size:
case Intrinsic::spv_group_id:
case Intrinsic::spv_insertelt:
case Intrinsic::spv_insertv:
case Intrinsic::spv_isinf:
case Intrinsic::spv_isnan:
case Intrinsic::spv_lerp:
case Intrinsic::spv_length:
case Intrinsic::spv_normalize:
case Intrinsic::spv_num_subgroups:
case Intrinsic::spv_num_workgroups:
case Intrinsic::spv_ptrcast:
case Intrinsic::spv_radians:
case Intrinsic::spv_reflect:
case Intrinsic::spv_refract:
case Intrinsic::spv_resource_getpointer:
case Intrinsic::spv_resource_handlefrombinding:
case Intrinsic::spv_resource_handlefromimplicitbinding:
case Intrinsic::spv_resource_nonuniformindex:
case Intrinsic::spv_rsqrt:
case Intrinsic::spv_saturate:
case Intrinsic::spv_sdot:
case Intrinsic::spv_sign:
case Intrinsic::spv_smoothstep:
case Intrinsic::spv_step:
case Intrinsic::spv_subgroup_id:
case Intrinsic::spv_subgroup_local_invocation_id:
case Intrinsic::spv_subgroup_max_size:
case Intrinsic::spv_subgroup_size:
case Intrinsic::spv_thread_id:
case Intrinsic::spv_thread_id_in_group:
case Intrinsic::spv_udot:
case Intrinsic::spv_undef:
case Intrinsic::spv_value_md:
case Intrinsic::spv_workgroup_size:
return false;
default:
return true;
}
}

// TODO(168736): We should make this either a flag in tabelgen
// or reduce our dependence on the global registry, so we can remove this
// function. It can easily be missed when new intrinsics are added.
static bool isOpcodeWithNoSideEffects(unsigned Opcode) {
switch (Opcode) {
case SPIRV::OpTypeVoid:
case SPIRV::OpTypeBool:
case SPIRV::OpTypeInt:
case SPIRV::OpTypeFloat:
case SPIRV::OpTypeVector:
case SPIRV::OpTypeMatrix:
case SPIRV::OpTypeImage:
case SPIRV::OpTypeSampler:
case SPIRV::OpTypeSampledImage:
case SPIRV::OpTypeArray:
case SPIRV::OpTypeRuntimeArray:
case SPIRV::OpTypeStruct:
case SPIRV::OpTypeOpaque:
case SPIRV::OpTypePointer:
case SPIRV::OpTypeFunction:
case SPIRV::OpTypeEvent:
case SPIRV::OpTypeDeviceEvent:
case SPIRV::OpTypeReserveId:
case SPIRV::OpTypeQueue:
case SPIRV::OpTypePipe:
case SPIRV::OpTypeForwardPointer:
case SPIRV::OpTypePipeStorage:
case SPIRV::OpTypeNamedBarrier:
case SPIRV::OpTypeAccelerationStructureNV:
case SPIRV::OpTypeCooperativeMatrixNV:
case SPIRV::OpTypeCooperativeMatrixKHR:
return true;
default:
return false;
}
}

bool isDead(const MachineInstr &MI, const MachineRegisterInfo &MRI) {
// If there are no definitions, then assume there is some other
// side-effect that makes this instruction live.
if (MI.getNumDefs() == 0)
return false;

for (const auto &MO : MI.all_defs()) {
Register Reg = MO.getReg();
if (Reg.isPhysical() || !MRI.use_nodbg_empty(Reg))
if (Reg.isPhysical()) {
LLVM_DEBUG(dbgs() << "Not dead: def of physical register " << Reg);
return false;
}
for (const auto &UseMI : MRI.use_nodbg_instructions(Reg)) {
if (UseMI.getOpcode() != SPIRV::OpName) {
LLVM_DEBUG(dbgs() << "Not dead: def " << MO << " has use in " << UseMI);
return false;
}
}
}

if (MI.getOpcode() == TargetOpcode::LOCAL_ESCAPE || MI.isFakeUse() ||
MI.isLifetimeMarker())
MI.isLifetimeMarker()) {
LLVM_DEBUG(
dbgs()
<< "Not dead: Opcode is LOCAL_ESCAPE, fake use, or lifetime marker.\n");
return false;
if (MI.isPHI())
}
if (MI.isPHI()) {
LLVM_DEBUG(dbgs() << "Dead: Phi instruction with no uses.\n");
return true;
}

// It is possible that the only side effect is that the instruction is
// referenced in the global registry. If that is the only side effect, the
// intrinsic is dead.
if (MI.getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS ||
MI.getOpcode() == TargetOpcode::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS) {
const auto &Intr = cast<GIntrinsic>(MI);
if (!intrinsicHasSideEffects(Intr.getIntrinsicID())) {
LLVM_DEBUG(dbgs() << "Dead: Intrinsic with no real side effects.\n");
return true;
}
}

if (MI.mayStore() || MI.isCall() ||
(MI.mayLoad() && MI.hasOrderedMemoryRef()) || MI.isPosition() ||
MI.isDebugInstr() || MI.isTerminator() || MI.isJumpTableDebugInfo())
MI.isDebugInstr() || MI.isTerminator() || MI.isJumpTableDebugInfo()) {
LLVM_DEBUG(dbgs() << "Not dead: instruction has side effects.\n");
return false;
return true;
}

if (isPreISelGenericOpcode(MI.getOpcode())) {
// TODO: Is there a generic way to check if the opcode has side effects?
LLVM_DEBUG(dbgs() << "Dead: Generic opcode with no uses.\n");
return true;
}

if (isOpcodeWithNoSideEffects(MI.getOpcode())) {
LLVM_DEBUG(dbgs() << "Dead: known opcode with no side effects\n");
return true;
}

return false;
}

void SPIRVInstructionSelector::removeOpNamesForDeadMI(MachineInstr &MI) const {
// Delete the OpName that uses the result if there is one.
for (const auto &MO : MI.all_defs()) {
Register Reg = MO.getReg();
if (Reg.isPhysical())
continue;
SmallVector<MachineInstr *, 4> UselessOpNames;
for (MachineInstr &UseMI : MRI->use_nodbg_instructions(Reg)) {
assert(UseMI.getOpcode() == SPIRV::OpName &&
"There is still a use of the dead function.");
UselessOpNames.push_back(&UseMI);
}
for (MachineInstr *OpNameMI : UselessOpNames) {
GR.invalidateMachineInstr(OpNameMI);
OpNameMI->eraseFromParent();
}
}
}

void SPIRVInstructionSelector::removeDeadInstruction(MachineInstr &MI) const {
salvageDebugInfo(*MRI, MI);
GR.invalidateMachineInstr(&MI);
removeOpNamesForDeadMI(MI);
MI.eraseFromParent();
}

bool SPIRVInstructionSelector::select(MachineInstr &I) {
Expand All @@ -533,6 +715,13 @@ bool SPIRVInstructionSelector::select(MachineInstr &I) {
assert(I.getParent() && "Instruction should be in a basic block!");
assert(I.getParent()->getParent() && "Instruction should be in a function!");

LLVM_DEBUG(dbgs() << "Checking if instruction is dead: " << I;);
if (isDead(I, *MRI)) {
LLVM_DEBUG(dbgs() << "Instruction is dead.\n");
removeDeadInstruction(I);
return true;
}

Register Opcode = I.getOpcode();
// If it's not a GMIR instruction, we've selected it already.
if (!isPreISelGenericOpcode(Opcode)) {
Expand Down Expand Up @@ -584,9 +773,7 @@ bool SPIRVInstructionSelector::select(MachineInstr &I) {
// if the instruction has been already made dead by folding it away
// erase it
LLVM_DEBUG(dbgs() << "Instruction is folded and dead.\n");
salvageDebugInfo(*MRI, I);
GR.invalidateMachineInstr(&I);
I.eraseFromParent();
removeDeadInstruction(I);
return true;
}

Expand Down
2 changes: 2 additions & 0 deletions llvm/test/CodeGen/SPIRV/OpVariable_order.ll
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
define void @main() {
entry:
%0 = alloca <2 x i32>, align 4
store <2 x i32> zeroinitializer, ptr %0, align 4
%1 = getelementptr <2 x i32>, ptr %0, i32 0, i32 0
%2 = alloca float, align 4
store float 0.0, ptr %2, align 4
ret void
}
3 changes: 3 additions & 0 deletions llvm/test/CodeGen/SPIRV/SpecConstants/restore-spec-type.ll
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
%Struct7 = type [2 x %Struct]
%Nested = type { %Struct7 }

@G = global %Struct zeroinitializer

define spir_kernel void @foo(ptr addrspace(4) %arg1, ptr addrspace(4) %arg2) {
entry:
%var = alloca %Struct
store %Struct zeroinitializer, ptr %var
%r1 = call %Struct @_Z29__spirv_SpecConstantComposite_1(float 1.0)
store %Struct %r1, ptr addrspace(4) %arg1
%r2 = call %Struct7 @_Z29__spirv_SpecConstantComposite_2(%Struct %r1, %Struct %r1)
Expand Down
Loading