Index: include/llvm/CodeGen/GlobalISel/CombinerHelper.h =================================================================== --- include/llvm/CodeGen/GlobalISel/CombinerHelper.h +++ include/llvm/CodeGen/GlobalISel/CombinerHelper.h @@ -25,6 +25,12 @@ class MachineRegisterInfo; class MachineInstr; +struct PreferredTuple { + LLT Ty; // The result type of the extend. + unsigned ExtendOpcode; // G_ANYEXT/G_SEXT/G_ZEXT + MachineInstr *MI; +}; + class CombinerHelper { MachineIRBuilder &Builder; MachineRegisterInfo &MRI; @@ -39,10 +45,14 @@ /// If \p MI is COPY, try to combine it. /// Returns true if MI changed. bool tryCombineCopy(MachineInstr &MI); + bool matchCombineCopy(MachineInstr &MI); + void applyCombineCopy(MachineInstr &MI); /// If \p MI is extend that consumes the result of a load, try to combine it. /// Returns true if MI changed. bool tryCombineExtendingLoads(MachineInstr &MI); + bool matchCombineExtendingLoads(MachineInstr &MI, PreferredTuple &MatchInfo); + void applyCombineExtendingLoads(MachineInstr &MI, PreferredTuple &MatchInfo); /// Try to transform \p MI by using all of the above /// combine functions. Returns true if changed. Index: include/llvm/TableGen/Record.h =================================================================== --- include/llvm/TableGen/Record.h +++ include/llvm/TableGen/Record.h @@ -1241,6 +1241,7 @@ void Profile(FoldingSetNodeID &ID) const; Init *getOperator() const { return Val; } + Record *getOperatorAsDef(ArrayRef Loc) const; StringInit *getName() const { return ValName; } @@ -1568,6 +1569,11 @@ /// field does not exist or if the value is not the right type. std::vector getValueAsListOfStrings(StringRef FieldName) const; + /// This method looks up the specified field and + /// returns its value as a vector of dags, throwing an exception if the + /// field does not exist or if the value is not the right type. + std::vector getValueAsListOfDags(StringRef FieldName) const; + /// This method looks up the specified field and returns its /// value as a Record, throwing an exception if the field does not exist or if /// the value is not the right type. Index: include/llvm/Target/GlobalISel/Combine.td =================================================================== --- /dev/null +++ include/llvm/Target/GlobalISel/Combine.td @@ -0,0 +1,190 @@ +//===- Combine.td - Combine rule definitions ---------------*- tablegen -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Declare GlobalISel combine rules and provide mechanisms to opt-out. +// +//===----------------------------------------------------------------------===// + +// Common base class for GICombineRule and GICombineGroup. +class GICombine { + // See GICombineGroup. We only declare it here to make the tablegen pass + // simpler. + list Rules = ?; +} + +// A group of combine rules that can be added to a GICombiner or another group. +class GICombineGroup rules> : GICombine { + // The rules contained in this group. The rules in a group are flattened into + // a single list and sorted into whatever order is most efficient. However, + // they will never be re-ordered in such that behaviour differs from the + // specified order. It is therefore possible to use the order of rules in this + // list to describe priorities. + let Rules = rules; +} + +// Declares a combiner class +class GICombiner rules> + : GICombineGroup { + // The class name to use in the generated output. + string Classname = classname; + // The name of a run-time compiler option that will be generated to disable + // specific rules within this combiner. + string DisableRuleOption = ?; + // Any modifications that should be applied to the rules added to this + // combiner. For example, a rule could be forcefully disabled. + list Modifiers = []; +} + +class GICombinerModifier; +def gi_disable_rule : GICombinerModifier; + + +// Matches any opcode in the given set +class OneOpcode opcodes> { + list Opcodes = opcodes; +} + +class GICombineRule : GICombine { + /// Defines the external interface of the match rule. This includes: + /// * The names of the root nodes (requires at least one) + /// * The names and of any immediate operands. + /// * The names and of any complex operands. + /// * Any macro substitutions/alternatives that should apply to this rule. + /// See GIDefKind for details. + dag Defs = defs; + + /// Defines the things which must be true for the pattern to match + /// See GIMatchKind for details + dag Match = match; + + // Code to perform the final checks for the match. It usually consists of a + // function call into the CombinerHelper. + // + // The root MachineInstr of the match can be pasted in using '${root}'. + // TODO: Make named components of the pattern available via '${name}' + // FIXME: Currently includes the transformation code too. + code MatchCode = matchcode; +} + + + +// ================== + +/// The kind of a value used in predicate arguments, etc. +class GIValueKind; +/// MachineRegisterInfo::getType(Operand.getReg()) +def gi_operand_type : GIValueKind; +/// MachineOperand::getImm(), MachineOperand::getCImm(), or +/// G_CONSTANT/G_FCONSTANT as appropriate. +def gi_apint_value : GIValueKind; +/// MachineOperand +def gi_operand : GIValueKind; +/// Custom values returned by predicates. +class gi_custom_type : GIValueKind; + +class GICombineRule2 { + /// Defines the external interface of the match rule. This includes: + /// * The names of the root nodes (requires at least one) + /// * The names and of any immediate operands. + /// * The names and of any complex operands. + /// * Any macro substitutions/alternatives that should apply to this rule. + /// See GIDefKind for details. + dag Defs = defs; + + /// Defines the things which must be true for the pattern to match + /// See GIMatchKind for details + dag Match = match; + + /// Defines the things which happen after the decision is made to apply a + /// combine rule. + /// See GIMatchKind for details + dag Apply = apply; +} + +/// The operator at the root of a GICombineRule2.Defs dag. +def gi_defs; + +/// All arguments of the gi_defs operator must be subclasses of GIDefKind or +/// sub-dags whose operator is GIDefKindWithArgs. +class GIDefKind; +class GIDefKindWithArgs; +/// Declare a root node. There must be at least one of these in every combine +/// rule. +def gi_root : GIDefKind; +/// A virtual/physical register +/// TODO: I'm not 100% sure these are needed. The gi_defs entry isn't conveying +/// any information. +def gi_reg : GIDefKind; +/// An immediate operand. This is a G_CONSTANT or G_FCONSTANT (depending on +/// MVT) or a direct Imm/CImm operand. +def gi_imm : GIDefKind; +/// A macro declaration instantiating multiple nodes and/or alternatives. +def gi_macro : GIDefKindWithArgs; +/// +class GIDefMatchData : GIDefKind { + string Type = type; +} + +def gi_extending_load_matchdata : GIDefMatchData<"PreferredTuple">; + +/// The operator at the root of a GICombineRule2.Match dag. +def gi_match; +/// All arguments of the gi_match operator must be either subclasses of +/// GIMatchKind, GIMatchKindWithArgs, or an MIR code block. +class GIMatchKind; +class GIMatchKindWithArgs; + +def gi_wip_match_opcode : GIMatchKindWithArgs; + +/// All predicates must subclass this. +class GIMatchPredicate : GIMatchKindWithArgs; + +/// The operator at the root of a GICombineRule2.Apply dag. +def gi_apply; +/// All arguments of the gi_apply operator must be subclasses of GIApplyKind, or +/// sub-dags whose operator is GIApplyKindWithArgs, or an MIR block. +class GIApplyKind; +class GIApplyKindWithArgs; +/// Create a new operand and give it a name for use in an MIR block. +def gi_create_operand : GIApplyKindWithArgs; +/// Execute arbitrary C++ +def gi_exec : GIApplyKindWithArgs; + +/// The body of a macro must either be a sub-dag whose operator is a subclass +/// of GIMacroBody or an MIR code block. +class GIMacroBody; +/// Specify alternatives for the macro body. +def gi_oneof : GIMacroBody; + +//def : GICombineRule2< +// (gi_defs gi_root:$root, gi_reg:$A, gi_imm:$MASK), +// (gi_match [{MIR +// %1 = G_AND %A, %MASK +// %root = G_TRUNC %1 +// }], +// (is_imm_all_ones gi_apint_value:$MASK, gi_operand_type:$root)), +// (gi_apply [{MIR %root = G_TRUNC %A }])>; + + +def copy_prop : GICombineRule< + (gi_defs gi_root:$root), + (gi_match (gi_wip_match_opcode COPY), + [{ return Helper.matchCombineCopy(${root}); }]), + [{ Helper.applyCombineCopy(${root}); }]>; + +def extending_loads : GICombineRule< + (gi_defs gi_root:$root, gi_extending_load_matchdata:$matchinfo), + (gi_match (gi_wip_match_opcode G_LOAD, G_SEXTLOAD, G_ZEXTLOAD), + [{ return Helper.matchCombineExtendingLoads(${root}, ${matchinfo}); }]), + [{ Helper.applyCombineExtendingLoads(${root}, ${matchinfo}); }]>; + +def trivial_combines : GICombineGroup<[copy_prop]>; +def combines_for_extload: GICombineGroup<[extending_loads]>; + +def all_combines : GICombineGroup<[trivial_combines, combines_for_extload]>; Index: lib/CodeGen/GlobalISel/Combiner.cpp =================================================================== --- lib/CodeGen/GlobalISel/Combiner.cpp +++ lib/CodeGen/GlobalISel/Combiner.cpp @@ -25,6 +25,12 @@ using namespace llvm; +namespace llvm { +cl::OptionCategory + GICombinerOptionCategory("GlobalISel Combiner", + "Control the rules which are enabled"); +} // end namespace llvm + namespace { /// This class acts as the glue the joins the CombinerHelper to the overall /// Combine algorithm. The CombinerHelper is intended to report the Index: lib/CodeGen/GlobalISel/CombinerHelper.cpp =================================================================== --- lib/CodeGen/GlobalISel/CombinerHelper.cpp +++ lib/CodeGen/GlobalISel/CombinerHelper.cpp @@ -30,6 +30,13 @@ } bool CombinerHelper::tryCombineCopy(MachineInstr &MI) { + if (matchCombineCopy(MI)) { + applyCombineCopy(MI); + return true; + } + return false; +} +bool CombinerHelper::matchCombineCopy(MachineInstr &MI) { if (MI.getOpcode() != TargetOpcode::COPY) return false; unsigned DstReg = MI.getOperand(0).getReg(); @@ -38,20 +45,18 @@ LLT SrcTy = MRI.getType(SrcReg); // Simple Copy Propagation. // a(sx) = COPY b(sx) -> Replace all uses of a with b. - if (DstTy.isValid() && SrcTy.isValid() && DstTy == SrcTy) { - MI.eraseFromParent(); - replaceRegWith(MRI, DstReg, SrcReg); + if (DstTy.isValid() && SrcTy.isValid() && DstTy == SrcTy) return true; - } return false; } +void CombinerHelper::applyCombineCopy(MachineInstr &MI) { + unsigned DstReg = MI.getOperand(0).getReg(); + unsigned SrcReg = MI.getOperand(1).getReg(); + MI.eraseFromParent(); + replaceRegWith(MRI, DstReg, SrcReg); +} namespace { -struct PreferredTuple { - LLT Ty; // The result type of the extend. - unsigned ExtendOpcode; // G_ANYEXT/G_SEXT/G_ZEXT - MachineInstr *MI; -}; /// Select a preference between two uses. CurrentUse is the current preference /// while *ForCandidate is attributes of the candidate under consideration. @@ -136,16 +141,16 @@ } // end anonymous namespace bool CombinerHelper::tryCombineExtendingLoads(MachineInstr &MI) { - struct InsertionPoint { - MachineOperand *UseMO; - MachineBasicBlock *InsertIntoBB; - MachineBasicBlock::iterator InsertBefore; - InsertionPoint(MachineOperand *UseMO, MachineBasicBlock *InsertIntoBB, - MachineBasicBlock::iterator InsertBefore) - : UseMO(UseMO), InsertIntoBB(InsertIntoBB), InsertBefore(InsertBefore) { - } - }; + PreferredTuple Preferred; + if (matchCombineExtendingLoads(MI, Preferred)) { + applyCombineExtendingLoads(MI, Preferred); + return true; + } + return false; +} +bool CombinerHelper::matchCombineExtendingLoads(MachineInstr &MI, + PreferredTuple &Preferred) { // We match the loads and follow the uses to the extend instead of matching // the extends and following the def to the load. This is because the load // must remain in the same position for correctness (unless we also add code @@ -175,7 +180,7 @@ : MI.getOpcode() == TargetOpcode::G_SEXTLOAD ? TargetOpcode::G_SEXT : TargetOpcode::G_ZEXT; - PreferredTuple Preferred = {LLT(), PreferredOpcode, nullptr}; + Preferred = {LLT(), PreferredOpcode, nullptr}; for (auto &UseMI : MRI.use_instructions(LoadValue.getReg())) { if (UseMI.getOpcode() == TargetOpcode::G_SEXT || UseMI.getOpcode() == TargetOpcode::G_ZEXT || @@ -194,6 +199,20 @@ assert(Preferred.Ty != LoadValueTy && "Extending to same type?"); LLVM_DEBUG(dbgs() << "Preferred use is: " << *Preferred.MI); + return true; +} + +void CombinerHelper::applyCombineExtendingLoads(MachineInstr &MI, + PreferredTuple &Preferred) { + struct InsertionPoint { + MachineOperand *UseMO; + MachineBasicBlock *InsertIntoBB; + MachineBasicBlock::iterator InsertBefore; + InsertionPoint(MachineOperand *UseMO, MachineBasicBlock *InsertIntoBB, + MachineBasicBlock::iterator InsertBefore) + : UseMO(UseMO), InsertIntoBB(InsertIntoBB), InsertBefore(InsertBefore) { + } + }; // Rewrite the load to the chosen extending load. unsigned ChosenDstReg = Preferred.MI->getOperand(0).getReg(); @@ -208,6 +227,7 @@ // Rewrite all the uses to fix up the types. SmallVector ScheduleForErase; SmallVector ScheduleForInsert; + auto &LoadValue = MI.getOperand(0); for (auto &UseMO : MRI.use_operands(LoadValue.getReg())) { MachineInstr *UseMI = UseMO.getParent(); @@ -307,8 +327,6 @@ EraseMI->eraseFromParent(); MI.getOperand(0).setReg(ChosenDstReg); Observer.changedInstr(MI); - - return true; } bool CombinerHelper::tryCombine(MachineInstr &MI) { Index: lib/TableGen/Record.cpp =================================================================== --- lib/TableGen/Record.cpp +++ lib/TableGen/Record.cpp @@ -1748,6 +1748,13 @@ ProfileDagInit(ID, Val, ValName, makeArrayRef(getTrailingObjects(), NumArgs), makeArrayRef(getTrailingObjects(), NumArgNames)); } +Record *DagInit::getOperatorAsDef(ArrayRef Loc) const { + if (DefInit *DefI = dyn_cast(Val)) + return DefI->getDef(); + PrintFatalError(Loc, "Expected record as operator"); + return nullptr; +} + Init *DagInit::resolveReferences(Resolver &R) const { SmallVector NewArgs; NewArgs.reserve(arg_size()); @@ -2076,6 +2083,20 @@ return Strings; } +std::vector +Record::getValueAsListOfDags(StringRef FieldName) const { + ListInit *List = getValueAsListInit(FieldName); + std::vector Dags; + for (Init *I : List->getValues()) { + if (DagInit *DI = dyn_cast(I)) + Dags.push_back(DI); + else + PrintFatalError(getLoc(), "Record `" + getName() + "', field `" + + FieldName + "' list is not entirely DagInit!"); + } + return Dags; +} + Record *Record::getValueAsDef(StringRef FieldName) const { const RecordVal *R = getValue(FieldName); if (!R || !R->getValue()) Index: lib/Target/AArch64/AArch64.td =================================================================== --- lib/Target/AArch64/AArch64.td +++ lib/Target/AArch64/AArch64.td @@ -273,6 +273,7 @@ include "AArch64Schedule.td" include "AArch64InstrInfo.td" +include "AArch64Combine.td" def AArch64InstrInfo : InstrInfo; Index: lib/Target/AArch64/AArch64Combine.td =================================================================== --- /dev/null +++ lib/Target/AArch64/AArch64Combine.td @@ -0,0 +1,21 @@ +//=- AArch64.td - Define AArch64 Combine Rules ---------------*- tablegen -*-=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// +//===----------------------------------------------------------------------===// + +include "llvm/Target/GlobalISel/Combine.td" + +def AArch64PreLegalizerCombiner: GICombiner< + "AArch64GenPreLegalizerCombiner", [all_combines]> { + let DisableRuleOption = "aarch64prelegalizercombiner-disable-rule"; + let Modifiers = [(gi_disable_rule copy_prop)]; +} +def AArch64PostLegalizerCombiner: GICombiner<"AArch64GenPostLegalizerCombiner", + [copy_prop]>; Index: lib/Target/AArch64/AArch64PreLegalizerCombiner.cpp =================================================================== --- lib/Target/AArch64/AArch64PreLegalizerCombiner.cpp +++ lib/Target/AArch64/AArch64PreLegalizerCombiner.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "AArch64TargetMachine.h" +#include "llvm/ADT/SparseBitVector.h" #include "llvm/CodeGen/GlobalISel/Combiner.h" #include "llvm/CodeGen/GlobalISel/CombinerHelper.h" #include "llvm/CodeGen/GlobalISel/CombinerInfo.h" @@ -26,12 +27,26 @@ using namespace llvm; using namespace MIPatternMatch; +#define AARCH64PRELEGALIZERCOMBINER_GENCOMBINERINFO_DEPS +#include "AArch64GenGICombiner.inc" +#undef AARCH64PRELEGALIZERCOMBINER_GENCOMBINERINFO_DEPS + namespace { +#define AARCH64PRELEGALIZERCOMBINER_GENCOMBINERINFO_H +#include "AArch64GenGICombiner.inc" +#undef AARCH64PRELEGALIZERCOMBINER_GENCOMBINERINFO_H + class AArch64PreLegalizerCombinerInfo : public CombinerInfo { public: + AArch64GenPreLegalizerCombiner Generated; + AArch64PreLegalizerCombinerInfo() : CombinerInfo(/*AllowIllegalOps*/ true, /*ShouldLegalizeIllegal*/ false, - /*LegalizerInfo*/ nullptr) {} + /*LegalizerInfo*/ nullptr) { + if (!Generated.parseCommandLineOption()) + report_fatal_error("Invalid rule identifier"); + } + virtual bool combine(CombinerChangeObserver &Observer, MachineInstr &MI, MachineIRBuilder &B) const override; }; @@ -39,20 +54,16 @@ bool AArch64PreLegalizerCombinerInfo::combine(CombinerChangeObserver &Observer, MachineInstr &MI, MachineIRBuilder &B) const { - CombinerHelper Helper(Observer, B); - - switch (MI.getOpcode()) { - default: - return false; - case TargetOpcode::G_LOAD: - case TargetOpcode::G_SEXTLOAD: - case TargetOpcode::G_ZEXTLOAD: - return Helper.tryCombineExtendingLoads(MI); - } + if (Generated.combineImpl(Observer, MI, B)) + return true; return false; } +#define AARCH64PRELEGALIZERCOMBINER_GENCOMBINERINFO_CPP +#include "AArch64GenGICombiner.inc" +#undef AARCH64PRELEGALIZERCOMBINER_GENCOMBINERINFO_CPP + // Pass boilerplate // ================ @@ -68,7 +79,7 @@ void getAnalysisUsage(AnalysisUsage &AU) const override; }; -} +} // end anonymous namespace void AArch64PreLegalizerCombiner::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); Index: lib/Target/AArch64/CMakeLists.txt =================================================================== --- lib/Target/AArch64/CMakeLists.txt +++ lib/Target/AArch64/CMakeLists.txt @@ -8,6 +8,8 @@ tablegen(LLVM AArch64GenDisassemblerTables.inc -gen-disassembler) tablegen(LLVM AArch64GenFastISel.inc -gen-fast-isel) tablegen(LLVM AArch64GenGlobalISel.inc -gen-global-isel) +tablegen(LLVM AArch64GenGICombiner.inc -gen-global-isel-combiner + -combiners='AArch64PreLegalizerCombiner') tablegen(LLVM AArch64GenInstrInfo.inc -gen-instr-info) tablegen(LLVM AArch64GenMCCodeEmitter.inc -gen-emitter) tablegen(LLVM AArch64GenMCPseudoLowering.inc -gen-pseudo-lowering) Index: test/CodeGen/AArch64/GlobalISel/prelegalizercombiner-extending-loads.mir =================================================================== --- test/CodeGen/AArch64/GlobalISel/prelegalizercombiner-extending-loads.mir +++ test/CodeGen/AArch64/GlobalISel/prelegalizercombiner-extending-loads.mir @@ -1,4 +1,8 @@ -# RUN: llc -O0 -run-pass=aarch64-prelegalizer-combiner -global-isel %s -o - | FileCheck %s +# RUN: llc -O0 -run-pass=aarch64-prelegalizer-combiner -global-isel %s -o - | \ +# RUN: FileCheck -check-prefixes=ALL,CHECK %s +# RUN: llc -O0 -run-pass=aarch64-prelegalizer-combiner -global-isel %s -o - \ +# RUN: -aarch64prelegalizercombiner-disable-rule=extending_loads | \ +# RUN: FileCheck -check-prefixes=ALL,DISABLED %s --- | target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" @@ -86,10 +90,12 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_anyext - ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 + ; ALL-LABEL: name: test_anyext + ; ALL: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_LOAD [[T0]](p0) :: (load 1 from %ir.addr) - ; CHECK: $w0 = COPY [[T1]](s32) + ; DISABLED: [[T2:%[0-9]+]]:_(s8) = G_LOAD %0(p0) :: (load 1 from %ir.addr) + ; DISABLED: [[T1:%[0-9]+]]:_(s32) = G_ANYEXT [[T2]] + ; ALL: $w0 = COPY [[T1]](s32) %0:_(p0) = COPY $x0 %1:_(s8) = G_LOAD %0 :: (load 1 from %ir.addr) %2:_(s32) = G_ANYEXT %1 @@ -101,9 +107,11 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_anyext - ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 + ; ALL-LABEL: name: test_anyext + ; ALL: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_LOAD [[T0]](p0) :: (load 1 from %ir.addr) + ; DISABLED: [[T2:%[0-9]+]]:_(s8) = G_LOAD [[T0]](p0) :: (load 1 from %ir.addr) + ; DISABLED: [[T1:%[0-9]+]]:_(s32) = G_ANYEXT [[T2]] ; CHECK: $w0 = COPY [[T1]](s32) %0:_(p0) = COPY $x0 %1:_(s8) = G_LOAD %0 :: (load 1 from %ir.addr) @@ -117,7 +125,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_signext + ; ALL-LABEL: name: test_signext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -132,7 +140,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_zeroext + ; ALL-LABEL: name: test_zeroext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_ZEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -147,7 +155,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_2anyext + ; ALL-LABEL: name: test_2anyext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_LOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -165,7 +173,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_1anyext_1signext + ; ALL-LABEL: name: test_1anyext_1signext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -183,7 +191,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_1xor_1signext + ; ALL-LABEL: name: test_1xor_1signext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: [[T2:%[0-9]+]]:_(s8) = G_TRUNC [[T1]] @@ -206,7 +214,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_1anyext_1zeroext + ; ALL-LABEL: name: test_1anyext_1zeroext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_ZEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -224,7 +232,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_1signext_1zeroext + ; ALL-LABEL: name: test_1signext_1zeroext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: [[T2:%[0-9]+]]:_(s8) = G_TRUNC [[T1]] @@ -244,7 +252,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_1anyext64_1signext32 + ; ALL-LABEL: name: test_1anyext64_1signext32 ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: [[T2:%[0-9]+]]:_(s64) = G_ANYEXT [[T1]] @@ -263,7 +271,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_1anyext32_1signext64 + ; ALL-LABEL: name: test_1anyext32_1signext64 ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s64) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: [[T2:%[0-9]+]]:_(s8) = G_TRUNC [[T1]] @@ -283,7 +291,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_2anyext32_1signext64 + ; ALL-LABEL: name: test_2anyext32_1signext64 ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s64) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: [[T2:%[0-9]+]]:_(s8) = G_TRUNC [[T1]] @@ -307,7 +315,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock_anyext + ; ALL-LABEL: name: test_multiblock_anyext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_LOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: G_BR %bb.1 @@ -325,7 +333,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock_signext + ; ALL-LABEL: name: test_multiblock_signext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -342,7 +350,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock_zeroext + ; ALL-LABEL: name: test_multiblock_zeroext ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_ZEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -359,7 +367,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock + ; ALL-LABEL: name: test_multiblock ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_LOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: $w0 = COPY [[T1]](s32) @@ -379,7 +387,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock_1anyext64_1signext32 + ; ALL-LABEL: name: test_multiblock_1anyext64_1signext32 ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s32) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: G_BR %bb.1 @@ -401,7 +409,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock_1anyext32_1signext64 + ; ALL-LABEL: name: test_multiblock_1anyext32_1signext64 ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s64) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: G_BR %bb.1 @@ -424,7 +432,7 @@ body: | bb.0.entry: liveins: $x0 - ; CHECK-LABEL: name: test_multiblock_2anyext32_1signext64 + ; ALL-LABEL: name: test_multiblock_2anyext32_1signext64 ; CHECK: [[T0:%[0-9]+]]:_(p0) = COPY $x0 ; CHECK: [[T1:%[0-9]+]]:_(s64) = G_SEXTLOAD [[T0]](p0) :: (load 1 from %ir.addr) ; CHECK: [[T2:%[0-9]+]]:_(s8) = G_TRUNC [[T1]] Index: utils/TableGen/CMakeLists.txt =================================================================== --- utils/TableGen/CMakeLists.txt +++ utils/TableGen/CMakeLists.txt @@ -24,6 +24,7 @@ FastISelEmitter.cpp FixedLenDecoderEmitter.cpp GlobalISelEmitter.cpp + GICombinerEmitter.cpp InfoByHwMode.cpp InstrInfoEmitter.cpp InstrDocsEmitter.cpp Index: utils/TableGen/GICombinerEmitter.cpp =================================================================== --- /dev/null +++ utils/TableGen/GICombinerEmitter.cpp @@ -0,0 +1,613 @@ +//===- GlobalCombinerEmitter.cpp - Generate a combiner --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +/// \file +/// +//===----------------------------------------------------------------------===// + +#include "CodeGenDAGPatterns.h" +#include "SubtargetFeatureInfo.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/CodeGenCoverage.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/LowLevelTypeImpl.h" +#include "llvm/Support/MachineValueType.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/TableGen/Error.h" +#include "llvm/TableGen/Record.h" +#include "llvm/TableGen/StringMatcher.h" +#include "llvm/TableGen/TableGenBackend.h" +using namespace llvm; + +#define DEBUG_TYPE "gicombiner-emitter" + +STATISTIC(NumPatternTotal, "Total number of patterns"); + +static cl::OptionCategory + GICombinerEmitterCat("Options for -gen-global-isel-combiner"); +static cl::list + SelectedCombiners("combiners", cl::desc("Emit the specified combiners"), + cl::cat(GICombinerEmitterCat), cl::CommaSeparated); + +namespace { +typedef uint64_t RuleID; + +/// Formats a reference to an Instruction-like Record as an Opcode enumarator +/// +/// For example: +/// def FOO : Instruction { let Namespace = "BAR"; } +/// would be formatted as: +/// BAR::FOO +class OpcodeEnumeratorFormatter { + Record &R; + +public: + OpcodeEnumeratorFormatter(Record &R) : R(R) {} + + Record &getDef() const { return R; } +}; + +inline raw_ostream &operator<<(raw_ostream &OS, + const OpcodeEnumeratorFormatter &Formatter) { + OS << Formatter.getDef().getValueAsString("Namespace") + << "::" << Formatter.getDef().getName(); + return OS; +} + +/// Declares data that is passed from the match stage to the apply stage. +class MatchDataInfo { + /// The symbol used in the tablegen patterns + StringRef PatternSymbol; + /// The data type for the variable + StringRef Type; + /// The name of the variable as declared in the generated matcher. + std::string VariableName; + +public: + MatchDataInfo(StringRef PatternSymbol, StringRef Type, StringRef VariableName) + : PatternSymbol(PatternSymbol), Type(Type), VariableName(VariableName) {} + + StringRef getPatternSymbol() const { return PatternSymbol; }; + StringRef getType() const { return Type; }; + StringRef getVariableName() const { return VariableName; }; +}; + +class RootInfo { + StringRef PatternSymbol; + +public: + RootInfo(StringRef PatternSymbol) : PatternSymbol(PatternSymbol) {} +}; + +class CombineRule { +public: + /// FIXME: This is a temporary measure until we have actual MIR matching + using const_root_opcode_iterator = std::vector::const_iterator; + + using const_matchdata_iterator = std::vector::const_iterator; + +protected: + /// A unique ID for this rule + /// ID's are used for debugging and run-time disabling of rules among other + /// things. + RuleID ID; + + /// The record defining this rule. + Record *TheDef; + + /// The roots of a match. These are the leaves of the DAG that are closest to + /// the end of the function. I.e. the nodes that are encountered without + /// following any edges of the DAG described by the pattern as we work our way + /// from the bottom of the function to the top. + std::vector Roots; + + /// The possible root opcodes. + /// FIXME: This is a temporary measure until we have actual pattern matching + std::vector PossibleRootOpcodes; + + /// A block of arbitrary C++ to finish testing the match. + /// FIXME: This is a temporary measure until we have actual pattern matching + std::string Code = ""; + + /// The MatchData defined by the match stage and required by the apply stage. + /// This allows the plumbing of arbitrary data from C++ predicates between the + /// stages. + /// + /// For example, suppose you have: + /// %A = + /// %0 = G_ADD %1, %A + /// you could define a GIMatchPredicate that walks %2, constant folds as much + /// as possible and returns an APInt containing the discovered constant. You + /// could then declare: + /// def gi_apint : GIDefMatchData<"APInt">; + /// add it to the rule with: + /// (gi_defs gi_root:$root, gi_apint:$constant) + /// evaluate it in the pattern with a C++ function that takes a + /// MachineOperand& and an APInt& with: + /// (gi_match [{MIR %root = G_ADD %0, %A }], + /// (constantfold gi_operand:$A, gi_apint:$constant)) + /// and finally use it in the apply stage with: + /// (gi_apply (gi_create_operand + /// [{ MachineOperand::CreateImm(${constant}.getZExtValue()); ]}, + /// gi_apint:$constant), + /// [{MIR %root = FOO %0, %constant }]) + std::vector MatchDataDecls; + + void declareMatchData(StringRef PatternSymbol, StringRef Type, + StringRef VarName); + + void parseDefs(); + void parseMatcher(); + +public: + CombineRule(RuleID ID, Record *R) : ID(ID), TheDef(R) { + parseDefs(); + parseMatcher(); + } + + RuleID getID() const { return ID; } + StringRef getName() const { return TheDef->getName(); } + Record *getDef() const { return TheDef; } + StringRef getCode() const { return Code; } + + iterator_range root_opcodes() const { + return make_range(PossibleRootOpcodes.begin(), PossibleRootOpcodes.end()); + } + + iterator_range matchdata_decls() const { + return make_range(MatchDataDecls.begin(), MatchDataDecls.end()); + } +}; + +class CodeExpansions { + StringMap Expansions; + +public: + void declare(StringRef Name, StringRef Expansion) { + bool Inserted = Expansions.try_emplace(Name, Expansion).second; + assert(Inserted && "Declared variable twice"); + } + + /// Import expansions for all declared MatchData in a rule + void declareFromRule(const CombineRule &Rule) { + for (const auto &I : Rule.matchdata_decls()) + declare(I.getPatternSymbol(), I.getVariableName()); + } + + std::string lookup(StringRef Variable) const { + return Expansions.lookup(Variable); + } +}; + +/// Emit the given code with all '${foo}' placeholders expanded to their +/// replacements. +/// +/// It's an error to use an undefined expansion and expansion-like output that +/// needs to be emitted verbatim can be escaped as '$${foo}' +/// +/// The emitted code can be given a custom indent to enable both indentation by +/// an arbitrary amount of whitespace and emission of the code as a comment. +class CodeExpander { + StringRef Code; + const CodeExpansions &Expansions; + StringRef Indent; + +public: + CodeExpander(StringRef Code, const CodeExpansions &Expansions) + : CodeExpander(Code, Expansions, " ") {} + CodeExpander(StringRef Code, const CodeExpansions &Expansions, + StringRef Indent) + : Code(Code), Expansions(Expansions), Indent(Indent) {} + + void emit(raw_ostream &OS) const { + StringRef Current = Code; + + while (!Current.empty()) { + size_t Pos = Current.find_first_of("$\n"); + if (Pos == StringRef::npos) { + OS << Current; + Current = ""; + continue; + } + + OS << Current.substr(0, Pos); + Current = Current.substr(Pos); + + if (Current.startswith("\n")) { + OS << "\n" << Indent; + Current = Current.drop_front(1); + continue; + } + + if (Current.startswith("$${")) { + OS << "${"; + Current = Current.drop_front(2); + continue; + } + + if (Current.startswith("${")) { + Current = Current.drop_front(2); + StringRef Var; + std::tie(Var, Current) = Current.split("}"); + OS << Expansions.lookup(Var); + continue; + } + + OS << "$"; + Current = Current.drop_front(1); + } + } +}; + +inline raw_ostream &operator<<(raw_ostream &OS, + const CodeExpander &Expander) { + Expander.emit(OS); + return OS; +} + +/// A convenience function to check that an Init refers to a specific def. This +/// is primarily useful for testing for gi_defs and similar in DagInit's since +/// DagInit's support any type inside them. +static bool isSpecificDef(const Init &N, StringRef Def) { + if (const DefInit *OpI = dyn_cast(&N)) + if (OpI->getDef()->getName() == Def) + return true; + return false; +} + +/// A convenience function to check that an Init refers to a def that is a +/// subclass of the given class and coerce it to a def if it is. This is +/// primarily useful for testing for subclasses of GIMatchKind and similar in +/// DagInit's since DagInit's support any type inside them. +static Record *getDefOfSubClass(const Init &N, StringRef Cls) { + if (const DefInit *OpI = dyn_cast(&N)) + if (OpI->getDef()->isSubClassOf(Cls)) + return OpI->getDef(); + return nullptr; +} + +/// A convenience function to check that an Init refers to a dag whose operator +/// is a def that is a subclass of the given class and coerce it to a dag if it +/// is. This is primarily useful for testing for subclasses of GIMatchKind and +/// similar in DagInit's since DagInit's support any type inside them. +static const DagInit *getDagWithOperatorOfSubClass(const Init &N, + StringRef Cls) { + if (const DagInit *I = dyn_cast(&N)) + if (I->getNumArgs() > 0) + if (const DefInit *OpI = dyn_cast(I->getOperator())) + if (OpI->getDef()->isSubClassOf(Cls)) + return I; + return nullptr; +} + +void CombineRule::declareMatchData(StringRef PatternSymbol, StringRef Type, + StringRef VarName) { + MatchDataDecls.emplace_back(PatternSymbol, Type, VarName); +} + +void CombineRule::parseDefs() { + DagInit *Defs = TheDef->getValueAsDag("Defs"); + + if (Defs->getOperatorAsDef(TheDef->getLoc())->getName() != "gi_defs") + PrintFatalError(TheDef->getLoc(), "Expected gi_defs operator"); + + for (unsigned I = 0; I < Defs->getNumArgs(); ++I) { + // Roots should be collected into Roots + if (isSpecificDef(*Defs->getArg(I), "gi_root")) { + Roots.emplace_back(Defs->getArgNameStr(I)); + continue; + } + + // Subclasses of GIDefMatchData should declare that this rule needs to pass + // data from the match stage to the apply stage, and ensure that the + // generated matcher has a suitable variable for it to do so. + if (Record *MatchDataRec = + getDefOfSubClass(*Defs->getArg(I), "GIDefMatchData")) { + declareMatchData(Defs->getArgNameStr(I), + MatchDataRec->getValueAsString("Type"), + llvm::to_string(llvm::format("MatchData%d", ID))); + continue; + } + + // Otherwise emit an appropriate error message. + if (getDefOfSubClass(*Defs->getArg(I), "GIDefKind")) + PrintFatalError(TheDef->getLoc(), + "This GIDefKind not implemented in tablegen"); + if (getDefOfSubClass(*Defs->getArg(I), "GIDefKindWithArgs")) + PrintFatalError(TheDef->getLoc(), + "This GIDefKindWithArgs not implemented in tablegen"); + PrintFatalError(TheDef->getLoc(), + "Expected a subclass of GIDefKind or a sub-dag whose " + "operator is of type GIDefKindWithArgs"); + } + + if (Roots.empty()) + PrintFatalError(TheDef->getLoc(), + "Combine rules must have at least one root"); + // For now, don't support multi-root rules. We'll come back to this later once + // we have the algorithm changes to support it. + if (Roots.size() > 1) + PrintFatalError(TheDef->getLoc(), + "Multi-root matches are not supported (yet)"); +} + +void CombineRule::parseMatcher() { + DagInit *Matchers = TheDef->getValueAsDag("Match"); + + if (Matchers->getOperatorAsDef(TheDef->getLoc())->getName() != "gi_match") + PrintFatalError(TheDef->getLoc(), "Expected gi_match operator"); + + for (unsigned I = 0; I < Matchers->getNumArgs(); ++I) { + if (const DagInit *Matcher = getDagWithOperatorOfSubClass( + *Matchers->getArg(I), "GIMatchKindWithArgs")) { + // Parse the temporary gi_wip_match_opcode matcher we have in lieu of + // supporting MIR matching. + if (Matcher->getOperatorAsDef(TheDef->getLoc())->getName() == + "gi_wip_match_opcode") { + if (Matcher->getNumArgs() == 0) + PrintFatalError(TheDef->getLoc(), + "Expected at least one arg to gi_wip_match_opcode"); + for (const auto &MatcherArg : Matcher->getArgs()) { + if (Record *InsnRec = getDefOfSubClass(*MatcherArg, "Instruction")) { + PossibleRootOpcodes.push_back(InsnRec); + continue; + } + PrintFatalError(TheDef->getLoc(), + "Expected an Instruction inside gi_wip_match_opcode"); + } + continue; + } + } + + // Parse arbitrary C++ code we have in lieu of supporting MIR matching + if (const CodeInit *CodeI = dyn_cast(Matchers->getArg(I))) { + assert(Code.empty() && + "Only one block of arbitrary code is currently permitted"); + Code = CodeI->getValue(); + continue; + } + + if (getDefOfSubClass(*Matchers->getArg(I), "GIMatchKind")) + PrintFatalError(TheDef->getLoc(), "GIMatchKind not implemented"); + if (getDefOfSubClass(*Matchers->getArg(I), "GIMatchKindWithArgs")) + PrintFatalError(TheDef->getLoc(), "GIMatchKindWithArgs not implemented"); + PrintFatalError(TheDef->getLoc(), + "Expected a subclass of GIMatchKind or a sub-dag whose " + "operator is of type GIMatchKindWithArgs"); + } +} + +class GICombinerEmitter { + StringRef Name; + const CodeGenTarget Target; + Record *Combiner; + std::vector Rules; + DenseMap RuleFromRecord; + +public: + explicit GICombinerEmitter(RecordKeeper &RK, StringRef Name); + + StringRef getClassName() const { + return Combiner->getValueAsString("Classname"); + } + + uint64_t getRuleIDFor(const Record *R) const { + return RuleFromRecord.lookup(R); + } + + void run(raw_ostream &OS); + + /// Emit the name matcher (guarded by #ifndef NDEBUG) used to disable rules in + /// response to the generated cl::opt. + void emitNameMatcher(raw_ostream &OS) const; +}; + +GICombinerEmitter::GICombinerEmitter(RecordKeeper &RK, StringRef Name) + : Name(Name), Target(RK), Combiner(RK.getDef(Name)) {} + +void GICombinerEmitter::emitNameMatcher(raw_ostream &OS) const { + std::vector> Cases; + Cases.reserve(Rules.size()); + + for (const auto &EnumeratedRule : Rules) { + std::string Code; + raw_string_ostream SS(Code); + SS << "return " << EnumeratedRule.getID() << ";\n"; + Cases.push_back(std::make_pair(EnumeratedRule.getName(), SS.str())); + } + + OS << "#ifndef NDEBUG\n" + << "static Optional getRuleIdxForIdentifier(StringRef RuleIdentifier) {\n" + << " uint64_t I;\n" + << " bool NotAnInteger = RuleIdentifier.getAsInteger(0, I);\n" + << " if (!NotAnInteger)\n" + << " return I;\n\n"; + StringMatcher Matcher("RuleIdentifier", Cases, OS); + Matcher.Emit(); + OS << " return None;\n" + << "}\n" + << "#endif // ifndef NDEBUG\n\n"; + + OS << "bool " << getClassName() + << "::setRuleDisabled(StringRef RuleIdentifier) {\n" + << "#ifndef NDEBUG\n" + << " std::pair RangePair = " + "RuleIdentifier.split('-');\n" + << " if (!RangePair.second.empty()) {\n" + << " const auto First = getRuleIdxForIdentifier(RangePair.first);\n" + << " const auto Last = getRuleIdxForIdentifier(RangePair.second);\n" + << " if (!First.hasValue() || !Last.hasValue())\n" + << " return false;\n" + << " if (First >= Last)\n" + << " report_fatal_error(\"Beginning of range should be before end of " + "range\");\n" + << " for (auto I = First.getValue(); I < Last.getValue(); ++I)\n" + << " DisabledRules.set(I);\n" + << " } else {\n" + << " const auto I = getRuleIdxForIdentifier(RangePair.first);\n" + << " if (!I.hasValue())\n" + << " return false;\n" + << " DisabledRules.set(I.getValue());\n" + << " return true;\n" + << " }\n"; + OS << "#else // ifndef NDEBUG\n" + << " llvm_unreachable(\"Cannot disable rules in non-asserts builds\");\n" + << "#endif // ifndef NDEBUG\n\n" + << " return false;\n" + << "}\n"; +} + +/// Recurse into GICombineGroup's and flatten the ruleset into a simple list. +static void gatherRules(std::vector &ActiveRules, + const std::vector &&RulesAndGroups) { + for (Record *R : RulesAndGroups) { + if (R->isValueUnset("Rules")) { + ActiveRules.emplace_back(NumPatternTotal, R); + ++NumPatternTotal; + } else + gatherRules(ActiveRules, R->getValueAsListOfDefs("Rules")); + } +} + +void GICombinerEmitter::run(raw_ostream &OS) { + gatherRules(Rules, Combiner->getValueAsListOfDefs("Rules")); + for (const auto &EnumeratedRule : Rules) + RuleFromRecord.try_emplace(EnumeratedRule.getDef(), EnumeratedRule.getID()); + + OS << "#ifdef " << Name.upper() << "_GENCOMBINERINFO_DEPS\n" + << "namespace llvm {\n" + << "extern cl::OptionCategory GICombinerOptionCategory;\n" + << "} // end namespace llvm\n" + << "#endif // ifdef " << Name.upper() << "_GENCOMBINERINFO_DEPS\n"; + + OS << "#ifdef " << Name.upper() << "_GENCOMBINERINFO_H\n" + << "class " << getClassName() << " {\n" + << " SparseBitVector<> DisabledRules;\n" + << "\n" + << "public:\n" + << " " << getClassName() << "() {\n"; + + for (const auto *Mod : Combiner->getValueAsListOfDags("Modifiers")) { + if (Mod->getOperatorAsDef(Combiner->getLoc())->getName() == + "gi_disable_rule") { + for (const auto *ModArg : Mod->getArgs()) { + if (const DefInit *ModArgDef = dyn_cast(ModArg)) + OS << " DisabledRules.set(" << getRuleIDFor(ModArgDef->getDef()) + << ");\n"; + else + PrintFatalError(Combiner->getLoc(), + "Expected def but found " + llvm::to_string(*ModArg)); + } + } else { + PrintFatalError(Combiner->getLoc(), + "Unexpected combiner modifier found " + llvm::to_string(*Mod)); + } + } + + OS << " }\n" + << "\n" + << " bool parseCommandLineOption();\n" + << " bool setRuleDisabled(StringRef RuleIdentifier);\n" + << "\n" + << " bool combineImpl(\n" + << " CombinerChangeObserver &Observer,\n" + << " MachineInstr &MI,\n" + << " MachineIRBuilder &B) const;\n" + << "};\n\n"; + + emitNameMatcher(OS); + OS << "#endif // ifdef " << Name.upper() << "_GENCOMBINERINFO_H\n\n"; + + OS << "#ifdef " << Name.upper() << "_GENCOMBINERINFO_CPP\n" + << "\n" + << "cl::list " << Name << "Option(\n" + << " \"" << Name.lower() << "-disable-rule\",\n" + << " cl::desc(\"Disable one or more combiner rules temporarily in " + << "the " << Name << " pass\"),\n" + << " cl::CommaSeparated,\n" + << " cl::Hidden,\n" + << " cl::cat(GICombinerOptionCategory));\n" + << "\n" + << "bool " << getClassName() << "::parseCommandLineOption() {\n" + << " for (const auto &Identifier : " << Name << "Option)\n" + << " if (!setRuleDisabled(Identifier))\n" + << " return false;\n" + << " return true;\n" + << "}\n\n"; + + OS << "bool " << getClassName() << "::combineImpl(\n" + << " CombinerChangeObserver &Observer,\n" + << " MachineInstr &MI,\n" + << " MachineIRBuilder &B) const {\n" + << " CombinerHelper Helper(Observer, B);\n\n"; + + for (const auto &Rule : Rules) + for (const auto &I : Rule.matchdata_decls()) + OS << " " << I.getType() << " " << I.getVariableName() << ";\n"; + OS << "\n"; + + for (const auto &Rule : Rules) { + unsigned Idx = Rule.getID(); + Record *R = Rule.getDef(); + ++NumPatternTotal; + + OS << " // " << R->getName() << "\n" + << " if (!DisabledRules.test(" << Idx << "))\n" + << " if ("; + { + StringRef Separator = ""; + for (const auto &Opcode : Rule.root_opcodes()) { + OS << Separator + << "MI.getOpcode() == " << OpcodeEnumeratorFormatter(*Opcode); + Separator = " ||\n "; + } + } + OS << ")\n"; + + CodeExpansions Expansions; + Expansions.declare("root", "MI"); + Expansions.declareFromRule(Rule); + + // FIXME: Single-use lambda's like this are a serious compile-time + // performance and memory issue. It's convenient for this early stage to + // defer some work to successive patches but we need to eliminate this + // before the ruleset grows to small-moderate size. Last time, it became a + // big problem for low-mem systems around the 500 rule mark but by the time + // we grow that large we should have merged the ISel match table mechanism + // with the Combiner. + OS << " if ([&]() { " << CodeExpander(Rule.getCode(), Expansions) + << " return true; }()) {\n" + << " " + << CodeExpander(R->getValueAsString("MatchCode"), Expansions) << "\n" + << " return true;\n" + << " }\n"; + } + OS << "\n return false;\n" + << "}\n" + << "#endif // ifdef " << Name.upper() << "_GENCOMBINERINFO_CPP\n"; +} + +} // end anonymous namespace + +//===----------------------------------------------------------------------===// + +namespace llvm { +void EmitGICombiner(RecordKeeper &RK, raw_ostream &OS) { + emitSourceFileHeader("Global Combiner", OS); + + if (SelectedCombiners.empty()) + PrintFatalError("No combiners selected with -combiners"); + for (const auto &Combiner : SelectedCombiners) + GICombinerEmitter(RK, Combiner).run(OS); +} +} // End llvm namespace Index: utils/TableGen/TableGen.cpp =================================================================== --- utils/TableGen/TableGen.cpp +++ utils/TableGen/TableGen.cpp @@ -50,6 +50,7 @@ GenAttributes, GenSearchableTables, GenGlobalISel, + GenGICombiner, GenX86EVEX2VEXTables, GenX86FoldTables, GenRegisterBank, @@ -113,6 +114,8 @@ "Generate generic binary-searchable table"), clEnumValN(GenGlobalISel, "gen-global-isel", "Generate GlobalISel selector"), + clEnumValN(GenGICombiner, "gen-global-isel-combiner", + "Generate GlobalISel combiner"), clEnumValN(GenX86EVEX2VEXTables, "gen-x86-EVEX2VEX-tables", "Generate X86 EVEX to VEX compress tables"), clEnumValN(GenX86FoldTables, "gen-x86-fold-tables", @@ -225,6 +228,9 @@ case GenGlobalISel: EmitGlobalISel(Records, OS); break; + case GenGICombiner: + EmitGICombiner(Records, OS); + break; case GenRegisterBank: EmitRegisterBank(Records, OS); break; Index: utils/TableGen/TableGenBackends.h =================================================================== --- utils/TableGen/TableGenBackends.h +++ utils/TableGen/TableGenBackends.h @@ -86,6 +86,7 @@ void EmitAttributes(RecordKeeper &RK, raw_ostream &OS); void EmitSearchableTables(RecordKeeper &RK, raw_ostream &OS); void EmitGlobalISel(RecordKeeper &RK, raw_ostream &OS); +void EmitGICombiner(RecordKeeper &RK, raw_ostream &OS); void EmitX86EVEX2VEXTables(RecordKeeper &RK, raw_ostream &OS); void EmitX86FoldTables(RecordKeeper &RK, raw_ostream &OS); void EmitRegisterBank(RecordKeeper &RK, raw_ostream &OS);