Demystify Complex Pattern in LLVM DAG-based ISel by example

590 阅读2分钟

1. example code in C

extern "C" void f() { short a = 5; }

2. Corresponding LLVM IR code

define void @f() {
entry:
  %a = alloca i16, align 2
  store i16 5, i16* %a, align 2
  ret void}

3. Corresponding Legalized DAG

Legalized selection DAG: %bb.0 'f:entry'
SelectionDAG has 6 nodes:
      t0: ch = EntryToken
    t5: ch = store<(store 2 into %ir.a)> t0, Constant:i16<5>, FrameIndex:i16<0>, undef:i16
  t6: ch = M88kISD::RET_FLAG t5

4. Instruction Target Description for Instruction Selection Pattern Matching

def ToL6 : SDNodeXForm<imm, [{
  int64_t Value = N->getSExtValue();
  return CurDAG->getTargetConstant(Value, SDLoc(N), MVT::i16);}]>;
class ImmediateAsmOperand<string name> : AsmOperandClass {
  let Name = name;
  let RenderMethod = "addImmOperands";}
def S6Imm : ImmediateAsmOperand<"S6Imm">;
// immediate operand
class ImmediateOp<ValueType vt, string asmop> : Operand<vt> {
  let PrintMethod = "print"#asmop#"Operand";
  // static DecodeStatus #DecoderMethod(MCInst &Inst, int64_t Imm,
  //                                    uint64_t Address, const void *Decoder)
  let DecoderMethod = "decode"#asmop#"Operand";
  let ParserMatchClass = !cast<AsmOperandClass>(asmop);
  let OperandType = "OPERAND_IMMEDIATE";
}
class ImmOpWithPattern<ValueType vt, string asmop, code pred, SDNodeXForm xform,
      SDNode ImmNode = imm> :
  ImmediateOp<vt, asmop>, PatLeaf<(vt ImmNode), pred, xform>;
multiclass Immediate<ValueType vt, code pred, SDNodeXForm xform, string asmop> {
  // for llvm ir constant
  def "" : ImmOpWithPattern<vt, asmop, pred, xform>;
  // for target constant
  def _timm : ImmOpWithPattern<vt, asmop, pred, xform, timm>;
}
defm imm16InL6 : Immediate<i16, [{
  int64_t Value = N->getSExtValue();
  return (-32 <= Value && Value <= 31);
}], ToL6, "S6Imm">;
def M88kMemAsmOperand : AsmOperandClass {
  let Name = "Mem";
  // OperandMatchResultTy XXXMCTargetAsmParser::parseMemOperand(OperandVector &);
  let ParserMethod = "parseMemOperand";
  //void XXXMCParsedAsmOperand::addMemOperand(MCInst &Inst, unsigned N) const
  let RenderMethod = "addMemOperand";
}
// Address operand MachineInstruction/MCInst Operand
def frameSlot : Operand<iPTR> {
  // void XXXInstPrinter::#PrintMethod(const MCInst *MI, unsigned opNo,
  //                                       const MCSubtargetInfo &STI,
  //                                       raw_ostream &O);
  let PrintMethod = "printMemOperand";
  // unsigned XXXMCCodeEmitter::#EncoderMethod(const MCInst &MI, unsigned OpNo,
  //                         SmallVectorImpl<MCFixup> &Fixups,
  //                         const MCSubtargetInfo &STI) const
  let EncoderMethod = "getMemEncoding";
  let MIOperandInfo = (ops GPROpnd, imm16InL6);
  let ParserMatchClass = M88kMemAsmOperand;}
def frameObject : 
  ComplexPattern<iPTR, 2, "SelectFrameObject", [frameindex], [SDNPWantParent]>;
def STm: F_LSR<0b0111,
               (outs), (ins GPROpnd:$r, frameSlot:$obj),
               !strconcat("str", " $r, $obj"),
               [(store GPROpnd:$r, frameObject:$obj)]>;
def MVI: F_MVI<(outs GPROpnd:$dr),
          (ins imm16InL6:$imm6),
          !strconcat("mvi", " $dr, $imm6"),[]>;
// patterns for constants
def : Pat<(i16 imm16InL6:$imm6),(MVI imm16InL6:$imm6)>;

That is quite a lot for comprehension. Let us demystify the whole process by reading that .td code snippet from bottom up.

First of all, t5: ch = store<(store 2 into %ir.a)> t0, Constant:i16<5>, FrameIndex:i16<0>, undef:i16 is what going to be matched by pattern.  

There are three parts in that operation or IR instruction (more formal words). 

  1. the store<(store 2 into %ir.a)> t0 operator.
  2. the Constant:i16<5> operand.
  3.  the FrameIndex:i16<0> operand.
  4. the undef:i16 part, which is corresponding the align 2 in LLVM IR, can be ignored for Instruction Selection. 

Our pattern for store is (store GPROpnd:$r, frameObject:$obj) which means store a register to a frame object, which is not the same as IR store in Legalized DAG. 

1. Match the Constant:i16<5> to get a register holding that constant.

We need to match the Constant:i16<5> via def:Pat<(i16 imm16InL6:$imm6),(MVI imm16InL6:$imm6)> to give us a register holding the constant.   imm16InL6 is an Operand as well as PatLeaf to match a constant (immediate) with predicate in code part for checking if the constant is fit in 6 bits. That bit width is restricted by the target ISA mvi instruction. The output of matched instruction is GPROpnd:$r, which shows in outs of 

def MVI: F_MVI<(outs GPROpnd:$dr),
          (ins imm16InL6:$imm6),
          !strconcat("mvi", " $dr, $imm6"),
          []>;

that matches the first operand of the pattern (store GPROpnd:$r, frameObject:$obj) for our target store instruction. Now for the second operand.

2. Match the FrameIndex:i16<0> operand.

FrameIndex is a reference to an object in stack frame. Here we use complex pattern def frameObject:ComplexPattern<iPTR, 2, "SelectFrameObject", [frameindex], [SDNPWantParent]>; to match it, which means use SelectFrameObject method of XXXDAGToDAGISel class to match a DAG with frameindex as its root. iPTR means the type of  match result is a pointer to memory. 2 means the matched result has 2 operands. SDNPWantParent means that method needs the parent node of frameindex, which in this case should be store. Therefore, we get the following method.

bool XXXDAGToDAGISel::SelectFrameObject(SDNode *Parent, SDValue frameIndex,
                                         SDValue &Base, SDValue &Offset)

when this method return true, that means the DAG rooted with frameindex processed successfully, and the result would be 2 SDValue, Base and Offset. Then turn into frameSlot operand, defined as

def frameSlot : Operand<iPTR> {
  // void XXXInstPrinter::#PrintMethod(const MCInst *MI, unsigned opNo,
  //                                       const MCSubtargetInfo &STI,
  //                                       raw_ostream &O);
  let PrintMethod = "printMemOperand";
  // unsigned XXXMCCodeEmitter::#EncoderMethod(const MCInst &MI, unsigned OpNo,
  //                         SmallVectorImpl<MCFixup> &Fixups,
  //                         const MCSubtargetInfo &STI) const
  let EncoderMethod = "getMemEncoding";
  
  let MIOperandInfo = (ops GPROpnd, imm16InL6);
  let ParserMatchClass = M88kMemAsmOperand;}

 See the type iPTR and MIOperandInfo which match the complex pattern result, its type and value.  The Operand in .td file represents an operand of MachineInstruction or MCInst. By the way, PrintMethod, EncoderMethod, DecoderMethod is used for that operand to output as asm text, binary and be decoded as binary respectively, and the ParserMatchClass is for parsing asm text back to MCInst Operand, as show in following image.

Now we satisfy the (store GPROpnd:$r, frameObject:$obj) pattern, then STm will be selected accordingly.