LLVM学习笔记(30)

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.0SDNode的派生类有:MachineSDNodeHandleSDNodeAddrSpaceCastSDNodeMemSDNodeAtomicSDNodeMemIntrinsicSDNodeShuffleVectorSDNodeConstantSDNodeConstantFPSDNodeGlobalAddressSDNodeFrameIndexSDNodeJumpTableSDNodeConstantPoolSDNodeTargetIndexSDNodeBasicBlockSDNodeBuildVectorSDNodeSrcValueSDNodeMDNodeSDNodeRegisterSDNodeBlockAddressSDNodeLabelSDNodeExternalSymbolSDNodeMCSymbolSDNodeCondCodeSDNodeVTSDNodeLSBaseSDNodeLoadSDNodeStoreSDNodeMaskedLoadStoreSDNodeMaskedLoadSDNodeMaskedStoreSDNodeMaskedGatherScatterSDNodeMaskedGatherSDNodeMaskedScatterSDNode

LLVM架构里,它自己构造了一个类型转换体系,由cast()dyn_cast()这类函数支持。这些函数通过类的静态方法classof()来判定一个对象是否为指定的派生类型。Cast()等的具体实现我们不在这里讨论,可以参考以前的文章《Llvm的类型转换系统》。

这里特别注意MachineSDNodeclassof()调用SDNodeisMachineOpcode()方法,即NodeType小于0时成立——在被选中时,对应SDNode对象的NodeType被置为-1。其他派生类型的classof()方法,如果有定义,都很简单。我们不一一列举。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值