2.2.4.2. 可复用的结构
2.2.4.2.1. PatFrag
高级语言的特征之一是支持数据与结构的复用,TD语言也吸取了这些长处,尝试为复杂而基本的操作提供复用的可能性。这就是PatFrag(SelectionTargetDAG.td):
606 class PatFrag<dag ops, dag frag, code pred = [{}],
607 SDNodeXForm xform = NOOP_SDNodeXForm> : SDPatternOperator {
608 dag Operands = ops;
609 dag Fragment = frag;
610 code PredicateCode = pred;
611 code ImmediateCode = [{}];
612 SDNodeXForm OperandTransform = xform;
613 }
在嵌入上一级结构时,PatFrag就代表了Fragment所指定的片段,而Operands用作该片段的操作数。PredicateCode则是所谓的谓词,它是一段嵌入生成的指令选择器的代码,只有满足这段代码所代表的条件,才会使用这个PatFrag。TableGen将在适当时把PatFrag展开为Fragment指定的片段,并使用Operands所指定的操作数替换片段中的操作数。
PatFrag也有文档没有给出的约束。首先,参数ops只能是以ops、ins、outs为操作符的dag,而操作数必须是node:$name的形式。其次,参数frag也只能使用特定的TD定义作为操作符,以node:$name或?:name的形式引用PatFrag的操作数,而且必须援引所有的操作数。
V7.0大幅修改了PatFrag的定义:
689 class PatFrag<dag ops, dag frag, code pred = [{}],
690 SDNodeXForm xform = NOOP_SDNodeXForm>
691 : PatFrags<ops, [frag], pred, xform>;
引入了PatFrags,它的Fragments部分(627行)是一个匹配片段列表。每个片段可以匹配DAG上某个东西,从单个节点到其他多个嵌套的片段。如果任一片段匹配那么整个片段组就匹配了。这使得使用同一个片段组来匹配“带溢出加法”与“普通加法”成为可能。
639行开始的域也会用于生成谓词(生成TreePredicateFn的成员方法)。这里谓词分为3类:load、store和atomic。IsLoad、IsStore与IsAtomic分别用于表示是否使用这些谓词。方法TreePredicateFn::getPredCode()将检查它们间的合理性与合法性。
624 class PatFrags<dag ops, list<dag> frags, code pred = [{}],
625 SDNodeXForm xform = NOOP_SDNodeXForm> : SDPatternOperator {
626 dag Operands = ops;
627 list<dag> Fragments = frags;
628 code PredicateCode = pred;
629 code GISelPredicateCode = [{}];
630 code ImmediateCode = [{}];
631 SDNodeXForm OperandTransform = xform;
632
633 // Define a few pre-packaged predicates. This helps GlobalISel import
634 // existing rules from SelectionDAG for many common cases.
635 // They will be tested prior to the code in pred and must not be used in
636 // ImmLeaf and its subclasses.
637
638 // Is the desired pre-packaged predicate for a load?
639 bit IsLoad = ?;
640 // Is the desired pre-packaged predicate for a store?
641 bit IsStore = ?;
642 // Is the desired pre-packaged predicate for an atomic?
643 bit IsAtomic = ?;
644
645 // cast<LoadSDNode>(N)->getAddressingMode() == ISD::UNINDEXED;
646 // cast<StoreSDNode>(N)->getAddressingMode() == ISD::UNINDEXED;
647 bit IsUnindexed = ?;
648
649 // cast<LoadSDNode>(N)->getExtensionType() != ISD::NON_EXTLOAD
650 bit IsNonExtLoad = ?;
651 // cast<LoadSDNode>(N)->getExtensionType() == ISD::EXTLOAD;
652 bit IsAnyExtLoad = ?;
653 // cast<LoadSDNode>(N)->getExtensionType() == ISD::SEXTLOAD;
654 bit IsSignExtLoad = ?;
655 // cast<LoadSDNode>(N)->getExtensionType() == ISD::ZEXTLOAD;
656 bit IsZeroExtLoad = ?;
657 // !cast<StoreSDNode>(N)->isTruncatingStore();
658 // cast<StoreSDNode>(N)->isTruncatingStore();
659 bit IsTruncStore = ?;
660
661 // cast<AtomicSDNode>(N)->getOrdering() == AtomicOrdering::Monotonic
662 bit IsAtomicOrderingMonotonic = ?;
663 // cast<AtomicSDNode>(N)->getOrdering() == AtomicOrdering::Acquire
664 bit IsAtomicOrderingAcquire = ?;
665 // cast<AtomicSDNode>(N)->getOrdering() == AtomicOrdering::Release
666 bit IsAtomicOrderingRelease = ?;
667 // cast<AtomicSDNode>(N)->getOrdering() == AtomicOrdering::AcquireRelease
668 bit IsAtomicOrderingAcquireRelease = ?;
669 // cast<AtomicSDNode>(N)->getOrdering() == AtomicOrdering::SequentiallyConsistent
670 bit IsAtomicOrderingSequentiallyConsistent = ?;
671
672 // isAcquireOrStronger(cast<AtomicSDNode>(N)->getOrdering())
673 // !isAcquireOrStronger(cast<AtomicSDNode>(N)->getOrdering())
674 bit IsAtomicOrderingAcquireOrStronger = ?;
675
676 // isReleaseOrStronger(cast<AtomicSDNode>(N)->getOrdering())
677 // !isReleaseOrStronger(cast<AtomicSDNode>(N)->getOrdering())
678 bit IsAtomicOrderingReleaseOrStronger = ?;
679
680 // cast<LoadSDNode>(N)->getMemoryVT() == MVT::<VT>;
681 // cast<StoreSDNode>(N)->getMemoryVT() == MVT::<VT>;
682 ValueType MemoryVT = ?;
683 // cast<LoadSDNode>(N)->getMemoryVT().getScalarType() == MVT::<VT>;
684 // cast<StoreSDNode>(N)->getMemoryVT().getScalarType() == MVT::<VT>;
685 ValueType ScalarMemoryVT = ?;
686 }
2.2.4.2.1.1. SNDodeForm
SDNodeXForm是一个特别的定义。它允许目标机器在匹配的情形下修改输出操作数(在输出dag里),这通常用于修改立即数。
574 class SDNodeXForm<SDNode opc, code xformFunction> {
575 SDNode Opcode = opc;
576 code XFormFunction = xformFunction;
577 }
XFormFunction给出了进行操作的代码片段,Opcode则是操作的对象。
上面607行的NOOP_SDNodeXForm用于表示空操作:
579 def NOOP_SDNodeXForm : SDNodeXForm<imm, [{}]>;
另一个来自X86机器的例子则是:
851 def ROT32L2R_imm8: SDNodeXForm<imm, [{
852 // Convert a ROTL shamt to a ROTR shamt on 32-bit integer.
853 return getI8Imm(32 - N->getZExtValue(), SDLoc(N));
854 }]>;
TableGen在生成后端代码时,会将853行的代码片段嵌入合适的地方,以对指定的操作数进行指定的处理。
2.2.4.2.1.2. PatFrag的例子
以下是X86机器中PatFrag的一种派生定义(v7.0的定义是一致的):
889 def loadi16 : PatFrag<(ops node:$ptr), (i16 (unindexedload node:$ptr)), [{
890 LoadSDNode *LD = cast<LoadSDNode>(N);
891 ISD::LoadExtType ExtType = LD->getExtensionType();
892 if (ExtType == ISD::NON_EXTLOAD)
893 return true;
894 if (ExtType == ISD::EXTLOAD)
895 return LD->getAlignment() >= 2 && !LD->isVolatile();
896 return false;
897 }]>;
这个结构描述的是载入16位有符号整数的操作。其中unindexedload也是一个PatFrag派生定义(所谓的unindexed就是实际地址已经算出,合并在基址指针里):
674 def unindexedload : PatFrag<(ops node:$ptr), (ld node:$ptr), [{
675 return cast<LoadSDNode>(N)->getAddressingMode() == ISD::UNINDEXED;
676 }]>;
V7.0的定义是这样的:
781 def unindexedload : PatFrag<(ops node:$ptr), (ld node:$ptr)> {
782 let IsLoad = 1;
783 let IsUnindexed = 1;
784 }
上面675行的谓词将由TreePredicateFn::getPredCode()方法根据782~783行的设置自动生成。
而在这个定义里,替换片段ld也是一个SDNode派生定义(v7.0的定义是一致的):
505 def ld: SDNode<"ISD::LOAD", SDTLoad,
506 [SDNPHasChain, SDNPMayLoad, SDNPMemOperand]>;
因为枚举值ISD::LOAD,ld匹配SelectionDAG的LoadSDNode。
loadi16的操作数是一个dag值,ops用于向TableGen说明其参数就是一个操作数。在TableGen看来,这三个定义使用的操作数是同一个,因为所有的操作数都叫“ptr”。
这个PatFrag在TableGen展开后,是这样的(在llvm的源代码根目录,输入:
$ llvm-tblgen lib/Target/X86/X86.td -I=include -I=lib/Target/X86
就可以得到仅包含简单def的输出——这相当于C/C++源代码的宏展开。TableGen并不使用这个结果,这只是为了让开发人员更好地查看、调试这些定义):
def loadi16 { // SDPatternOperator PatFrag
SDNodeXForm PatFrag:xform = NOOP_SDNodeXForm;
dag Operands = (ops node:$ptr);
dag Fragment = (i16 (unindexedload node:$ptr));
string PredicateCode = "
LoadSDNode *LD = cast<LoadSDNode>(N);
ISD::LoadExtType ExtType = LD->getExtensionType();
if (ExtType == ISD::NON_EXTLOAD)
return true;
if (ExtType == ISD::EXTLOAD)
return LD->getAlignment() >= 2 && !LD->isVolatile();
return false;
";
string ImmediateCode = "";
SDNodeXForm OperandTransform = NOOP_SDNodeXForm;
string NAME = ?;
}
注意loadi16是PatFrag的派生定义,虽然展开结果没有明确指出,但TableGen知道它是一个PatFrag实例,并在处理中记录了这个信息。
V7.0上的结果是:
def loadi16 { // SDPatternOperator PatFrags PatFrag
list<SDNodeProperty> Properties = [];
dag Operands = (ops node:$ptr);
list<dag> Fragments = [(i16 (unindexedload node:$ptr))];
code PredicateCode = [{
LoadSDNode *LD = cast<LoadSDNode>(N);
ISD::LoadExtType ExtType = LD->getExtensionType();
if (ExtType == ISD::NON_EXTLOAD)
return true;
if (ExtType == ISD::EXTLOAD)
return LD->getAlignment() >= 2 && !LD->isVolatile();
return false;
}];
code GISelPredicateCode = [{}];
code ImmediateCode = [{}];
SDNodeXForm OperandTransform = NOOP_SDNodeXForm;
bit IsLoad = ?;
bit IsStore = ?;
bit IsAtomic = ?;
bit IsUnindexed = ?;
bit IsNonExtLoad = ?;
bit IsAnyExtLoad = ?;
bit IsSignExtLoad = ?;
bit IsZeroExtLoad = ?;
bit IsTruncStore = ?;
bit IsAtomicOrderingMonotonic = ?;
bit IsAtomicOrderingAcquire = ?;
bit IsAtomicOrderingRelease = ?;
bit IsAtomicOrderingAcquireRelease = ?;
bit IsAtomicOrderingSequentiallyConsistent = ?;
bit IsAtomicOrderingAcquireOrStronger = ?;
bit IsAtomicOrderingReleaseOrStronger = ?;
ValueType MemoryVT = ?;
ValueType ScalarMemoryVT = ?;
}
2.2.4.2.2. 其他特殊PatFrag派生类
这包括以下的这些定义,它们都是PatFrag的派生类,可视为PatFrag的偏特化(Target.td,v7.0挪到TargetSelectionDAG.td中)。
618 class OutPatFrag<dag ops, dag frag>
619 : PatFrag<ops, frag, [{}], NOOP_SDNodeXForm>;
623 class PatLeaf<dag frag, code pred = [{}], SDNodeXForm xform = NOOP_SDNodeXForm>
624 : PatFrag<(ops), frag, pred, xform>;
640 class ImmLeaf<ValueType vt, code pred, SDNodeXForm xform = NOOP_SDNodeXForm>
641 : PatFrag<(ops), (vt imm), [{}], xform> {
642 let ImmediateCode = pred;
643 bit FastIselShouldIgnore = 0;
644 }
OutPatFrag适用于输出模板,不需要处理操作数的代码片段。而PatLeaf则是没有操作数,可以简洁地定义立即数等通用结构。ImmLeaf则视为引入限定的PatLeaf,专用于立即数。
V7.0扩展了ImmLeaf的定义(增加两个域IsAPInt与IsAPFloat),并在此基础上派生了IntImmLeaf与FPImmLeaf,分别用于表示整形与浮点型的立即数。同时定义了整形与浮点向量类型的PatLeaf派生定义,以及全1及全0的向量立即数。
2.2.5. 另一种匹配方式
另一种匹配指令的方法是通过Pattern(Target.td,v7.0挪到TargetSelectionDAG.td中)。它是这样的一个定义:
1067 class Pattern<dag patternToMatch, list<dag> resultInstrs> {
1068 dag PatternToMatch = patternToMatch;
1069 list<dag> ResultInstrs = resultInstrs;
1070 list<Predicate> Predicates = []; // See class Instruction in Target.td.
1071 int AddedComplexity = 0; // See class Instruction in Target.td.
1072 }
对于一些CPU,尤其CISC系统,一些比较复杂的操作,比如乘加、移位加,既可以通过一组指令完成,也存在一条可以完成这个操作的比较复杂的指令。通常比较复杂的指令要更高效一些,而且通常产生的执行代码更小。因此在这种情形下,编译器最好能选择复杂的指令。Pattern就有助于解决这个问题。参数patternToMatch给出这个复杂操作的DAG模式,resultInstrs则列出匹配指令的匹配模式,LLVM将使用贪婪匹配自动完成这个指令选择。
因为通常只会输出一条指令,还因此定义了Pat作为一个方便的记法。
1076 class Pat<dag pattern, dag result> : Pattern<pattern, [result]>;
2.2.5.1. 谓词Predicate
在Pattern与Instruction的定义中都有谓词部分(Predicates),只有满足谓词设定的条件,Pattern与Instruction的匹配才能继续。Predicate定义在Target.td文件里:
477 class Predicate<string cond> {
478 string CondString = cond;
479
480 /// AssemblerMatcherPredicate - If this feature can be used by the assembler
481 /// matcher, this is true. Targets should set this by inheriting their
482 /// feature from the AssemblerPredicate class in addition to Predicate.
483 bit AssemblerMatcherPredicate = 0;
484
485 /// AssemblerCondString - Name of the subtarget feature being tested used
486 /// as alternative condition string used for assembler matcher.
487 /// e.g. "ModeThumb" is translated to "(Bits & ModeThumb) != 0".
488 /// "!ModeThumb" is translated to "(Bits & ModeThumb) == 0".
489 /// It can also list multiple features separated by ",".
490 /// e.g. "ModeThumb,FeatureThumb2" is translated to
491 /// "(Bits & ModeThumb) != 0 && (Bits & FeatureThumb2) != 0".
492 string AssemblerCondString = "";
493
494 /// PredicateName - User-level name to use for the predicate. Mainly for use
495 /// in diagnostics such as missing feature errors in the asm matcher.
496 string PredicateName = "";
497 }
V7.0增加了域:bit RecomputePerFunction = 0;表示每次函数改变都要重新计算谓词。
Predicate实际上封装了一段嵌入代码。下面是一个具体的谓词定义(X86InstrInfo.td):
787 def HasBMI: Predicate<"Subtarget->hasBMI()">;
这个谓词测试目标机器是否支持BMI指令(位操作指令,Bit Manipulate Instruction),这是在Haswell微架构里引入的,分BMI1与BMI2两个指令集。这里是BMI1,下面这个是测试BMI2:
788 def HasBMI2: Predicate<"Subtarget->hasBMI2()">;
很显然,Predicate定义中的CondString是一个C++代码片段,TableGen将会把它插入到生成代码中合适的地方。
谓词主要是用于筛选处理器所支持的指令集。另外,也可以对该处理器架构的某些有局限的指令进行筛选,比如某个处理器的比特设置指令比较慢,应该用别的指令替代(不过在LLVM,目前后者还只是设想)。这些谓词的来源与声明我们在指令调度的时候再来看。
2.2.5.2. 指令定义的例子
下面是使用谓词HasBMI的指令定义的例子(X86InstrArithmetic.td,v7.0对这些定义都增加了设置AddedComplexity = -6):
1280 let Predicates = [HasBMI] in {
1281 def : Pat<(and (not GR32:$src1), GR32:$src2),
1282 (ANDN32rr GR32:$src1, GR32:$src2)>;
1283 def : Pat<(and (not GR64:$src1), GR64:$src2),
1284 (ANDN64rr GR64:$src1, GR64:$src2)>;
1285 def : Pat<(and (not GR32:$src1), (loadi32 addr:$src2)),
1286 (ANDN32rm GR32:$src1, addr:$src2)>;
1287 def : Pat<(and (not GR64:$src1), (loadi64 addr:$src2)),
1288 (ANDN64rm GR64:$src1, addr:$src2)>;
1289 }
匹配的模式并不复杂,其中GR32/GR64也是TableGen的特殊对象,专门表示32/64位通用寄存器。而在作为ResultInstrs的部分,ANDNXX是通过下面结构来定义的(X86InstrArithmetic.td,v7.0由于x86不再使用InstrItinClass,下面的IIC_BIN_NONMEM与IIC_BIN_MEM都去掉了。另外,1275行的对应设置增加了AddedComplexity = -6):
1262 multiclass bmi_andn<string mnemonic, RegisterClass RC, X86MemOperand x86memop,
1263 PatFrag ld_frag> {
1264 def rr : I<0xF2, MRMSrcReg, (outs RC:$dst), (ins RC:$src1, RC:$src2),
1265 !strconcat(mnemonic, "\t{$src2, $src1, $dst|$dst, $src1, $src2}"),
1266 [(set RC:$dst, EFLAGS, (X86and_flag (not RC:$src1), RC:$src2))],
1267 IIC_BIN_NONMEM>, Sched<[WriteALU]>;
1268 def rm : I<0xF2, MRMSrcMem, (outs RC:$dst), (ins RC:$src1, x86memop:$src2),
1269 !strconcat(mnemonic, "\t{$src2, $src1, $dst|$dst, $src1, $src2}"),
1270 [(set RC:$dst, EFLAGS,
1271 (X86and_flag (not RC:$src1), (ld_frag addr:$src2)))], IIC_BIN_MEM>,
1272 Sched<[WriteALULd, ReadAfterLd]>;
1273 }
1274
1275 let Predicates = [HasBMI], Defs = [EFLAGS] in {
1276 defm ANDN32 : bmi_andn<"andn{l}", GR32, i32mem, loadi32>, T8PS, VEX_4V;
1277 defm ANDN64 : bmi_andn<"andn{q}", GR64, i64mem, loadi64>, T8PS, VEX_4V, VEX_W;
1278 }
Multiclass与defm用于一次定义多个类及定义。在defm处,产生的def的名字将是该defm名字与对应multiclass中def名字的结合,例如ANDN32将产生ANDN32rr与ANDN32rm。TableGen支持几个类似C的内置函数,比如上面的strconcat,要使用这些函数必须以“!”开头。
以1281行的定义为例,这个例子将匹配一个“and (not GR32:$src1), GR32:$src2)”模式的IR dag匹配为一条ANDN32rr指令。实际上这个定义将ISD::AND选中到X86ISD::AND(在支持BMI指令的情形下),而两者要匹配的操作数是一样的(因此如果谓词成立的话,选中ISD::AND,也必然选中X86ISD::AND)。
这其中的区别是,ISD::AND是前端从C/C++程序得到的LLVM IR形式的DAG中的一部分,它不关心受影响的标记,而且也是目标机器无关的。X86ISD::AND则可能会影响标记(或受标记影响),而且可能是特定目标机器的优化指令(比如这里的BMI指令,如果不支持BMI指令,就需要两条指令来完成ANDN32rr的操作)。
事实上,2.1.4节中给出的IMUL16rri例子,在X86InstrCompiler.td也有一个对应的匿名Pat定义,将ISD::MUL匹配作X86ISD::MUL。
另外,这个例子还包含了一些有趣的深层约束。在这个例子里它使用的操作数的类型是X86MemOperand,它有这样的定义(X86InstrInfo.td,v7.0将299行最后参数改为SEGMENT_REG):
296 class X86MemOperand<string printMethod,
297 AsmOperandClass parserMatchClass = X86MemAsmOperand> : Operand<iPTR> {
298 let PrintMethod = printMethod;
299 let MIOperandInfo = (ops ptr_rc, i8imm, ptr_rc_nosp, i32imm, i8imm);
300 let ParserMatchClass = parserMatchClass;
301 let OperandType = "OPERAND_MEMORY";
302 }
这样的操作数有时需要比较复杂的计算,比如上面的X86取址。这需要描述其中涉及的的子操作数,由299行的MIOperandInfo保存它们。注意,这是一个dag值,操作符必须是ops。
与这个操作数对应,在相关Instruction定义的Pattern域里,addr是这样一个ComplexPattern定义(X86InstrInfo.td):
701 def addr: ComplexPattern<iPTR, 5, "SelectAddr", [], [SDNPWantParent]>;
在这个定义中,numops是5,正好是子操作数的数目,而这些子操作数最终会传递给addr中指定的方法SelectAddr。
上面的所有这些定义TableGen首先使用TGLexer与TGParser进行词法、语法解析,所有的class与def定义都会生成一个对应的Record实例。而所有的值则会创建为Init的派生实例,这还包括A.B,A[B]这样的表达式。所有的Record实例都保存在一个RecordKeeper类型的容器里,在这个容器里,class与def的定义是分开放置的,因此它们彼此间可以同名。
实际上,TD文件中所有的值都会被创建为Init的派生实例。在LLVM源代码目录/include/llvm/ TableGen/Record.h中给出了描述Init派生类型的枚举类型:
235 enum InitKind {
236 IK_BitInit, // true/false
237 IK_FirstTypedInit,
238 IK_BitsInit, // { a, b, c }
239 IK_DagInit, // Dag值
240 IK_DefInit, // 在描述里援引的一个def
241 IK_FieldInit, // X.Y
242 IK_IntInit,
243 IK_ListInit, // [AL, AH, CL]
244 IK_FirstOpInit,
245 IK_BinOpInit,
246 IK_TernOpInit,
247 IK_UnOpInit,
248 IK_LastOpInit,
249 IK_StringInit,
250 IK_VarInit, // 对整个变量对象的引用
251 IK_VarListElementInit, // List[4]
252 IK_LastTypedInit,
253 IK_UnsetInit, // 未初始化
254 IK_VarBitInit // 对变量或域的一个比特的访问
255 };
注意237与252行,在这中间的是所谓的“有类型的Init”,它们实际上是继承自TypedInit。另外,244与248行的IK_FirstOpInit与IK_LastOpInit也不代表具体的Init派生类型,而是表示它们之间的是所谓的“代表操作数的Init”(它们实际继承自OpInit,OpInit又继承自TypedInit)。其中IK_BinOpInit代表二元操作符的操作数,IK_TernOpInit则代表三元操作符(TableGen支持IF,Foreach,Subst这3个三元操作符)的操作符,IK_UnOpInit则是一元操作符的操作数。其余参见注释说明。
2.2.5.3. 指令展开的例子
对于这个Pat定义:
1281 def : Pat<(and (not GR32:$src1), GR32:$src2),
1282 (ANDN32rr GR32:$src1, GR32:$src2)>;
展开后的结果是:
def anonymous_2021 { // Pattern Pat
dag PatternToMatch = (and (not GR32:$src1), GR32:$src2);
list<dag> ResultInstrs = [(ANDN32rr GR32:$src1, GR32:$src2)];
list<Predicate> Predicates = [HasBMI];
int AddedComplexity = 0;
string NAME = ?;
}
V7.0的结果则是:
def anonymous_1038 { // Pattern Pat
dag PatternToMatch = (and (not GR32:$src1), GR32:$src2);
list<dag> ResultInstrs = [(ANDN32rr GR32:$src1, GR32:$src2)];
list<Predicate> Predicates = [HasBMI];
int AddedComplexity = -6;
}