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
list
list
list
描述一条指令,也就是描述这条指令的属性,总结一下:
1. 指令编码(encoding)
2. 汇编输出(asm string)
3. 对寄存器的操作(使用、改写什么寄存器,寄存器的分类)
4. 指令匹配的条件(在什么条件下能将LLVM dag转化成这条指令)
5. 匹配LLVM的dag(LLVM dag的操作数和指令操作数的对应关系)
6. 其它一些指令属性(例如标记成跳转指令,立即数生成指令,用于编译器优化)
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。
这里只简单介绍了描述一条指令的基本思路,而且是可描述的简单指令,实际中还会遇到很多问题,下一篇再讲吧。