From: Daniel Sanders Date: Tue, 15 Aug 2017 13:50:09 +0000 (+0000) Subject: Re-commit with some instrumentation: [globalisel][tablegen] Support zero-instruction... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b5378b63e391d27ad4a6ebed1e504b4ca6886624;p=llvm Re-commit with some instrumentation: [globalisel][tablegen] Support zero-instruction emission. Summary: Support the case where an operand of a pattern is also the whole of the result pattern. In this case the original result and all its uses must be replaced by the operand. However, register class restrictions can require a COPY. This patch handles both cases by always emitting the copy and leaving it for the register allocator to optimize. The previous commit failed on the windows bots and this one is likely to fail on those same bots. However, the added instrumentation should reveal a particular isHigherPriorityThan() evaluation which I'm expecting to expose that these machines are weighing priority of two rules differently from the non-windows machines. Reviewers: ab, t.p.northover, qcolombet, rovka, aditya_nandakumar Subscribers: javed.absar, kristof.beyls, igorb, llvm-commits Differential Revision: https://reviews.llvm.org/D36084 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@310919 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Target/AArch64/AArch64InstructionSelector.cpp b/lib/Target/AArch64/AArch64InstructionSelector.cpp index 1b45f87bc72..22586a1708e 100644 --- a/lib/Target/AArch64/AArch64InstructionSelector.cpp +++ b/lib/Target/AArch64/AArch64InstructionSelector.cpp @@ -1163,9 +1163,19 @@ bool AArch64InstructionSelector::select(MachineInstr &I) const { case TargetOpcode::G_INTTOPTR: - case TargetOpcode::G_BITCAST: + // The importer is currently unable to import pointer types since they + // didn't exist in SelectionDAG. return selectCopy(I, TII, MRI, TRI, RBI); + case TargetOpcode::G_BITCAST: + // Imported SelectionDAG rules can handle every bitcast except those that + // bitcast from a type to the same type. Ideally, these shouldn't occur + // but we might not run an optimizer that deletes them. + if (MRI.getType(I.getOperand(0).getReg()) == + MRI.getType(I.getOperand(1).getReg())) + return selectCopy(I, TII, MRI, TRI, RBI); + return false; + case TargetOpcode::G_FPEXT: { if (MRI.getType(I.getOperand(0).getReg()) != LLT::scalar(64)) { DEBUG(dbgs() << "G_FPEXT to type " << Ty diff --git a/test/CodeGen/AArch64/GlobalISel/select-bitcast.mir b/test/CodeGen/AArch64/GlobalISel/select-bitcast.mir index 5e4034d1624..fe077a25f7c 100644 --- a/test/CodeGen/AArch64/GlobalISel/select-bitcast.mir +++ b/test/CodeGen/AArch64/GlobalISel/select-bitcast.mir @@ -11,6 +11,8 @@ define void @bitcast_s64_fpr() { ret void } define void @bitcast_s64_gpr_fpr() { ret void } define void @bitcast_s64_fpr_gpr() { ret void } + define void @bitcast_s64_v2f32_fpr() { ret void } + define void @bitcast_s64_v8i8_fpr() { ret void } ... --- @@ -210,3 +212,53 @@ body: | %1(s64) = G_BITCAST %0 %x0 = COPY %1(s64) ... + +--- +# CHECK-LABEL: name: bitcast_s64_v2f32_fpr +name: bitcast_s64_v2f32_fpr +legalized: true +regBankSelected: true + +# CHECK: registers: +# CHECK-NEXT: - { id: 0, class: fpr64, preferred-register: '' } +# CHECK-NEXT: - { id: 1, class: fpr64, preferred-register: '' } +registers: + - { id: 0, class: fpr } + - { id: 1, class: fpr } + +# CHECK: body: +# CHECK: %0 = COPY %d0 +# CHECK: %1 = COPY %0 +body: | + bb.0: + liveins: %d0 + + %0(s64) = COPY %d0 + %1(<2 x s32>) = G_BITCAST %0 + %x0 = COPY %1(<2 x s32>) +... + +--- +# CHECK-LABEL: name: bitcast_s64_v8i8_fpr +name: bitcast_s64_v8i8_fpr +legalized: true +regBankSelected: true + +# CHECK: registers: +# CHECK-NEXT: - { id: 0, class: fpr64, preferred-register: '' } +# CHECK-NEXT: - { id: 1, class: fpr64, preferred-register: '' } +registers: + - { id: 0, class: fpr } + - { id: 1, class: fpr } + +# CHECK: body: +# CHECK: %0 = COPY %d0 +# CHECK: %1 = COPY %0 +body: | + bb.0: + liveins: %d0 + + %0(s64) = COPY %d0 + %1(<8 x s8>) = G_BITCAST %0 + %x0 = COPY %1(<8 x s8>) +... diff --git a/test/TableGen/GlobalISelEmitter.td b/test/TableGen/GlobalISelEmitter.td index b9163d1945d..b479240d765 100644 --- a/test/TableGen/GlobalISelEmitter.td +++ b/test/TableGen/GlobalISelEmitter.td @@ -83,6 +83,13 @@ def HasC : Predicate<"Subtarget->hasC()"> { let RecomputePerFunction = 1; } // CHECK-NEXT: return Features; // CHECK-NEXT: } +// CHECK-LABEL: enum { +// CHECK-NEXT: GILLT_s32, +// CHECK-NEXT: } +// CHECK-NEXT: const static LLT TypeObjects[] = { +// CHECK-NEXT: LLT::scalar(32), +// CHECK-NEXT: }; + // CHECK: bool MyTargetInstructionSelector::selectImpl(MachineInstr &I) const { // CHECK-NEXT: MachineFunction &MF = *I.getParent()->getParent(); // CHECK-NEXT: MachineRegisterInfo &MRI = MF.getRegInfo(); diff --git a/utils/TableGen/GlobalISelEmitter.cpp b/utils/TableGen/GlobalISelEmitter.cpp index d1821b64dd8..5cce1f5218c 100644 --- a/utils/TableGen/GlobalISelEmitter.cpp +++ b/utils/TableGen/GlobalISelEmitter.cpp @@ -110,30 +110,28 @@ public: const LLT &get() const { return Ty; } /// This ordering is used for std::unique() and std::sort(). There's no - /// particular logic behind the order. + /// particular logic behind the order but either A < B or B < A must be + /// true if A != B. bool operator<(const LLTCodeGen &Other) const { + if (Ty.isValid() != Other.Ty.isValid()) + return Ty.isValid() < Other.Ty.isValid(); if (!Ty.isValid()) - return Other.Ty.isValid(); - if (Ty.isScalar()) { - if (!Other.Ty.isValid()) - return false; - if (Other.Ty.isScalar()) - return Ty.getSizeInBits() < Other.Ty.getSizeInBits(); return false; - } - if (Ty.isVector()) { - if (!Other.Ty.isValid() || Other.Ty.isScalar()) - return false; - if (Other.Ty.isVector()) { - if (Ty.getNumElements() < Other.Ty.getNumElements()) - return true; - if (Ty.getNumElements() > Other.Ty.getNumElements()) - return false; - return Ty.getSizeInBits() < Other.Ty.getSizeInBits(); - } - return false; - } - llvm_unreachable("Unhandled LLT"); + + if (Ty.isVector() != Other.Ty.isVector()) + return Ty.isVector() < Other.Ty.isVector(); + if (Ty.isScalar() != Other.Ty.isScalar()) + return Ty.isScalar() < Other.Ty.isScalar(); + if (Ty.isPointer() != Other.Ty.isPointer()) + return Ty.isPointer() < Other.Ty.isPointer(); + + if (Ty.isPointer() && Ty.getAddressSpace() != Other.Ty.getAddressSpace()) + return Ty.getAddressSpace() < Other.Ty.getAddressSpace(); + + if (Ty.isVector() && Ty.getNumElements() != Other.Ty.getNumElements()) + return Ty.getNumElements() < Other.Ty.getNumElements(); + + return Ty.getSizeInBits() < Other.Ty.getSizeInBits(); } }; @@ -182,14 +180,6 @@ static Error failedImport(const Twine &Reason) { static Error isTrivialOperatorNode(const TreePatternNode *N) { std::string Explanation = ""; std::string Separator = ""; - if (N->isLeaf()) { - if (isa(N->getLeafValue())) - return Error::success(); - - Explanation = "Is a leaf"; - Separator = ", "; - } - if (N->hasAnyPredicate()) { Explanation = Separator + "Has a predicate (" + explainPredicates(N) + ")"; Separator = ", "; @@ -200,7 +190,7 @@ static Error isTrivialOperatorNode(const TreePatternNode *N) { Separator = ", "; } - if (!N->isLeaf() && !N->hasAnyPredicate() && !N->getTransformFn()) + if (!N->hasAnyPredicate() && !N->getTransformFn()) return Error::success(); return failedImport(Explanation); @@ -458,7 +448,9 @@ class RuleMatcher { /// A list of actions that need to be taken when all predicates in this rule /// have succeeded. +public: std::vector> Actions; +protected: typedef std::map DefinedInsnVariablesMap; @@ -634,8 +626,12 @@ protected: LLTCodeGen Ty; public: + static std::set KnownTypes; + LLTOperandMatcher(const LLTCodeGen &Ty) - : OperandPredicateMatcher(OPM_LLT), Ty(Ty) {} + : OperandPredicateMatcher(OPM_LLT), Ty(Ty) { + KnownTypes.insert(Ty); + } static bool classof(const OperandPredicateMatcher *P) { return P->getKind() == OPM_LLT; @@ -651,6 +647,8 @@ public: } }; +std::set LLTOperandMatcher::KnownTypes; + /// Generates code to check that an operand is a particular target constant. class ComplexPatternOperandMatcher : public OperandPredicateMatcher { protected: @@ -1436,7 +1434,9 @@ public: class BuildMIAction : public MatchAction { private: unsigned InsnID; +public: const CodeGenInstruction *I; +private: const InstructionMatcher &Matched; std::vector> OperandRenderers; @@ -2287,8 +2287,42 @@ Expected GlobalISelEmitter::runOnPattern(const PatternToMatch &P) { return failedImport("Src pattern root isn't a trivial operator (" + toString(std::move(Err)) + ")"); - if (Dst->isLeaf()) + InstructionMatcher &InsnMatcherTemp = M.addInstructionMatcher(Src->getName()); + unsigned TempOpIdx = 0; + auto InsnMatcherOrError = + createAndImportSelDAGMatcher(InsnMatcherTemp, Src, TempOpIdx); + if (auto Error = InsnMatcherOrError.takeError()) + return std::move(Error); + InstructionMatcher &InsnMatcher = InsnMatcherOrError.get(); + + if (Dst->isLeaf()) { + Record *RCDef = getInitValueAsRegClass(Dst->getLeafValue()); + + const CodeGenRegisterClass &RC = Target.getRegisterClass(RCDef); + if (RCDef) { + // We need to replace the def and all its uses with the specified + // operand. However, we must also insert COPY's wherever needed. + // For now, emit a copy and let the register allocator clean up. + auto &DstI = Target.getInstruction(RK.getDef("COPY")); + const auto &DstIOperand = DstI.Operands[0]; + + OperandMatcher &OM0 = InsnMatcher.getOperand(0); + OM0.setSymbolicName(DstIOperand.Name); + OM0.addPredicate(RC); + + auto &DstMIBuilder = M.addAction(0, &DstI, InsnMatcher); + DstMIBuilder.addRenderer(0, InsnMatcher, DstIOperand.Name); + DstMIBuilder.addRenderer(0, InsnMatcher, Dst->getName()); + M.addAction(0, 0, RC); + + // We're done with this pattern! It's eligible for GISel emission; return + // it. + ++NumPatternImported; + return std::move(M); + } + return failedImport("Dst pattern root isn't a known leaf"); + } // Start with the defined operands (i.e., the results of the root operator). Record *DstOp = Dst->getOperator(); @@ -2301,14 +2335,6 @@ Expected GlobalISelEmitter::runOnPattern(const PatternToMatch &P) { to_string(Src->getExtTypes().size()) + " def(s) vs " + to_string(DstI.Operands.NumDefs) + " def(s))"); - InstructionMatcher &InsnMatcherTemp = M.addInstructionMatcher(Src->getName()); - unsigned TempOpIdx = 0; - auto InsnMatcherOrError = - createAndImportSelDAGMatcher(InsnMatcherTemp, Src, TempOpIdx); - if (auto Error = InsnMatcherOrError.takeError()) - return std::move(Error); - InstructionMatcher &InsnMatcher = InsnMatcherOrError.get(); - // The root of the match also has constraints on the register bank so that it // matches the result instruction. unsigned OpIdx = 0; @@ -2456,16 +2482,37 @@ void GlobalISelEmitter::run(raw_ostream &OS) { Rules.push_back(std::move(MatcherOrErr.get())); } - std::stable_sort(Rules.begin(), Rules.end(), - [&](const RuleMatcher &A, const RuleMatcher &B) { - if (A.isHigherPriorityThan(B)) { - assert(!B.isHigherPriorityThan(A) && "Cannot be more important " - "and less important at " - "the same time"); - return true; - } - return false; - }); + const auto &IsRuleEmittingOpcode = [](const RuleMatcher &A, StringRef I) -> bool { + const BuildMIAction &BMI = (const BuildMIAction &)*A.Actions[1]; + if (BMI.I->TheDef->getName() == I) + return true; + return false; + }; + + std::stable_sort(Rules.begin(), Rules.end(), [&](const RuleMatcher &A, + const RuleMatcher &B) { + bool EmitResult = false; + bool AIsADD8ri = false; + if (IsRuleEmittingOpcode(A, "ADD8ri") && IsRuleEmittingOpcode(B, "INC8r")) { + EmitResult = true; + AIsADD8ri = true; + } + if (IsRuleEmittingOpcode(B, "ADD8ri") && IsRuleEmittingOpcode(A, "INC8r")) + EmitResult = true; + + if (EmitResult) + errs() << "A=" << (AIsADD8ri ? "ADD8ri" : "INC8r") << ", B=" << (!AIsADD8ri ? "ADD8ri" : "INC8r") << "\n"; + if (A.isHigherPriorityThan(B)) { + if (EmitResult) + errs() << "A higher priority than B\n"; + assert(!B.isHigherPriorityThan(A) && "Cannot be more important " + "and less important at " + "the same time"); + return true; + } else if (EmitResult) + errs() << "A lower priority than B\n"; + return false; + }); std::vector ComplexPredicates = RK.getAllDerivedDefinitions("GIComplexOperandMatcher"); @@ -2535,16 +2582,9 @@ void GlobalISelEmitter::run(raw_ostream &OS) { // Emit a table containing the LLT objects needed by the matcher and an enum // for the matcher to reference them with. - std::vector TypeObjects = { - LLT::scalar(8), LLT::scalar(16), LLT::scalar(32), - LLT::scalar(64), LLT::scalar(80), LLT::vector(8, 1), - LLT::vector(16, 1), LLT::vector(32, 1), LLT::vector(64, 1), - LLT::vector(8, 8), LLT::vector(16, 8), LLT::vector(32, 8), - LLT::vector(64, 8), LLT::vector(4, 16), LLT::vector(8, 16), - LLT::vector(16, 16), LLT::vector(32, 16), LLT::vector(2, 32), - LLT::vector(4, 32), LLT::vector(8, 32), LLT::vector(16, 32), - LLT::vector(2, 64), LLT::vector(4, 64), LLT::vector(8, 64), - }; + std::vector TypeObjects; + for (const auto &Ty : LLTOperandMatcher::KnownTypes) + TypeObjects.push_back(Ty); std::sort(TypeObjects.begin(), TypeObjects.end()); OS << "enum {\n"; for (const auto &TypeObject : TypeObjects) {