Index: include/llvm/CodeGen/GlobalISel/InstructionSelector.h =================================================================== --- include/llvm/CodeGen/GlobalISel/InstructionSelector.h +++ include/llvm/CodeGen/GlobalISel/InstructionSelector.h @@ -184,6 +184,12 @@ /// - RendererID - The renderer to call GIR_ComplexRenderer, + /// Render a G_CONSTANT operator as a zero-extended immediate. + /// - NewInsnID - Instruction ID to modify + /// - OldInsnID - Instruction ID to copy from + /// The operand index is implicitly 1. + GIR_CopyConstantAsUImm, + /// Constrain an instruction operand to a register class. /// - InsnID - Instruction ID to modify /// - OpIdx - Operand index Index: include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h =================================================================== --- include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h +++ include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h @@ -346,6 +346,24 @@ break; } + case GIR_CopyConstantAsUImm: { + int64_t NewInsnID = MatchTable[CurrentIdx++]; + int64_t OldInsnID = MatchTable[CurrentIdx++]; + assert(OutMIs[NewInsnID] && "Attempted to add to undefined instruction"); + assert(State.MIs[OldInsnID]->getOpcode() == TargetOpcode::G_CONSTANT && "Expected G_CONSTANT"); + if (State.MIs[OldInsnID]->getOperand(1).isCImm()) { + // FIXME: Some targets (e.g. Mips) should sign-extend instead of zero extend. + OutMIs[NewInsnID].addImm( + State.MIs[OldInsnID]->getOperand(1).getCImm()->getZExtValue()); + } else if (State.MIs[OldInsnID]->getOperand(1).isImm()) + OutMIs[NewInsnID].add(State.MIs[OldInsnID]->getOperand(1)); + else + llvm_unreachable("Expected Imm or CImm operand"); + DEBUG(dbgs() << CurrentIdx << ": GIR_CopyConstantAsUImm(OutMIs[" << NewInsnID + << "], MIs[" << OldInsnID << "])\n"); + break; + } + case GIR_ConstrainOperandRC: { int64_t InsnID = MatchTable[CurrentIdx++]; int64_t OpIdx = MatchTable[CurrentIdx++]; Index: include/llvm/Target/GlobalISel/SelectionDAGCompat.td =================================================================== --- include/llvm/Target/GlobalISel/SelectionDAGCompat.td +++ include/llvm/Target/GlobalISel/SelectionDAGCompat.td @@ -33,7 +33,7 @@ def : GINodeEquiv; // G_INTTOPTR - SelectionDAG has no equivalent. // G_PTRTOINT - SelectionDAG has no equivalent. -// G_CONSTANT - Not needed since constants aren't operators. +def : GINodeEquiv; // G_FCONSTANT - Not needed since constants aren't operators. def : GINodeEquiv; def : GINodeEquiv; Index: lib/Target/AArch64/AArch64InstructionSelector.cpp =================================================================== --- lib/Target/AArch64/AArch64InstructionSelector.cpp +++ lib/Target/AArch64/AArch64InstructionSelector.cpp @@ -706,7 +706,8 @@ return false; } } else { - if (Ty != s32 && Ty != s64 && Ty != p0) { + // s32 and s64 are covered by tablegen. + if (Ty != p0) { DEBUG(dbgs() << "Unable to materialize integer " << Ty << " constant, expected: " << s32 << ", " << s64 << ", or " << p0 << '\n'); Index: test/CodeGen/AArch64/GlobalISel/select-imm.mir =================================================================== --- /dev/null +++ test/CodeGen/AArch64/GlobalISel/select-imm.mir @@ -0,0 +1,53 @@ +# RUN: llc -O0 -mtriple=aarch64-- -run-pass=instruction-select -verify-machineinstrs -global-isel %s -o - | FileCheck %s + +--- | + target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" + + define void @imm_s32_gpr() { ret void } + define void @imm_s64_gpr() { ret void } + +... + +--- +# Check that we select a 32-bit immediate into a MOVi32imm. +# CHECK-LABEL: name: imm_s32_gpr +name: imm_s32_gpr +legalized: true +regBankSelected: true + +# CHECK: registers: +# CHECK-NEXT: - { id: 0, class: gpr32, preferred-register: '' } +registers: + - { id: 0, class: gpr } + +# CHECK: body: +# CHECK: %0 = MOVi32imm 4294966062 +body: | + bb.0: + liveins: %w0, %w1 + + %0(s32) = G_CONSTANT i32 -1234 + %w0 = COPY %0(s32) +... + +--- +# Check that we select a 64-bit immediate into a MOVi64imm. +# CHECK-LABEL: name: imm_s64_gpr +name: imm_s64_gpr +legalized: true +regBankSelected: true + +# CHECK: registers: +# CHECK-NEXT: - { id: 0, class: gpr64, preferred-register: '' } +registers: + - { id: 0, class: gpr } + +# CHECK: body: +# CHECK: %0 = MOVi64imm 1234 +body: | + bb.0: + liveins: %w0, %w1 + + %0(s64) = G_CONSTANT i64 1234 + %w0 = COPY %0(s64) +... Index: test/CodeGen/X86/GlobalISel/select-inc.mir =================================================================== --- test/CodeGen/X86/GlobalISel/select-inc.mir +++ test/CodeGen/X86/GlobalISel/select-inc.mir @@ -15,7 +15,7 @@ # ALL: registers: # ALL-NEXT: - { id: 0, class: gr8, preferred-register: '' } # INC-NEXT: - { id: 1, class: gpr, preferred-register: '' } -# ADD-NEXT: - { id: 1, class: gr8, preferred-register: '' } +# ADD-NEXT: - { id: 1, class: gpr, preferred-register: '' } # ALL-NEXT: - { id: 2, class: gr8, preferred-register: '' } registers: - { id: 0, class: gpr } @@ -23,8 +23,7 @@ - { id: 2, class: gpr } # ALL: %0 = COPY %al # INC-NEXT: %2 = INC8r %0 -# ADD-NEXT: %1 = MOV8ri 1 -# ADD-NEXT: %2 = ADD8rr %0, %1 +# ADD-NEXT: %2 = ADD8ri %0, 1 body: | bb.1 (%ir-block.0): liveins: %al Index: test/TableGen/GlobalISelEmitter.td =================================================================== --- test/TableGen/GlobalISelEmitter.td +++ test/TableGen/GlobalISelEmitter.td @@ -599,7 +599,7 @@ def : Pat<(i32 (bitconvert FPR32:$src1)), (COPY_TO_REGCLASS FPR32:$src1, GPR32)>; -//===- Test a simple pattern with just a leaf immediate. ------------------===// +//===- Test a simple pattern with just a specific leaf immediate. ---------===// // CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 15*/ [[LABEL:[0-9]+]], // CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/2, @@ -620,9 +620,31 @@ def MOV1 : I<(outs GPR32:$dst), (ins), [(set GPR32:$dst, 1)]>; -//===- Test a pattern with an MBB operand. --------------------------------===// +//===- Test a simple pattern with just a leaf immediate. ------------------===// // CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 16*/ [[LABEL:[0-9]+]], +// CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/2, +// CHECK-NEXT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_CONSTANT, +// 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: // No predicates +// CHECK-NEXT: // (imm:i32):$imm => (MOVimm:i32 (imm:i32):$imm) +// CHECK-NEXT: GIR_BuildMI, /*InsnID*/0, /*Opcode*/MyTarget::MOVimm, +// CHECK-NEXT: GIR_Copy, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/0, // dst +// CHECK-NEXT: GIR_CopyConstantAsUImm, /*NewInsnID*/0, /*OldInsnID*/0, // imm +// CHECK-NEXT: GIR_MergeMemOperands, /*InsnID*/0, +// CHECK-NEXT: GIR_EraseFromParent, /*InsnID*/0, +// CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, +// CHECK-NEXT: GIR_Done, +// CHECK-NEXT: // Label 16: @[[LABEL]] + +def MOVimm : I<(outs GPR32:$dst), (ins i32imm:$imm), [(set GPR32:$dst, imm:$imm)]>; + +//===- Test a pattern with an MBB operand. --------------------------------===// + +// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 17*/ [[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 @@ -631,12 +653,13 @@ // CHECK-NEXT: GIR_MutateOpcode, /*InsnID*/0, /*RecycleInsnID*/0, /*Opcode*/MyTarget::BR, // CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, // CHECK-NEXT: GIR_Done, -// CHECK-NEXT: // Label 16: @[[LABEL]] +// CHECK-NEXT: // Label 17: @[[LABEL]] + +def BR : I<(outs), (ins unknown:$target), + [(br bb:$target)]>; + // CHECK-NEXT: GIM_Reject, // CHECK-NEXT: }; // CHECK-NEXT: if (executeMatchTable(*this, OutMIs, State, MatcherInfo, MatchTable0, TII, MRI, TRI, RBI, AvailableFeatures)) { // CHECK-NEXT: return true; // CHECK-NEXT: } - -def BR : I<(outs), (ins unknown:$target), - [(br bb:$target)]>; Index: utils/TableGen/GlobalISelEmitter.cpp =================================================================== --- utils/TableGen/GlobalISelEmitter.cpp +++ utils/TableGen/GlobalISelEmitter.cpp @@ -477,7 +477,7 @@ RuleMatcher(RuleMatcher &&Other) = default; RuleMatcher &operator=(RuleMatcher &&Other) = default; - InstructionMatcher &addInstructionMatcher(); + InstructionMatcher &addInstructionMatcher(StringRef SymbolicName); void addRequiredFeature(Record *Feature); const std::vector &getRequiredFeatures() const; @@ -501,6 +501,8 @@ return make_range(defined_insn_vars_begin(), defined_insn_vars_end()); } + const InstructionMatcher &getInstructionMatcher(StringRef SymbolicName) const; + void emitCaptureOpcodes(MatchTable &Table); void emit(MatchTable &Table); @@ -575,8 +577,8 @@ /// are represented by a virtual register defined by a G_CONSTANT instruction. enum PredicateKind { OPM_ComplexPattern, - OPM_Instruction, OPM_IntrinsicID, + OPM_Instruction, OPM_Int, OPM_LiteralInt, OPM_LLT, @@ -619,9 +621,7 @@ /// Compare the priority of this object and B. /// /// Returns true if this object is more important than B. - virtual bool isHigherPriorityThan(const OperandPredicateMatcher &B) const { - return Kind < B.Kind; - }; + virtual bool isHigherPriorityThan(const OperandPredicateMatcher &B) const; /// Report the maximum number of temporary operands needed by the predicate /// matcher. @@ -987,6 +987,10 @@ return false; }; + + bool isConstantInstruction() const { + return I->TheDef->getName() == "G_CONSTANT"; + } }; /// Generates code to check that a set of predicates and operands match for a @@ -1004,7 +1008,11 @@ /// condition is always true. OperandVec Operands; + std::string SymbolicName; + public: + InstructionMatcher(StringRef SymbolicName) : SymbolicName(SymbolicName) {} + /// Add an operand to the matcher. OperandMatcher &addOperand(unsigned OpIdx, const std::string &SymbolicName, unsigned AllocatedTemporariesBaseID) { @@ -1041,6 +1049,7 @@ llvm_unreachable("Failed to lookup operand"); } + StringRef getSymbolicName() const { return SymbolicName; } unsigned getNumOperands() const { return Operands.size(); } OperandVec::iterator operands_begin() { return Operands.begin(); } OperandVec::iterator operands_end() { return Operands.end(); } @@ -1116,6 +1125,14 @@ return A + Operand->countRendererFns(); }); } + + bool isConstantInstruction() const { + for (const auto &P : predicates()) + if (const InstructionOpcodeMatcher *Opcode = + dyn_cast(P.get())) + return Opcode->isConstantInstruction(); + return false; + } }; /// Generates code to check that the operand is a register defined by an @@ -1132,9 +1149,9 @@ std::unique_ptr InsnMatcher; public: - InstructionOperandMatcher() + InstructionOperandMatcher(StringRef SymbolicName) : OperandPredicateMatcher(OPM_Instruction), - InsnMatcher(new InstructionMatcher()) {} + InsnMatcher(new InstructionMatcher(SymbolicName)) {} static bool classof(const OperandPredicateMatcher *P) { return P->getKind() == OPM_Instruction; @@ -1168,6 +1185,7 @@ enum RendererKind { OR_Copy, OR_CopySubReg, + OR_CopyConstantAsImm, OR_Imm, OR_Register, OR_ComplexPattern @@ -1202,7 +1220,9 @@ CopyRenderer(unsigned NewInsnID, const InstructionMatcher &Matched, StringRef SymbolicName) : OperandRenderer(OR_Copy), NewInsnID(NewInsnID), Matched(Matched), - SymbolicName(SymbolicName) {} + SymbolicName(SymbolicName) { + assert(!SymbolicName.empty() && "Cannot copy from an unspecified source"); + } static bool classof(const OperandRenderer *R) { return R->getKind() == OR_Copy; @@ -1221,6 +1241,38 @@ } }; +/// A CopyConstantAsImmRenderer emits code to render a G_CONSTANT instruction to +/// an extended immediate operand. +class CopyConstantAsImmRenderer : public OperandRenderer { +protected: + unsigned NewInsnID; + /// The name of the operand. + const std::string SymbolicName; + bool Signed; + +public: + CopyConstantAsImmRenderer(unsigned NewInsnID, StringRef SymbolicName) + : OperandRenderer(OR_CopyConstantAsImm), NewInsnID(NewInsnID), + SymbolicName(SymbolicName), Signed(false) {} + + static bool classof(const OperandRenderer *R) { + return R->getKind() == OR_CopyConstantAsImm; + } + + const StringRef getSymbolicName() const { return SymbolicName; } + + void emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule) const override { + const InstructionMatcher &InsnMatcher = Rule.getInstructionMatcher(SymbolicName); + unsigned OldInsnVarID = Rule.getInsnVarID(InsnMatcher); + Table << MatchTable::Opcode(Signed ? "GIR_CopyConstantAsSImm" + : "GIR_CopyConstantAsUImm") + << MatchTable::Comment("NewInsnID") << MatchTable::IntValue(NewInsnID) + << MatchTable::Comment("OldInsnID") + << MatchTable::IntValue(OldInsnVarID) + << MatchTable::Comment(SymbolicName) << MatchTable::LineBreak; + } +}; + /// A CopySubRegRenderer emits code to copy a single register operand from an /// existing instruction to the one being built and indicate that only a /// subregister should be copied. @@ -1523,8 +1575,8 @@ } }; -InstructionMatcher &RuleMatcher::addInstructionMatcher() { - Matchers.emplace_back(new InstructionMatcher()); +InstructionMatcher &RuleMatcher::addInstructionMatcher(StringRef SymbolicName) { + Matchers.emplace_back(new InstructionMatcher(SymbolicName)); return *Matchers.back(); } @@ -1569,6 +1621,15 @@ llvm_unreachable("Matched Insn was not captured in a local variable"); } +const InstructionMatcher & +RuleMatcher::getInstructionMatcher(StringRef SymbolicName) const { + for (const auto &I : InsnVariableIDs) + if (I.first->getSymbolicName() == SymbolicName) + return *I.first; + llvm_unreachable( + ("Failed to lookup instruction " + SymbolicName).str().c_str()); +} + /// Emit MatchTable opcodes to check the shape of the match and capture /// instructions into local variables. void RuleMatcher::emitCaptureOpcodes(MatchTable &Table) { @@ -1696,6 +1757,32 @@ }); } +bool OperandPredicateMatcher::isHigherPriorityThan( + const OperandPredicateMatcher &B) const { + // Generally speaking, an instruction is more important than an Int or a + // LiteralInt because it can cover more nodes but theres an exception to + // this. G_CONSTANT's are less important than either of those two because they + // are more permissive. + if (const InstructionOperandMatcher *AOM = + dyn_cast(this)) { + if (AOM->getInsnMatcher().isConstantInstruction()) { + if (B.Kind == OPM_Int) { + return false; + } + } + } + if (const InstructionOperandMatcher *BOM = + dyn_cast(&B)) { + if (BOM->getInsnMatcher().isConstantInstruction()) { + if (Kind == OPM_Int) { + return true; + } + } + } + + return Kind < B.Kind; +}; + //===- GlobalISelEmitter class --------------------------------------------===// class GlobalISelEmitter { @@ -1841,6 +1928,14 @@ } else { assert(SrcGIOrNull && "Expected to have already found an equivalent Instruction"); + if (SrcGIOrNull->TheDef->getName() == "G_CONSTANT") { + // imm still has an operand but we don't need to do anything with it + // here since we don't support ImmLeaf predicates yet. However, we still + // need to note the hidden operand to get GIM_CheckNumOperands correct. + InsnMatcher.addOperand(OpIdx++, "", TempOpIdx); + return InsnMatcher; + } + // Match the used operands (i.e. the children of the operator). for (unsigned i = 0, e = Src->getNumChildren(); i != e; ++i) { TreePatternNode *SrcChild = Src->getChild(i); @@ -1902,7 +1997,7 @@ if (!SrcChild->isLeaf()) { // Map the node to a gMIR instruction. InstructionOperandMatcher &InsnOperand = - OM.addPredicate(); + OM.addPredicate(SrcChild->getName()); auto InsnMatcherOrError = createAndImportSelDAGMatcher( InsnOperand.getInsnMatcher(), SrcChild, TempOpIdx); if (auto Error = InsnMatcherOrError.takeError()) @@ -1957,9 +2052,9 @@ Error GlobalISelEmitter::importExplicitUseRenderer( BuildMIAction &DstMIBuilder, TreePatternNode *DstChild, const InstructionMatcher &InsnMatcher) const { - // The only non-leaf child we accept is 'bb': it's an operator because - // BasicBlockSDNode isn't inline, but in MI it's just another operand. if (!DstChild->isLeaf()) { + // We accept 'bb' here. It's an operator because BasicBlockSDNode isn't + // inline, but in MI it's just another operand. if (DstChild->getOperator()->isSubClassOf("SDNode")) { auto &ChildSDNI = CGP.getSDNodeInfo(DstChild->getOperator()); if (ChildSDNI.getSDClassName() == "BasicBlockSDNode") { @@ -1968,6 +2063,17 @@ return Error::success(); } } + + // Similarly, imm is an operator in TreePatternNode's view but must be + // rendered as operands. + // FIXME: The target should be able to choose sign-extended when appropriate + // (e.g. on Mips). + if (DstChild->getOperator()->getName() == "imm") { + DstMIBuilder.addRenderer(0, + DstChild->getName()); + return Error::success(); + } + return failedImport("Dst pattern child isn't a leaf node or an MBB"); } @@ -2191,7 +2297,7 @@ to_string(Src->getExtTypes().size()) + " def(s) vs " + to_string(DstI.Operands.NumDefs) + " def(s))"); - InstructionMatcher &InsnMatcherTemp = M.addInstructionMatcher(); + InstructionMatcher &InsnMatcherTemp = M.addInstructionMatcher(Src->getName()); unsigned TempOpIdx = 0; auto InsnMatcherOrError = createAndImportSelDAGMatcher(InsnMatcherTemp, Src, TempOpIdx);