Skip to content

Commit 35dfeb7

Browse files
s-perronKeenuts
andauthored
[SPIRV] Enable DCE in instruction selection and update tests (#168428)
The instruction selection pass for SPIR-V now performs dead code elimination (DCE). This change removes unused instructions, leading to more optimized SPIR-V output. As a consequence of this, several tests were updated to ensure their continued correctness and to prevent previously tested code from being optimized away. Specifically: - Many tests now store computed values into global variables to ensure they are not eliminated by DCE, allowing their code generation to be verified. - The test `keep-tracked-const.ll` was removed because it no longer tested its original intent. The check statements in this test were for constants generated when expanding a G_TRUNC instruction, which is now removed by DCE instead of being expanded. - A new test, `remove-dead-type-intrinsics.ll`, was added to confirm that dead struct types are correctly removed by the compiler. These updates improve the SPIR-V backends optimization capabilities and maintain the robustness of the test suite. --------- Co-authored-by: Nathan Gauër <[email protected]>
1 parent ff0c347 commit 35dfeb7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1124
-133
lines changed

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "llvm/ADT/APInt.h"
2323
#include "llvm/IR/Constants.h"
2424
#include "llvm/IR/DiagnosticInfo.h"
25+
#include "llvm/IR/Function.h"
2526
#include "llvm/IR/IntrinsicInst.h"
2627
#include "llvm/IR/Intrinsics.h"
2728
#include "llvm/IR/IntrinsicsSPIRV.h"
@@ -224,14 +225,43 @@ SPIRVType *SPIRVGlobalRegistry::getOpTypeVoid(MachineIRBuilder &MIRBuilder) {
224225
}
225226

226227
void SPIRVGlobalRegistry::invalidateMachineInstr(MachineInstr *MI) {
227-
// TODO:
228-
// - review other data structure wrt. possible issues related to removal
229-
// of a machine instruction during instruction selection.
228+
// Other maps that may hold MachineInstr*:
229+
// - VRegToTypeMap: We cannot remove the definitions of `MI` from
230+
// VRegToTypeMap because some calls to invalidateMachineInstr are replacing MI
231+
// with another instruction defining the same register. We expect that if MI
232+
// is a type instruction, and it is still referenced in VRegToTypeMap, then
233+
// those registers are dead or the VRegToTypeMap is out-of-date. We do not
234+
// expect passes to ask for the SPIR-V type of a dead register. If the
235+
// VRegToTypeMap is out-of-date already, then there was an error before. We
236+
// cannot add an assert to verify this because the VRegToTypeMap can be
237+
// out-of-date.
238+
// - FunctionToInstr & FunctionToInstrRev: At this point, we should not be
239+
// deleting functions. No need to update.
240+
// - AliasInstMDMap: Would require a linear search, and the Intel Alias
241+
// instruction are not instructions instruction selection will be able to
242+
// remove.
243+
244+
const SPIRVSubtarget &ST = MI->getMF()->getSubtarget<SPIRVSubtarget>();
245+
const SPIRVInstrInfo *TII = ST.getInstrInfo();
246+
assert(!TII->isAliasingInstr(*MI) &&
247+
"Cannot invalidate aliasing instructions.");
248+
assert(MI->getOpcode() != SPIRV::OpFunction &&
249+
"Cannot invalidate OpFunction.");
250+
251+
if (MI->getOpcode() == SPIRV::OpFunctionCall) {
252+
if (const auto *F = dyn_cast<Function>(MI->getOperand(2).getGlobal())) {
253+
auto It = ForwardCalls.find(F);
254+
if (It != ForwardCalls.end()) {
255+
It->second.erase(MI);
256+
if (It->second.empty())
257+
ForwardCalls.erase(It);
258+
}
259+
}
260+
}
261+
230262
const MachineFunction *MF = MI->getMF();
231263
auto It = LastInsertedTypeMap.find(MF);
232-
if (It == LastInsertedTypeMap.end())
233-
return;
234-
if (It->second == MI)
264+
if (It != LastInsertedTypeMap.end() && It->second == MI)
235265
LastInsertedTypeMap.erase(MF);
236266
// remove from the duplicate tracker to avoid incorrect reuse
237267
erase(MI);
@@ -314,7 +344,7 @@ Register SPIRVGlobalRegistry::createConstFP(const ConstantFP *CF,
314344
LLT LLTy = LLT::scalar(BitWidth);
315345
Register Res = CurMF->getRegInfo().createGenericVirtualRegister(LLTy);
316346
CurMF->getRegInfo().setRegClass(Res, &SPIRV::fIDRegClass);
317-
assignFloatTypeToVReg(BitWidth, Res, I, TII);
347+
assignSPIRVTypeToVReg(SpvType, Res, *CurMF);
318348

319349
MachineInstr *DepMI = const_cast<MachineInstr *>(SpvType);
320350
MachineIRBuilder MIRBuilder(*DepMI->getParent(), DepMI->getIterator());

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 195 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ class SPIRVInstructionSelector : public InstructionSelector {
9494

9595
private:
9696
void resetVRegsType(MachineFunction &MF);
97+
void removeDeadInstruction(MachineInstr &MI) const;
98+
void removeOpNamesForDeadMI(MachineInstr &MI) const;
9799

98100
// tblgen-erated 'select' implementation, used as the initial selector for
99101
// the patterns that don't require complex C++.
@@ -510,22 +512,202 @@ static bool isConstReg(MachineRegisterInfo *MRI, Register OpReg) {
510512
return false;
511513
}
512514

515+
// TODO(168736): We should make this either a flag in tabelgen
516+
// or reduce our dependence on the global registry, so we can remove this
517+
// function. It can easily be missed when new intrinsics are added.
518+
519+
// Most SPIR-V instrinsics are considered to have side-effects in their tablegen
520+
// definition because they are referenced in the global registry. This is a list
521+
// of intrinsics that have no side effects other than their references in the
522+
// global registry.
523+
static bool intrinsicHasSideEffects(Intrinsic::ID ID) {
524+
switch (ID) {
525+
// This is not an exhaustive list and may need to be updated.
526+
case Intrinsic::spv_all:
527+
case Intrinsic::spv_alloca:
528+
case Intrinsic::spv_any:
529+
case Intrinsic::spv_bitcast:
530+
case Intrinsic::spv_const_composite:
531+
case Intrinsic::spv_cross:
532+
case Intrinsic::spv_degrees:
533+
case Intrinsic::spv_distance:
534+
case Intrinsic::spv_extractelt:
535+
case Intrinsic::spv_extractv:
536+
case Intrinsic::spv_faceforward:
537+
case Intrinsic::spv_fdot:
538+
case Intrinsic::spv_firstbitlow:
539+
case Intrinsic::spv_firstbitshigh:
540+
case Intrinsic::spv_firstbituhigh:
541+
case Intrinsic::spv_frac:
542+
case Intrinsic::spv_gep:
543+
case Intrinsic::spv_global_offset:
544+
case Intrinsic::spv_global_size:
545+
case Intrinsic::spv_group_id:
546+
case Intrinsic::spv_insertelt:
547+
case Intrinsic::spv_insertv:
548+
case Intrinsic::spv_isinf:
549+
case Intrinsic::spv_isnan:
550+
case Intrinsic::spv_lerp:
551+
case Intrinsic::spv_length:
552+
case Intrinsic::spv_normalize:
553+
case Intrinsic::spv_num_subgroups:
554+
case Intrinsic::spv_num_workgroups:
555+
case Intrinsic::spv_ptrcast:
556+
case Intrinsic::spv_radians:
557+
case Intrinsic::spv_reflect:
558+
case Intrinsic::spv_refract:
559+
case Intrinsic::spv_resource_getpointer:
560+
case Intrinsic::spv_resource_handlefrombinding:
561+
case Intrinsic::spv_resource_handlefromimplicitbinding:
562+
case Intrinsic::spv_resource_nonuniformindex:
563+
case Intrinsic::spv_rsqrt:
564+
case Intrinsic::spv_saturate:
565+
case Intrinsic::spv_sdot:
566+
case Intrinsic::spv_sign:
567+
case Intrinsic::spv_smoothstep:
568+
case Intrinsic::spv_step:
569+
case Intrinsic::spv_subgroup_id:
570+
case Intrinsic::spv_subgroup_local_invocation_id:
571+
case Intrinsic::spv_subgroup_max_size:
572+
case Intrinsic::spv_subgroup_size:
573+
case Intrinsic::spv_thread_id:
574+
case Intrinsic::spv_thread_id_in_group:
575+
case Intrinsic::spv_udot:
576+
case Intrinsic::spv_undef:
577+
case Intrinsic::spv_value_md:
578+
case Intrinsic::spv_workgroup_size:
579+
return false;
580+
default:
581+
return true;
582+
}
583+
}
584+
585+
// TODO(168736): We should make this either a flag in tabelgen
586+
// or reduce our dependence on the global registry, so we can remove this
587+
// function. It can easily be missed when new intrinsics are added.
588+
static bool isOpcodeWithNoSideEffects(unsigned Opcode) {
589+
switch (Opcode) {
590+
case SPIRV::OpTypeVoid:
591+
case SPIRV::OpTypeBool:
592+
case SPIRV::OpTypeInt:
593+
case SPIRV::OpTypeFloat:
594+
case SPIRV::OpTypeVector:
595+
case SPIRV::OpTypeMatrix:
596+
case SPIRV::OpTypeImage:
597+
case SPIRV::OpTypeSampler:
598+
case SPIRV::OpTypeSampledImage:
599+
case SPIRV::OpTypeArray:
600+
case SPIRV::OpTypeRuntimeArray:
601+
case SPIRV::OpTypeStruct:
602+
case SPIRV::OpTypeOpaque:
603+
case SPIRV::OpTypePointer:
604+
case SPIRV::OpTypeFunction:
605+
case SPIRV::OpTypeEvent:
606+
case SPIRV::OpTypeDeviceEvent:
607+
case SPIRV::OpTypeReserveId:
608+
case SPIRV::OpTypeQueue:
609+
case SPIRV::OpTypePipe:
610+
case SPIRV::OpTypeForwardPointer:
611+
case SPIRV::OpTypePipeStorage:
612+
case SPIRV::OpTypeNamedBarrier:
613+
case SPIRV::OpTypeAccelerationStructureNV:
614+
case SPIRV::OpTypeCooperativeMatrixNV:
615+
case SPIRV::OpTypeCooperativeMatrixKHR:
616+
return true;
617+
default:
618+
return false;
619+
}
620+
}
621+
513622
bool isDead(const MachineInstr &MI, const MachineRegisterInfo &MRI) {
623+
// If there are no definitions, then assume there is some other
624+
// side-effect that makes this instruction live.
625+
if (MI.getNumDefs() == 0)
626+
return false;
627+
514628
for (const auto &MO : MI.all_defs()) {
515629
Register Reg = MO.getReg();
516-
if (Reg.isPhysical() || !MRI.use_nodbg_empty(Reg))
630+
if (Reg.isPhysical()) {
631+
LLVM_DEBUG(dbgs() << "Not dead: def of physical register " << Reg);
517632
return false;
633+
}
634+
for (const auto &UseMI : MRI.use_nodbg_instructions(Reg)) {
635+
if (UseMI.getOpcode() != SPIRV::OpName) {
636+
LLVM_DEBUG(dbgs() << "Not dead: def " << MO << " has use in " << UseMI);
637+
return false;
638+
}
639+
}
518640
}
641+
519642
if (MI.getOpcode() == TargetOpcode::LOCAL_ESCAPE || MI.isFakeUse() ||
520-
MI.isLifetimeMarker())
643+
MI.isLifetimeMarker()) {
644+
LLVM_DEBUG(
645+
dbgs()
646+
<< "Not dead: Opcode is LOCAL_ESCAPE, fake use, or lifetime marker.\n");
521647
return false;
522-
if (MI.isPHI())
648+
}
649+
if (MI.isPHI()) {
650+
LLVM_DEBUG(dbgs() << "Dead: Phi instruction with no uses.\n");
523651
return true;
652+
}
653+
654+
// It is possible that the only side effect is that the instruction is
655+
// referenced in the global registry. If that is the only side effect, the
656+
// intrinsic is dead.
657+
if (MI.getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS ||
658+
MI.getOpcode() == TargetOpcode::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS) {
659+
const auto &Intr = cast<GIntrinsic>(MI);
660+
if (!intrinsicHasSideEffects(Intr.getIntrinsicID())) {
661+
LLVM_DEBUG(dbgs() << "Dead: Intrinsic with no real side effects.\n");
662+
return true;
663+
}
664+
}
665+
524666
if (MI.mayStore() || MI.isCall() ||
525667
(MI.mayLoad() && MI.hasOrderedMemoryRef()) || MI.isPosition() ||
526-
MI.isDebugInstr() || MI.isTerminator() || MI.isJumpTableDebugInfo())
668+
MI.isDebugInstr() || MI.isTerminator() || MI.isJumpTableDebugInfo()) {
669+
LLVM_DEBUG(dbgs() << "Not dead: instruction has side effects.\n");
527670
return false;
528-
return true;
671+
}
672+
673+
if (isPreISelGenericOpcode(MI.getOpcode())) {
674+
// TODO: Is there a generic way to check if the opcode has side effects?
675+
LLVM_DEBUG(dbgs() << "Dead: Generic opcode with no uses.\n");
676+
return true;
677+
}
678+
679+
if (isOpcodeWithNoSideEffects(MI.getOpcode())) {
680+
LLVM_DEBUG(dbgs() << "Dead: known opcode with no side effects\n");
681+
return true;
682+
}
683+
684+
return false;
685+
}
686+
687+
void SPIRVInstructionSelector::removeOpNamesForDeadMI(MachineInstr &MI) const {
688+
// Delete the OpName that uses the result if there is one.
689+
for (const auto &MO : MI.all_defs()) {
690+
Register Reg = MO.getReg();
691+
if (Reg.isPhysical())
692+
continue;
693+
SmallVector<MachineInstr *, 4> UselessOpNames;
694+
for (MachineInstr &UseMI : MRI->use_nodbg_instructions(Reg)) {
695+
assert(UseMI.getOpcode() == SPIRV::OpName &&
696+
"There is still a use of the dead function.");
697+
UselessOpNames.push_back(&UseMI);
698+
}
699+
for (MachineInstr *OpNameMI : UselessOpNames) {
700+
GR.invalidateMachineInstr(OpNameMI);
701+
OpNameMI->eraseFromParent();
702+
}
703+
}
704+
}
705+
706+
void SPIRVInstructionSelector::removeDeadInstruction(MachineInstr &MI) const {
707+
salvageDebugInfo(*MRI, MI);
708+
GR.invalidateMachineInstr(&MI);
709+
removeOpNamesForDeadMI(MI);
710+
MI.eraseFromParent();
529711
}
530712

531713
bool SPIRVInstructionSelector::select(MachineInstr &I) {
@@ -534,6 +716,13 @@ bool SPIRVInstructionSelector::select(MachineInstr &I) {
534716
assert(I.getParent() && "Instruction should be in a basic block!");
535717
assert(I.getParent()->getParent() && "Instruction should be in a function!");
536718

719+
LLVM_DEBUG(dbgs() << "Checking if instruction is dead: " << I;);
720+
if (isDead(I, *MRI)) {
721+
LLVM_DEBUG(dbgs() << "Instruction is dead.\n");
722+
removeDeadInstruction(I);
723+
return true;
724+
}
725+
537726
Register Opcode = I.getOpcode();
538727
// If it's not a GMIR instruction, we've selected it already.
539728
if (!isPreISelGenericOpcode(Opcode)) {
@@ -585,9 +774,7 @@ bool SPIRVInstructionSelector::select(MachineInstr &I) {
585774
// if the instruction has been already made dead by folding it away
586775
// erase it
587776
LLVM_DEBUG(dbgs() << "Instruction is folded and dead.\n");
588-
salvageDebugInfo(*MRI, I);
589-
GR.invalidateMachineInstr(&I);
590-
I.eraseFromParent();
777+
removeDeadInstruction(I);
591778
return true;
592779
}
593780

llvm/test/CodeGen/SPIRV/OpVariable_order.ll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
define void @main() {
1414
entry:
1515
%0 = alloca <2 x i32>, align 4
16+
store <2 x i32> zeroinitializer, ptr %0, align 4
1617
%1 = getelementptr <2 x i32>, ptr %0, i32 0, i32 0
1718
%2 = alloca float, align 4
19+
store float 0.0, ptr %2, align 4
1820
ret void
1921
}

llvm/test/CodeGen/SPIRV/SpecConstants/restore-spec-type.ll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
%Struct7 = type [2 x %Struct]
3030
%Nested = type { %Struct7 }
3131

32+
@G = global %Struct zeroinitializer
33+
3234
define spir_kernel void @foo(ptr addrspace(4) %arg1, ptr addrspace(4) %arg2) {
3335
entry:
3436
%var = alloca %Struct
37+
store %Struct zeroinitializer, ptr %var
3538
%r1 = call %Struct @_Z29__spirv_SpecConstantComposite_1(float 1.0)
3639
store %Struct %r1, ptr addrspace(4) %arg1
3740
%r2 = call %Struct7 @_Z29__spirv_SpecConstantComposite_2(%Struct %r1, %Struct %r1)

0 commit comments

Comments
 (0)