diff --git a/include/llvm/CodeGen/GlobalISel/InstructionSelector.h b/include/llvm/CodeGen/GlobalISel/InstructionSelector.h index 4935e900773..ebfcec133b6 100644 --- a/include/llvm/CodeGen/GlobalISel/InstructionSelector.h +++ b/include/llvm/CodeGen/GlobalISel/InstructionSelector.h @@ -364,7 +364,10 @@ protected: bool isBaseWithConstantOffset(const MachineOperand &Root, const MachineRegisterInfo &MRI) const; - bool isObviouslySafeToFold(MachineInstr &MI) const; + /// Return true if MI can obviously be folded into IntoMI. + /// MI and IntoMI do not need to be in the same basic blocks, but MI must + /// preceed IntoMI. + bool isObviouslySafeToFold(MachineInstr &MI, MachineInstr &IntoMI) const; }; } // end namespace llvm diff --git a/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h b/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h index 84b6ec9beea..0747205f447 100644 --- a/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h +++ b/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h @@ -401,7 +401,7 @@ bool InstructionSelector::executeMatchTable( dbgs() << CurrentIdx << ": GIM_CheckIsSafeToFold(MIs[" << InsnID << "])\n"); assert(State.MIs[InsnID] != nullptr && "Used insn before defined"); - if (!isObviouslySafeToFold(*State.MIs[InsnID])) { + if (!isObviouslySafeToFold(*State.MIs[InsnID], *State.MIs[0])) { if (handleReject() == RejectAndGiveUp) return false; } diff --git a/lib/CodeGen/GlobalISel/InstructionSelector.cpp b/lib/CodeGen/GlobalISel/InstructionSelector.cpp index 2a563c9bf5c..b8503de36fe 100644 --- a/lib/CodeGen/GlobalISel/InstructionSelector.cpp +++ b/lib/CodeGen/GlobalISel/InstructionSelector.cpp @@ -116,7 +116,13 @@ bool InstructionSelector::isBaseWithConstantOffset( return true; } -bool InstructionSelector::isObviouslySafeToFold(MachineInstr &MI) const { +bool InstructionSelector::isObviouslySafeToFold(MachineInstr &MI, + MachineInstr &IntoMI) const { + // Immediate neighbours are already folded. + if (MI.getParent() == IntoMI.getParent() && + std::next(MI.getIterator()) == IntoMI.getIterator()) + return true; + return !MI.mayLoadOrStore() && !MI.hasUnmodeledSideEffects() && MI.implicit_operands().begin() == MI.implicit_operands().end(); } diff --git a/test/CodeGen/AArch64/GlobalISel/select-load.mir b/test/CodeGen/AArch64/GlobalISel/select-load.mir index 00f6c9418b7..04fa74c5562 100644 --- a/test/CodeGen/AArch64/GlobalISel/select-load.mir +++ b/test/CodeGen/AArch64/GlobalISel/select-load.mir @@ -30,6 +30,9 @@ define void @load_gep_32_s8_fpr(i8* %addr) { ret void } define void @load_v2s32(i64 *%addr) { ret void } + + define void @sextload_s32_from_s16(i16 *%addr) { ret void } + define void @zextload_s32_from_s16(i16 *%addr) { ret void } ... --- @@ -468,3 +471,40 @@ body: | %1(<2 x s32>) = G_LOAD %0 :: (load 8 from %ir.addr) %d0 = COPY %1(<2 x s32>) ... +--- +name: sextload_s32_from_s16 +legalized: true +regBankSelected: true + +body: | + bb.0: + liveins: %w0 + + ; CHECK-LABEL: name: sextload_s32_from_s16 + ; CHECK: [[COPY:%[0-9]+]]:gpr64sp = COPY %x0 + ; CHECK: [[T0:%[0-9]+]]:gpr32 = LDRSHWui [[COPY]], 0 :: (load 2 from %ir.addr) + ; CHECK: %w0 = COPY [[T0]] + %0:gpr(p0) = COPY %x0 + %1:gpr(s16) = G_LOAD %0 :: (load 2 from %ir.addr) + %2:gpr(s32) = G_SEXT %1 + %w0 = COPY %2(s32) +... + +--- +name: zextload_s32_from_s16 +legalized: true +regBankSelected: true + +body: | + bb.0: + liveins: %w0 + + ; CHECK-LABEL: name: zextload_s32_from_s16 + ; CHECK: [[COPY:%[0-9]+]]:gpr64sp = COPY %x0 + ; CHECK: [[T0:%[0-9]+]]:gpr32 = LDRHHui [[COPY]], 0 :: (load 2 from %ir.addr) + ; CHECK: %w0 = COPY [[T0]] + %0:gpr(p0) = COPY %x0 + %1:gpr(s16) = G_LOAD %0 :: (load 2 from %ir.addr) + %2:gpr(s32) = G_ZEXT %1 + %w0 = COPY %2(s32) +... diff --git a/test/TableGen/GlobalISelEmitter.td b/test/TableGen/GlobalISelEmitter.td index dd80640c946..f36240eec3c 100644 --- a/test/TableGen/GlobalISelEmitter.td +++ b/test/TableGen/GlobalISelEmitter.td @@ -89,9 +89,11 @@ def HasC : Predicate<"Subtarget->hasC()"> { let RecomputePerFunction = 1; } // CHECK-LABEL: // LLT Objects. // CHECK-NEXT: enum { +// CHECK-NEXT: GILLT_s16, // CHECK-NEXT: GILLT_s32, // CHECK-NEXT: } // CHECK-NEXT: const static LLT TypeObjects[] = { +// CHECK-NEXT: LLT::scalar(16), // CHECK-NEXT: LLT::scalar(32), // CHECK-NEXT: }; @@ -845,9 +847,43 @@ def MOVfpimmz : I<(outs FPR32:$dst), (ins f32imm:$imm), [(set FPR32:$dst, fpimmz def LOAD : I<(outs GPR32:$dst), (ins GPR32:$src1), [(set GPR32:$dst, (load GPR32:$src1))]>; -//===- Test a pattern with an MBB operand. --------------------------------===// + +//===- Test a simple pattern with a sextload -------------------------------===// // CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 23*/ [[LABEL:[0-9]+]], +// CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/2, +// CHECK-NEXT: GIM_RecordInsn, /*DefineMI*/1, /*MI*/0, /*OpIdx*/1, // MIs[1] +// CHECK-NEXT: GIM_CheckNumOperands, /*MI*/1, /*Expected*/2, +// CHECK-NEXT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_SEXT, +// CHECK-NEXT: // MIs[0] dst +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/0, /*Type*/GILLT_s32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/0, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // MIs[0] Operand 1 +// CHECK-NEXT: GIM_CheckType, /*MI*/0, /*Op*/1, /*Type*/GILLT_s16, +// CHECK-NEXT: GIM_CheckOpcode, /*MI*/1, TargetOpcode::G_LOAD, +// CHECK-NEXT: GIM_CheckNonAtomic, /*MI*/1, +// CHECK-NEXT: // MIs[1] Operand 0 +// CHECK-NEXT: GIM_CheckType, /*MI*/1, /*Op*/0, /*Type*/GILLT_s16, +// CHECK-NEXT: // MIs[1] src1 +// CHECK-NEXT: GIM_CheckPointerToAny, /*MI*/1, /*Op*/1, /*SizeInBits*/32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/1, /*Op*/1, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: GIM_CheckIsSafeToFold, /*InsnID*/1, +// CHECK-NEXT: // (sext:{ *:[i32] } (ld:{ *:[i16] } GPR32:{ *:[i32] }:$src1)<>) => (SEXTLOAD:{ *:[i32] } GPR32:{ *:[i32] }:$src1) +// CHECK-NEXT: GIR_BuildMI, /*InsnID*/0, /*Opcode*/MyTarget::SEXTLOAD, +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/0, // dst +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/1, /*OpIdx*/1, // src1 +// CHECK-NEXT: GIR_MergeMemOperands, /*InsnID*/0, /*MergeInsnID's*/0, 1, GIU_MergeMemOperands_EndOfList, +// CHECK-NEXT: GIR_EraseFromParent, /*InsnID*/0, +// CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, +// CHECK-NEXT: GIR_Done, +// CHECK-NEXT: // Label 23: @[[LABEL]] + +def SEXTLOAD : I<(outs GPR32:$dst), (ins GPR32:$src1), + [(set GPR32:$dst, (sextloadi16 GPR32:$src1))]>; + +//===- Test a pattern with an MBB operand. --------------------------------===// + +// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 24*/ [[LABEL:[0-9]+]], // CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/1, // CHECK-NEXT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_BR, // CHECK-NEXT: // MIs[0] target @@ -856,7 +892,7 @@ def LOAD : I<(outs GPR32:$dst), (ins GPR32:$src1), // CHECK-NEXT: GIR_MutateOpcode, /*InsnID*/0, /*RecycleInsnID*/0, /*Opcode*/MyTarget::BR, // CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, // CHECK-NEXT: GIR_Done, -// CHECK-NEXT: // Label 23: @[[LABEL]] +// CHECK-NEXT: // Label 24: @[[LABEL]] def BR : I<(outs), (ins unknown:$target), [(br bb:$target)]>; diff --git a/utils/TableGen/CodeGenDAGPatterns.cpp b/utils/TableGen/CodeGenDAGPatterns.cpp index 3b400c1262e..c07a7267bbe 100644 --- a/utils/TableGen/CodeGenDAGPatterns.cpp +++ b/utils/TableGen/CodeGenDAGPatterns.cpp @@ -2744,8 +2744,10 @@ void TreePattern::dump() const { print(errs()); } // CodeGenDAGPatterns implementation // -CodeGenDAGPatterns::CodeGenDAGPatterns(RecordKeeper &R) : - Records(R), Target(R), LegalVTS(Target.getLegalValueTypes()) { +CodeGenDAGPatterns::CodeGenDAGPatterns(RecordKeeper &R, + PatternRewriterFn PatternRewriter) + : Records(R), Target(R), LegalVTS(Target.getLegalValueTypes()), + PatternRewriter(PatternRewriter) { Intrinsics = CodeGenIntrinsicTable(Records, false); TgtIntrinsics = CodeGenIntrinsicTable(Records, true); @@ -3536,6 +3538,8 @@ void CodeGenDAGPatterns::ParseInstructions() { TreePattern *I = TheInst.getPattern(); if (!I) continue; // No pattern. + if (PatternRewriter) + PatternRewriter(I); // FIXME: Assume only the first tree is the pattern. The others are clobber // nodes. TreePatternNode *Pattern = I->getTree(0); @@ -3936,6 +3940,8 @@ void CodeGenDAGPatterns::ParsePatterns() { Temp.getOnlyTree()->hasPossibleType()) { ListInit *Preds = CurPattern->getValueAsListInit("Predicates"); int Complexity = CurPattern->getValueAsInt("AddedComplexity"); + if (PatternRewriter) + PatternRewriter(Pattern); AddPatternToMatch( Pattern, PatternToMatch( diff --git a/utils/TableGen/CodeGenDAGPatterns.h b/utils/TableGen/CodeGenDAGPatterns.h index 1f7e9fb98d3..d3bb99a5a8f 100644 --- a/utils/TableGen/CodeGenDAGPatterns.h +++ b/utils/TableGen/CodeGenDAGPatterns.h @@ -25,6 +25,7 @@ #include "llvm/Support/MathExtras.h" #include #include +#include #include #include #include @@ -780,6 +781,7 @@ public: const std::vector &getTrees() const { return Trees; } unsigned getNumTrees() const { return Trees.size(); } TreePatternNode *getTree(unsigned i) const { return Trees[i]; } + void setTree(unsigned i, TreePatternNode *Tree) { Trees[i] = Tree; } TreePatternNode *getOnlyTree() const { assert(Trees.size() == 1 && "Doesn't have exactly one pattern!"); return Trees[0]; @@ -1029,8 +1031,12 @@ class CodeGenDAGPatterns { TypeSetByHwMode LegalVTS; + using PatternRewriterFn = std::function; + PatternRewriterFn PatternRewriter; + public: - CodeGenDAGPatterns(RecordKeeper &R); + CodeGenDAGPatterns(RecordKeeper &R, + PatternRewriterFn PatternRewriter = nullptr); CodeGenTarget &getTargetInfo() { return Target; } const CodeGenTarget &getTargetInfo() const { return Target; } diff --git a/utils/TableGen/GlobalISelEmitter.cpp b/utils/TableGen/GlobalISelEmitter.cpp index 08649d7f9b5..8a0d625ca44 100644 --- a/utils/TableGen/GlobalISelEmitter.cpp +++ b/utils/TableGen/GlobalISelEmitter.cpp @@ -2356,6 +2356,9 @@ private: Expected runOnPattern(const PatternToMatch &P); void declareSubtargetFeature(Record *Predicate); + + TreePatternNode *fixupPatternNode(TreePatternNode *N); + void fixupPatternTrees(TreePattern *P); }; void GlobalISelEmitter::gatherNodeEquivs() { @@ -2377,8 +2380,8 @@ Record *GlobalISelEmitter::findNodeEquiv(Record *N) const { } GlobalISelEmitter::GlobalISelEmitter(RecordKeeper &RK) - : RK(RK), CGP(RK), Target(CGP.getTargetInfo()), - CGRegs(RK, Target.getHwModes()) {} + : RK(RK), CGP(RK, [&](TreePattern *P) { fixupPatternTrees(P); }), + Target(CGP.getTargetInfo()), CGRegs(RK, Target.getHwModes()) {} //===- Emitter ------------------------------------------------------------===// @@ -3233,6 +3236,7 @@ void GlobalISelEmitter::run(raw_ostream &OS) { // Look through the SelectionDAG patterns we found, possibly emitting some. for (const PatternToMatch &Pat : CGP.ptms()) { ++NumPatternTotal; + auto MatcherOrErr = runOnPattern(Pat); // The pattern analysis can fail, indicating an unsupported pattern. @@ -3483,6 +3487,75 @@ void GlobalISelEmitter::declareSubtargetFeature(Record *Predicate) { Predicate, SubtargetFeatureInfo(Predicate, SubtargetFeatures.size())); } +TreePatternNode *GlobalISelEmitter::fixupPatternNode(TreePatternNode *N) { + if (!N->isLeaf()) { + for (unsigned I = 0, E = N->getNumChildren(); I < E; ++I) { + TreePatternNode *OrigChild = N->getChild(I); + TreePatternNode *NewChild = fixupPatternNode(OrigChild); + if (OrigChild != NewChild) + N->setChild(I, NewChild); + } + + if (N->getOperator()->getName() == "ld") { + // If it's a signext-load we need to adapt the pattern slightly. We need + // to split the node into (sext (ld ...)), remove the <> predicate, + // and then apply the <> predicate by updating the result type + // of the load. + // + // For example: + // (ld:[i32] [iPTR])<><><> + // must be transformed into: + // (sext:[i32] (ld:[i16] [iPTR])<>) + // + // Likewise for zeroext-load. + + std::vector Predicates; + bool IsSignExtLoad = false; + bool IsZeroExtLoad = false; + Record *MemVT = nullptr; + for (const auto &P : N->getPredicateFns()) { + if (P.isLoad() && P.isSignExtLoad()) { + IsSignExtLoad = true; + continue; + } + if (P.isLoad() && P.isZeroExtLoad()) { + IsZeroExtLoad = true; + continue; + } + if (P.isLoad() && P.getMemoryVT()) { + MemVT = P.getMemoryVT(); + continue; + } + Predicates.push_back(P); + } + + if ((IsSignExtLoad || IsZeroExtLoad) && MemVT) { + assert(((IsSignExtLoad && !IsZeroExtLoad) || + (!IsSignExtLoad && IsZeroExtLoad)) && + "IsSignExtLoad and IsZeroExtLoad are mutually exclusive"); + TreePatternNode *Ext = new TreePatternNode( + RK.getDef(IsSignExtLoad ? "sext" : "zext"), {N}, 1); + Ext->setType(0, N->getType(0)); + N->clearPredicateFns(); + N->setPredicateFns(Predicates); + N->setType(0, getValueType(MemVT)); + return Ext; + } + } + } + + return N; +} + +void GlobalISelEmitter::fixupPatternTrees(TreePattern *P) { + for (unsigned I = 0, E = P->getNumTrees(); I < E; ++I) { + TreePatternNode *OrigTree = P->getTree(I); + TreePatternNode *NewTree = fixupPatternNode(OrigTree); + if (OrigTree != NewTree) + P->setTree(I, NewTree); + } +} + } // end anonymous namespace //===----------------------------------------------------------------------===//