From cfba328183f56765721b59e7fe8eb2a261bff340 Mon Sep 17 00:00:00 2001 From: Remi Segard Date: Sat, 4 Feb 2023 08:23:46 -0400 Subject: [PATCH] [GlobalISel] Enable patterns with multiple output operands for the GlobalISelEmitter This enables writing patterns with mutliple output operands in the input pattern for GlobalISel --- .../GlobalISelEmitter-multiple-output-discard.td | 42 +++++++++++ .../TableGen/GlobalISelEmitter-multiple-output.td | 86 ++++++++++++++++++++++ llvm/utils/TableGen/GlobalISelEmitter.cpp | 31 ++++---- 3 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 llvm/test/TableGen/GlobalISelEmitter-multiple-output-discard.td create mode 100644 llvm/test/TableGen/GlobalISelEmitter-multiple-output.td diff --git a/llvm/test/TableGen/GlobalISelEmitter-multiple-output-discard.td b/llvm/test/TableGen/GlobalISelEmitter-multiple-output-discard.td new file mode 100644 index 0000000..5f442b0 --- /dev/null +++ b/llvm/test/TableGen/GlobalISelEmitter-multiple-output-discard.td @@ -0,0 +1,42 @@ +// RUN: llvm-tblgen -gen-global-isel -optimize-match-table=false -warn-on-skipped-patterns -I %p/../../include -I %p/Common %s -o - < %s | FileCheck %s + +include "llvm/Target/Target.td" +include "GlobalISelEmitterCommon.td" + +// Verify that patterns will add temp registers if NumDstDef > NumSrcDef when NumSrcDef >= 1 +// and that these temp registers are marked as dead +// Note: This is an extension of the test GlobalISelEmitter-output-discard.td + +def THREE_OUTS : I<(outs GPR32:$out1, GPR32:$out2, GPR32:$out3), (ins GPR32:$in1), []>; + +def SDTTwoOut : SDTypeProfile<2, 1, [ + SDTCisInt<0>, SDTCisInt<1> +]>; +def two_out : SDNode<"MyTgt::ONE_OUT", SDTTwoOut, []>; +def G_TWO_OUT : MyTargetGenericInstruction{ + let OutOperandList = (outs type0:$out1, type0:$out2); + let InOperandList = (ins type0:$in); +} +def : GINodeEquiv; + +def : Pat<(two_out GPR32:$val), (THREE_OUTS GPR32:$val)>; + +// CHECK: GIM_CheckOpcode, /*MI*/0, MyTarget::G_TWO_OUT, +// CHECK-NEXT: // MIs[0] out1 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/0, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/0, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] out2 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/1, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/1, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] val +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/2, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // (two_out:{ *:[i32] }:{ *:[i32] } GPR32:{ *:[i32] }:$val) => (THREE_OUTS:{ *:[i32] }:{ *:[i32] }:{ *:[i32] } GPR32:{ *:[i32] }:$val) +// CHECK-NEXT: GIR_MakeTempReg, /*TempRegID*/0, /*TypeID*/GILLT_s32, +// CHECK-NEXT: GIR_BuildMI, /*InsnID*/0, /*Opcode*/MyTarget::THREE_OUTS, +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/0, // out1 +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/1, // out2 +// CHECK-NEXT: GIR_AddTempRegister, /*InsnID*/0, /*TempRegID*/0, /*TempRegFlags*/RegState::Define|RegState::Dead, +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/2, // val +// CHECK-NEXT: GIR_EraseFromParent, /*InsnID*/0, +// CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, diff --git a/llvm/test/TableGen/GlobalISelEmitter-multiple-output.td b/llvm/test/TableGen/GlobalISelEmitter-multiple-output.td new file mode 100644 index 0000000..c98ac73 --- /dev/null +++ b/llvm/test/TableGen/GlobalISelEmitter-multiple-output.td @@ -0,0 +1,86 @@ +// RUN: llvm-tblgen -gen-global-isel -optimize-match-table=false -warn-on-skipped-patterns -I %p/../../include -I %p/Common %s -o - < %s | FileCheck %s + +include "llvm/Target/Target.td" +include "GlobalISelEmitterCommon.td" + +// Test the generation of patterns with multiple output operands and makes sure that +// we are able to create a new instruction if necessary, or just simply change the +// opcode if the input and output operands of the generic instruction are the same +// as the target-specific instruction + +// Verify that patterns with multiple outputs are translated + +// Test where only the opcode is mutated during ISel + +let Constraints = "$ptr_out = $addr" in +def LDPost : I<(outs GPR32:$val, GPR32:$ptr_out), (ins GPR32:$addr, GPR32:$off), []>; +def SDTLoadPost : SDTypeProfile<2, 2, [ + SDTCisInt<0>, SDTCisSameAs<1,2>, SDTCisPtrTy<2>, SDTCisInt<3>, +]>; +def loadpost : SDNode<"MyTgt::LOADPOST", SDTLoadPost, [ + SDNPHasChain, SDNPMayLoad, SDNPMemOperand +]>; +def G_POST_LOAD : MyTargetGenericInstruction{ + let OutOperandList = (outs type0:$val, type1:$ptr_out); + let InOperandList = (ins type1:$ptr, type2:$off); +} +def : GINodeEquiv; +def : Pat<(loadpost (p0 GPR32:$addr), (i32 GPR32:$off)), + (LDPost GPR32:$addr, GPR32:$off) +>; + +// CHECK: GIM_CheckOpcode, /*MI*/0, MyTarget::G_POST_LOAD, +// CHECK-NEXT: // MIs[0] val +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/0, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/0, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] ptr_out +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/1, /*Type*/GILLT_p0s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/1, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] addr +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_p0s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/2, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] off +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/3, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/3, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // (loadpost:{ *:[i32] }:{ *:[i32] } GPR32:{ *:[i32] }:$addr, GPR32:{ *:[i32] }:$off) => (LDPost:{ *:[i32] }:{ *:[i32] } GPR32:{ *:[i32] }:$addr, GPR32:{ *:[i32] }:$off) +// CHECK-NEXT: GIR_MutateOpcode, /*InsnID*/0, /*RecycleInsnID*/0, /*Opcode*/MyTarget::LDPost, +// CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, + +// Test where a whole new MIR instruction is created during ISel + +def TWO_INS : I<(outs GPR32:$out1, GPR32:$out2), (ins GPR32:$in1, GPR32:$in2), []>; + +def SDTTwoIn : SDTypeProfile<2, 2, [ + SDTCisInt<0>, SDTCisInt<1>, SDTCisInt<2>, SDTCisInt<3> +]>; +def two_in : SDNode<"MyTgt::TWO_IN", SDTTwoIn, []>; +def G_TWO_IN : MyTargetGenericInstruction{ + let OutOperandList = (outs type0:$out1, type0:$out2); + let InOperandList = (ins type0:$in1, type0:$in2); +} +def : GINodeEquiv; + +// Swap the input operands for an easy way to force the creation of a new instruction +def : Pat<(two_in GPR32:$i1, GPR32:$i2), (TWO_INS GPR32:$i2, GPR32:$i1)>; + +// CHECK: GIM_CheckOpcode, /*MI*/0, MyTarget::G_TWO_IN, +// CHECK-NEXT: // MIs[0] out1 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/0, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/0, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] out2 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/1, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/1, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] i1 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/2, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] i2 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/3, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/3, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // (two_in:{ *:[i32] }:{ *:[i32] } GPR32:{ *:[i32] }:$i1, GPR32:{ *:[i32] }:$i2) => (TWO_INS:{ *:[i32] }:{ *:[i32] } GPR32:{ *:[i32] }:$i2, GPR32:{ *:[i32] }:$i1) +// CHECK-NEXT: GIR_BuildMI, /*InsnID*/0, /*Opcode*/MyTarget::TWO_INS, +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/0, // out1 +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/1, // out2 +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/3, // i2 +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/2, // i1 +// CHECK-NEXT: GIR_EraseFromParent, /*InsnID*/0, +// CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, diff --git a/llvm/utils/TableGen/GlobalISelEmitter.cpp b/llvm/utils/TableGen/GlobalISelEmitter.cpp index c79c799..480dadd 100644 --- a/llvm/utils/TableGen/GlobalISelEmitter.cpp +++ b/llvm/utils/TableGen/GlobalISelEmitter.cpp @@ -3640,10 +3640,9 @@ private: createInstructionRenderer(action_iterator InsertPt, RuleMatcher &M, const TreePatternNode *Dst); - Expected - importExplicitDefRenderers(action_iterator InsertPt, RuleMatcher &M, - BuildMIAction &DstMIBuilder, - const TreePatternNode *Dst); + Expected importExplicitDefRenderers( + action_iterator InsertPt, RuleMatcher &M, BuildMIAction &DstMIBuilder, + const TreePatternNode *Src, const TreePatternNode *Dst); Expected importExplicitUseRenderers(action_iterator InsertPt, RuleMatcher &M, @@ -3989,9 +3988,6 @@ Expected GlobalISelEmitter::createAndImportSelDAGMatcher( const CodeGenInstruction *SrcGIOrNull = nullptr; // Start with the defined operands (i.e., the results of the root operator). - if (Src->getExtTypes().size() > 1) - return failedImport("Src pattern has multiple results"); - if (Src->isLeaf()) { Init *SrcInit = Src->getLeafValue(); if (isa(SrcInit)) { @@ -4618,8 +4614,9 @@ Expected GlobalISelEmitter::createAndImportInstructionRenderer( CopyToPhysRegMIBuilder.addRenderer(PhysInput.first); } - if (auto Error = importExplicitDefRenderers(InsertPt, M, DstMIBuilder, Dst) - .takeError()) + if (auto Error = + importExplicitDefRenderers(InsertPt, M, DstMIBuilder, Src, Dst) + .takeError()) return std::move(Error); if (auto Error = importExplicitUseRenderers(InsertPt, M, DstMIBuilder, Dst) @@ -4774,22 +4771,22 @@ Expected GlobalISelEmitter::createInstructionRenderer( Expected GlobalISelEmitter::importExplicitDefRenderers( action_iterator InsertPt, RuleMatcher &M, BuildMIAction &DstMIBuilder, - const TreePatternNode *Dst) { + const TreePatternNode *Src, const TreePatternNode *Dst) { const CodeGenInstruction *DstI = DstMIBuilder.getCGI(); - const unsigned NumDefs = DstI->Operands.NumDefs; - if (NumDefs == 0) + const unsigned SrcNumDefs = Src->getExtTypes().size(); + const unsigned DstNumDefs = DstI->Operands.NumDefs; + if (DstNumDefs == 0) return InsertPt; - DstMIBuilder.addRenderer(DstI->Operands[0].Name); + for (unsigned I = 0; I < SrcNumDefs; ++I) + DstMIBuilder.addRenderer(DstI->Operands[I].Name); // Some instructions have multiple defs, but are missing a type entry // (e.g. s_cc_out operands). - if (Dst->getExtTypes().size() < NumDefs) + if (Dst->getExtTypes().size() < DstNumDefs) return failedImport("unhandled discarded def"); - // Patterns only handle a single result, so any result after the first is an - // implicitly dead def. - for (unsigned I = 1; I < NumDefs; ++I) { + for (unsigned I = SrcNumDefs; I < DstNumDefs; ++I) { const TypeSetByHwMode &ExtTy = Dst->getExtType(I); if (!ExtTy.isMachineValueType()) return failedImport("unsupported typeset"); -- 2.7.4