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,10 @@ /// - PredicateID - The ID of the predicate function to call GIM_CheckCxxInsnPredicate, + /// Check if there's no use of the first 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,25 @@ return false; break; } + case GIM_CheckHasNoUse: { + int64_t InsnID = MatchTable[CurrentIdx++]; + + DEBUG_WITH_TYPE(TgtInstructionSelector::getName(), + dbgs() << CurrentIdx << ": GIM_CheckHasNoUse(MIs[" + << InsnID << "]\n"); + + const MachineInstr *MI = State.MIs[InsnID]; + assert(MI && "Used insn before defined"); + assert(MI->getNumDefs() > 0 && "No defs"); + const Register Res = MI->getOperand(0).getReg(); + + 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 @@ -805,6 +805,10 @@ // They will be tested prior to the code in pred and must not be used in // ImmLeaf and its subclasses. + // If set to true, a predicate is added that checks for the absence of use of + // the first result. + bit HasNoUse = ?; + // 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/HasNoUse.td b/llvm/test/TableGen/HasNoUse.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/HasNoUse.td @@ -0,0 +1,39 @@ +// RUN: llvm-tblgen -gen-dag-isel -I %p/../../include -I %p/Common %s -o - < %s | FileCheck -check-prefix=SDAG %s +// 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 HasNoUse predicate + +def NO_RET_ATOMIC_ADD : I<(outs), (ins GPR32Op:$src0, GPR32Op:$src1), []>; + +// SDAG: case 0: { +// SDAG-NEXT: // Predicate_atomic_load_add_no_ret_32 +// SDAG-NEXT: SDNode *N = Node; +// SDAG-NEXT: (void)N; +// SDAG-NEXT: if (cast(N)->getMemoryVT() != MVT::i32) return false; +// SDAG-NEXT: if (!SDValue(N, 0).use_empty()) return false; +// SDAG-NEXT: return true; + +// GISEL: 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_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, +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,9 @@ // Predicate code uses the PatFrag's captured operands. bool usesOperands() 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 @@ -902,7 +902,7 @@ } bool TreePredicateFn::hasPredCode() const { - return isLoad() || isStore() || isAtomic() || + return isLoad() || isStore() || isAtomic() || hasNoUse() || !PatFragRec->getRecord()->getValueAsString("PredicateCode").empty(); } @@ -1124,6 +1124,9 @@ .str(); } + if (hasNoUse()) + Code += "if (!SDValue(N, 0).use_empty()) return false;\n"; + std::string PredicateCode = std::string(PatFragRec->getRecord()->getValueAsString("PredicateCode")); @@ -1167,6 +1170,9 @@ bool TreePredicateFn::usesOperands() const { return isPredefinedPredicateEqualTo("PredicateCodeUsesOperands", 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.hasNoUse()) + continue; + if (Predicate.isNonExtLoad() || Predicate.isAnyExtLoad() || Predicate.isSignExtLoad() || Predicate.isZeroExtLoad()) continue; @@ -1119,6 +1122,7 @@ IPM_MemoryAddressSpace, IPM_MemoryAlignment, IPM_VectorSplatImm, + IPM_NoUse, IPM_GenericPredicate, OPM_SameOperand, OPM_ComplexPattern, @@ -2238,6 +2242,29 @@ } }; +/// Generates code to check for the absence of use of the result. +// TODO? Generalize this to support checking for one use. +class NoUsePredicateMatcher : public InstructionPredicateMatcher { +public: + NoUsePredicateMatcher(unsigned InsnVarID) + : InstructionPredicateMatcher(IPM_NoUse, InsnVarID) {} + + static bool classof(const PredicateMatcher *P) { + return P->getKind() == IPM_NoUse; + } + + bool isIdentical(const PredicateMatcher &B) const override { + return InstructionPredicateMatcher::isIdentical(B); + } + + void emitPredicateOpcodes(MatchTable &Table, + RuleMatcher &Rule) const override { + Table << MatchTable::Opcode("GIM_CheckHasNoUse") + << MatchTable::Comment("MI") << MatchTable::IntValue(InsnVarID) + << MatchTable::LineBreak; + } +}; + /// Generates code to check that a set of predicates and operands match for a /// particular instruction. /// @@ -4000,6 +4027,17 @@ 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.hasNoUse()) { + InsnMatcher.addPredicate(); + HasAddedBuiltinMatcher = true; + } + if (Predicate.hasGISelPredicateCode()) { if (Predicate.usesOperands()) { assert(WaitingForNamedOperands == 0 && @@ -5206,16 +5244,31 @@ 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))"); + + bool FoundNoUsePred = false; + for (const auto &Pred : InsnMatcher.predicates()) { + if ((FoundNoUsePred = isa(Pred.get()))) + break; + } + if (!FoundNoUsePred) + 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()) { - (void)VTy; + unsigned N = std::min(DstNumDefs, SrcNumDefs); + for (unsigned I = 0; I < N; ++I) { + const TypeSetByHwMode &VTy = Src->getExtType(I); const auto &DstIOperand = DstI.Operands[OpIdx]; Record *DstIOpRec = DstIOperand.Rec;