llvm学习笔记(4)

2.2.2.2. 一般操作数

寄存器以外的一般的操作数由Operand描述,它定义如下:


593

class
Operand
: DAGOperand {


594

ValueType Type = ty;


595

string PrintMethod =”printOperand”;


596

string EncoderMethod = “”;


597

string DecoderMethod = “”;


598

string OperandType =”OPERAND_UNKNOWN”;


599

dag
MIOperandInfo = ( ops
);

600


601

//MCOperandPredicate – Optionally, a code fragment operating on


602

// constMCOperand &MCOp, and returning a bool, to indicate if


603

// the value ofMCOp is valid for the specific subclass of Operand


604

code MCOperandPredicate;

605


606

//ParserMatchClass – The “match class” that operands of this type fit


607

// in. Matchclasses are used to define the order in which instructions are


608

// match, toensure that which instructions gets matched is deterministic.


609

//


610

// The targetspecific parser must be able to classify an parsed operand into


611

// a uniqueclass, which does not partially overlap with any other classes. It


612

// can match asubset of some other class, in which case the AsmOperandClass


613

// should declarethe other operand as one of its super classes.


614

AsmOperandClass ParserMatchClass =ImmAsmOperand;


615

}

成员MIOperandInfo则必须是操作符为ops的dag值,它用于指定多个子操作数,有子操作数的操作数称为复合操作数。对于复杂指令使用的操作数,子操作数是不可缺的。例如,X86里向SSE寄存器载入一个标量的TD定义(来自X86InstrFragmentsSIMD.td):


374

def
sse_load_f32: ComplexPattern
<v4f32, 5,"SelectScalarSSELoad", [],


375

[SDNPHasChain,SDNPMayLoad, SDNPMemOperand,


376

SDNPWantRoot]>;

这个定义将用作其他复杂的,涉及内存值载入寄存器的指令操作数,比如指令VCVTSD2SI(来自X86InstrSSE.td):


1670

let
Predicates =[UseAVX] in
{


1671

defm
VCVTSD2SI : sse12_cvt_sint<0x2D, VR128, GR32,


1672

int_x86_sse2_cvtsd2si, sdmem,sse_load_f64, “cvtsd2si”,


1673

SSE_CVT_SD2SI>, XD, VEX,VEX_LIG;


1674

defm
VCVTSD2SI64 : sse12_cvt_sint<0x2D, VR128,GR64,


1675

int_x86_sse2_cvtsd2si64,sdmem, sse_load_f64, “cvtsd2si”,


1676

SSE_CVT_SD2SI>, XD, VEX,VEX_W, VEX_LIG;


1677

}

这一点在其基类中显示得很清楚:


1638

multiclass
sse12_cvt_sint<bits opc, RegisterClass SrcRC, RegisterClass DstRC,


1639

Intrinsic Int, Operandmemop, ComplexPattern mem_cpat,


1640

string asm, OpndItinsitins> {


1641

def
rr :SI<opc, MRMSrcReg, ( outs
DstRC:$dst), ( ins
SrcRC:$src),


1642

! strconcat
(asm,”t{$src, $dst|$dst, $src}”),


1643

[( set
DstRC:$dst, (Int SrcRC:$src))], itins.rr>,


1644

Sched;


1645

def
rm :SI<opc, MRMSrcMem, ( outs
DstRC:$dst), ( ins
memop:$src),


1646

! strconcat
(asm,”t{$src, $dst|$dst, $src}”),


1647

[( set
DstRC:$dst, (Int mem_cpat:$src))], itins.rm>,


1648

Sched;


1649

}

ComplexPattern描述的模式要求以C++形式模式匹配代码,它定义在TargetSelectionDAG.td。


1088

class
ComplexPattern
<ValueType ty, int numops, string fn,


1089

list roots =[], list props = []> {


1090

ValueType Ty = ty;


1091

int NumOperands = numops;


1092

string SelectFunc = fn;


1093

list RootNodes = roots;


1094

list Properties =props;


1095

}

如果RootNodes不是空,它就指定了SelectFunc所要匹配的节点。而如果RootNodes是空,所要匹配的节点就由Properties指定。下面是X86的两个例子:


701

def
addr:ComplexPattern;


702

def
lea32addr : ComplexPattern<i32, 5,"SelectLEAAddr",


703

[add, sub, mul,X86mul_imm, shl, or, frameindex],


704

[]>;

Addr没有RootNodes,属性SDNPWantParent指示SelectAddr对包含它的dag值进行匹配。而lea32addr的RootNodes则指示SelectLEAAddr对add,sub,X86mul_imm,frameindex,shl,or及mul类型的节点进行匹配。

同样,sse_load_f32也是对包含它的dag值使用SelectScalarSSELoad进行匹配。它期望操作数的类型是v4f32,对应的Operand对应是:


381

def
ssmem
: Operand
{


382

let
PrintMethod = “printf32mem”;


383

let
MIOperandInfo = ( ops
ptr_rc, i8imm,ptr_rc_nosp, i32imm, i8imm);


384

let
ParserMatchClass = X86Mem32AsmOperand;


385

let
OperandType = “OPERAND_MEMORY”;


386

}

383行的MIOperandInfo指出了ssmem实际上要用到5个操作数。而LLVM对此提供了374行所指出的函数SelectScalarSSELoad,它的原型是:

bool X86DAGToDAGISel::SelectScalarSSELoad(SDNode *Root,

SDValue N, SDValue &Base,

SDValue &Scale, SDValue &Index,

SDValue&Disp, SDValue &Segment,

SDValue &PatternNodeWithChain)

SelectScalarSSELoad只被sse_load_f32及sse_load_f64这两个ComplexPattern调用(在TableGen生成的文件X86GenDAGISel.inc中):

Result.resize(NextRes+6);

return SelectScalarSSELoad(Root, N,Result[NextRes+0].first, Result[NextRes+1].first, Result[NextRes+2].first,Result[NextRes+3].first, Result[NextRes+4].first, Result[NextRes+5].first);

从中可以知道ptr_rc保存基址,第一个i8imm保存Scale,ptr_rc_nosp保存索引,i32imm保存偏移,第二个i8imm保存段寄存器。

这些子操作数也同样可以被援引,但需要具名,如下例(来自SystemZOperands.td):


464

def
brtarget16tls :PCRelTLSOperand {


465

let
MIOperandInfo = ( ops
brtarget16:$func,tlssym:$sym);


466

let
EncoderMethod = “getPC16DBLTLSEncoding”;


467

let
DecoderMethod = “decodePC16DBLOperand”;


468

}

通过$brtarget16tls.func来援引brtarget16tls中brtarget16类型子操作数中的$func子操作数。

2.2.3. 约束条件

LLVM的文档并未说明上述Instruction声明中各个域之间存在的约束条件。实际上,不是所有的域都是自由的,以一个例子来说明:


310

def
MMX_MOVQ2DQrr : MMXS2SIi8<0xD6, MRMSrcReg, ( outs
VR128:$dst),


311

( ins
VR64:$src), “movq2dqt{$src, $dst|$dst,$src}”,


312

[( set
VR128:$dst,


313

(v2i64


314

(scalar_to_vector


315

(i64 (bitconvert (x86mmxVR64:$src))))))],


316

IIC_MMX_MOVQ_RR>;

这是一个Instruction的深度派生声明,刚才看到的dag值的例子就摘自这里(313~315行)。它将赋给Instruction声明333行的Pattern成员,而310行的(outs VR128:$dst)则将赋给327行的成员OutOperandList,311行的(ins VR64:$src)给328行的InOperandList。这三个成员间存在这样的约束:OutOperandList必须以outs作为操作符,InOperandList则必须以ins作为操作符,而且两者中所有的操作数必须具名。另外,Pattern中出现的符号名(即这个例子中的$src,$dst)必须来自OutOperandList以及InOperandList,而且分别作为输入、输出操作数。

另外,成员Constraints是一个字符串,用于描述对输入、输出操作数的约束(Instruction定义的435行)。目前有两种约束形式。其一是@earlyclobber$reg,表示寄存器reg的内容很早就被破坏,另一个是绑定约束,其形式是$src = $dst。显然,这些名字都必须是输入、输出操作数的名字。

另外,311行的字符串”movq2dqt{$src, $dst|$dst, $src}”会赋给329行的AsmString,是这个Instruction声明所对应的汇编语句。在指令生成遍(阶段),将以这个语句为模板,生成汇编代码。

Instruction声明430行的Itinerary与431行的SchedRW都是用于描述指令在目标机器上执行的方方面面,它们是指令调度的依据。它们的定义依赖于具体的目标机器。

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

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

喜欢 (0)or分享给?

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

使用声明 | 英豪名录