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
引入了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
646 // cast
647 bit IsUnindexed = ?;
648
649 // cast
650 bit IsNonExtLoad = ?;
651 // cast
652 bit IsAnyExtLoad = ?;
653 // cast
654 bit IsSignExtLoad = ?;
655 // cast
656 bit IsZeroExtLoad = ?;
657 // !cast
658 // cast
659 bit IsTruncStore = ?;
660
661 // cast
662 bit IsAtomicOrderingMonotonic = ?;
663 // cast
664 bit IsAtomicOrderingAcquire = ?;
665 // cast
666 bit IsAtomicOrderingRelease = ?;
667 // cast
668 bit IsAtomicOrderingAcquireRelease = ?;
669 // cast
670 bit IsAtomicOrderingSequentiallyConsistent = ?;
671
672 // isAcquireOrStronger(cast
673 // !isAcquireOrStronger(cast
674 bit IsAtomicOrderingAcquireOrStronger = ?;
675
676 // isReleaseOrStronger(cast
677 // !isReleaseOrStronger(cast
678 bit IsAtomicOrderingReleaseOrStronger = ?;
679
680 // cast
681 // cast
682 ValueType MemoryVT = ?;
683 // cast
684 // cast
685 ValueType ScalarMemoryVT = ?;
686 }
2.2.4.2.1.1. SNDodeForm
SDNodeXForm是一个特别的定义。它允许目标机器在匹配的情形下修改输出操作数(在输出dag里),这通常用于修改立即数。
574 class SDNodeXForm
575 SDNode Opcode = opc;
576 code XFormFunction = xformFunction;
577 }
XFormFunction给出了进行操作的代码片段,Opcode则是操作的对象。
上面607行的NOOP_SDNodeXForm用于表示空操作:
579 def NOOP_SDNodeXForm : SDNodeXForm
另一个来自X86机器的例子则是:
851 def ROT32L2R_imm8: SDNodeXForm 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 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 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 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 dag Operands = (ops node:$ptr); list code PredicateCode = [{ LoadSDNode *LD = cast 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 623 class PatLeaf<dag frag, code pred = [{}], SDNodeXForm xform = NOOP_SDNodeXForm> 624 : PatFrag<(ops), frag, pred, xform>; 640 class ImmLeaf 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 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 2.2.5.1. 谓词Predicate 在Pattern与Instruction的定义中都有谓词部分(Predicates),只有满足谓词设定的条件,Pattern与Instruction的匹配才能继续。Predicate定义在Target.td文件里: 477 class Predicate 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 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 297 AsmOperandClass parserMatchClass = X86MemAsmOperand> : Operand 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 在这个定义中,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 list int AddedComplexity = 0; string NAME = ?; } V7.0的结果则是: def anonymous_1038 { // Pattern Pat dag PatternToMatch = (and (not GR32:$src1), GR32:$src2); list list int AddedComplexity = -6; }