forked from OSchip/llvm-project
[GlobalISel][SelectionDAG] Implement the HasNoUse builtin predicate
This change introduces the HasNoUse builtin predicate in PatFrags that checks for the absence of use of the first result operand. GlobalISelEmitter will allow source PatFrags with this predicate to be matched with destination instructions with empty outs. This predicate is required for selecting the no-return variant of atomic instructions in AMDGPU. Differential Revision: https://reviews.llvm.org/D125212
This commit is contained in:
parent
7504c7a877
commit
c42fe5bd7a
|
@ -196,6 +196,10 @@ enum {
|
|||
/// - 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
|
||||
|
|
|
@ -379,6 +379,25 @@ bool InstructionSelector::executeMatchTable(
|
|||
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++];
|
||||
|
|
|
@ -809,6 +809,10 @@ class PatFrags<dag ops, list<dag> frags, code pred = [{}],
|
|||
// 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?
|
||||
|
|
|
@ -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<MemSDNode>(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)<<P:Predicate_atomic_load_add_no_ret_32>> => (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<atomic_load_add>;
|
||||
|
||||
def : Pat <
|
||||
(atomic_load_add_no_ret_32 iPTR:$src0, i32:$src1),
|
||||
(NO_RET_ATOMIC_ADD GPR32:$src0, GPR32:$src1)
|
||||
>;
|
|
@ -932,7 +932,7 @@ TreePredicateFn::TreePredicateFn(TreePattern *N) : PatFragRec(N) {
|
|||
}
|
||||
|
||||
bool TreePredicateFn::hasPredCode() const {
|
||||
return isLoad() || isStore() || isAtomic() ||
|
||||
return isLoad() || isStore() || isAtomic() || hasNoUse() ||
|
||||
!PatFragRec->getRecord()->getValueAsString("PredicateCode").empty();
|
||||
}
|
||||
|
||||
|
@ -1154,6 +1154,9 @@ std::string TreePredicateFn::getPredCode() const {
|
|||
.str();
|
||||
}
|
||||
|
||||
if (hasNoUse())
|
||||
Code += "if (!SDValue(N, 0).use_empty()) return false;\n";
|
||||
|
||||
std::string PredicateCode =
|
||||
std::string(PatFragRec->getRecord()->getValueAsString("PredicateCode"));
|
||||
|
||||
|
@ -1197,6 +1200,9 @@ bool TreePredicateFn::isPredefinedPredicateEqualTo(StringRef Field,
|
|||
bool TreePredicateFn::usesOperands() const {
|
||||
return isPredefinedPredicateEqualTo("PredicateCodeUsesOperands", true);
|
||||
}
|
||||
bool TreePredicateFn::hasNoUse() const {
|
||||
return isPredefinedPredicateEqualTo("HasNoUse", true);
|
||||
}
|
||||
bool TreePredicateFn::isLoad() const {
|
||||
return isPredefinedPredicateEqualTo("IsLoad", true);
|
||||
}
|
||||
|
|
|
@ -541,6 +541,9 @@ public:
|
|||
// 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?
|
||||
|
|
|
@ -331,6 +331,9 @@ static Error isTrivialOperatorNode(const TreePatternNode *N) {
|
|||
if (Predicate.isImmediatePattern())
|
||||
continue;
|
||||
|
||||
if (Predicate.hasNoUse())
|
||||
continue;
|
||||
|
||||
if (Predicate.isNonExtLoad() || Predicate.isAnyExtLoad() ||
|
||||
Predicate.isSignExtLoad() || Predicate.isZeroExtLoad())
|
||||
continue;
|
||||
|
@ -1119,6 +1122,7 @@ public:
|
|||
IPM_MemoryAddressSpace,
|
||||
IPM_MemoryAlignment,
|
||||
IPM_VectorSplatImm,
|
||||
IPM_NoUse,
|
||||
IPM_GenericPredicate,
|
||||
OPM_SameOperand,
|
||||
OPM_ComplexPattern,
|
||||
|
@ -2238,6 +2242,29 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/// 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 @@ Expected<InstructionMatcher &> GlobalISelEmitter::createAndImportSelDAGMatcher(
|
|||
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<NoUsePredicateMatcher>();
|
||||
HasAddedBuiltinMatcher = true;
|
||||
}
|
||||
|
||||
if (Predicate.hasGISelPredicateCode()) {
|
||||
if (Predicate.usesOperands()) {
|
||||
assert(WaitingForNamedOperands == 0 &&
|
||||
|
@ -5206,16 +5244,31 @@ Expected<RuleMatcher> GlobalISelEmitter::runOnPattern(const PatternToMatch &P) {
|
|||
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<NoUsePredicateMatcher>(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;
|
||||
|
|
Loading…
Reference in New Issue