LLVM(四):指令描述td部分


重要的指令属性

include/llvm/Target/Target.td中的instruction类是所有指令的父类。

//===----------------------------------------------------------------------===//
// Instruction set description - These classes correspond to the C++ classes in
// the Target/TargetInstrInfo.h file.
//
class Instruction {
  string Namespace = "";

  dag OutOperandList;       // An dag containing the MI def operand list.
  dag InOperandList;        // An dag containing the MI use operand list.
  string AsmString = "";    // The .s format to print the instruction with.

  // Pattern - Set to the DAG pattern for this instruction, if we know of one,
  // otherwise, uninitialized.
  list Pattern;

  // The follow state will eventually be inferred automatically from the
  // instruction pattern.

  list Uses = []; // Default to using no non-operand registers
  list Defs = []; // Default to modifying no non-operand registers

  // Predicates - List of predicates which will be turned into isel matching
  // code.
  list Predicates = [];

  // Size - Size of encoded instruction, or zero if the size cannot be determined
  // from the opcode.
  int Size = 0;

  // DecoderNamespace - The "namespace" in which this instruction exists, on
  // targets like ARM which multiple ISA namespaces exist.
  string DecoderNamespace = "";

  // Code size, for instruction selection.
  // FIXME: What does this actually mean?
  int CodeSize = 0;

每个变量都有注释,在这里选几个比较重要的变量说明:

dag OutOperandList              MI(Machine Instruction)改写的操作数

dag InOperandList          MI(Machine Instruction)使用的操作数

string AsmString             汇编输出格式

list Pattern            匹配的dag

list Users        使用的不包含在操作数中的寄存器(例如和状态位有关指令,某些体系结构HILO寄存器相关指令等)

list Defs          改写的不包含在操作数中的寄存器(同上)

list Predicates    指令匹配的条件

 

描述一条指令,也就是描述这条指令的属性,总结一下:

1.     指令编码(encoding)

2.     汇编输出(asm string)

3.     对寄存器的操作(使用、改写什么寄存器,寄存器的分类)

4.     指令匹配的条件(在什么条件下能将LLVM dag转化成这条指令)

5.     匹配LLVM的dag(LLVM dag的操作数和指令操作数的对应关系)

6.     其它一些指令属性(例如标记成跳转指令,立即数生成指令,用于编译器优化)

ARM实现简单介绍

InstARM同时继承于Encoding(编码)类和指令模板类。Encoding类中的bits<32>Inst变量就代表ARMInst的32位编码,而InstThumb继承于指令模板和T1Encoding(16位编码),Inst变量用于object文件的输出。)

 

根据指令编码格式,操作类型,继承InstARM类分别定义了:I、InoP、sI、XI…等指令类型。然后这些类型下面的指令有些还可以再细分出共同点,对共同点变量进行赋值,再定义出更多的指令分类,最终将指令所有的变量赋值,实现了指令的描述,大致就是这个思路。

如果你要做一个后端的指令描述,需要先将指令进行分类,我的建议是先将编码相似的分类,然后将其中指令行为不同的再分成多类。

 

 

例如:看最简单的ADD SUB等指令的实现


先看AsI1类型的指令:

class AsI1 opcod, dag oops, dag iops, Format f, InstrItinClass itin,
           string opc, string asm, list pattern>
  : sI {
  let Inst{24-21} = opcod;
  let Inst{27-26} = 0b00;
}

我们看到AsI1指令类型的编码都是这样的,24-21位是opcode,27-26位是0b00。

这里还要关注到几个变量oops、iops、asm、pattern,分别为instruction继承来的改写的操作数、使用的操作数、汇编输出、匹配dag。

在父类sI中是这样写的:

  let OutOperandList = oops;
  let InOperandList = !con(iops, (ins pred:$p, cc_out:$s));
  let AsmString = !strconcat(opc, "${s}${p}", asm);
  let Pattern = pattern;

 

let TwoOperandAliasConstraint = "$Rn = $Rd" in
multiclass AsI1_bin_irs opcod, string opc,
                     InstrItinClass iii, InstrItinClass iir, InstrItinClass iis,
                        PatFrag opnode, bit Commutable = 0> {
  // The register-immediate version is re-materializable. This is useful
  // in particular for taking the address of a local.
  let isReMaterializable = 1 in {
  def ri : AsI1,
           Sched<[WriteALU, ReadALU]> {
    bits<4> Rd;
    bits<4> Rn;
    bits<12> imm;
    let Inst{25} = 1;
    let Inst{19-16} = Rn;
    let Inst{15-12} = Rd;
    let Inst{11-0} = imm;
  }
  }
  def rr : AsI1,
           Sched<[WriteALU, ReadALU, ReadALU]> {
    bits<4> Rd;
    bits<4> Rn;
    bits<4> Rm;
    let Inst{25} = 0;
    let isCommutable = Commutable;
    let Inst{19-16} = Rn;
    let Inst{15-12} = Rd;
    let Inst{11-4} = 0b00000000;
    let Inst{3-0} = Rm;
  }

  def rsi : AsI1,
            Sched<[WriteALUsi, ReadALU]> {
    bits<4> Rd;
    bits<4> Rn;
    bits<12> shift;
    let Inst{25} = 0;
    let Inst{19-16} = Rn;
    let Inst{15-12} = Rd;
    let Inst{11-5} = shift{11-5};
    let Inst{4} = 0;
    let Inst{3-0} = shift{3-0};
  }

  def rsr : AsI1,
            Sched<[WriteALUsr, ReadALUsr]> {
    bits<4> Rd;
    bits<4> Rn;
    bits<12> shift;
                                                                            1120,5        19%
图没贴完


这里涉及multiclass,意思就是批量实现4个类。命名分别为:

1.     AsI1_bin_irs_ri

2.     AsI1_bin_irs_rr

3.     AsI1_bin_irs_rsi

4.     AsI1_bin_irs_rsr

 

再看一下有哪些指令是继承AsI1_bin_irs类实现的,搜索一下发现有

ADD、SUB、AND、ORR、EOR、BIC六条指令。

熟悉ARM指令的同学就可以看出,这6条指令的共同点,也就是为什么要分类上面的multiclass的原因,就是他们的操作数都有4种形式。

例如ADD的操作数,可以是RI(寄存器加立即数)、RR(寄存器加寄存器)、RSI(寄存器加立即数偏移)、RSR(寄存器加寄存器偏移)

在这个multiclass类中,分别对这4种类型进行指令编码的赋值。

 

defm ADD  : AsI1_bin_irs<0b0100, "add",
                         IIC_iALUi, IIC_iALUr, IIC_iALUsr,
                         BinOpFrag<(add  node:$LHS, node:$RHS)>, 1>;
defm SUB  : AsI1_bin_irs<0b0010, "sub",
                         IIC_iALUi, IIC_iALUr, IIC_iALUsr,
                         BinOpFrag<(sub  node:$LHS, node:$RHS)>>;

这里注意对应multiclass 需要用defm。

在ADD实现中:

0b0100为opcode。

IIC*是和流水线优化相关的一些属性,这里不讨论。

“add”就是汇编输出的一部分。


BinOpFrag继承PatFrag,是一种通用写法。代表llvmdag中的左值右值相加操作。

最后的1表示之前的Commutable属性,表示两个操作数可换位置。很显然,SUB指令中这个值为0。


这里只简单介绍了描述一条指令的基本思路,而且是可描述的简单指令,实际中还会遇到很多问题,下一篇再讲吧。

 

你可能感兴趣的:(LLVM)