[llvm] [GlobalISel][TableGen] MIR Pattern Variadics (PR #100563)
Pierre van Houtryve via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 25 05:29:43 PDT 2024
https://github.com/Pierre-vh created https://github.com/llvm/llvm-project/pull/100563
Allow for matching & rewriting a variable number of arguments in an instructions.
Solves #87459
>From de9e0f1e7bf6cc616b1135b0aaf1cf924b916d44 Mon Sep 17 00:00:00 2001
From: pvanhout <pierre.vanhoutryve at amd.com>
Date: Thu, 25 Jul 2024 14:28:52 +0200
Subject: [PATCH] [GlobalISel][TableGen] MIR Pattern Variadics
Allow for matching & rewriting a variable number of arguments in an instructions.
Solves #87459
---
llvm/docs/GlobalISel/MIRPatterns.rst | 55 +++++-
.../CodeGen/GlobalISel/GIMatchTableExecutor.h | 16 ++
.../GlobalISel/GIMatchTableExecutorImpl.h | 33 ++++
.../include/llvm/Target/GlobalISel/Combine.td | 10 +
.../match-table-variadics.td | 54 ++++-
.../operand-types.td | 26 ++-
.../typeof-errors.td | 14 +-
.../variadic-errors.td | 75 +++++++
.../TableGen/Common/GlobalISel/CodeExpander.h | 2 +-
.../GlobalISel/GlobalISelMatchTable.cpp | 65 +++---
.../Common/GlobalISel/GlobalISelMatchTable.h | 65 ++++--
.../TableGen/Common/GlobalISel/Patterns.cpp | 39 ++++
.../TableGen/Common/GlobalISel/Patterns.h | 28 ++-
.../TableGen/GlobalISelCombinerEmitter.cpp | 185 +++++++++++++-----
llvm/utils/TableGen/GlobalISelEmitter.cpp | 4 +-
15 files changed, 567 insertions(+), 104 deletions(-)
create mode 100644 llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td
diff --git a/llvm/docs/GlobalISel/MIRPatterns.rst b/llvm/docs/GlobalISel/MIRPatterns.rst
index 7e6d88683d491..abc23985a6fac 100644
--- a/llvm/docs/GlobalISel/MIRPatterns.rst
+++ b/llvm/docs/GlobalISel/MIRPatterns.rst
@@ -115,7 +115,7 @@ GITypeOf
``GITypeOf<"$x">`` is a ``GISpecialType`` that allows for the creation of a
register or immediate with the same type as another (register) operand.
-Operand:
+Type Parameters:
* An operand name as a string, prefixed by ``$``.
@@ -143,6 +143,57 @@ Semantics:
(apply (G_FSUB $dst, $src, $tmp),
(G_FNEG GITypeOf<"$dst">:$tmp, $src))>;
+GIVariadic
+~~~~~~~~~~
+
+``GIVariadic<>`` is a ``GISpecialType`` that allows for matching 1 or
+more operands remaining on an instruction.
+
+Type Parameters:
+
+* The minimum number of additional operands to match. Must be greater than zero.
+
+ * Default is 1.
+
+* The maximum number of additional operands to match. Must be strictly greater
+ than the minimum.
+
+ * 0 can be used to indicate there is no upper limit.
+ * Default is 0.
+
+Semantics:
+
+* ``GIVariadic<>`` operands can not be defs.
+* ``GIVariadic<>`` operands can only appear as the last operand in a 'match' pattern.
+* Each instance within a 'match' pattern must be uniquely named.
+* Re-using a ``GIVariadic<>`` operand in an 'apply' pattern will result in all
+ the matched operands being copied from the original instruction.
+* The min/max operands will result in the matcher checking that the number of operands
+ falls within that range.
+* ``GIVariadic<>`` operands can be used in C++ code within a rule, which will
+ result in the operand name being expanded to an iterator range containing all
+ the matched operands, similar to the one returned by ``MachineInstr::operands()``.
+
+.. code-block:: text
+
+ def build_vector_to_unmerge: GICombineRule <
+ (defs root:$root),
+ (match (G_BUILD_VECTOR $root, GIVariadic<>:$args),
+ [{ return checkBuildVectorToUnmerge(${args}); }]),
+ (apply (G_UNMERGE_VALUES $root, $args))
+ >;
+
+.. code-block:: text
+
+ // Will additionally check the number of operands is >= 3 and <= 5.
+ // ($root is one operand, then 2 to 4 variadic operands).
+ def build_vector_to_unmerge: GICombineRule <
+ (defs root:$root),
+ (match (G_BUILD_VECTOR $root, GIVariadic<2, 4>:$two_to_four),
+ [{ return checkBuildVectorToUnmerge(${two_to_four}); }]),
+ (apply (G_UNMERGE_VALUES $root, $two_to_four))
+ >;
+
Builtin Operations
------------------
@@ -240,6 +291,8 @@ This a non-exhaustive list of known issues with MIR patterns at this time.
match. e.g. if a pattern needs to work on both i32 and i64, you either
need to leave it untyped and check the type in C++, or duplicate the
pattern.
+* ``GISpecialType`` operands are not allowed within a ``GICombinePatFrag``.
+* ``GIVariadic<>`` matched operands must each have a unique name.
GICombineRule
-------------
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h
index cc2dd2f4e489c..d7b9c96f1c01e 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h
@@ -133,6 +133,12 @@ enum {
/// - Ops(ULEB128) - Expected number of operands
GIM_CheckNumOperands,
+ /// Check the instruction has a number of operands <= or >= than given number.
+ /// - InsnID(ULEB128) - Instruction ID
+ /// - Ops(ULEB128) - Number of operands
+ GIM_CheckNumOperandsLE,
+ GIM_CheckNumOperandsGE,
+
/// Check an immediate predicate on the specified instruction
/// - InsnID(ULEB128) - Instruction ID
/// - Pred(2) - The predicate to test
@@ -294,12 +300,15 @@ enum {
/// Check the specified operands are identical.
/// The IgnoreCopies variant looks through COPY instructions before
/// comparing the operands.
+ /// The "All" variants check all operands starting from the index.
/// - InsnID(ULEB128) - Instruction ID
/// - OpIdx(ULEB128) - Operand index
/// - OtherInsnID(ULEB128) - Other instruction ID
/// - OtherOpIdx(ULEB128) - Other operand index
GIM_CheckIsSameOperand,
GIM_CheckIsSameOperandIgnoreCopies,
+ GIM_CheckAllSameOperand,
+ GIM_CheckAllSameOperandIgnoreCopies,
/// Check we can replace all uses of a register with another.
/// - OldInsnID(ULEB128)
@@ -362,6 +371,13 @@ enum {
/// GIR_Copy but with both New/OldInsnIDs omitted and defaulting to zero.
GIR_RootToRootCopy,
+ /// Copies all operand starting from OpIdx in OldInsnID into the new
+ /// instruction NewInsnID.
+ /// - NewInsnID(ULEB128) - Instruction ID to modify
+ /// - OldInsnID(ULEB128) - Instruction ID to copy from
+ /// - OpIdx(ULEB128) - The first operand to copy
+ GIR_CopyRemaining,
+
/// Copy an operand to the specified instruction or add a zero register if the
/// operand is a zero immediate.
/// - NewInsnID(ULEB128) - Instruction ID to modify
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h
index 90b4fe5518c87..5a5a750ac6b4a 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h
@@ -309,6 +309,23 @@ bool GIMatchTableExecutor::executeMatchTable(
break;
}
+ case GIM_CheckNumOperandsGE:
+ case GIM_CheckNumOperandsLE: {
+ uint64_t InsnID = readULEB();
+ uint64_t Expected = readULEB();
+ const bool IsLE = (MatcherOpcode == GIM_CheckNumOperandsLE);
+ DEBUG_WITH_TYPE(TgtExecutor::getName(),
+ dbgs() << CurrentIdx << ": GIM_CheckNumOperands"
+ << (IsLE ? "LE" : "GE") << "(MIs[" << InsnID
+ << "], Expected=" << Expected << ")\n");
+ assert(State.MIs[InsnID] != nullptr && "Used insn before defined");
+ const unsigned NumOps = State.MIs[InsnID]->getNumOperands();
+ if (IsLE ? (NumOps <= Expected) : (NumOps >= Expected)) {
+ if (handleReject() == RejectAndGiveUp)
+ return false;
+ }
+ break;
+ }
case GIM_CheckNumOperands: {
uint64_t InsnID = readULEB();
uint64_t Expected = readULEB();
@@ -1081,6 +1098,22 @@ bool GIMatchTableExecutor::executeMatchTable(
break;
}
+ case GIR_CopyRemaining: {
+ uint64_t NewInsnID = readULEB();
+ uint64_t OldInsnID = readULEB();
+ uint64_t OpIdx = readULEB();
+ assert(OutMIs[NewInsnID] && "Attempted to add to undefined instruction");
+ MachineInstr &OldMI = *State.MIs[OldInsnID];
+ MachineInstrBuilder &NewMI = OutMIs[NewInsnID];
+ for (const auto &Op : drop_begin(OldMI.operands(), OpIdx))
+ NewMI.add(Op);
+ DEBUG_WITH_TYPE(TgtExecutor::getName(),
+ dbgs() << CurrentIdx << ": GIR_CopyRemaining(OutMIs["
+ << NewInsnID << "], MIs[" << OldInsnID
+ << "], /*start=*/" << OpIdx << ")\n");
+ break;
+ }
+
case GIR_CopyOrAddZeroReg: {
uint64_t NewInsnID = readULEB();
uint64_t OldInsnID = readULEB();
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index 3ef0636ebf1c7..5c5ad1f6077a6 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -128,6 +128,16 @@ class GITypeOf<string opName> : GISpecialType {
string OpName = opName;
}
+// The type of an operand that can match a variable amount of operands.
+// This type contains a minimum and maximum number of operands to match.
+// The minimum must be 1 or more, as we cannot have an operand representing
+// zero operands, and the max can be zero (which means "unlimited") or a value
+// greater than the minimum.
+class GIVariadic<int min = 1, int max = 0> : GISpecialType {
+ int MinArgs = min;
+ int MaxArgs = max;
+}
+
//===----------------------------------------------------------------------===//
// Pattern Builtins
//===----------------------------------------------------------------------===//
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td
index 86ae031caecb5..e3061e2f4d3e5 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td
@@ -28,19 +28,31 @@ def InstTest3 : GICombineRule<
(match (G_UNMERGE_VALUES $a, $b, $c, $d)),
(apply [{ APPLY }])>;
+def VariadicTypeTestCxx : GICombineRule<
+ (defs root:$a),
+ (match (G_BUILD_VECTOR $a, GIVariadic<2, 4>:$b)),
+ (apply [{ ${b} }])>;
+
+def VariadicTypeTestReuse : GICombineRule<
+ (defs root:$a),
+ (match (G_BUILD_VECTOR $a, $c, GIVariadic<2, 4>:$b)),
+ (apply (G_MERGE_VALUES $a, $b, $c))>;
+
def MyCombiner: GICombiner<"GenMyCombiner", [
InstTest0,
InstTest1,
InstTest2,
- InstTest3
+ InstTest3,
+ VariadicTypeTestCxx,
+ VariadicTypeTestReuse
]>;
// CHECK: const uint8_t *GenMyCombiner::getMatchTable() const {
// CHECK-NEXT: constexpr static uint8_t MatchTable0[] = {
-// CHECK-NEXT: GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2([[#LOWER:]]), GIMT_Encode2([[#UPPER:]]), /*)*//*default:*//*Label 2*/ GIMT_Encode4([[#DEFAULT:]]),
+// CHECK-NEXT: GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2(70), GIMT_Encode2(74), /*)*//*default:*//*Label 2*/ GIMT_Encode4(127),
// CHECK-NEXT: /*TargetOpcode::G_UNMERGE_VALUES*//*Label 0*/ GIMT_Encode4(26), GIMT_Encode4(0), GIMT_Encode4(0),
// CHECK-NEXT: /*TargetOpcode::G_BUILD_VECTOR*//*Label 1*/ GIMT_Encode4(55),
-// CHECK-NEXT: // Label 0: @[[#%u, mul(UPPER-LOWER, 4) + 10]]
+// CHECK-NEXT: // Label 0: @26
// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 3*/ GIMT_Encode4(40), // Rule ID 2 //
// CHECK-NEXT: GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule2Enabled),
// CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/2,
@@ -77,7 +89,35 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
// CHECK-NEXT: // Combiner Rule #1: InstTest1
// CHECK-NEXT: GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner0),
// CHECK-NEXT: // Label 5: @69
-// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 6*/ GIMT_Encode4(83), // Rule ID 0 //
+// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 6*/ GIMT_Encode4(86), // Rule ID 4 //
+// CHECK-NEXT: GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule4Enabled),
+// CHECK-NEXT: GIM_CheckNumOperandsGE, /*MI*/0, /*Expected*/3,
+// CHECK-NEXT: GIM_CheckNumOperandsLE, /*MI*/0, /*Expected*/5,
+// CHECK-NEXT: // MIs[0] a
+// CHECK-NEXT: // No operand predicates
+// CHECK-NEXT: // MIs[0] b
+// CHECK-NEXT: // No operand predicates
+// CHECK-NEXT: // Combiner Rule #4: VariadicTypeTestCxx
+// CHECK-NEXT: GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner1),
+// CHECK-NEXT: // Label 6: @86
+// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 7*/ GIMT_Encode4(112), // Rule ID 5 //
+// CHECK-NEXT: GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule5Enabled),
+// CHECK-NEXT: GIM_CheckNumOperandsGE, /*MI*/0, /*Expected*/4,
+// CHECK-NEXT: GIM_CheckNumOperandsLE, /*MI*/0, /*Expected*/6,
+// CHECK-NEXT: // MIs[0] a
+// CHECK-NEXT: // No operand predicates
+// CHECK-NEXT: // MIs[0] c
+// CHECK-NEXT: // No operand predicates
+// CHECK-NEXT: // MIs[0] b
+// CHECK-NEXT: // No operand predicates
+// CHECK-NEXT: // Combiner Rule #5: VariadicTypeTestReuse
+// CHECK-NEXT: GIR_BuildRootMI, /*Opcode*/GIMT_Encode2(TargetOpcode::G_MERGE_VALUES),
+// CHECK-NEXT: GIR_RootToRootCopy, /*OpIdx*/0, // a
+// CHECK-NEXT: GIR_CopyRemaining, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/2, // b
+// CHECK-NEXT: GIR_RootToRootCopy, /*OpIdx*/1, // c
+// CHECK-NEXT: GIR_EraseRootFromParent_Done,
+// CHECK-NEXT: // Label 7: @112
+// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 8*/ GIMT_Encode4(126), // Rule ID 0 //
// CHECK-NEXT: GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule0Enabled),
// CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/4,
// CHECK-NEXT: // MIs[0] a
@@ -90,10 +130,10 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
// CHECK-NEXT: // No operand predicates
// CHECK-NEXT: // Combiner Rule #0: InstTest0
// CHECK-NEXT: GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner0),
-// CHECK-NEXT: // Label 6: @83
+// CHECK-NEXT: // Label 8: @126
// CHECK-NEXT: GIM_Reject,
-// CHECK-NEXT: // Label 2: @[[#%u, DEFAULT]]
+// CHECK-NEXT: // Label 2: @127
// CHECK-NEXT: GIM_Reject,
-// CHECK-NEXT: }; // Size: [[#%u, DEFAULT + 1]] bytes
+// CHECK-NEXT: }; // Size: 128 bytes
// CHECK-NEXT: return MatchTable0;
// CHECK-NEXT: }
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td
index 4769bed972401..9b5598661c8de 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td
@@ -104,8 +104,32 @@ def TypeOfProp : GICombineRule<
(apply (G_ANYEXT $x, GITypeOf<"$y">:$tmp),
(G_ANYEXT $tmp, $y))>;
+// CHECK: (CombineRule name:VariadicTypeTest id:3 root:a
+// CHECK-NEXT: (MatchPats
+// CHECK-NEXT: <match_root>__VariadicTypeTest_match_0:(CodeGenInstructionPattern G_UNMERGE_VALUES operands:[<def>$a, <def>$b, GIVariadic<1,0>:$z])
+// CHECK-NEXT: )
+// CHECK-NEXT: (ApplyPats
+// CHECK-NEXT: <apply_root>__VariadicTypeTest_apply_0:(CodeGenInstructionPattern G_UNMERGE_VALUES operands:[<def>$a, <def>$b, GIVariadic<1,0>:$z])
+// CHECK-NEXT: )
+// CHECK-NEXT: (OperandTable MatchPats
+// CHECK-NEXT: a -> __VariadicTypeTest_match_0
+// CHECK-NEXT: b -> __VariadicTypeTest_match_0
+// CHECK-NEXT: z -> <live-in>
+// CHECK-NEXT: )
+// CHECK-NEXT: (OperandTable ApplyPats
+// CHECK-NEXT: a -> __VariadicTypeTest_apply_0
+// CHECK-NEXT: b -> __VariadicTypeTest_apply_0
+// CHECK-NEXT: z -> <live-in>
+// CHECK-NEXT: )
+// CHECK-NEXT: )
+def VariadicTypeTest: GICombineRule<
+ (defs root:$a),
+ (match (G_UNMERGE_VALUES $a, $b, GIVariadic<>:$z)),
+ (apply (G_UNMERGE_VALUES $a, $b, $z))>;
+
def MyCombiner: GICombiner<"GenMyCombiner", [
InstTest0,
PatFragTest0,
- TypeOfProp
+ TypeOfProp,
+ VariadicTypeTest,
]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td
index ee7b8f5f3a39c..900a4bb902f7c 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td
@@ -21,7 +21,7 @@ def UnknownOperand : GICombineRule<
(match (G_ZEXT $dst, $src)),
(apply (G_ANYEXT $dst, (GITypeOf<"$unknown"> 0)))>;
-// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GISpecialType is not supported in 'match' patterns
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GITypeOf is not supported in 'match' patterns
// CHECK: :[[@LINE+1]]:{{[0-9]+}}: note: operand 1 of '__UseInMatch_match_0' has type 'GITypeOf<$dst>'
def UseInMatch : GICombineRule<
(defs root:$dst),
@@ -41,7 +41,7 @@ def UseInPF: GICombineRule<
(match (PFWithTypeOF $dst)),
(apply (G_ANYEXT $dst, (i32 0)))>;
-// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GISpecialType is not supported in 'match' patterns
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GITypeOf is not supported in 'match' patterns
// CHECK: :[[@LINE+1]]:{{[0-9]+}}: note: operand 1 of '__InferredUseInMatch_match_0' has type 'GITypeOf<$dst>'
def InferredUseInMatch : GICombineRule<
(defs root:$dst),
@@ -63,6 +63,13 @@ def TypeOfApplyTmp : GICombineRule<
(apply (G_ANYEXT $dst, i32:$tmp),
(G_ANYEXT $tmp, (GITypeOf<"$tmp"> 0)))>;
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: type 'GITypeOf<$src>' is ill-formed: 'src' is a variadic pack operand
+def TypeOfVariadic : GICombineRule<
+ (defs root:$dst),
+ (match (G_ZEXT $dst, GIVariadic<>:$src)),
+ (apply (G_ANYEXT GITypeOf<"$src">:$tmp, $src),
+ (G_ANYEXT $dst, $tmp))>;
+
// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse one or more rules
def MyCombiner: GICombiner<"GenMyCombiner", [
NoDollarSign,
@@ -71,5 +78,6 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
UseInPF,
InferredUseInMatch,
InferenceConflict,
- TypeOfApplyTmp
+ TypeOfApplyTmp,
+ TypeOfVariadic
]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td
new file mode 100644
index 0000000000000..68929c09da8d2
--- /dev/null
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td
@@ -0,0 +1,75 @@
+// RUN: not llvm-tblgen -I %p/../../../include -gen-global-isel-combiner \
+// RUN: -combiners=MyCombiner %s 2>&1| \
+// RUN: FileCheck %s -implicit-check-not=error:
+
+include "llvm/Target/Target.td"
+include "llvm/Target/GlobalISel/Combine.td"
+
+def MyTargetISA : InstrInfo;
+def MyTarget : Target { let InstructionSet = MyTargetISA; }
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'G_BUILD_VECTOR': GIVariadic can only be used on the last operand
+def VariadicNotLastInList : GICombineRule<
+ (defs root:$dst),
+ (match (G_BUILD_VECTOR $dst, $a, GIVariadic<>:$b, $c)),
+ (apply (G_ANYEXT $dst, $a))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'G_IMPLICIT_DEF': GIVariadic cannot be used on defs
+def VariadicAsDef : GICombineRule<
+ (defs root:$dst),
+ (match (G_IMPLICIT_DEF GIVariadic<1>:$dst)),
+ (apply [{ APPLY }])>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: conflicting types for operand 'args': 'GIVariadic<2,4>' vs 'GIVariadic<3,6>'
+def ConflictingInference : GICombineRule<
+ (defs root:$dst),
+ (match (G_BUILD_VECTOR $dst, GIVariadic<2, 4>:$args)),
+ (apply (G_MERGE_VALUES $dst, GIVariadic<3, 6>:$args))>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: cannot parse operand type: minimum number of arguments must be greater than zero in GIVariadic
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(G_BUILD_VECTOR ?:$dst, anonymous_8021:$a)'
+def InvalidBounds0 : GICombineRule<
+ (defs root:$dst),
+ (match (G_BUILD_VECTOR $dst, GIVariadic<0>:$a)),
+ (apply [{ APPLY }])>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: cannot parse operand type: maximum number of arguments (1) must be zero, or greater than the minimum number of arguments (1) in GIVariadic
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(G_BUILD_VECTOR ?:$dst, anonymous_8022:$a)'
+def InvalidBounds1 : GICombineRule<
+ (defs root:$dst),
+ (match (G_BUILD_VECTOR $dst, GIVariadic<1,1>:$a)),
+ (apply [{ APPLY }])>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: each instance of a GIVariadic operand must have a unique name within the match patterns
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: note: 'c' is used multiple times
+def VariadicTypeTestEqOp : GICombineRule<
+ (defs root:$a),
+ (match (G_MERGE_VALUES $b, $c),
+ (G_BUILD_VECTOR $a, $b, GIVariadic<2, 4>:$c)),
+ (apply (G_MERGE_VALUES $a, $c))>;
+
+// TODO: We could support this if needed
+
+// CHECK: :[[@LINE+3]]:{{[0-9]+}}: error: GISpecialType is not supported in GICombinePatFrag
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: note: operand 1 of '__PFWithVariadic_alt0_pattern_0' has type 'GIVariadic<1,0
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Could not parse GICombinePatFrag 'PFWithVariadic'
+def PFWithVariadic: GICombinePatFrag<
+ (outs $dst), (ins),
+ [(pattern (G_ANYEXT $dst, GIVariadic<>:$b))]>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(PFWithVariadic ?:$dst)'
+def UseInPF: GICombineRule<
+ (defs root:$dst),
+ (match (PFWithVariadic $dst)),
+ (apply (G_ANYEXT $dst, (i32 0)))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse one or more rules
+def MyCombiner: GICombiner<"GenMyCombiner", [
+ VariadicNotLastInList,
+ VariadicAsDef,
+ ConflictingInference,
+ InvalidBounds0,
+ InvalidBounds1,
+ VariadicTypeTestEqOp,
+ UseInPF
+]>;
diff --git a/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h b/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h
index 0b1e6ceab52c2..345da613f8435 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h
@@ -32,7 +32,7 @@ class raw_ostream;
class CodeExpander {
StringRef Code;
const CodeExpansions &Expansions;
- const ArrayRef<SMLoc> &Loc;
+ ArrayRef<SMLoc> Loc;
bool ShowExpansions;
StringRef Indent;
diff --git a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp
index e011e78c67b15..d0dc515e83eb7 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp
+++ b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp
@@ -687,10 +687,6 @@ StringRef RuleMatcher::getOpcode() const {
return Matchers.front()->getOpcode();
}
-unsigned RuleMatcher::getNumOperands() const {
- return Matchers.front()->getNumOperands();
-}
-
LLTCodeGen RuleMatcher::getFirstConditionAsRootType() {
InstructionMatcher &InsnMatcher = *Matchers.front();
if (!InsnMatcher.predicates_empty())
@@ -1344,6 +1340,7 @@ std::string OperandMatcher::getOperandExpr(unsigned InsnVarID) const {
unsigned OperandMatcher::getInsnVarID() const { return Insn.getInsnVarID(); }
TempTypeIdx OperandMatcher::getTempTypeIdx(RuleMatcher &Rule) {
+ assert(!IsVariadic && "Cannot use this on variadic operands!");
if (TTIdx >= 0) {
// Temp type index not assigned yet, so assign one and add the necessary
// predicate.
@@ -1506,8 +1503,20 @@ StringRef InstructionOpcodeMatcher::getOperandType(unsigned OpIdx) const {
void InstructionNumOperandsMatcher::emitPredicateOpcodes(
MatchTable &Table, RuleMatcher &Rule) const {
- Table << MatchTable::Opcode("GIM_CheckNumOperands")
- << MatchTable::Comment("MI") << MatchTable::ULEB128Value(InsnVarID)
+ StringRef Opc;
+ switch (CK) {
+ case CK_Eq:
+ Opc = "GIM_CheckNumOperands";
+ break;
+ case CK_GE:
+ Opc = "GIM_CheckNumOperandsGE";
+ break;
+ case CK_LE:
+ Opc = "GIM_CheckNumOperandsLE";
+ break;
+ }
+ Table << MatchTable::Opcode(Opc) << MatchTable::Comment("MI")
+ << MatchTable::ULEB128Value(InsnVarID)
<< MatchTable::Comment("Expected")
<< MatchTable::ULEB128Value(NumOperands) << MatchTable::LineBreak;
}
@@ -1695,12 +1704,15 @@ void MIFlagsInstructionPredicateMatcher::emitPredicateOpcodes(
OperandMatcher &
InstructionMatcher::addOperand(unsigned OpIdx, const std::string &SymbolicName,
- unsigned AllocatedTemporariesBaseID) {
- Operands.emplace_back(new OperandMatcher(*this, OpIdx, SymbolicName,
- AllocatedTemporariesBaseID));
+ unsigned AllocatedTemporariesBaseID,
+ bool IsVariadic) {
+ assert(Operands.empty() ||
+ !Operands.back()->isVariadic() &&
+ "Cannot add more operands after a variadic operand");
+ Operands.emplace_back(new OperandMatcher(
+ *this, OpIdx, SymbolicName, AllocatedTemporariesBaseID, IsVariadic));
if (!SymbolicName.empty())
Rule.defineOperand(SymbolicName, *Operands.back());
-
return *Operands.back();
}
@@ -1726,9 +1738,10 @@ OperandMatcher &InstructionMatcher::addPhysRegInput(Record *Reg, unsigned OpIdx,
void InstructionMatcher::emitPredicateOpcodes(MatchTable &Table,
RuleMatcher &Rule) {
- if (NumOperandsCheck)
- InstructionNumOperandsMatcher(InsnVarID, getNumOperands())
+ if (canAddNumOperandsCheck()) {
+ InstructionNumOperandsMatcher(InsnVarID, getNumOperandMatchers())
.emitPredicateOpcodes(Table, Rule);
+ }
// First emit all instruction level predicates need to be verified before we
// can verify operands.
@@ -1793,11 +1806,14 @@ void InstructionMatcher::optimize() {
Stash.push_back(predicates_pop_front());
if (Stash.back().get() == &OpcMatcher) {
- if (NumOperandsCheck && OpcMatcher.isVariadicNumOperands() &&
- getNumOperands() != 0)
- Stash.emplace_back(
- new InstructionNumOperandsMatcher(InsnVarID, getNumOperands()));
- NumOperandsCheck = false;
+ // FIXME: Is this even needed still? Why the isVariadicNumOperands check?
+ if (canAddNumOperandsCheck() &&
+ OpcMatcher.isVariadicNumOperands() && getNumOperandMatchers() != 0) {
+ Stash.emplace_back(new InstructionNumOperandsMatcher(
+ InsnVarID,
+ getNumOperandMatchers()));
+ }
+ AllowNumOpsCheck = false;
for (auto &OM : Operands)
for (auto &OP : OM->predicates())
@@ -1862,11 +1878,13 @@ OperandRenderer::~OperandRenderer() {}
void CopyRenderer::emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule,
unsigned NewInsnID, unsigned OldInsnID,
- unsigned OpIdx, StringRef Name) {
- if (NewInsnID == 0 && OldInsnID == 0) {
+ unsigned OpIdx, StringRef Name,
+ bool ForVariadic) {
+ if (!ForVariadic && NewInsnID == 0 && OldInsnID == 0) {
Table << MatchTable::Opcode("GIR_RootToRootCopy");
} else {
- Table << MatchTable::Opcode("GIR_Copy") << MatchTable::Comment("NewInsnID")
+ Table << MatchTable::Opcode(ForVariadic ? "GIR_CopyRemaining" : "GIR_Copy")
+ << MatchTable::Comment("NewInsnID")
<< MatchTable::ULEB128Value(NewInsnID)
<< MatchTable::Comment("OldInsnID")
<< MatchTable::ULEB128Value(OldInsnID);
@@ -1880,8 +1898,9 @@ void CopyRenderer::emitRenderOpcodes(MatchTable &Table,
RuleMatcher &Rule) const {
const OperandMatcher &Operand = Rule.getOperandMatcher(SymbolicName);
unsigned OldInsnVarID = Rule.getInsnVarID(Operand.getInstructionMatcher());
+
emitRenderOpcodes(Table, Rule, NewInsnID, OldInsnVarID, Operand.getOpIdx(),
- SymbolicName);
+ SymbolicName, Operand.isVariadic());
}
//===- CopyPhysRegRenderer ------------------------------------------------===//
@@ -2127,10 +2146,10 @@ void CustomOperandRenderer::emitRenderOpcodes(MatchTable &Table,
bool BuildMIAction::canMutate(RuleMatcher &Rule,
const InstructionMatcher *Insn) const {
- if (!Insn)
+ if (!Insn || Insn->hasVariadicMatcher())
return false;
- if (OperandRenderers.size() != Insn->getNumOperands())
+ if (OperandRenderers.size() != Insn->getNumOperandMatchers())
return false;
for (const auto &Renderer : enumerate(OperandRenderers)) {
diff --git a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h
index 9e345dceddf52..62292a4972c24 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h
@@ -641,6 +641,8 @@ class RuleMatcher : public Matcher {
return make_range(actions_begin(), actions_end());
}
+ bool hasOperand(StringRef SymbolicName) const { return DefinedOperands.contains(SymbolicName); }
+
void defineOperand(StringRef SymbolicName, OperandMatcher &OM);
void definePhysRegOperand(Record *Reg, OperandMatcher &OM);
@@ -678,7 +680,6 @@ class RuleMatcher : public Matcher {
const PredicateMatcher &getFirstCondition() const override;
LLTCodeGen getFirstConditionAsRootType();
bool hasFirstCondition() const override;
- unsigned getNumOperands() const;
StringRef getOpcode() const;
// FIXME: Remove this as soon as possible
@@ -1255,12 +1256,15 @@ class OperandMatcher : public PredicateListMatcher<OperandPredicateMatcher> {
TempTypeIdx TTIdx = 0;
+ // TODO: has many implications, figure them all out
+ bool IsVariadic = false;
+
public:
OperandMatcher(InstructionMatcher &Insn, unsigned OpIdx,
const std::string &SymbolicName,
- unsigned AllocatedTemporariesBaseID)
+ unsigned AllocatedTemporariesBaseID, bool IsVariadic = false)
: Insn(Insn), OpIdx(OpIdx), SymbolicName(SymbolicName),
- AllocatedTemporariesBaseID(AllocatedTemporariesBaseID) {}
+ AllocatedTemporariesBaseID(AllocatedTemporariesBaseID), IsVariadic(IsVariadic) {}
bool hasSymbolicName() const { return !SymbolicName.empty(); }
StringRef getSymbolicName() const { return SymbolicName; }
@@ -1272,7 +1276,8 @@ class OperandMatcher : public PredicateListMatcher<OperandPredicateMatcher> {
/// Construct a new operand predicate and add it to the matcher.
template <class Kind, class... Args>
std::optional<Kind *> addPredicate(Args &&...args) {
- if (isSameAsAnotherOperand())
+ // TODO: Should variadic ops support predicates?
+ if (isSameAsAnotherOperand() || IsVariadic)
return std::nullopt;
Predicates.emplace_back(std::make_unique<Kind>(
getInsnVarID(), getOpIdx(), std::forward<Args>(args)...));
@@ -1282,6 +1287,8 @@ class OperandMatcher : public PredicateListMatcher<OperandPredicateMatcher> {
unsigned getOpIdx() const { return OpIdx; }
unsigned getInsnVarID() const;
+ bool isVariadic() const { return IsVariadic; }
+
/// If this OperandMatcher has not been assigned a TempTypeIdx yet, assigns it
/// one and adds a `RecordRegisterType` predicate to this matcher. If one has
/// already been assigned, simply returns it.
@@ -1405,20 +1412,32 @@ class InstructionOpcodeMatcher : public InstructionPredicateMatcher {
};
class InstructionNumOperandsMatcher final : public InstructionPredicateMatcher {
+public:
+ enum CheckKind {
+ CK_Eq,
+ CK_LE,
+ CK_GE
+ };
+
+private:
unsigned NumOperands = 0;
+ CheckKind CK;
public:
- InstructionNumOperandsMatcher(unsigned InsnVarID, unsigned NumOperands)
+
+ InstructionNumOperandsMatcher(unsigned InsnVarID, unsigned NumOperands, CheckKind CK = CK_Eq)
: InstructionPredicateMatcher(IPM_NumOperands, InsnVarID),
- NumOperands(NumOperands) {}
+ NumOperands(NumOperands), CK(CK) {}
static bool classof(const PredicateMatcher *P) {
return P->getKind() == IPM_NumOperands;
}
bool isIdentical(const PredicateMatcher &B) const override {
- return InstructionPredicateMatcher::isIdentical(B) &&
- NumOperands == cast<InstructionNumOperandsMatcher>(&B)->NumOperands;
+ if(!InstructionPredicateMatcher::isIdentical(B))
+ return false;
+ const auto &Other = *cast<InstructionNumOperandsMatcher>(&B);
+ return NumOperands == Other.NumOperands && CK == Other.CK;
}
void emitPredicateOpcodes(MatchTable &Table,
@@ -1729,20 +1748,29 @@ class InstructionMatcher final : public PredicateListMatcher<PredicateMatcher> {
/// The operands to match. All rendered operands must be present even if the
/// condition is always true.
OperandVec Operands;
- bool NumOperandsCheck = true;
std::string SymbolicName;
unsigned InsnVarID;
+ bool AllowNumOpsCheck;
/// PhysRegInputs - List list has an entry for each explicitly specified
/// physreg input to the pattern. The first elt is the Register node, the
/// second is the recorded slot number the input pattern match saved it in.
SmallVector<std::pair<Record *, unsigned>, 2> PhysRegInputs;
+ bool canAddNumOperandsCheck() const {
+ // Add if it's allowed, and:
+ // - We don't have a variadic operand
+ // - We don't already have such a check.
+ return AllowNumOpsCheck && !hasVariadicMatcher() && none_of(Predicates, [&](const auto& P){
+ return P->getKind() == InstructionPredicateMatcher::IPM_NumOperands;
+ });
+ }
+
public:
- InstructionMatcher(RuleMatcher &Rule, StringRef SymbolicName,
- bool NumOpsCheck = true)
- : Rule(Rule), NumOperandsCheck(NumOpsCheck), SymbolicName(SymbolicName) {
+ InstructionMatcher(RuleMatcher &Rule, StringRef SymbolicName, bool AllowNumOpsCheck = true
+ )
+ : Rule(Rule), SymbolicName(SymbolicName), AllowNumOpsCheck(AllowNumOpsCheck) {
// We create a new instruction matcher.
// Get a new ID for that instruction.
InsnVarID = Rule.implicitlyDefineInsnVar(*this);
@@ -1762,7 +1790,7 @@ class InstructionMatcher final : public PredicateListMatcher<PredicateMatcher> {
/// Add an operand to the matcher.
OperandMatcher &addOperand(unsigned OpIdx, const std::string &SymbolicName,
- unsigned AllocatedTemporariesBaseID);
+ unsigned AllocatedTemporariesBaseID, bool IsVariadic = false);
OperandMatcher &getOperand(unsigned OpIdx);
OperandMatcher &addPhysRegInput(Record *Reg, unsigned OpIdx,
unsigned TempOpIdx);
@@ -1772,7 +1800,10 @@ class InstructionMatcher final : public PredicateListMatcher<PredicateMatcher> {
}
StringRef getSymbolicName() const { return SymbolicName; }
- unsigned getNumOperands() const { return Operands.size(); }
+
+ unsigned getNumOperandMatchers() const { return Operands.size(); }
+ bool hasVariadicMatcher() const { return !Operands.empty() && Operands.back()->isVariadic(); }
+
OperandVec::iterator operands_begin() { return Operands.begin(); }
OperandVec::iterator operands_end() { return Operands.end(); }
iterator_range<OperandVec::iterator> operands() {
@@ -1834,9 +1865,9 @@ class InstructionOperandMatcher : public OperandPredicateMatcher {
public:
InstructionOperandMatcher(unsigned InsnVarID, unsigned OpIdx,
RuleMatcher &Rule, StringRef SymbolicName,
- bool NumOpsCheck = true)
+ bool AllowNumOpsCheck = true)
: OperandPredicateMatcher(OPM_Instruction, InsnVarID, OpIdx),
- InsnMatcher(new InstructionMatcher(Rule, SymbolicName, NumOpsCheck)),
+ InsnMatcher(new InstructionMatcher(Rule, SymbolicName, AllowNumOpsCheck)),
Flags(Rule.getGISelFlags()) {}
static bool classof(const PredicateMatcher *P) {
@@ -1917,7 +1948,7 @@ class CopyRenderer : public OperandRenderer {
static void emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule,
unsigned NewInsnID, unsigned OldInsnID,
- unsigned OpIdx, StringRef Name);
+ unsigned OpIdx, StringRef Name, bool ForVariadic = false);
void emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule) const override;
};
diff --git a/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp b/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp
index 28a9bcc2568c3..14eb927987100 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp
+++ b/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp
@@ -46,6 +46,33 @@ std::optional<PatternType> PatternType::get(ArrayRef<SMLoc> DiagLoc,
return PT;
}
+ if (R->isSubClassOf(VariadicClassName)) {
+ const int64_t Min = R->getValueAsInt("MinArgs");
+ const int64_t Max = R->getValueAsInt("MaxArgs");
+
+ if (Min == 0) {
+ PrintError(
+ DiagLoc,
+ DiagCtx +
+ ": minimum number of arguments must be greater than zero in " +
+ VariadicClassName);
+ return std::nullopt;
+ }
+
+ if (Max <= Min && Max != 0) {
+ PrintError(DiagLoc,
+ DiagCtx +
+ ": maximum number of arguments (" + Twine(Max) + ") must be zero, or greater "
+ "than the minimum number of arguments (" +
+ Twine(Min) + ") in " + VariadicClassName);
+ return std::nullopt;
+ }
+
+ PatternType PT(PT_VariadicPack);
+ PT.Data.VPTI = {unsigned(Min), unsigned(Max)};
+ return PT;
+ }
+
PrintError(DiagLoc, DiagCtx + ": unknown type '" + R->getName() + "'");
return std::nullopt;
}
@@ -66,6 +93,11 @@ const Record *PatternType::getLLTRecord() const {
return Data.Def;
}
+VariadicPackTypeInfo PatternType::getVariadicPackTypeInfo() const {
+ assert(isVariadicPack());
+ return Data.VPTI;
+}
+
bool PatternType::operator==(const PatternType &Other) const {
if (Kind != Other.Kind)
return false;
@@ -77,6 +109,8 @@ bool PatternType::operator==(const PatternType &Other) const {
return Data.Def == Other.Data.Def;
case PT_TypeOf:
return Data.Str == Other.Data.Str;
+ case PT_VariadicPack:
+ return Data.VPTI == Other.Data.VPTI;
}
llvm_unreachable("Unknown Type Kind");
@@ -90,6 +124,10 @@ std::string PatternType::str() const {
return Data.Def->getName().str();
case PT_TypeOf:
return (TypeOfClassName + "<$" + getTypeOfOpName() + ">").str();
+ case PT_VariadicPack:
+ return (VariadicClassName + "<" + Twine(Data.VPTI.Min) + "," +
+ Twine(Data.VPTI.Max) + ">")
+ .str();
}
llvm_unreachable("Unknown type!");
@@ -525,6 +563,7 @@ bool PatFrag::checkSemantics() {
case Pattern::K_CXX:
continue;
case Pattern::K_CodeGenInstruction:
+ // TODO: Allow VarArgs?
if (cast<CodeGenInstructionPattern>(Pat.get())->diagnoseAllSpecialTypes(
Def.getLoc(), PatternType::SpecialTyClassName +
" is not supported in " + ClassName))
diff --git a/llvm/utils/TableGen/Common/GlobalISel/Patterns.h b/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
index 76d018bdbd71c..2d25ce37ed76c 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
@@ -45,21 +45,36 @@ class RuleMatcher;
//===- PatternType --------------------------------------------------------===//
+struct VariadicPackTypeInfo {
+ VariadicPackTypeInfo(unsigned Min, unsigned Max) : Min(Min), Max(Max) {
+ assert(Min >= 1 && (Max >= Min || Max == 0));
+ }
+
+ bool operator==(const VariadicPackTypeInfo &Other) const {
+ return Min == Other.Min && Max == Other.Max;
+ }
+
+ unsigned Min;
+ unsigned Max;
+};
+
/// Represent the type of a Pattern Operand.
///
/// Types have two form:
/// - LLTs, which are straightforward.
-/// - Special types, e.g. GITypeOf
+/// - Special types, e.g. GITypeOf, Variadic arguments list.
class PatternType {
public:
static constexpr StringLiteral SpecialTyClassName = "GISpecialType";
static constexpr StringLiteral TypeOfClassName = "GITypeOf";
+ static constexpr StringLiteral VariadicClassName = "GIVariadic";
enum PTKind : uint8_t {
PT_None,
PT_ValueType,
PT_TypeOf,
+ PT_VariadicPack,
};
PatternType() : Kind(PT_None), Data() {}
@@ -70,11 +85,15 @@ class PatternType {
bool isNone() const { return Kind == PT_None; }
bool isLLT() const { return Kind == PT_ValueType; }
- bool isSpecial() const { return isTypeOf(); }
+ bool isSpecial() const { return isTypeOf() || isVariadicPack(); }
bool isTypeOf() const { return Kind == PT_TypeOf; }
+ bool isVariadicPack() const { return Kind == PT_VariadicPack; }
+
+ PTKind getKind() const { return Kind; }
StringRef getTypeOfOpName() const;
const Record *getLLTRecord() const;
+ VariadicPackTypeInfo getVariadicPackTypeInfo() const;
explicit operator bool() const { return !isNone(); }
@@ -95,6 +114,9 @@ class PatternType {
/// PT_TypeOf -> Operand name (without the '$')
StringRef Str;
+
+ /// PT_VariadicPack -> min-max number of operands allowed.
+ VariadicPackTypeInfo VPTI;
} Data;
};
@@ -313,6 +335,8 @@ class InstructionPattern : public Pattern {
InstructionOperand &getOperand(unsigned K) { return Operands[K]; }
const InstructionOperand &getOperand(unsigned K) const { return Operands[K]; }
+ const InstructionOperand &operands_back() const { return Operands.back(); }
+
/// When this InstructionPattern is used as the match root, returns the
/// operands that must be redefined in the 'apply' pattern for the rule to be
/// valid.
diff --git a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
index e8fbaed0f50e8..fd7bb68c86cd9 100644
--- a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
@@ -99,8 +99,13 @@ void declareInstExpansion(CodeExpansions &CE, const BuildMIAction &A,
void declareOperandExpansion(CodeExpansions &CE, const OperandMatcher &OM,
StringRef Name) {
- CE.declare(Name, "State.MIs[" + to_string(OM.getInsnVarID()) +
- "]->getOperand(" + to_string(OM.getOpIdx()) + ")");
+ if (OM.isVariadic()) {
+ CE.declare(Name, "drop_begin(State.MIs[" + to_string(OM.getInsnVarID()) +
+ "]->operands(), " + to_string(OM.getOpIdx()) + ")");
+ } else {
+ CE.declare(Name, "State.MIs[" + to_string(OM.getInsnVarID()) +
+ "]->getOperand(" + to_string(OM.getOpIdx()) + ")");
+ }
}
void declareTempRegExpansion(CodeExpansions &CE, unsigned TempRegID,
@@ -128,18 +133,6 @@ LLTCodeGen getLLTCodeGen(const PatternType &PT) {
return *MVTToLLT(getValueType(PT.getLLTRecord()));
}
-LLTCodeGenOrTempType getLLTCodeGenOrTempType(const PatternType &PT,
- RuleMatcher &RM) {
- assert(!PT.isNone());
-
- if (PT.isLLT())
- return getLLTCodeGen(PT);
-
- assert(PT.isTypeOf());
- auto &OM = RM.getOperandMatcher(PT.getTypeOfOpName());
- return OM.getTempTypeIdx(RM);
-}
-
//===- PrettyStackTrace Helpers ------------------------------------------===//
class PrettyStackTraceParse : public PrettyStackTraceEntry {
@@ -668,6 +661,9 @@ class CombineRuleBuilder {
return CGT.getInstruction(RuleDef.getRecords().getDef("G_CONSTANT"));
}
+ std::optional<LLTCodeGenOrTempType>
+ getLLTCodeGenOrTempType(const PatternType &PT, RuleMatcher &RM);
+
void PrintError(Twine Msg) const { ::PrintError(&RuleDef, Msg); }
void PrintWarning(Twine Msg) const { ::PrintWarning(RuleDef.getLoc(), Msg); }
void PrintNote(Twine Msg) const { ::PrintNote(RuleDef.getLoc(), Msg); }
@@ -960,6 +956,24 @@ void CombineRuleBuilder::verify() const {
}
#endif
+std::optional<LLTCodeGenOrTempType>
+CombineRuleBuilder::getLLTCodeGenOrTempType(const PatternType &PT,
+ RuleMatcher &RM) {
+ assert(!PT.isNone());
+
+ if (PT.isLLT())
+ return getLLTCodeGen(PT);
+
+ assert(PT.isTypeOf());
+ auto &OM = RM.getOperandMatcher(PT.getTypeOfOpName());
+ if (OM.isVariadic()) {
+ PrintError("type '" + PT.str() + "' is ill-formed: '" +
+ OM.getSymbolicName() + "' is a variadic pack operand");
+ return std::nullopt;
+ }
+ return OM.getTempTypeIdx(RM);
+}
+
void CombineRuleBuilder::print(raw_ostream &OS,
const PatternAlternatives &Alts) const {
SmallVector<std::string, 1> Strings(
@@ -1079,11 +1093,18 @@ bool CombineRuleBuilder::typecheckPatterns() {
// match patterns.
for (auto &Pat : values(MatchPats)) {
if (auto *IP = dyn_cast<InstructionPattern>(Pat.get())) {
- if (IP->diagnoseAllSpecialTypes(
- RuleDef.getLoc(), PatternType::SpecialTyClassName +
- " is not supported in 'match' patterns")) {
- return false;
+ bool HasDiag = false;
+ for (const auto &[Idx, Op] : enumerate(IP->operands())) {
+ if (Op.getType().isTypeOf()) {
+ PrintError(PatternType::TypeOfClassName +
+ " is not supported in 'match' patterns");
+ PrintNote("operand " + Twine(Idx) + " of '" + IP->getName() +
+ "' has type '" + Op.getType().str() + "'");
+ HasDiag = true;
+ }
}
+ if (HasDiag)
+ return false;
}
}
return true;
@@ -1145,6 +1166,29 @@ bool CombineRuleBuilder::buildPermutationsToEmit() {
bool CombineRuleBuilder::checkSemantics() {
assert(MatchRoot && "Cannot call this before findRoots()");
+ const auto CheckVariadicOperands = [&](const InstructionPattern &IP, bool IsMatch) {
+ for (auto &Op : IP.operands()) {
+ if (!Op.getType().isVariadicPack())
+ continue;
+
+ if (IsMatch && &Op != &IP.operands_back()) {
+ PrintError("'" + IP.getInstName() +
+ "': " + PatternType::VariadicClassName +
+ " can only be used on the last operand");
+ return false;
+ }
+
+ if (Op.isDef()) {
+ PrintError("'" + IP.getInstName() +
+ "': " + PatternType::VariadicClassName +
+ " cannot be used on defs");
+ return false;
+ }
+ }
+
+ return true;
+ };
+
bool UsesWipMatchOpcode = false;
for (const auto &Match : MatchPats) {
const auto *Pat = Match.second.get();
@@ -1155,19 +1199,26 @@ bool CombineRuleBuilder::checkSemantics() {
continue;
}
- // MIFlags in match cannot use the following syntax: (MIFlags $mi)
- if (const auto *CGP = dyn_cast<CodeGenInstructionPattern>(Pat)) {
- if (auto *FI = CGP->getMIFlagsInfo()) {
- if (!FI->copy_flags().empty()) {
- PrintError(
- "'match' patterns cannot refer to flags from other instructions");
- PrintNote("MIFlags in '" + CGP->getName() +
- "' refer to: " + join(FI->copy_flags(), ", "));
- return false;
+ if (const auto IP = dyn_cast<InstructionPattern>(Pat)) {
+ if(!CheckVariadicOperands(*IP, /*IsMatch=*/true))
+ return false;
+
+ // MIFlags in match cannot use the following syntax: (MIFlags $mi)
+ if (const auto *CGP = dyn_cast<CodeGenInstructionPattern>(Pat)) {
+ if (auto *FI = CGP->getMIFlagsInfo()) {
+ if (!FI->copy_flags().empty()) {
+ PrintError(
+ "'match' patterns cannot refer to flags from other instructions");
+ PrintNote("MIFlags in '" + CGP->getName() +
+ "' refer to: " + join(FI->copy_flags(), ", "));
+ return false;
+ }
}
}
+ continue;
}
+
const auto *AOP = dyn_cast<AnyOpcodePattern>(Pat);
if (!AOP)
continue;
@@ -1197,6 +1248,9 @@ bool CombineRuleBuilder::checkSemantics() {
if (!IP)
continue;
+ if (!CheckVariadicOperands(*IP, /*IsMatch=*/false))
+ return false;
+
if (UsesWipMatchOpcode) {
PrintError("cannot use wip_match_opcode in combination with apply "
"instruction patterns!");
@@ -1839,7 +1893,7 @@ bool CombineRuleBuilder::emitCXXMatchApply(CodeExpansions &CE, RuleMatcher &M,
for (auto &Pat : ApplyPats) {
auto *CXXPat = cast<CXXPattern>(Pat.second.get());
CodeExpander Expander(CXXPat->getRawCode(), CE, RuleDef.getLoc(),
- /*ShowExpansions=*/ false);
+ /*ShowExpansions=*/false);
OS << LS;
Expander.emit(OS);
}
@@ -1939,8 +1993,8 @@ bool CombineRuleBuilder::emitInstructionApplyPattern(
continue;
}
- // Determine what we're dealing with. Are we replace a matched instruction?
- // Creating a new one?
+ // Determine what we're dealing with. Are we replacing a matched
+ // instruction? Creating a new one?
auto OpLookupRes = MatchOpTable.lookup(OpName);
if (OpLookupRes.Found) {
if (OpLookupRes.isLiveIn()) {
@@ -1986,8 +2040,11 @@ bool CombineRuleBuilder::emitInstructionApplyPattern(
declareTempRegExpansion(CE, TempRegID, OpName);
// Always insert the action at the beginning, otherwise we may end up
// using the temp reg before it's available.
- M.insertAction<MakeTempRegisterAction>(
- M.actions_begin(), getLLTCodeGenOrTempType(Ty, M), TempRegID);
+ auto Result = getLLTCodeGenOrTempType(Ty, M);
+ if (!Result)
+ return false;
+ M.insertAction<MakeTempRegisterAction>(M.actions_begin(), *Result,
+ TempRegID);
}
DstMI.addRenderer<TempRegRenderer>(TempRegID, /*IsDef=*/true);
@@ -2044,16 +2101,18 @@ bool CombineRuleBuilder::emitCodeGenInstructionApplyImmOperand(
}
auto ImmTy = getLLTCodeGenOrTempType(Ty, M);
+ if (!ImmTy)
+ return false;
if (isGConstant) {
- DstMI.addRenderer<ImmRenderer>(O.getImmValue(), ImmTy);
+ DstMI.addRenderer<ImmRenderer>(O.getImmValue(), *ImmTy);
return true;
}
unsigned TempRegID = M.allocateTempRegID();
// Ensure MakeTempReg & the BuildConstantAction occur at the beginning.
auto InsertIt = M.insertAction<MakeTempRegisterAction>(M.actions_begin(),
- ImmTy, TempRegID);
+ *ImmTy, TempRegID);
M.insertAction<BuildConstantAction>(++InsertIt, TempRegID, O.getImmValue());
DstMI.addRenderer<TempRegRenderer>(TempRegID);
return true;
@@ -2159,6 +2218,8 @@ bool CombineRuleBuilder::emitCodeGenInstructionMatchPattern(
assert(RemappedO.isNamedOperand() == OriginalO.isNamedOperand() &&
"Cannot remap an unnamed operand to a named one!");
+ const auto Ty = RemappedO.getType();
+
const auto OpName =
RemappedO.isNamedOperand() ? RemappedO.getOperandName().str() : "";
@@ -2170,11 +2231,41 @@ bool CombineRuleBuilder::emitCodeGenInstructionMatchPattern(
// RealIdx = expected index in the MachineInstr.
const unsigned RealIdx =
(P.isIntrinsic() && !OriginalO.isDef()) ? (Idx + 1) : Idx;
+
+ if (Ty.isVariadicPack() && M.hasOperand(OpName)) {
+ // TODO: We could add some CheckIsSameOperand opcode variant that checks
+ // all operands. We could also just emit a C++ code snippet lazily to do
+ // the check since it's probably fairly rare that we need to do it.
+ //
+ // I'm just not sure it's worth the effort at this stage.
+ PrintError("each instance of a " + PatternType::VariadicClassName +
+ " operand must have a unique name within the match patterns");
+ PrintNote("'" + OpName + "' is used multiple times");
+ return false;
+ }
+
OperandMatcher &OM =
- IM.addOperand(RealIdx, OpName, AllocatedTemporariesBaseID++);
+ IM.addOperand(RealIdx, OpName, AllocatedTemporariesBaseID++,
+ /*IsVariadic=*/Ty.isVariadicPack());
if (!OpName.empty())
declareOperandExpansion(CE, OM, OriginalO.getOperandName());
+ if (Ty.isVariadicPack()) {
+ // In the presence of variadics, the InstructionMatcher won't insert a
+ // InstructionNumOperandsMatcher implicitly, so we have to emit our own.
+ assert((Idx + 1) == P.operands_size() &&
+ "VariadicPack isn't last operand!");
+ auto VPTI = Ty.getVariadicPackTypeInfo();
+ assert(VPTI.Min > 0 && (VPTI.Max == 0 || VPTI.Max > VPTI.Min));
+ IM.addPredicate<InstructionNumOperandsMatcher>(
+ RealIdx + VPTI.Min, InstructionNumOperandsMatcher::CK_GE);
+ if (VPTI.Max) {
+ IM.addPredicate<InstructionNumOperandsMatcher>(
+ RealIdx + VPTI.Max, InstructionNumOperandsMatcher::CK_LE);
+ }
+ break;
+ }
+
// Handle immediates.
if (RemappedO.hasImmValue()) {
if (isLiteralImm(P, Idx))
@@ -2190,16 +2281,14 @@ bool CombineRuleBuilder::emitCodeGenInstructionMatchPattern(
// for that Operand. "OM" here is always a new OperandMatcher.
//
// Always emit a check for unnamed operands.
- if (OpName.empty() ||
- !M.getOperandMatcher(OpName).contains<LLTOperandMatcher>()) {
- if (const auto Ty = RemappedO.getType()) {
- // TODO: We could support GITypeOf here on the condition that the
- // OperandMatcher exists already. Though it's clunky to make this work
- // and isn't all that useful so it's just rejected in typecheckPatterns
- // at this time.
- assert(Ty.isLLT() && "Only LLTs are supported in match patterns!");
- OM.addPredicate<LLTOperandMatcher>(getLLTCodeGen(Ty));
- }
+ if (Ty && (OpName.empty() ||
+ !M.getOperandMatcher(OpName).contains<LLTOperandMatcher>())) {
+ // TODO: We could support GITypeOf here on the condition that the
+ // OperandMatcher exists already. Though it's clunky to make this work
+ // and isn't all that useful so it's just rejected in typecheckPatterns
+ // at this time.
+ assert(Ty.isLLT());
+ OM.addPredicate<LLTOperandMatcher>(getLLTCodeGen(Ty));
}
// Stop here if the operand is a def, or if it had no name.
@@ -2558,8 +2647,10 @@ GICombinerEmitter::buildMatchTable(MutableArrayRef<RuleMatcher> Rules) {
const Matcher *B) {
auto *L = static_cast<const RuleMatcher *>(A);
auto *R = static_cast<const RuleMatcher *>(B);
- return std::make_tuple(OpcodeOrder[L->getOpcode()], L->getNumOperands()) <
- std::make_tuple(OpcodeOrder[R->getOpcode()], R->getNumOperands());
+ return std::make_tuple(OpcodeOrder[L->getOpcode()],
+ L->insnmatchers_front().getNumOperandMatchers()) <
+ std::make_tuple(OpcodeOrder[R->getOpcode()],
+ R->insnmatchers_front().getNumOperandMatchers());
});
for (Matcher *Rule : InputRules)
diff --git a/llvm/utils/TableGen/GlobalISelEmitter.cpp b/llvm/utils/TableGen/GlobalISelEmitter.cpp
index c29cb4edec181..9b5275b4a1d85 100644
--- a/llvm/utils/TableGen/GlobalISelEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelEmitter.cpp
@@ -2248,8 +2248,8 @@ GlobalISelEmitter::buildMatchTable(MutableArrayRef<RuleMatcher> Rules,
const Matcher *B) {
auto *L = static_cast<const RuleMatcher *>(A);
auto *R = static_cast<const RuleMatcher *>(B);
- return std::tuple(OpcodeOrder[L->getOpcode()], L->getNumOperands()) <
- std::tuple(OpcodeOrder[R->getOpcode()], R->getNumOperands());
+ return std::tuple(OpcodeOrder[L->getOpcode()], L->insnmatchers_front().getNumOperandMatchers()) <
+ std::tuple(OpcodeOrder[R->getOpcode()], R->insnmatchers_front().getNumOperandMatchers());
});
for (Matcher *Rule : InputRules)
More information about the llvm-commits
mailing list