diff --git a/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelector.h b/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelector.h --- a/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelector.h +++ b/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelector.h @@ -196,6 +196,14 @@ /// - PredicateID - The ID of the predicate function to call GIM_CheckCxxInsnPredicate, + /// Check if there's at least one use of the result. + /// - InsnID - Instruction ID + GIM_CheckHasUse, + + /// Check if there's no use of the result. + /// - InsnID - Instruction ID + GIM_CheckHasNoUse, + /// Check the type for the specified operand /// - InsnID - Instruction ID /// - OpIdx - Operand index diff --git a/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h b/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h --- a/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h +++ b/llvm/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h @@ -379,6 +379,33 @@ return false; break; } + case GIM_CheckHasUse: + case GIM_CheckHasNoUse: { + int64_t InsnID = MatchTable[CurrentIdx++]; + + DEBUG_WITH_TYPE(TgtInstructionSelector::getName(), + dbgs() << CurrentIdx << ": GIM_CheckHas{No}Use(MIs[" + << InsnID << "]\n"); + + const MachineInstr *MI = State.MIs[InsnID]; + assert(MI != nullptr && "Used insn before defined"); + assert(MI->getNumDefs() > 0 && "No defs"); + const Register Res = MI->getOperand(0).getReg(); + + if (MatcherOpcode == GIM_CheckHasUse) { + if (MRI.use_nodbg_empty(Res)) { + if (handleReject() == RejectAndGiveUp) + return false; + } + + } else { + if (!MRI.use_nodbg_empty(Res)) { + if (handleReject() == RejectAndGiveUp) + return false; + } + } + break; + } case GIM_CheckAtomicOrdering: { int64_t InsnID = MatchTable[CurrentIdx++]; AtomicOrdering Ordering = (AtomicOrdering)MatchTable[CurrentIdx++]; diff --git a/llvm/include/llvm/Target/TargetSelectionDAG.td b/llvm/include/llvm/Target/TargetSelectionDAG.td --- a/llvm/include/llvm/Target/TargetSelectionDAG.td +++ b/llvm/include/llvm/Target/TargetSelectionDAG.td @@ -789,6 +789,16 @@ // They will be tested prior to the code in pred and must not be used in // ImmLeaf and its subclasses. + // TODO: Support these in SelectionDAG. + // If set to true, a predicate is added that checks for at least one use of + // the result; Otherwise, no predicate is added. + bit HasUse = ?; + // If set to true, a predicate is added that checks for no use of the result; + // Otherwise, no predicate is added. + bit HasNoUse = ?; + // The TableGen backend asserts that both HasUse and HasNoUse is not set to + // true. + // Is the desired pre-packaged predicate for a load? bit IsLoad = ?; // Is the desired pre-packaged predicate for a store? diff --git a/llvm/test/TableGen/GlobalISelEmitter-HasUse.td b/llvm/test/TableGen/GlobalISelEmitter-HasUse.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/GlobalISelEmitter-HasUse.td @@ -0,0 +1,59 @@ +// RUN: llvm-tblgen -gen-global-isel -warn-on-skipped-patterns -I %p/../../include -I %p/Common %s -o - < %s | FileCheck -check-prefix=GISEL %s + +include "llvm/Target/Target.td" +include "GlobalISelEmitterCommon.td" + +// Test the Has{No}Use predicates + +def RET_ATOMIC_ADD : I<(outs GPR32Op:$dst), (ins GPR32Op:$src0, GPR32Op:$src1), []>; +def NO_RET_ATOMIC_ADD : I<(outs), (ins GPR32Op:$src0, GPR32Op:$src1), []>; + +// GISEL: GIM_Try, /*On fail goto*//*Label 0*/ [[L0:[0-9]+]], +// GISEL-NEXT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_ATOMICRMW_ADD, +// GISEL-NEXT: GIM_CheckType, /*MI*/0, /*Op*/0, /*Type*/GILLT_s32, +// GISEL-NEXT: GIM_CheckType, /*MI*/0, /*Op*/2, /*Type*/GILLT_s32, +// GISEL-NEXT: GIM_CheckMemorySizeEqualTo, /*MI*/0, /*MMO*/0, /*Size*/4, +// GISEL-NEXT: GIM_Try, /*On fail goto*//*Label 1*/ [[L1:[0-9]+]], // Rule ID 0 // +// GISEL-NEXT: GIM_CheckHasUse, /*MI*/0, +// GISEL-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/0, /*RC*/MyTarget::GPR32RegClassID, +// GISEL-NEXT: // MIs[0] src0 +// GISEL-NEXT: GIM_CheckPointerToAny, /*MI*/0, /*Op*/1, /*SizeInBits*/0, +// GISEL-NEXT: // (atomic_load_add:{ *:[i32] } iPTR:{ *:[iPTR] }:$src0, i32:{ *:[i32] }:$src1)<> => (RET_ATOMIC_ADD:{ *:[i32] } GPR32:{ *:[i32] }:$src0, GPR32:{ *:[i32] }:$src1) +// GISEL-NEXT: GIR_MutateOpcode, /*InsnID*/0, /*RecycleInsnID*/0, /*Opcode*/MyTarget::RET_ATOMIC_ADD, +// GISEL-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, +// GISEL-NEXT: // GIR_Coverage, 0, +// GISEL-NEXT: GIR_Done, +// GISEL-NEXT: // Label 1: @[[L1]] +// GISEL-NEXT: GIM_Try, /*On fail goto*//*Label 2*/ [[L2:[0-9]+]], // Rule ID 1 // +// GISEL-NEXT: GIM_CheckHasNoUse, /*MI*/0, +// GISEL-NEXT: // MIs[0] src0 +// GISEL-NEXT: GIM_CheckPointerToAny, /*MI*/0, /*Op*/1, /*SizeInBits*/0, +// GISEL-NEXT: // (atomic_load_add:{ *:[i32] } iPTR:{ *:[iPTR] }:$src0, i32:{ *:[i32] }:$src1)<> => (NO_RET_ATOMIC_ADD GPR32:{ *:[i32] }:$src0, GPR32:{ *:[i32] }:$src1) +// GISEL-NEXT: GIR_BuildMI, /*InsnID*/0, /*Opcode*/MyTarget::NO_RET_ATOMIC_ADD, +// GISEL-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/1, // src0 +// GISEL-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/2, // src1 +// GISEL-NEXT: GIR_MergeMemOperands, /*InsnID*/0, /*MergeInsnID's*/0, GIU_MergeMemOperands_EndOfList, +// GISEL-NEXT: GIR_EraseFromParent, /*InsnID*/0, +// GISEL-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, +// GISEL-NEXT: // GIR_Coverage, 1, +// GISEL-NEXT: GIR_Done, +// GISEL-NEXT: // Label 2: @[[L2]] +// GISEL-NEXT: GIM_Reject, +// GISEL-NEXT: // Label 0: @[[L0]] +// GISEL-NEXT: GIM_Reject, + +let HasUse = true in +defm atomic_load_add_ret : binary_atomic_op; + +def : Pat < + (atomic_load_add_ret_32 iPTR:$src0, i32:$src1), + (RET_ATOMIC_ADD GPR32:$src0, GPR32:$src1) +>; + +let HasNoUse = true in +defm atomic_load_add_no_ret : binary_atomic_op; + +def : Pat < + (atomic_load_add_no_ret_32 iPTR:$src0, i32:$src1), + (NO_RET_ATOMIC_ADD GPR32:$src0, GPR32:$src1) +>; diff --git a/llvm/utils/TableGen/CodeGenDAGPatterns.h b/llvm/utils/TableGen/CodeGenDAGPatterns.h --- a/llvm/utils/TableGen/CodeGenDAGPatterns.h +++ b/llvm/utils/TableGen/CodeGenDAGPatterns.h @@ -538,6 +538,11 @@ // Predicate code uses the PatFrag's captured operands. bool usesOperands() const; + // Check if the HasUse predicate is set. + bool hasUse() const; + // Check if the HasNoUse predicate is set. + bool hasNoUse() const; + // Is the desired predefined predicate for a load? bool isLoad() const; // Is the desired predefined predicate for a store? diff --git a/llvm/utils/TableGen/CodeGenDAGPatterns.cpp b/llvm/utils/TableGen/CodeGenDAGPatterns.cpp --- a/llvm/utils/TableGen/CodeGenDAGPatterns.cpp +++ b/llvm/utils/TableGen/CodeGenDAGPatterns.cpp @@ -1167,6 +1167,12 @@ bool TreePredicateFn::usesOperands() const { return isPredefinedPredicateEqualTo("PredicateCodeUsesOperands", true); } +bool TreePredicateFn::hasUse() const { + return isPredefinedPredicateEqualTo("HasUse", true); +} +bool TreePredicateFn::hasNoUse() const { + return isPredefinedPredicateEqualTo("HasNoUse", true); +} bool TreePredicateFn::isLoad() const { return isPredefinedPredicateEqualTo("IsLoad", true); } diff --git a/llvm/utils/TableGen/GlobalISelEmitter.cpp b/llvm/utils/TableGen/GlobalISelEmitter.cpp --- a/llvm/utils/TableGen/GlobalISelEmitter.cpp +++ b/llvm/utils/TableGen/GlobalISelEmitter.cpp @@ -331,6 +331,9 @@ if (Predicate.isImmediatePattern()) continue; + if (Predicate.hasUse() || Predicate.hasNoUse()) + continue; + if (Predicate.isNonExtLoad() || Predicate.isAnyExtLoad() || Predicate.isSignExtLoad() || Predicate.isZeroExtLoad()) continue; @@ -1119,6 +1122,7 @@ IPM_MemoryAddressSpace, IPM_MemoryAlignment, IPM_VectorSplatImm, + IPM_Use, IPM_GenericPredicate, OPM_SameOperand, OPM_ComplexPattern, @@ -2238,6 +2242,36 @@ } }; +/// Generates code to check for use of the result. +class UsePredicateMatcher : public InstructionPredicateMatcher { +protected: + // TODO? Generalize this to support checking of one use. + bool HasUse; + +public: + UsePredicateMatcher(unsigned InsnVarID, bool HasUse) + : InstructionPredicateMatcher(IPM_Use, InsnVarID), HasUse(HasUse) {} + + static bool classof(const PredicateMatcher *P) { + return P->getKind() == IPM_Use; + } + + bool isIdentical(const PredicateMatcher &B) const override { + return HasUse == cast(&B)->HasUse; + } + + void emitPredicateOpcodes(MatchTable &Table, + RuleMatcher &Rule) const override { + StringRef Opcode = HasUse ? "GIM_CheckHasUse" : "GIM_CheckHasNoUse"; + + Table << MatchTable::Opcode(Opcode) << MatchTable::Comment("MI") + << MatchTable::IntValue(InsnVarID) << MatchTable::LineBreak; + } + + bool hasUse() const { return HasUse; } + bool hasNoUse() const { return !HasUse; } +}; + /// Generates code to check that a set of predicates and operands match for a /// particular instruction. /// @@ -4000,6 +4034,23 @@ if (auto Error = InsnMatcherOrError.takeError()) return std::move(Error); + // FIXME: This should be part of addBuiltinPredicates(). If we add this at + // the start of addBuiltinPredicates() without returning, then there might + // be cases where we hit the last return before which the + // HasAddedBuiltinMatcher will be set to false. The predicate could be + // missed if we add it in the middle or at the end due to return statements + // after the addPredicate<>() calls. + if (Predicate.hasUse()) { + assert(!Predicate.hasNoUse() && + "Both HasUse and HasNoUse is set to true"); + InsnMatcher.addPredicate(/* HasUse */ true); + HasAddedBuiltinMatcher = true; + } else if (Predicate.hasNoUse()) { + assert(!Predicate.hasUse() && "Both HasUse and HasNoUse is set to true"); + InsnMatcher.addPredicate(/* HasUse */ false); + HasAddedBuiltinMatcher = true; + } + if (Predicate.hasGISelPredicateCode()) { if (Predicate.usesOperands()) { assert(WaitingForNamedOperands == 0 && @@ -5206,15 +5257,38 @@ auto &DstI = Target.getInstruction(DstOp); StringRef DstIName = DstI.TheDef->getName(); - if (DstI.Operands.NumDefs < Src->getExtTypes().size()) - return failedImport("Src pattern result has more defs than dst MI (" + - to_string(Src->getExtTypes().size()) + " def(s) vs " + - to_string(DstI.Operands.NumDefs) + " def(s))"); + unsigned DstNumDefs = DstI.Operands.NumDefs, + SrcNumDefs = Src->getExtTypes().size(); + if (DstNumDefs < SrcNumDefs) { + if (DstNumDefs != 0) + return failedImport("Src pattern result has more defs than dst MI (" + + to_string(SrcNumDefs) + " def(s) vs " + + to_string(DstNumDefs) + " def(s))"); + + const UsePredicateMatcher *UsePred = nullptr; + for (const auto &Pred : InsnMatcher.predicates()) { + if ((UsePred = dyn_cast(Pred.get()))) { + if (UsePred->hasUse()) { + return failedImport("Src pattern result has " + + to_string(SrcNumDefs) + + " def(s) with the HasUse predicate set to true " + "but Dst MI has no def"); + } + break; + } + } + if (!UsePred) + return failedImport("Src pattern result has " + to_string(SrcNumDefs) + + " def(s) without the HasNoUse predicate set " + "to true but Dst MI has no def"); + } // The root of the match also has constraints on the register bank so that it // matches the result instruction. unsigned OpIdx = 0; - for (const TypeSetByHwMode &VTy : Src->getExtTypes()) { + unsigned N = DstNumDefs < SrcNumDefs ? DstNumDefs : SrcNumDefs; + for (unsigned I = 0; I < N; ++I) { + const TypeSetByHwMode &VTy = Src->getExtType(I); (void)VTy; const auto &DstIOperand = DstI.Operands[OpIdx];