[TableGen] HasOneUse builtin predicate on PatFrags (#91578)

This predicate tells GlobalISelEmitter and DAGISelEmitter to check that
the instruction to emit has only one use of its result. This can be used
on a PatFrag instead of defining custom predicates for both emitters per
record that requires it.
This commit is contained in:
jofrn 2024-05-20 06:18:49 -08:00 committed by GitHub
parent 1553b21f6d
commit d0dc29c208
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 84 additions and 12 deletions

View File

@ -212,6 +212,10 @@ enum {
/// - InsnID(ULEB128) - Instruction ID
GIM_CheckHasNoUse,
/// Check if there's one use of the first result.
/// - InsnID(ULEB128) - Instruction ID
GIM_CheckHasOneUse,
/// Check the type for the specified operand
/// - InsnID(ULEB128) - Instruction ID
/// - OpIdx(ULEB128) - Operand index

View File

@ -468,7 +468,24 @@ bool GIMatchTableExecutor::executeMatchTable(
if (handleReject() == RejectAndGiveUp)
return false;
}
break;
}
case GIM_CheckHasOneUse: {
uint64_t InsnID = readULEB();
DEBUG_WITH_TYPE(TgtExecutor::getName(),
dbgs() << CurrentIdx << ": GIM_CheckHasOneUse(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.hasOneNonDBGUse(Res)) {
if (handleReject() == RejectAndGiveUp)
return false;
}
break;
}
case GIM_CheckAtomicOrdering: {

View File

@ -884,6 +884,9 @@ class PatFrags<dag ops, list<dag> frags, code pred = [{}],
// If set to true, a predicate is added that checks for the absence of use of
// the first result.
bit HasNoUse = ?;
// If set to true, a predicate is added that checks for the sole use of
// the first result.
bit HasOneUse = ?;
// Is the desired pre-packaged predicate for a load?
bit IsLoad = ?;

View File

@ -1,5 +1,7 @@
// RUN: llvm-tblgen -gen-dag-isel -I %p/../../include -I %p/Common %s 2>&1 | FileCheck -check-prefix=SDAG %s
// RUN: llvm-tblgen -gen-global-isel -I %p/../../include -I %p/Common %s 2>&1 | FileCheck -check-prefix=GISEL %s
// RUN: llvm-tblgen -gen-dag-isel -I %p/../../include -I %p/Common %s 2>&1 | FileCheck -check-prefixes=SDAG,SCUSTOM %s
// RUN: llvm-tblgen -gen-dag-isel -I %p/../../include -I %p/Common %s -DHASONEUSE 2>&1 | FileCheck -check-prefixes=SDAG,SBUILTIN %s
// RUN: llvm-tblgen -gen-global-isel -I %p/../../include -I %p/Common %s 2>&1 | FileCheck -check-prefixes=GISEL,GCUSTOM %s
// RUN: llvm-tblgen -gen-global-isel -I %p/../../include -I %p/Common %s -DHASONEUSE 2>&1 | FileCheck -check-prefixes=GISEL,GBUILTIN %s
include "llvm/Target/Target.td"
include "GlobalISelEmitterCommon.td"
@ -31,11 +33,16 @@ def : GINodeEquiv<G_TGT_MUL24, TGTmul24_impl>;
def TGTmul24_oneuse : PatFrag<
(ops node:$src0, node:$src1),
(TGTmul24 $src0, $src1),
[{ return N->hasOneUse(); }]> {
(TGTmul24 $src0, $src1)
#ifndef HASONEUSE
, [{ return N->hasOneUse(); }]> {
let GISelPredicateCode = [{
return MRI->hasOneNonDBGUse(MI.getOperand(0).getReg());
}];
#else
> {
let HasOneUse = 1;
#endif
}
// SDAG: OPC_CheckOpcode, TARGET_VAL(ISD::INTRINSIC_W_CHAIN),
@ -44,19 +51,26 @@ def TGTmul24_oneuse : PatFrag<
// SDAG: OPC_CheckOpcode, TARGET_VAL(TargetISD::MUL24),
// SDAG: OPC_CheckPredicate0, // Predicate_TGTmul24_oneuse
// GISEL: GIM_CheckOpcode, /*MI*/1, GIMT_Encode2(TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS),
// GISEL: GIM_CheckIntrinsicID, /*MI*/1, /*Op*/1, GIMT_Encode2(Intrinsic::tgt_mul24),
// GISEL: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// SCUSTOM: return N->hasOneUse();
// SBUILTIN: if (!SDValue(N, 0).hasOneUse()) return false;
// GISEL: GIM_CheckOpcode, /*MI*/1, GIMT_Encode2(TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS),
// GISEL: GIM_CheckIntrinsicID, /*MI*/1, /*Op*/1, GIMT_Encode2(Intrinsic::tgt_mul24),
// GISEL: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// GBUILTIN: GIM_CheckHasOneUse, /*MI*/1,
// GCUSTOM: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// GISEL: GIM_CheckOpcode, /*MI*/1, GIMT_Encode2(TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS),
// GISEL: GIM_CheckIntrinsicID, /*MI*/1, /*Op*/1, GIMT_Encode2(Intrinsic::tgt_mul24),
// GBUILTIN: GIM_CheckHasOneUse, /*MI*/1,
// GCUSTOM: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// GISEL: GIM_CheckOpcode, /*MI*/1, GIMT_Encode2(MyTarget::G_TGT_MUL24),
// GISEL: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// GBUILTIN: GIM_CheckHasOneUse, /*MI*/1,
// GCUSTOM: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// GISEL: GIM_CheckOpcode, /*MI*/1, GIMT_Encode2(MyTarget::G_TGT_MUL24),
// GISEL: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
// GBUILTIN: GIM_CheckHasOneUse, /*MI*/1,
// GCUSTOM: GIM_CheckCxxInsnPredicate, /*MI*/1, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_TGTmul24_oneuse),
def inst_mad24 : I<
(outs GPR32:$dst),
(ins GPR32:$src0, GPR32:$src1, GPR32:$src2),

View File

@ -903,7 +903,7 @@ TreePredicateFn::TreePredicateFn(TreePattern *N) : PatFragRec(N) {
}
bool TreePredicateFn::hasPredCode() const {
return isLoad() || isStore() || isAtomic() || hasNoUse() ||
return isLoad() || isStore() || isAtomic() || hasNoUse() || hasOneUse() ||
!PatFragRec->getRecord()->getValueAsString("PredicateCode").empty();
}
@ -1140,6 +1140,8 @@ std::string TreePredicateFn::getPredCode() const {
if (hasNoUse())
Code += "if (!SDValue(N, 0).use_empty()) return false;\n";
if (hasOneUse())
Code += "if (!SDValue(N, 0).hasOneUse()) return false;\n";
std::string PredicateCode =
std::string(PatFragRec->getRecord()->getValueAsString("PredicateCode"));
@ -1187,6 +1189,9 @@ bool TreePredicateFn::usesOperands() const {
bool TreePredicateFn::hasNoUse() const {
return isPredefinedPredicateEqualTo("HasNoUse", true);
}
bool TreePredicateFn::hasOneUse() const {
return isPredefinedPredicateEqualTo("HasOneUse", true);
}
bool TreePredicateFn::isLoad() const {
return isPredefinedPredicateEqualTo("IsLoad", true);
}

View File

@ -533,6 +533,8 @@ public:
// Check if the HasNoUse predicate is set.
bool hasNoUse() const;
// Check if the HasOneUse predicate is set.
bool hasOneUse() const;
// Is the desired predefined predicate for a load?
bool isLoad() const;

View File

@ -806,6 +806,7 @@ public:
IPM_MemoryAlignment,
IPM_VectorSplatImm,
IPM_NoUse,
IPM_OneUse,
IPM_GenericPredicate,
IPM_MIFlags,
OPM_SameOperand,
@ -1691,6 +1692,28 @@ public:
}
};
/// Generates code to check that the first result has only one use.
class OneUsePredicateMatcher : public InstructionPredicateMatcher {
public:
OneUsePredicateMatcher(unsigned InsnVarID)
: InstructionPredicateMatcher(IPM_OneUse, InsnVarID) {}
static bool classof(const PredicateMatcher *P) {
return P->getKind() == IPM_OneUse;
}
bool isIdentical(const PredicateMatcher &B) const override {
return InstructionPredicateMatcher::isIdentical(B);
}
void emitPredicateOpcodes(MatchTable &Table,
RuleMatcher &Rule) const override {
Table << MatchTable::Opcode("GIM_CheckHasOneUse")
<< MatchTable::Comment("MI") << MatchTable::ULEB128Value(InsnVarID)
<< MatchTable::LineBreak;
}
};
/// Generates code to check that a set of predicates and operands match for a
/// particular instruction.
///

View File

@ -207,7 +207,7 @@ static Error isTrivialOperatorNode(const TreePatternNode &N) {
if (Predicate.isImmediatePattern())
continue;
if (Predicate.hasNoUse())
if (Predicate.hasNoUse() || Predicate.hasOneUse())
continue;
if (Predicate.isNonExtLoad() || Predicate.isAnyExtLoad() ||
@ -782,6 +782,10 @@ Expected<InstructionMatcher &> GlobalISelEmitter::createAndImportSelDAGMatcher(
InsnMatcher.addPredicate<NoUsePredicateMatcher>();
HasAddedBuiltinMatcher = true;
}
if (Predicate.hasOneUse()) {
InsnMatcher.addPredicate<OneUsePredicateMatcher>();
HasAddedBuiltinMatcher = true;
}
if (Predicate.hasGISelPredicateCode()) {
if (Predicate.usesOperands()) {