LLVM学习笔记(6)

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的操作数,而且必须援引所有的操作数。


1.1.1.1.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<imm, [{


852

// Convert a ROTLshamt to a ROTR shamt on 32-bit integer.


853

return getI8Imm(32 – N->getZExtValue(),SDLoc(N));


854

}]>;

TableGen在生成后端代码时,会将853行的代码片段嵌入合适的地方,以对指定的操作数进行指定的处理。


1.1.1.1.1.2.
PatFrag
的例子

以下是X86机器中PatFrag的一种派生定义:


889

def
loadi16
: PatFrag<(ops node:$ptr), (i16 (unindexedload node:$ptr)), [{


890

LoadSDNode *LD = cast(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<(opsnode:$ptr), ( ld
node:$ptr), [{


675

returncast(N)->getAddressingMode() == ISD::UNINDEXED;


676

}]>;

而在这个定义里,替换片段ld也是一个SDNode派生定义:


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并不使用这个结果,这只是为了让开发人员更好地查看、调试这些定义):

defloadi16 { //SDPatternOperator PatFrag

SDNodeXForm PatFrag:xform = NOOP_SDNodeXForm;

dag Operands = (ops node:$ptr);

dag Fragment = (i16 (unindexedloadnode:$ptr));

string PredicateCode = “

LoadSDNode *LD = cast(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实例,并在处理中记录了这个信息。

2.2.4.2.2. 其他特殊PatFrag派生类

这包括以下的这些定义,它们都是PatFrag的派生类,可视为PatFrag的偏特化(Target.td)。


618

class
OutPatFrag
< dag
ops, dag
frag>


619

: PatFrag;


623

class
PatLeaf
< dag
frag, code pred = [{}], SDNodeXForm xform =NOOP_SDNodeXForm>


624

:PatFrag;


640

class
ImmLeaf


641

: PatFrag{


642

let
ImmediateCode = pred;


643

bit FastIselShouldIgnore = 0;


644

}

OutPatFrag适用于输出模板,不需要操作操作数的代码片段。而PatLeaf则是没有操作数,可以简洁地定义立即数等通用结构。ImmLeaf则视为引入限定的PatLeaf,专用于立即数。

2.2.5. 另一种匹配方式

另一种匹配指令的方法是通过Pattern(Target.td)。它是这样的一个定义:


1067

class
Pattern
< dag
patternToMatch, list< dag
>resultInstrs> {


1068

dag
PatternToMatch = patternToMatch;


1069

list< dag
>ResultInstrs = resultInstrs;


1070

list Predicates = []; // See classInstruction in Target.td.


1071

int AddedComplexity = 0; // See classInstruction 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, thisis true. Targets should set this byinheriting their


482

/// feature fromthe AssemblerPredicate class in addition to Predicate.


483

bit AssemblerMatcherPredicate = 0;

484


485

/// AssemblerCondString- Name of the subtarget feature being tested used


486

/// asalternative 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 alsolist 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

/// indiagnostics such as missing feature errors in the asm matcher.


496

string PredicateName = “”;


497

}

它实际上是一段嵌入代码的封装容器。下面是一个具体的谓词的定义(X86InstrInfo.td):


787

def
HasBMI
:PredicatehasBMI()”>;

这个谓词测试目标机器是否支持BMI指令(位操作指令,BitManipulate Instruction),这是在Haswell微架构里引入的,分BMI1与BMI2两个指令集。这里是BMI1,下面这个是测试BMI2:


788

def
HasBMI2
:PredicatehasBMI2()”>;

很显然,Predicate定义中的CondString是一个C++代码片段,TableGen将会把它插入到生成代码中合适的地方。

谓词主要是用于筛选处理器所支持的指令集。另外,也可以对该处理器架构的某些有局限的指令进行筛选,比如某个处理器的比特设置指令比较慢,应该用别的指令替代(不过在LLVM,目前后者还只是设想)。这些谓词的来源与声明我们在指令调度的时候再来看。

2.2.5.2. 指令定义的例子

下面是使用谓词HasBMI的指令定义的例子(X86InstrArithmetic.td):


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的部分,ANDN XX
是通过下面结构来定义的(X86InstrArithmetic.td):


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;


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;


1273

}

1274


1275

let
Predicates = [HasBMI], Defs = [EFLAGS] in
{


1276

defm
ANDN32 :bmi_andn, T8PS, VEX_4V;


1277

defm
ANDN64 :bmi_andn, T8PS, VEX_4V,VEX_W;


1278

}

Multiclass与defm用于一次定义多个类及定义。在defm处,产生的def的名字将是该defm名字与对应multiclass中def名字的结合,例如ANDN32将产生ANDN32rr与ANDN32rm。TableGen支持几个类似C的内置函数,比如上面的strconcat,要使用这些函数必须以“!”开头。

以1281行的定义为例,这个例子将匹配一个“and (notGR32:$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):


296

class
X86MemOperand
<string printMethod,


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)>;

展开后的结果是:

defanonymous_2021 { // Pattern Pat

dag PatternToMatch = (and (not GR32:$src1),GR32:$src2);

list ResultInstrs = [(ANDN32rrGR32:$src1, GR32:$src2)];

list Predicates = [HasBMI];

int AddedComplexity = 0;

string NAME = ?;

稿源:wuhui_gdnt的专栏 (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合技术 » LLVM学习笔记(6)

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录