From 508747d41851c6a0dcd44b7e14b6e5de08634d73 Mon Sep 17 00:00:00 2001 From: Daniel Sanders Date: Mon, 16 Oct 2017 00:56:30 +0000 Subject: [PATCH] [globalisel][tablegen] Implement unindexed load, non-extending load, and MemVT checks Summary: This includes some context-sensitivity in the MVT to LLT conversion so that pointer types are tested correctly. FIXME: I'm not happy with the way this is done since everything is a special-case. I've yet to find a reasonable way to implement it. select-load.mir fails because <1 x s64> loads in tablegen get priority over s64 loads. This is fixed in the next patch and as such they should be committed together, I've posted them separately to help with the review. Depends on D37456 Reviewers: ab, qcolombet, t.p.northover, rovka, aditya_nandakumar Subscribers: kristof.beyls, javed.absar, llvm-commits, igorb Differential Revision: https://reviews.llvm.org/D37457 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@315884 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../CodeGen/GlobalISel/InstructionSelector.h | 5 + .../GlobalISel/InstructionSelectorImpl.h | 16 ++- .../AArch64/GlobalISel/select-load.mir | 30 +++++ test/TableGen/GlobalISelEmitter.td | 24 +++- utils/TableGen/GlobalISelEmitter.cpp | 122 +++++++++++++++--- 5 files changed, 177 insertions(+), 20 deletions(-) diff --git a/include/llvm/CodeGen/GlobalISel/InstructionSelector.h b/include/llvm/CodeGen/GlobalISel/InstructionSelector.h index d86f02b8ae2..9bc126ed726 100644 --- a/include/llvm/CodeGen/GlobalISel/InstructionSelector.h +++ b/include/llvm/CodeGen/GlobalISel/InstructionSelector.h @@ -117,6 +117,11 @@ enum { /// - OpIdx - Operand index /// - Expected type GIM_CheckType, + /// Check the type of a pointer to any address space. + /// - InsnID - Instruction ID + /// - OpIdx - Operand index + /// - SizeInBits - The size of the pointer value in bits. + GIM_CheckPointerToAny, /// Check the register bank for the specified operand /// - InsnID - Instruction ID /// - OpIdx - Operand index diff --git a/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h b/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h index d5400177f82..7fb413fceac 100644 --- a/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h +++ b/include/llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h @@ -244,7 +244,21 @@ bool InstructionSelector::executeMatchTable( } break; } - + case GIM_CheckPointerToAny: { + int64_t InsnID = MatchTable[CurrentIdx++]; + int64_t OpIdx = MatchTable[CurrentIdx++]; + int64_t SizeInBits = MatchTable[CurrentIdx++]; + DEBUG(dbgs() << CurrentIdx << ": GIM_CheckPointerToAny(MIs[" << InsnID + << "]->getOperand(" << OpIdx + << "), SizeInBits=" << SizeInBits << ")\n"); + assert(State.MIs[InsnID] != nullptr && "Used insn before defined"); + const LLT &Ty = MRI.getType(State.MIs[InsnID]->getOperand(OpIdx).getReg()); + if (!Ty.isPointer() || Ty.getSizeInBits() != SizeInBits) { + if (handleReject() == RejectAndGiveUp) + return false; + } + break; + } case GIM_CheckRegBankForClass: { int64_t InsnID = MatchTable[CurrentIdx++]; int64_t OpIdx = MatchTable[CurrentIdx++]; diff --git a/test/CodeGen/AArch64/GlobalISel/select-load.mir b/test/CodeGen/AArch64/GlobalISel/select-load.mir index d00b98d148b..7e0c9d6ebc0 100644 --- a/test/CodeGen/AArch64/GlobalISel/select-load.mir +++ b/test/CodeGen/AArch64/GlobalISel/select-load.mir @@ -1,5 +1,9 @@ # RUN: llc -mtriple=aarch64-- -run-pass=instruction-select -verify-machineinstrs -global-isel %s -o - | FileCheck %s +# This patch temporarily causes LD1Onev1d to match instead of LDRDui on a +# couple functions. A patch to support iPTR will follow that fixes this. +# XFAIL: * + --- | target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" @@ -28,6 +32,7 @@ define void @load_gep_64_s16_fpr(i16* %addr) { ret void } define void @load_gep_32_s8_fpr(i8* %addr) { ret void } + define void @load_v2s32(i64 *%addr) { ret void } ... --- @@ -513,3 +518,28 @@ body: | %3(s8) = G_LOAD %2 :: (load 1 from %ir.addr) %b0 = COPY %3 ... +--- +# CHECK-LABEL: name: load_v2s32 +name: load_v2s32 +legalized: true +regBankSelected: true + +# CHECK: registers: +# CHECK-NEXT: - { id: 0, class: gpr64sp, preferred-register: '' } +# CHECK-NEXT: - { id: 1, class: fpr64, preferred-register: '' } +registers: + - { id: 0, class: gpr } + - { id: 1, class: fpr } + +# CHECK: body: +# CHECK: %0 = COPY %x0 +# CHECK: %1 = LD1Onev2s %0 +# CHECK: %d0 = COPY %1 +body: | + bb.0: + liveins: %x0 + + %0(p0) = COPY %x0 + %1(<2 x s32>) = G_LOAD %0 :: (load 4 from %ir.addr) + %d0 = COPY %1(<2 x s32>) +... diff --git a/test/TableGen/GlobalISelEmitter.td b/test/TableGen/GlobalISelEmitter.td index 609d1c5ec51..8d88403b9bc 100644 --- a/test/TableGen/GlobalISelEmitter.td +++ b/test/TableGen/GlobalISelEmitter.td @@ -814,9 +814,29 @@ def MOVimm : I<(outs GPR32:$dst), (ins i32imm:$imm), [(set GPR32:$dst, imm:$imm) def fpimmz : FPImmLeafisExactlyValue(0.0); }]>; def MOVfpimmz : I<(outs FPR32:$dst), (ins f32imm:$imm), [(set FPR32:$dst, fpimmz:$imm)]>; -//===- Test a pattern with an MBB operand. --------------------------------===// +//===- Test a simple pattern with inferred pointer operands. ---------------===// // CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 22*/ [[LABEL:[0-9]+]], +// CHECK-NEXT: GIM_CheckNumOperands, /*MI*/0, /*Expected*/2, +// CHECK-NEXT: GIM_CheckOpcode, /*MI*/0, TargetOpcode::G_LOAD, +// CHECK-NEXT: GIM_CheckNonAtomic, /*MI*/0, +// 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] src1 +// CHECK-NEXT: GIM_CheckPointerToAny, /*MI*/0, /*Op*/1, /*SizeInBits*/32, +// CHECK-NEXT: GIM_CheckRegBankForClass, /*MI*/0, /*Op*/1, /*RC*/MyTarget::GPR32RegClassID, +// CHECK-NEXT: // (ld:{ *:[i32] } GPR32:{ *:[i32] }:$src1)<><> => (LOAD:{ *:[i32] } GPR32:{ *:[i32] }:$src1) +// CHECK-NEXT: GIR_MutateOpcode, /*InsnID*/0, /*RecycleInsnID*/0, /*Opcode*/MyTarget::LOAD, +// CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, +// CHECK-NEXT: GIR_Done, +// CHECK-NEXT: // Label 22: @[[LABEL]] + +def LOAD : I<(outs GPR32:$dst), (ins GPR32:$src1), + [(set GPR32:$dst, (load GPR32:$src1))]>; +//===- Test a pattern with an MBB operand. --------------------------------===// + +// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 23*/ [[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 @@ -825,7 +845,7 @@ def MOVfpimmz : I<(outs FPR32:$dst), (ins f32imm:$imm), [(set FPR32:$dst, fpimmz // CHECK-NEXT: GIR_MutateOpcode, /*InsnID*/0, /*RecycleInsnID*/0, /*Opcode*/MyTarget::BR, // CHECK-NEXT: GIR_ConstrainSelectedInstOperands, /*InsnID*/0, // CHECK-NEXT: GIR_Done, -// CHECK-NEXT: // Label 22: @[[LABEL]] +// CHECK-NEXT: // Label 23: @[[LABEL]] def BR : I<(outs), (ins unknown:$target), [(br bb:$target)]>; diff --git a/utils/TableGen/GlobalISelEmitter.cpp b/utils/TableGen/GlobalISelEmitter.cpp index e19b4392689..3bfaa887301 100644 --- a/utils/TableGen/GlobalISelEmitter.cpp +++ b/utils/TableGen/GlobalISelEmitter.cpp @@ -103,6 +103,12 @@ public: OS << "GILLT_v" << Ty.getNumElements() << "s" << Ty.getScalarSizeInBits(); return; } + if (Ty.isPointer()) { + OS << "GILLT_p" << Ty.getAddressSpace(); + if (Ty.getSizeInBits() > 0) + OS << "s" << Ty.getSizeInBits(); + return; + } llvm_unreachable("Unhandled LLT"); } @@ -116,6 +122,11 @@ public: << Ty.getScalarSizeInBits() << ")"; return; } + if (Ty.isPointer() && Ty.getSizeInBits() > 0) { + OS << "LLT::pointer(" << Ty.getAddressSpace() << ", " + << Ty.getSizeInBits() << ")"; + return; + } llvm_unreachable("Unhandled LLT"); } @@ -152,9 +163,11 @@ class InstructionMatcher; /// MVTs that don't map cleanly to an LLT (e.g., iPTR, *any, ...). static Optional MVTToLLT(MVT::SimpleValueType SVT) { MVT VT(SVT); + if (VT.isVector() && VT.getVectorNumElements() != 1) return LLTCodeGen( LLT::vector(VT.getVectorNumElements(), VT.getScalarSizeInBits())); + if (VT.isInteger() || VT.isFloatingPoint()) return LLTCodeGen(LLT::scalar(VT.getSizeInBits())); return None; @@ -228,6 +241,11 @@ static Error isTrivialOperatorNode(const TreePatternNode *N) { if (Predicate.isImmediatePattern()) continue; + if (Predicate.isLoad() && Predicate.isUnindexed()) + continue; + + if (Predicate.isNonExtLoad()) + continue; HasUnsupportedPredicate = true; Explanation = Separator + "Has a predicate (" + explainPredicates(N) + ")"; Separator = ", "; @@ -661,6 +679,7 @@ public: OPM_Int, OPM_LiteralInt, OPM_LLT, + OPM_PointerToAny, OPM_RegBank, OPM_MBB, }; @@ -748,6 +767,37 @@ public: std::set LLTOperandMatcher::KnownTypes; +/// Generates code to check that an operand is a pointer to any address space. +/// +/// In SelectionDAG, the types did not describe pointers or address spaces. As a +/// result, iN is used to describe a pointer of N bits to any address space and +/// PatFrag predicates are typically used to constrain the address space. There's +/// no reliable means to derive the missing type information from the pattern so +/// imported rules must test the components of a pointer separately. +/// +/// SizeInBits must be non-zero and the matched pointer must be that size. +/// TODO: Add support for iPTR via SizeInBits==0 and a subtarget query. +class PointerToAnyOperandMatcher : public OperandPredicateMatcher { +protected: + unsigned SizeInBits; + +public: + PointerToAnyOperandMatcher(unsigned SizeInBits) + : OperandPredicateMatcher(OPM_PointerToAny), SizeInBits(SizeInBits) {} + + static bool classof(const OperandPredicateMatcher *P) { + return P->getKind() == OPM_PointerToAny; + } + + void emitPredicateOpcodes(MatchTable &Table, RuleMatcher &Rule, + unsigned InsnVarID, unsigned OpIdx) const override { + Table << MatchTable::Opcode("GIM_CheckPointerToAny") << MatchTable::Comment("MI") + << MatchTable::IntValue(InsnVarID) << MatchTable::Comment("Op") + << MatchTable::IntValue(OpIdx) << MatchTable::Comment("SizeInBits") + << MatchTable::IntValue(SizeInBits) << MatchTable::LineBreak; + } +}; + /// Generates code to check that an operand is a particular target constant. class ComplexPatternOperandMatcher : public OperandPredicateMatcher { protected: @@ -927,6 +977,22 @@ public: InstructionMatcher &getInstructionMatcher() const { return Insn; } + Error addTypeCheckPredicate(const TypeSetByHwMode &VTy, + bool OperandIsAPointer) { + auto OpTyOrNone = VTy.isMachineValueType() + ? MVTToLLT(VTy.getMachineValueType().SimpleTy) + : None; + if (!OpTyOrNone) + return failedImport("unsupported type"); + + if (OperandIsAPointer) + addPredicate( + OpTyOrNone->get().getSizeInBits()); + else + addPredicate(*OpTyOrNone); + return Error::success(); + } + /// Emit MatchTable opcodes to capture instructions into the MIs table. void emitCaptureOpcodes(MatchTable &Table, RuleMatcher &Rule, unsigned InsnVarID) const { @@ -2070,7 +2136,8 @@ private: Error importComplexPatternOperandMatcher(OperandMatcher &OM, Record *R, unsigned &TempOpIdx) const; Error importChildMatcher(RuleMatcher &Rule, InstructionMatcher &InsnMatcher, - const TreePatternNode *SrcChild, unsigned OpIdx, + const TreePatternNode *SrcChild, + bool OperandIsAPointer, unsigned OpIdx, unsigned &TempOpIdx) const; Expected createAndImportInstructionRenderer(RuleMatcher &M, const TreePatternNode *Dst, @@ -2164,17 +2231,12 @@ Expected GlobalISelEmitter::createAndImportSelDAGMatcher( unsigned OpIdx = 0; for (const TypeSetByHwMode &VTy : Src->getExtTypes()) { - auto OpTyOrNone = VTy.isMachineValueType() - ? MVTToLLT(VTy.getMachineValueType().SimpleTy) - : None; - if (!OpTyOrNone) - return failedImport( - "Result of Src pattern operator has an unsupported type"); - // Results don't have a name unless they are the root node. The caller will // set the name if appropriate. OperandMatcher &OM = InsnMatcher.addOperand(OpIdx++, "", TempOpIdx); - OM.addPredicate(*OpTyOrNone); + if (auto Error = OM.addTypeCheckPredicate(VTy, false /* OperandIsAPointer */)) + return failedImport(toString(std::move(Error)) + + " for result of Src pattern operator"); } for (const auto &Predicate : Src->getPredicateFns()) { @@ -2186,6 +2248,25 @@ Expected GlobalISelEmitter::createAndImportSelDAGMatcher( continue; } + // No check required. A G_LOAD is an unindexed load. + if (Predicate.isLoad() && Predicate.isUnindexed()) + continue; + + // No check required. G_LOAD by itself is a non-extending load. + if (Predicate.isNonExtLoad()) + continue; + + if (Predicate.isLoad() && Predicate.getMemoryVT() != nullptr) { + Optional MemTyOrNone = + MVTToLLT(getValueType(Predicate.getMemoryVT())); + + if (!MemTyOrNone) + return failedImport("MemVT could not be converted to LLT"); + + InsnMatcher.getOperand(0).addPredicate(MemTyOrNone.getValue()); + continue; + } + return failedImport("Src pattern child has predicate (" + explainPredicates(Src) + ")"); } @@ -2217,6 +2298,13 @@ Expected GlobalISelEmitter::createAndImportSelDAGMatcher( for (unsigned i = 0, e = Src->getNumChildren(); i != e; ++i) { TreePatternNode *SrcChild = Src->getChild(i); + // SelectionDAG allows pointers to be represented with iN since it doesn't + // distinguish between pointers and integers but they are different types in GlobalISel. + // Coerce integers to pointers to address space 0 if the context indicates a pointer. + // TODO: Find a better way to do this, SDTCisPtrTy? + bool OperandIsAPointer = + SrcGIOrNull->TheDef->getName() == "G_LOAD" && i == 0; + // For G_INTRINSIC/G_INTRINSIC_W_SIDE_EFFECTS, the operand immediately // following the defs is an intrinsic ID. if ((SrcGIOrNull->TheDef->getName() == "G_INTRINSIC" || @@ -2232,8 +2320,9 @@ Expected GlobalISelEmitter::createAndImportSelDAGMatcher( return failedImport("Expected IntInit containing instrinsic ID)"); } - if (auto Error = importChildMatcher(Rule, InsnMatcher, SrcChild, OpIdx++, - TempOpIdx)) + if (auto Error = + importChildMatcher(Rule, InsnMatcher, SrcChild, OperandIsAPointer, + OpIdx++, TempOpIdx)) return std::move(Error); } } @@ -2256,6 +2345,7 @@ Error GlobalISelEmitter::importComplexPatternOperandMatcher( Error GlobalISelEmitter::importChildMatcher(RuleMatcher &Rule, InstructionMatcher &InsnMatcher, const TreePatternNode *SrcChild, + bool OperandIsAPointer, unsigned OpIdx, unsigned &TempOpIdx) const { OperandMatcher &OM = @@ -2278,12 +2368,10 @@ Error GlobalISelEmitter::importChildMatcher(RuleMatcher &Rule, } } - Optional OpTyOrNone = None; - if (ChildTypes.front().isMachineValueType()) - OpTyOrNone = MVTToLLT(ChildTypes.front().getMachineValueType().SimpleTy); - if (!OpTyOrNone) - return failedImport("Src operand has an unsupported type (" + to_string(*SrcChild) + ")"); - OM.addPredicate(*OpTyOrNone); + if (auto Error = + OM.addTypeCheckPredicate(ChildTypes.front(), OperandIsAPointer)) + return failedImport(toString(std::move(Error)) + " for Src operand (" + + to_string(*SrcChild) + ")"); // Check for nested instructions. if (!SrcChild->isLeaf()) { -- 2.40.0