[llvm] cfba328 - [GlobalISel] Enable patterns with multiple output operands for the GlobalISelEmitter

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Sat Feb 4 06:08:38 PST 2023


Author: Remi Segard
Date: 2023-02-04T10:08:32-04:00
New Revision: cfba328183f56765721b59e7fe8eb2a261bff340

URL: https://github.com/llvm/llvm-project/commit/cfba328183f56765721b59e7fe8eb2a261bff340
DIFF: https://github.com/llvm/llvm-project/commit/cfba328183f56765721b59e7fe8eb2a261bff340.diff

LOG: [GlobalISel] Enable patterns with multiple output operands for the GlobalISelEmitter

This enables writing patterns with mutliple output operands in the input pattern for GlobalISel

Added: 
    llvm/test/TableGen/GlobalISelEmitter-multiple-output-discard.td
    llvm/test/TableGen/GlobalISelEmitter-multiple-output.td

Modified: 
    llvm/utils/TableGen/GlobalISelEmitter.cpp

Removed: 
    


################################################################################
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 0000000000000..5f442b067d9cd
--- /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<G_TWO_OUT, two_out>;
+
+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 0000000000000..c98ac73c61af7
--- /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<G_POST_LOAD, loadpost>;
+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<G_TWO_IN, two_in>;
+
+// 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 c79c79948a80d..480dadd297c07 100644
--- a/llvm/utils/TableGen/GlobalISelEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelEmitter.cpp
@@ -3640,10 +3640,9 @@ class GlobalISelEmitter {
   createInstructionRenderer(action_iterator InsertPt, RuleMatcher &M,
                             const TreePatternNode *Dst);
 
-  Expected<action_iterator>
-  importExplicitDefRenderers(action_iterator InsertPt, RuleMatcher &M,
-                             BuildMIAction &DstMIBuilder,
-                             const TreePatternNode *Dst);
+  Expected<action_iterator> importExplicitDefRenderers(
+      action_iterator InsertPt, RuleMatcher &M, BuildMIAction &DstMIBuilder,
+      const TreePatternNode *Src, const TreePatternNode *Dst);
 
   Expected<action_iterator>
   importExplicitUseRenderers(action_iterator InsertPt, RuleMatcher &M,
@@ -3989,9 +3988,6 @@ Expected<InstructionMatcher &> 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<IntInit>(SrcInit)) {
@@ -4618,8 +4614,9 @@ Expected<BuildMIAction &> GlobalISelEmitter::createAndImportInstructionRenderer(
     CopyToPhysRegMIBuilder.addRenderer<CopyPhysRegRenderer>(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<action_iterator> GlobalISelEmitter::createInstructionRenderer(
 
 Expected<action_iterator> 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<CopyRenderer>(DstI->Operands[0].Name);
+  for (unsigned I = 0; I < SrcNumDefs; ++I)
+    DstMIBuilder.addRenderer<CopyRenderer>(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");


        


More information about the llvm-commits mailing list