TableGen/GlobalISel: Partially handle immAllOnesV/immAllZerosV

These should really match either G_BUILD_VECTOR or
G_BUILD_VECTOR_TRUNC, but there doesn't seem to be an existing
mechanism for matching alternative opcodes. There is GIM_SwitchOpcode,
but it seems to assume it's oly only used for matcher optimization.

I could also omit any opcode check and rely on the matcher directly
checking the opcode, but the table optimizer currently assumes there
has to be an opcode check.

Also doesn't try to handle undef elements like the DAG version.
This commit is contained in:
Matt Arsenault 2020-08-01 10:39:21 -04:00 committed by Matt Arsenault
parent e9eb2dc332
commit 5c5e6d951e
10 changed files with 208 additions and 22 deletions

View File

@ -164,6 +164,15 @@ enum {
GIM_CheckMemorySizeEqualToLLT,
GIM_CheckMemorySizeLessThanLLT,
GIM_CheckMemorySizeGreaterThanLLT,
/// Check if this is a vector that can be treated as a vector splat
/// constant. This is valid for both G_BUILD_VECTOR as well as
/// G_BUILD_VECTOR_TRUNC. For AllOnes refers to individual bits, so a -1
/// element.
/// - InsnID - Instruction ID
GIM_CheckIsBuildVectorAllOnes,
GIM_CheckIsBuildVectorAllZeros,
/// Check a generic C++ instruction predicate
/// - InsnID - Instruction ID
/// - PredicateID - The ID of the predicate function to call

View File

@ -321,6 +321,35 @@ bool InstructionSelector::executeMatchTable(
return false;
break;
}
case GIM_CheckIsBuildVectorAllOnes:
case GIM_CheckIsBuildVectorAllZeros: {
int64_t InsnID = MatchTable[CurrentIdx++];
DEBUG_WITH_TYPE(TgtInstructionSelector::getName(),
dbgs() << CurrentIdx
<< ": GIM_CheckBuildVectorAll{Zeros|Ones}(MIs["
<< InsnID << "])\n");
assert(State.MIs[InsnID] != nullptr && "Used insn before defined");
const MachineInstr *MI = State.MIs[InsnID];
assert((MI->getOpcode() == TargetOpcode::G_BUILD_VECTOR ||
MI->getOpcode() == TargetOpcode::G_BUILD_VECTOR_TRUNC) &&
"Expected G_BUILD_VECTOR or G_BUILD_VECTOR_TRUNC");
if (MatcherOpcode == GIM_CheckIsBuildVectorAllOnes) {
if (!isBuildVectorAllOnes(*MI, MRI)) {
if (handleReject() == RejectAndGiveUp)
return false;
}
} else {
if (!isBuildVectorAllZeros(*MI, MRI)) {
if (handleReject() == RejectAndGiveUp)
return false;
}
}
break;
}
case GIM_CheckCxxInsnPredicate: {
int64_t InsnID = MatchTable[CurrentIdx++];
int64_t Predicate = MatchTable[CurrentIdx++];

View File

@ -228,5 +228,15 @@ LLT getGCDType(LLT OrigTy, LLT TargetTy);
/// If \p MI is not a splat, returns None.
Optional<int> getSplatIndex(MachineInstr &MI);
/// Return true if the specified instruction is a G_BUILD_VECTOR or
/// G_BUILD_VECTOR_TRUNC where all of the elements are 0 or undef.
bool isBuildVectorAllZeros(const MachineInstr &MI,
const MachineRegisterInfo &MRI);
/// Return true if the specified instruction is a G_BUILD_VECTOR or
/// G_BUILD_VECTOR_TRUNC where all of the elements are ~0 or undef.
bool isBuildVectorAllOnes(const MachineInstr &MI,
const MachineRegisterInfo &MRI);
} // End namespace llvm.
#endif

View File

@ -13,6 +13,7 @@
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/Twine.h"
#include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h"
#include "llvm/CodeGen/GlobalISel/MIPatternMatch.h"
#include "llvm/CodeGen/GlobalISel/RegisterBankInfo.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
@ -27,6 +28,7 @@
#define DEBUG_TYPE "globalisel-utils"
using namespace llvm;
using namespace MIPatternMatch;
Register llvm::constrainRegToClass(MachineRegisterInfo &MRI,
const TargetInstrInfo &TII,
@ -667,3 +669,37 @@ Optional<int> llvm::getSplatIndex(MachineInstr &MI) {
return SplatValue;
}
static bool isBuildVectorOp(unsigned Opcode) {
return Opcode == TargetOpcode::G_BUILD_VECTOR ||
Opcode == TargetOpcode::G_BUILD_VECTOR_TRUNC;
}
// TODO: Handle mixed undef elements.
static bool isBuildVectorConstantSplat(const MachineInstr &MI,
const MachineRegisterInfo &MRI,
int64_t SplatValue) {
if (!isBuildVectorOp(MI.getOpcode()))
return false;
const unsigned NumOps = MI.getNumOperands();
for (unsigned I = 1; I != NumOps; ++I) {
Register Element = MI.getOperand(I).getReg();
int64_t ElementValue;
if (!mi_match(Element, MRI, m_ICst(ElementValue)) ||
ElementValue != SplatValue)
return false;
}
return true;
}
bool llvm::isBuildVectorAllZeros(const MachineInstr &MI,
const MachineRegisterInfo &MRI) {
return isBuildVectorConstantSplat(MI, MRI, 0);
}
bool llvm::isBuildVectorAllOnes(const MachineInstr &MI,
const MachineRegisterInfo &MRI) {
return isBuildVectorConstantSplat(MI, MRI, -1);
}

View File

@ -956,8 +956,8 @@ define <2 x i32> @abspattern1(<2 x i32> %a) nounwind {
; DAG: abs.2s
; DAG-NEXT: ret
; GISEL: neg.2s
; GISEL: cmge.2s
; GISEL: sub.2s
; GISEL: fcsel
; GISEL: fcsel
%tmp1neg = sub <2 x i32> zeroinitializer, %a
@ -1038,8 +1038,8 @@ define <2 x i64> @abspattern7(<2 x i64> %a) nounwind {
; DAG: abs.2d
; DAG-NEXT: ret
; GISEL: neg.2d
; GISEL: cmge.2d
; GISEL: sub.2d
; GISEL: fcsel
; GISEL: fcsel
%tmp1neg = sub <2 x i64> zeroinitializer, %a

View File

@ -178,10 +178,7 @@ define amdgpu_ps <2 x float> @v_andn2_i64_vs(i64 %src0, i64 inreg %src1) {
define amdgpu_ps <2 x i32> @s_andn2_v2i32(<2 x i32> inreg %src0, <2 x i32> inreg %src1) {
; GCN-LABEL: s_andn2_v2i32:
; GCN: ; %bb.0:
; GCN-NEXT: s_mov_b32 s0, -1
; GCN-NEXT: s_mov_b32 s1, s0
; GCN-NEXT: s_xor_b64 s[0:1], s[4:5], s[0:1]
; GCN-NEXT: s_and_b64 s[0:1], s[2:3], s[0:1]
; GCN-NEXT: s_andn2_b64 s[0:1], s[2:3], s[4:5]
; GCN-NEXT: ; return to shader part epilog
%not.src1 = xor <2 x i32> %src1, <i32 -1, i32 -1>
%and = and <2 x i32> %src0, %not.src1
@ -191,10 +188,7 @@ define amdgpu_ps <2 x i32> @s_andn2_v2i32(<2 x i32> inreg %src0, <2 x i32> inreg
define amdgpu_ps <2 x i32> @s_andn2_v2i32_commute(<2 x i32> inreg %src0, <2 x i32> inreg %src1) {
; GCN-LABEL: s_andn2_v2i32_commute:
; GCN: ; %bb.0:
; GCN-NEXT: s_mov_b32 s0, -1
; GCN-NEXT: s_mov_b32 s1, s0
; GCN-NEXT: s_xor_b64 s[0:1], s[4:5], s[0:1]
; GCN-NEXT: s_and_b64 s[0:1], s[0:1], s[2:3]
; GCN-NEXT: s_andn2_b64 s[0:1], s[2:3], s[4:5]
; GCN-NEXT: ; return to shader part epilog
%not.src1 = xor <2 x i32> %src1, <i32 -1, i32 -1>
%and = and <2 x i32> %not.src1, %src0

View File

@ -178,10 +178,7 @@ define amdgpu_ps <2 x float> @v_orn2_i64_vs(i64 %src0, i64 inreg %src1) {
define amdgpu_ps <2 x i32> @s_orn2_v2i32(<2 x i32> inreg %src0, <2 x i32> inreg %src1) {
; GCN-LABEL: s_orn2_v2i32:
; GCN: ; %bb.0:
; GCN-NEXT: s_mov_b32 s0, -1
; GCN-NEXT: s_mov_b32 s1, s0
; GCN-NEXT: s_xor_b64 s[0:1], s[4:5], s[0:1]
; GCN-NEXT: s_or_b64 s[0:1], s[2:3], s[0:1]
; GCN-NEXT: s_orn2_b64 s[0:1], s[2:3], s[4:5]
; GCN-NEXT: ; return to shader part epilog
%not.src1 = xor <2 x i32> %src1, <i32 -1, i32 -1>
%or = or <2 x i32> %src0, %not.src1
@ -191,10 +188,7 @@ define amdgpu_ps <2 x i32> @s_orn2_v2i32(<2 x i32> inreg %src0, <2 x i32> inreg
define amdgpu_ps <2 x i32> @s_orn2_v2i32_commute(<2 x i32> inreg %src0, <2 x i32> inreg %src1) {
; GCN-LABEL: s_orn2_v2i32_commute:
; GCN: ; %bb.0:
; GCN-NEXT: s_mov_b32 s0, -1
; GCN-NEXT: s_mov_b32 s1, s0
; GCN-NEXT: s_xor_b64 s[0:1], s[4:5], s[0:1]
; GCN-NEXT: s_or_b64 s[0:1], s[0:1], s[2:3]
; GCN-NEXT: s_orn2_b64 s[0:1], s[2:3], s[4:5]
; GCN-NEXT: ; return to shader part epilog
%not.src1 = xor <2 x i32> %src1, <i32 -1, i32 -1>
%or = or <2 x i32> %not.src1, %src0

View File

@ -16,6 +16,10 @@ def B0 : Register<"b0"> { let Namespace = "MyTarget"; }
def GPR8 : RegisterClass<"MyTarget", [i8], 8, (add B0)>;
def GPR8Op : RegisterOperand<GPR8>;
def V0 : Register<"v0"> { let Namespace = "MyTarget"; }
def VecReg128 : RegisterClass<"MyTarget", [v4i32], 128, (add V0)>;
def p0 : PtrValueType <i32, 0>;
class I<dag OOps, dag IOps, list<dag> Pat>

View File

@ -0,0 +1,45 @@
// RUN: llvm-tblgen -gen-global-isel -warn-on-skipped-patterns -optimize-match-table=false -I %p/../../include -I %p/Common %s -o - | FileCheck -check-prefixes=GISEL-NOOPT,GISEL %s
// RUN: llvm-tblgen -gen-global-isel -warn-on-skipped-patterns -optimize-match-table=true -I %p/../../include -I %p/Common %s -o - | FileCheck -check-prefixes=GISEL-OPT,GISEL %s
include "llvm/Target/Target.td"
include "GlobalISelEmitterCommon.td"
// GISEL-OPT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_v4s16,
// GISEL-OPT: GIM_CheckOpcode, /*MI*/1, TargetOpcode::G_BUILD_VECTOR,
// GISEL-OPT: GIM_CheckIsBuildVectorAllZeros, /*MI*/1,
// GISEL-OPT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_v4s16,
// GISEL-OPT: GIM_CheckOpcode, /*MI*/1, TargetOpcode::G_BUILD_VECTOR,
// GISEL-OPT: GIM_CheckIsBuildVectorAllOnes, /*MI*/1,
// GISEL-NOOPT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_LSHR,
// GISEL-NOOPT: // MIs[0] Operand 2
// GISEL-NOOPT-NEXT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_v4s16,
// GISEL-NOOPT-NEXT: GIM_RecordInsn, /*DefineMI*/1, /*MI*/0, /*OpIdx*/2, // MIs[1]
// GISEL-NOOPT-NEXT: GIM_CheckOpcode, /*MI*/1, TargetOpcode::G_BUILD_VECTOR,
// GISEL-NOOPT-NEXT: GIM_CheckIsBuildVectorAllOnes, /*MI*/1,
// GISEL-NOOPT-NEXT: // MIs[1] Operand 0
// GISEL-NOOPT-NEXT: GIM_CheckType, /*MI*/1, /*Op*/0, /*Type*/GILLT_v4s16,
// GISEL-NOOPT-NEXT: GIM_CheckIsSafeToFold, /*InsnID*/1,
// GISEL-NOOPT-NEXT: // (srl:{ *:[v4i32] } v4i32:{ *:[v4i32] }:$src0, immAllOnesV:{ *:[v4i16] }) => (VFOOONES:{ *:[v4i32] } v4i32:{ *:[v4i32] }:$src0)
def VFOOONES : I<(outs VecReg128:$dst), (ins VecReg128:$src0),
[(set v4i32:$dst, (srl v4i32:$src0, (v4i16 immAllOnesV)))]
>;
// GISEL-NOOPT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_SHL,
// GISEL-NOOPT: // MIs[0] Operand 2
// GISEL-NOOPT-NEXT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_v4s16,
// GISEL-NOOPT-NEXT: GIM_RecordInsn, /*DefineMI*/1, /*MI*/0, /*OpIdx*/2, // MIs[1]
// GISEL-NOOPT-NEXT: GIM_CheckOpcode, /*MI*/1, TargetOpcode::G_BUILD_VECTOR,
// GISEL-NOOPT-NEXT: GIM_CheckIsBuildVectorAllZeros, /*MI*/1,
// GISEL-NOOPT-NEXT: // MIs[1] Operand 0
// GISEL-NOOPT-NEXT: GIM_CheckType, /*MI*/1, /*Op*/0, /*Type*/GILLT_v4s16,
// GISEL-NOOPT-NEXT: GIM_CheckIsSafeToFold, /*InsnID*/1,
// GISEL-NOOPT-NEXT: // (shl:{ *:[v4i32] } v4i32:{ *:[v4i32] }:$src0, immAllZerosV:{ *:[v4i16] }) => (VFOOZERO:{ *:[v4i32] } v4i32:{ *:[v4i32] }:$src0)
def VFOOZERO : I<(outs VecReg128:$dst), (ins VecReg128:$src0),
[(set v4i32:$dst, (shl v4i32:$src0, (v4i16 immAllZerosV)))]
>;

View File

@ -1089,6 +1089,7 @@ public:
IPM_MemoryVsLLTSize,
IPM_MemoryAddressSpace,
IPM_MemoryAlignment,
IPM_VectorSplatImm,
IPM_GenericPredicate,
OPM_SameOperand,
OPM_ComplexPattern,
@ -2021,6 +2022,42 @@ public:
}
};
// Matcher for immAllOnesV/immAllZerosV
class VectorSplatImmPredicateMatcher : public InstructionPredicateMatcher {
public:
enum SplatKind {
AllZeros,
AllOnes
};
private:
SplatKind Kind;
public:
VectorSplatImmPredicateMatcher(unsigned InsnVarID, SplatKind K)
: InstructionPredicateMatcher(IPM_VectorSplatImm, InsnVarID), Kind(K) {}
static bool classof(const PredicateMatcher *P) {
return P->getKind() == IPM_VectorSplatImm;
}
bool isIdentical(const PredicateMatcher &B) const override {
return InstructionPredicateMatcher::isIdentical(B) &&
Kind == static_cast<const VectorSplatImmPredicateMatcher &>(B).Kind;
}
void emitPredicateOpcodes(MatchTable &Table,
RuleMatcher &Rule) const override {
if (Kind == AllOnes)
Table << MatchTable::Opcode("GIM_CheckIsBuildVectorAllOnes");
else
Table << MatchTable::Opcode("GIM_CheckIsBuildVectorAllZeros");
Table << MatchTable::Comment("MI") << MatchTable::IntValue(InsnVarID);
Table << MatchTable::LineBreak;
}
};
/// Generates code to check an arbitrary C++ instruction predicate.
class GenericInstructionPredicateMatcher : public InstructionPredicateMatcher {
protected:
@ -2077,8 +2114,9 @@ protected:
SmallVector<std::pair<Record *, unsigned>, 2> PhysRegInputs;
public:
InstructionMatcher(RuleMatcher &Rule, StringRef SymbolicName)
: Rule(Rule), SymbolicName(SymbolicName) {
InstructionMatcher(RuleMatcher &Rule, StringRef SymbolicName,
bool NumOpsCheck = true)
: Rule(Rule), NumOperandsCheck(NumOpsCheck), SymbolicName(SymbolicName) {
// We create a new instruction matcher.
// Get a new ID for that instruction.
InsnVarID = Rule.implicitlyDefineInsnVar(*this);
@ -2267,9 +2305,10 @@ protected:
public:
InstructionOperandMatcher(unsigned InsnVarID, unsigned OpIdx,
RuleMatcher &Rule, StringRef SymbolicName)
RuleMatcher &Rule, StringRef SymbolicName,
bool NumOpsCheck = true)
: OperandPredicateMatcher(OPM_Instruction, InsnVarID, OpIdx),
InsnMatcher(new InstructionMatcher(Rule, SymbolicName)) {}
InsnMatcher(new InstructionMatcher(Rule, SymbolicName, NumOpsCheck)) {}
static bool classof(const PredicateMatcher *P) {
return P->getKind() == OPM_Instruction;
@ -4078,6 +4117,32 @@ Error GlobalISelEmitter::importChildMatcher(
if (ChildRec->getName() == "srcvalue")
return Error::success();
const bool ImmAllOnesV = ChildRec->getName() == "immAllOnesV";
if (ImmAllOnesV || ChildRec->getName() == "immAllZerosV") {
auto MaybeInsnOperand = OM.addPredicate<InstructionOperandMatcher>(
InsnMatcher.getRuleMatcher(), SrcChild->getName(), false);
InstructionOperandMatcher &InsnOperand = **MaybeInsnOperand;
ValueTypeByHwMode VTy = ChildTypes.front().getValueTypeByHwMode();
InsnOperand.getInsnMatcher().addPredicate<InstructionOpcodeMatcher>(
&Target.getInstruction(RK.getDef("G_BUILD_VECTOR")));
// TODO: Handle both G_BUILD_VECTOR and G_BUILD_VECTOR_TRUNC We could
// theoretically not emit any opcode check, but getOpcodeMatcher currently
// has to succeed.
OperandMatcher &OM =
InsnOperand.getInsnMatcher().addOperand(0, "", TempOpIdx);
if (auto Error =
OM.addTypeCheckPredicate(VTy, false /* OperandIsAPointer */))
return failedImport(toString(std::move(Error)) +
" for result of Src pattern operator");
InsnOperand.getInsnMatcher().addPredicate<VectorSplatImmPredicateMatcher>(
ImmAllOnesV ? VectorSplatImmPredicateMatcher::AllOnes
: VectorSplatImmPredicateMatcher::AllZeros);
return Error::success();
}
return failedImport(
"Src pattern child def is an unsupported tablegen class");
}