2.2.2.2. 一般操作数
寄存器以外的一般的操作数由Operand描述,它定义如下(v7.0将597行的DecoderMethod替换为bit hasCompleteDecoder = 1):
593 class Operand<ValueType ty> : 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 // const MCOperand &MCOp, and returning a bool, to indicate if
603 // the value of MCOp 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. Match classes are used to define the order in which instructions are
608 // match, to ensure that which instructions gets matched is deterministic.
609 //
610 // The target specific parser must be able to classify an parsed operand into
611 // a unique class, which does not partially overlap with any other classes. It
612 // can match a subset of some other class, in which case the AsmOperandClass
613 // should declare the other operand as one of its super classes.
614 AsmOperandClass ParserMatchClass = ImmAsmOperand;
615 }
成员MIOperandInfo则必须是操作符为ops的dag值,它用于指定多个子操作数,有子操作数的操作数称为复合操作数。对于复杂指令使用的操作数,子操作数是不可缺的。例如,X86里向SSE寄存器载入一个标量的TD定义(来自X86InstrFragmentsSIMD.td,v7.0多了SDNPWantParent属性):
374 def sse_load_f32: ComplexPattern<v4f32, 5, "SelectScalarSSELoad", [],
375 [SDNPHasChain, SDNPMayLoad, SDNPMemOperand,
376 SDNPWantRoot]>;
这个定义将用于其他复杂的,涉及内存值载入寄存器的指令操作数,比如指令VCVTSD2SI(来自X86InstrSSE.td,v7.0的定义略有不同,但本质没有区别):
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 }
这一点在其基类sse12_cvt_sint中显示得很清楚:
1638 multiclass sse12_cvt_sint<bits<8> opc, RegisterClass SrcRC, RegisterClass DstRC,
1639 Intrinsic Int, Operand memop, ComplexPattern mem_cpat,
1640 string asm, OpndItins itins> {
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<[itins.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<[itins.Sched.Folded]>;
1649 }
ComplexPattern描述的模式要求以C++形式模式匹配代码,它定义在TargetSelectionDAG.td。
1088 class ComplexPattern<ValueType ty, int numops, string fn,
1089 list<SDNode> roots = [], list<SDNodeProperty> props = []> {
1090 ValueType Ty = ty;
1091 int NumOperands = numops;
1092 string SelectFunc = fn;
1093 list<SDNode> RootNodes = roots;
1094 list<SDNodeProperty> Properties = props;
1095 }
如果RootNodes不是空,它就指定了SelectFunc所要匹配的节点。而如果RootNodes是空,所要匹配的节点就由Properties指定。下面是X86的两个例子:
701 def addr: ComplexPattern<iPTR, 5, "SelectAddr", [], [SDNPWantParent]>;
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对应是(v7.0中MIOperandInfo最后的参数是SEGMENT_REG):
381 def ssmem: Operand<v4f32> {
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,它的原型是(v7.0因为增加了SDNPWantParent属性,函数声明里在第二个参数的位置插入了:SDNode *Parent):
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中,同样在v7.0中,在第二个参数位置插入一个Parent实参):
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<OtherVT, PCRelTLS16> {
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声明中各个域之间存在的约束条件。实际上,不是所有的域都是自由的,以一个例子来说明(在v7.0中X86指令定义不再直接从InstrItinClass派生,改为通过WriteRes派生定义来描述。因此下例不再使用316行的IIC_MMX_MOVQ_RR作为基类):
310 def MMX_MOVQ2DQrr : MMXS2SIi8<0xD6, MRMSrcReg, (outs VR128:$dst),
311 (ins VR64:$src), "movq2dq\t{$src, $dst|$dst, $src}",
312 [(set VR128:$dst,
313 (v2i64
314 (scalar_to_vector
315 (i64 (bitconvert (x86mmx VR64:$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,而且分别作为输入、输出操作数。
另外,Instruction成员Constraints是一个字符串,用于描述对输入、输出操作数的约束(定义的435行)。目前有两种约束形式。其一是@earlyclobber $reg,表示寄存器reg的内容很早就被破坏,另一个是绑定约束,其形式是$src = $dst。显然,这些名字都必须是输入、输出操作数的名字。
另外,311行的字符串"movq2dq\t{$src, $dst|$dst, $src}"会赋给329行的AsmString,是这个Instruction声明所对应的汇编语句。在指令生成遍(阶段),将以这个语句为模板,生成汇编代码。
Instruction声明430行的Itinerary与431行的SchedRW都是用于描述指令在目标机器上执行的方方面面,它们是指令调度的依据。它们的定义依赖于具体的目标机器。