3.4.4.3. 生成代码如何辅助指令选择
3.4.4.3.1. 概述
我们首先以一个例子来说明这个过程是怎么开始的,这个例子来自Eli Bendersky的a deeper look into the llvm code generator part I:
Eli Bendersky给出了这样一些简单的IR:
define i64 @imul(i64 %a, i64 %b) nounwind readnone {
entry:
%mul = mul nsw i64 %b, %a
ret i64 %mul
}
它是在x64机器上用Clang(选项-emit-llvm)编译下面的C代码得到的:
long imul(long a, long b) {
return a * b;
}
代码生成器完成的第一件事是把IR转换为一个selectionDAG表示。下图是刚开始的DAG,就在构建出来之后。
实际上这个SelectionDAG是从最顶上的EntryToken开始构建的(即以函数的语句顺序),节点保存在ilist<SDNode>类型的容器里(SelectionDAG的AllNodes,这个容器是一个链式容器,图中没有显示将各个节点连接起来的边)。
在进行指令选择之前,会对这个容器进行一次拓扑排序,然后以反拓扑序对SelectionDAG遍历,即从最底下的GraphRoot节点开始,自底向上遍历。需要这个次序,是因为指令选择后得到的节点将替换被选中的节点,即被选中节点的使用者需要使用选择得到节点来代替被选中的节点。而且这个次序对维持节点间chain与glue的关联关系也是最高效的。这样的次序使得只要一次遍历就能完成指令选择与替换。
在指令选择后DAG的外观如下。在图中原有的mul节点被X86特定的IMUL64rr指令节点所替代。另外RET_FLAG节点被X86的RET指令节点替代。
注意,其中chain节点间的次序是受保护的,而glue节点所连接的节点则是:在调度期间,指令调度器不会在它们中间插入其他代码。关于chain与glue属性的声明,一方面,我们可以在声明描述操作的SDNode定义时通过SDNPHasChain与SDNPInGlue,SDNPOutGlue来声明这些属性。另一方面,部分操作节点目前还不是通过SDNode来描述的,比如上面的CopyToReg及CopyFromReg,则在构建具体的节点时确定其chain与glue属性。基本上访问内存的操作节点会带有chain属性,操作数进行算术操作的节点一般会带有glue属性。
Chain操作数如果存在的话,总是第一个操作数,而glue操作数如果存在的话,则总是最后一个操作数。这是TableGen强制的规定。
3.4.4.2. 基本数据结构
3.4.4.2.1. SDNode
在被前端处理后,源代码会被转换为LLVM IR形式,在经过一系列优化处理后,在指令选择前,这个形式被称为SelectionDAG。此时,这个DAG中的节点是SDNode的实例。类SDNode定义在文件SelectionDAGNodes.h中,它包含了下列的数据:
324 class SDNode : public FoldingSetNode, public ilist_node<SDNode> {
325 private:
326 /// The operation that this node performs.
327 int16_t NodeType;
328
329 /// This is true if OperandList was new[]'d. If true,
330 /// then they will be delete[]'d when the node is destroyed.
331 uint16_t OperandsNeedDelete : 1;
332
333 /// This tracks whether this node has one or more dbg_value
334 /// nodes corresponding to it.
335 uint16_t HasDebugValue : 1;
336
337 protected:
338 /// This member is defined by this class, but is not used for
339 /// anything. Subclasses can use it to hold whatever state they find useful.
340 /// This field is initialized to zero by the ctor.
341 uint16_t SubclassData : 14;
342
343 private:
344 /// Unique id per SDNode in the DAG.
345 int NodeId;
346
347 /// The values that are used by this operation.
348 SDUse *OperandList;
349
350 /// The types of the values this node defines. SDNode's may
351 /// define multiple values simultaneously.
352 const EVT *ValueList;
353
354 /// List of uses for this SDNode.
355 SDUse *UseList;
356
357 /// The number of entries in the Operand/Value list.
358 unsigned short NumOperands, NumValues;
359
360 /// Source line information.
361 DebugLoc debugLoc;
362
363 // The ordering of the SDNodes. It roughly corresponds to the ordering of the
364 // original LLVM instructions.
365 // This is used for turning off scheduling, because we'll forgo
366 // the normal scheduling algorithms and output the instructions according to
367 // this ordering.
368 unsigned IROrder;
SDNode派生自FoldingSetNode与模板类ilist_node。后者提供了类似于一个双向链表的功能,它也是LLVM用于表示基本块的BasicBlock的基类。前者则是类FoldingSetImpl定义的嵌套类Node的typedef,FoldingSetImpl的派生类FoldingSet提供一个以FoldingSetNode为单元的哈希表。在类SelectionDAG里,成员CSEMap就是保存这些代表DAG的SDNode对象的所在(CSE是Common Subexpression Elimination的缩写,公共子表达式消除)。从这两个类派生,SDNode就可以同时置身于BasicBlock与CSEMap里。
345行的NodeId用于在CSEMap里唯一地识别SDNode实例。在完成指令选择后,NodeId将被设置为-1。在编译优化中,变量(值)的定义与使用是非常重要的信息。为此在编译理论中,对一个变量有所谓的定义链,而对某个特定的变量定义则有所谓的使用链。SelectionDAG是SSA形式的,一个SDNode可能代表了变量的一个定义,也可能使用了变量的某个定义。如果是变量的某个定义,它就需要维护一个使用链,记录对它的使用,这就是355行的UseList的用处——构成Def-Use链。而如果它使用了某些变量的特定定义,也需要维护一个使用链来记录这些操作数(348行OperandList)。它们的类型SDUse包含了如下的成员:
233 class SDUse {
234 /// Val - The value being used.
235 SDValue Val;
236 /// User - The user of this value.
237 SDNode *User;
238 /// Prev, Next - Pointers to the uses list of the SDNode referred by
239 /// this operand.
240 SDUse **Prev, *Next;
235行所援引的SDValue实际上包含了一个指向产生这个值的SDNode实例,因为SDNode所代表的操作可以有多个结果,因此还包含了一个成员ResNo来指示使用第几个结果。
105 class SDValue {
106 friend struct DenseMapInfo<SDValue>;
107
108 SDNode *Node; // The node defining the value we are using.
109 unsigned ResNo; // Which return value of the node we are using.
在SDUse定义237行,User指向使用Val的SDNode对象,这就形成了Use-Def链(SDNode本身可以构成一个双向链表)。
V7.0大幅改造了SDNode的定义,使之更科学。现在这个类定义有下列数据成员: 482 class SDNode : public FoldingSetNode, public ilist_node<SDNode> { 483 private: 484 /// The operation that this node performs. 485 int16_t NodeType; 486 487 protected: 488 // We define a set of mini-helper classes to help us interpret the bits in our 489 // SubclassData. These are designed to fit within a uint16_t so they pack 490 // with NodeType. 491 492 class SDNodeBitfields { 493 friend class SDNode; 494 friend class MemIntrinsicSDNode; 495 friend class MemSDNode; 496 friend class SelectionDAG; 497 498 uint16_t HasDebugValue : 1; 499 uint16_t IsMemIntrinsic : 1; 500 uint16_t IsDivergent : 1; 501 }; 502 enum { NumSDNodeBits = 3 }; 503 504 class ConstantSDNodeBitfields { 505 friend class ConstantSDNode; 506 507 uint16_t : NumSDNodeBits; 508 509 uint16_t IsOpaque : 1; 510 }; 511 512 class MemSDNodeBitfields { 513 friend class MemSDNode; 514 friend class MemIntrinsicSDNode; 515 friend class AtomicSDNode; 516 517 uint16_t : NumSDNodeBits; 518 519 uint16_t IsVolatile : 1; 520 uint16_t IsNonTemporal : 1; 521 uint16_t IsDereferenceable : 1; 522 uint16_t IsInvariant : 1; 523 }; 524 enum { NumMemSDNodeBits = NumSDNodeBits + 4 }; 525 526 class LSBaseSDNodeBitfields { 527 friend class LSBaseSDNode; 528 529 uint16_t : NumMemSDNodeBits; 530 531 uint16_t AddressingMode : 3; // enum ISD::MemIndexedMode 532 }; 533 enum { NumLSBaseSDNodeBits = NumMemSDNodeBits + 3 }; 534 535 class LoadSDNodeBitfields { 536 friend class LoadSDNode; 537 friend class MaskedLoadSDNode; 538 539 uint16_t : NumLSBaseSDNodeBits; 540 541 uint16_t ExtTy : 2; // enum ISD::LoadExtType 542 uint16_t IsExpanding : 1; 543 }; 544 545 class StoreSDNodeBitfields { 546 friend class StoreSDNode; 547 friend class MaskedStoreSDNode; 548 549 uint16_t : NumLSBaseSDNodeBits; 550 551 uint16_t IsTruncating : 1; 552 uint16_t IsCompressing : 1; 553 }; 554 555 union { 556 char RawSDNodeBits[sizeof(uint16_t)]; 557 SDNodeBitfields SDNodeBits; 558 ConstantSDNodeBitfields ConstantSDNodeBits; 559 MemSDNodeBitfields MemSDNodeBits; 560 LSBaseSDNodeBitfields LSBaseSDNodeBits; 561 LoadSDNodeBitfields LoadSDNodeBits; 562 StoreSDNodeBitfields StoreSDNodeBits; 563 }; 564 565 // RawSDNodeBits must cover the entirety of the union. This means that all of 566 // the union's members must have size <= RawSDNodeBits. We write the RHS as 567 // "2" instead of sizeof(RawSDNodeBits) because MSVC can't handle the latter. 568 static_assert(sizeof(SDNodeBitfields) <= 2, "field too wide"); 569 static_assert(sizeof(ConstantSDNodeBitfields) <= 2, "field too wide"); 570 static_assert(sizeof(MemSDNodeBitfields) <= 2, "field too wide"); 571 static_assert(sizeof(LSBaseSDNodeBitfields) <= 2, "field too wide"); 572 static_assert(sizeof(LoadSDNodeBitfields) <= 2, "field too wide"); 573 static_assert(sizeof(StoreSDNodeBitfields) <= 2, "field too wide"); 574 575 private: 576 friend class SelectionDAG; 577 // TODO: unfriend HandleSDNode once we fix its operand handling. 578 friend class HandleSDNode; 579 580 /// Unique id per SDNode in the DAG. 581 int NodeId = -1; 582 583 /// The values that are used by this operation. 584 SDUse *OperandList = nullptr; 585 586 /// The types of the values this node defines. SDNode's may 587 /// define multiple values simultaneously. 588 const EVT *ValueList; 589 590 /// List of uses for this SDNode. 591 SDUse *UseList = nullptr; 592 593 /// The number of entries in the Operand/Value list. 594 unsigned short NumOperands = 0; 595 unsigned short NumValues; 596 597 // The ordering of the SDNodes. It roughly corresponds to the ordering of the 598 // original LLVM instructions. 599 // This is used for turning off scheduling, because we'll forgo 600 // the normal scheduling algorithms and output the instructions according to 601 // this ordering. 602 unsigned IROrder; 603 604 /// Source line information. 605 DebugLoc debugLoc; 606 607 /// Return a pointer to the specified value type. 608 static const EVT *getValueTypeList(EVT VT); 609 610 SDNodeFlags Flags; 611 612 public: 613 /// Unique and persistent id per SDNode in the DAG. 614 /// Used for debug printing. 615 uint16_t PersistentId; 现在的定义干净整洁了不少,也更紧凑,便于日后的扩展。 |
3.4.4.2.2. SDNode的派生定义
在指令选择后创建的MachineSDNode是SDNode的一个简单派生类。最主要的改变是加入了对内容使用的描述。
2149 class MachineSDNode : public SDNode {
2150 public:
2151 typedef MachineMemOperand **mmo_iterator;
2152
2153 private:
2154 friend class SelectionDAG;
2155 MachineSDNode(unsigned Opc, unsigned Order, const DebugLoc DL, SDVTList VTs)
2156 : SDNode(Opc, Order, DL, VTs), MemRefs(nullptr), MemRefsEnd(nullptr) {}
2157
2158 /// Operands for this instruction, if they fit here. If
2159 /// they don't, this field is unused.
2160 SDUse LocalOperands[4];
2161
2162 /// Memory reference descriptions for this instruction.
2163 mmo_iterator MemRefs;
2164 mmo_iterator MemRefsEnd;
2165
2166 public:
2167 mmo_iterator memoperands_begin() const { return MemRefs; }
2168 mmo_iterator memoperands_end() const { return MemRefsEnd; }
2168 bool memoperands_empty() const { return MemRefsEnd == MemRefs; }
2170
2171 /// Assign this MachineSDNodes's memory reference descriptor
2172 /// list. This does not transfer ownership.
2173 void setMemRefs(mmo_iterator NewMemRefs, mmo_iterator NewMemRefsEnd) {
2174 for (mmo_iterator MMI = NewMemRefs, MME = NewMemRefsEnd; MMI != MME; ++MMI)
2175 assert(*MMI && "Null mem ref detected!");
2176 MemRefs = NewMemRefs;
2177 MemRefsEnd = NewMemRefsEnd;
2178 }
2179
2180 static bool classof(const SDNode *N) {
2181 return N->isMachineOpcode();
2182 }
2183 };
MemRefs与MemRefsEnd可视为指向一些内存片段。这些内存片段由MachineMemOperand描述。
在v7.0中SDNode的派生类有:MachineSDNode,HandleSDNode,AddrSpaceCastSDNode,MemSDNode,AtomicSDNode,MemIntrinsicSDNode,ShuffleVectorSDNode,ConstantSDNode,ConstantFPSDNode,GlobalAddressSDNode,FrameIndexSDNode,JumpTableSDNode,ConstantPoolSDNode,TargetIndexSDNode,BasicBlockSDNode,BuildVectorSDNode,SrcValueSDNode,MDNodeSDNode,RegisterSDNode,BlockAddressSDNode,LabelSDNode,ExternalSymbolSDNode,MCSymbolSDNode,CondCodeSDNode,VTSDNode,LSBaseSDNode,LoadSDNode,StoreSDNode,MaskedLoadStoreSDNode,MaskedLoadSDNode,MaskedStoreSDNode,MaskedGatherScatterSDNode,MaskedGatherSDNode,MaskedScatterSDNode。 在LLVM架构里,它自己构造了一个类型转换体系,由cast(),dyn_cast()这类函数支持。这些函数通过类的静态方法classof()来判定一个对象是否为指定的派生类型。Cast()等的具体实现我们不在这里讨论,可以参考以前的文章《Llvm的类型转换系统》。 这里特别注意MachineSDNode的classof()调用SDNode的isMachineOpcode()方法,即NodeType小于0时成立——在被选中时,对应SDNode对象的NodeType被置为-1。其他派生类型的classof()方法,如果有定义,都很简单。我们不一一列举。 |