LLVM筆記(10) - 指令選擇(二) lowering


本節主要介紹SelectionDAG的構成與lowering的過程. SelectionDAG是LLVM指令選擇的基礎框架, 不論是O0編譯時使用的FastISel還是O2編譯時使用的SelectionDAGISel都會使用SelectionDAG來描述輸入的程序流. 將輸入的IR轉換成SelectionDAG的過程被稱作lowering, 在lowering之前我們通過IR表示一段程序, 在lowering之后我們使用SelectionDAG來描述同一段程序.
我們首先從一個簡單的case入手講解SelectionDAG的基本概念, 然后介紹對應的數據結構以及如何建立SelectionDAG(即lowering的流程), 最后總結lowering的注意點以及在支持一個新架構時我們需要做什么.
PS: 本文假定讀者已熟悉LLVM IR即其對應代碼實現, 對涉及的中端概念不再贅述.
PPS: 由於不同的架構支持的指令集/寄存器/調用約定均不相同, lowering過程也包含架構相關的代碼. 如未特殊說明, 本文中涉及的架構相關的代碼均指RISCV架構(相對於X86以及AArch64, RISCV指令集更精簡, 且歷史包袱也較小, 更適合入門理解).

打印SelectionDAG graph

讓我們首先從一個簡單的case開始入手, test()函數會做一些算術運算與訪問內存操作, 並會調用test2()函數, 但它沒有循環與條件跳轉, 因此只包含一個basic block. 打印它的IR如下所示.

[00:04:16] hansy@hansy:~$ cat 1.c
int test2(int p1);
int test(int *p1, int p2, int p3, char p4) {
  int ret, base, off;
  base = p1[0];
  off = p4 & 0x1f;
  ret = (base * p3)  << off;
  p1[p2] = ret;
  return test2(ret);
}
[00:04:19] hansy@hansy:~$ ~/llvm-mono/llvm_install/bin/clang --target=riscv32 -O2 1.c -S -emit-llvm && cat 1.ll 
; ModuleID = '1.c'
source_filename = "1.c"
target datalayout = "e-m:e-p:32:32-i64:64-n32-S128"
target triple = "riscv32"
; Function Attrs: nounwind
define dso_local i32 @test(i32* nocapture %p1, i32 %p2, i32 %p3, i8 zeroext %p4) local_unnamed_addr #0 {
entry:是比較少見的依賴
  %0 = load i32, i32* %p1, align 4, !tbaa !2
  %1 = and i8 %p4, 31
  %and = zext i8 %1 to i32
  %mul = mul nsw i32 %0, %p3
  %shl = shl i32 %mul, %and
  %arrayidx1 = getelementptr inbounds i32, i32* %p1, i32 %p2
  store i32 %shl, i32* %arrayidx1, align 4, !tbaa !2
  %call = tail call i32 @test2(i32 %shl) #2
  ret i32 %call
}
declare dso_local i32 @test2(i32) local_unnamed_addr #1

attributes #0 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+a,+c,+m,+relax" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+a,+c,+m,+relax" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 10.0.0 (https://github.com/crazyrabbit0424/llvm-project.git 905b002c139f039a32ab9bf1fad63d745d12423f)"}
!2 = !{!3, !3, i64 0}
!3 = !{!"int", !4, i64 0}是比較少見的依賴
!4 = !{!"omnipotent char", !5, i64 0}
!5 = !{!"Simple C/C++ TBAA"}

我們可以使用LLVM內置的debug命令將指令選擇階段的SelectionDAG信息保存下來並寫入dot文件, 然后通過dot/xdot圖形化顯示出來.

[00:23:24] hansy@hansy:~$ ~/llvm-mono/llvm_install/bin/clang --target=riscv32 -O2 1.c -S -mllvm -view-dag-combine1-dags=true 
Writing '/tmp/dag.test-c5e6fc.dot'...  done. 
Trying 'xdg-open' program... Remember to erase graph file: /tmp/dag.test-c5e6fc.dot

命令行中-view-dag-combine1-dags=true表示在第一次combine之前打印SelectionDAG(即lowering之后的graph), 類似的選項還有:

command accepted argument method
-view-legalize-types-dags= bool print graph before legalize type
-view-legalize-dags= bool print graph before legalize
-view-dag-combine2-dags= bool print graph before second combine
-view-dag-combine-lt-dags= bool print graph after legalize type
-view-isel-dags= bool print graph before do instruction selection
-view-sched-dags= bool print graph before schedule dag
-view-sunit-dags= bool print graph after schedule dag
-filter-view-dags= string print graph on each stage only for basic block whose name matches the option

以上文case為例, 打印的graph如下所示.

這里我們將文字打印也貼出來方便對照理解.

Initial selection DAG: %bb.0 'test:entry'
SelectionDAG has 29 nodes:
  t0: ch = EntryToken
  t2: i32,ch = CopyFromReg t0, Register:i32 %0
  t12: i32 = Constant<0>
  t14: i32,ch = load<(load 4 from %ir.p1, !tbaa !2)> t0, t2, undef:i32
      t6: i32,ch = CopyFromReg t0, Register:i32 %2
    t18: i32 = mul nsw t14, t6
            t8: i32,ch = CopyFromReg t0, Register:i32 %3
          t10: i32 = AssertZext t8, ValueType:ch:i8
        t11: i8 = truncate t10
      t16: i8 = and t11, Constant:i8<31>
    t17: i32 = zero_extend t16
  t19: i32 = shl t18, t17
  t24: i32 = GlobalAddress<i32 (i32)* @test2> 0
          t4: i32,ch = CopyFromReg t0, Register:i32 %1
        t21: i32 = shl t4, Constant:i32<2>
      t22: i32 = add t2, t21
    t23: ch = store<(store 4 into %ir.arrayidx1, !tbaa !2)> t14:1, t19, t22, undef:i32
  t26: ch,glue = CopyToReg t23, Register:i32 $x10, t19
  t28: ch,glue = RISCVISD::TAIL t26, TargetGlobalAddress:i32<i32 (i32)* @test2> 0 [TF=1], Register:i32 $x10, t26:1

我們將在后面詳細解釋圖中每個符號的作用, 這里我們先簡單介紹一下如何閱讀這樣的圖.
圖中每個大的方塊是一個名為SDNode的節點, 代表一個操作數或操作符. 根據不同的類型每個方塊又划分為三行或四行子方塊.
其中表示操作數的節點包含三行, 從上到下依次代表節點類型枚舉, 節點編號與節點的數據類型. 以左上角t3節點為例它代表一個數據類型為i32的寄存器操作數, 其節點編號為t3. 又比如最右側的t15節點它代表一個數據類型為i8的常量, 其值為31, 節點編號t15.
表示操作符的節點包含四行, 其中最上層一行表示這個操作符所接受的輸入操作數的序號, 后三行與表示操作數的節點類似, 注意操作符本身沒有數據類型, 它的數據類型是指這個操作符輸出的操作數的數據類型. 以t11節點為例, 它代表一個截斷操作, 其輸出一個i8類型的數據, truncate操作接受一個輸入操作數.
節點間的箭頭代表了節點的依賴關系, 圖中一共有三類箭頭, 黑色實線, 藍色虛線以及紅色實線, 它們分別代表三類依賴關系.
黑色實線代表數據依賴, 箭頭指向的數據來源. 以t19節點為例, 節點左移操作接受兩個輸入操作數, 被移位數據是比較少見的依賴的操作數(序號為0)的來源是t18, 移位位數的操作數(序號為1)的來源是t16. 其結果被store操作(節點t23)與拷貝操作(節點t26)引用.
藍色虛線代表一種特殊的依賴關系(chain dependence). 它指的是節點在調度時不能被移動到所依賴的節點之前. 以t23節點為例, store操作與load操作可能訪問同一內存地址, 那么編譯器需要保守處理將store指令放在load操作后. 然而store與load之間並沒有直接的數據依賴關系(盡管在這個case中兩者可以通過t18間接保證依賴, 但也可能出現完全沒有依賴的情況), 因此編譯器會為其添加一個額外的chain依賴. 為生成chain依賴, 編譯器會為被依賴節點額外添加一個名為ch類型的輸出操作數, 為依賴的來源節點額外添加一個輸入操作數.
紅色實線類似藍色虛線, 但它代表一種更強的關系(glue), 它指的是被glue的節點在調度時不能被分開. chain依賴的節點之間相互順序不能改變, 但是兩者可以分開調度, 也可以合並在一起, 而glue依賴要求節點必須綁定在一起. glue通常比較少見, 在上文的case中t26與t28之間存在一條glue依賴, 原因是t28是tail call操作, t26是函數調用前設置傳參. 根據RISCV calling convention函數傳參寄存器為x10-x17, 為保證正確性需要將兩個節點綁定到一起, 否則可能存在第三個節點被調度到兩者之間並在后面寄存器分配時分配到x10導致傳參被覆寫.

DAG圖的組成

LLVM使用SelectionDAG類(defined in include/llvm/CodeGen/SelectionDAG.h)表示一個架構無關的底層數據依賴圖, 它將串聯整個指令選擇流程, 所以包含了許多成員與接口, 這里我們只截取與lowering相關的部分.

class SelectionDAG {
  const TargetLowering *TLI = nullptr;
  FunctionLoweringInfo * FLI = nullptr;

  /// The starting token.
  SDNode EntryNode;

  /// The root of the entire DAG.
  SDValue Root;

  /// A linked list of nodes in the current DAG.
  ilist<SDNode> AllNodes;
};

其中TargetLowering負責處理架構相關的lowering, FunctionLoweringInfo負責處理函數調用約定, 這兩個成員后面會介紹. EntryNode與Root分別指向圖的入口節點與根節點, 通過根節點即可遍歷整個圖. 注意到這里使用的是SDNode與SDValue, 而非IR中的Node與Value. 在解釋兩者區別前讓我們先思考一個問題: 如果使用IR來表示上文中的DAG有什么困難(換言之為什么不使用IR來直接表示DAG)?

  1. 首先最直觀的一點是IR中每個節點只能有一個輸出(表示一個操作符/操作數的對象Node本身只有一個輸出值), 而DAG中的節點通常有多個輸出, 並且輸出不一定是一個參與運算的值.
  2. 進一步的之所以產生多個輸出的原因之一是DAG會包含非數據的依賴關系. 我們可以通過Value的成員UseList獲取顯示引用這個操作符/操作數的使用者, 但無法獲取非數據依賴的引用關系.
    也就是說DAG體現了節點間約束的關系, 而IR只能表示節點的數據流圖. 為解決這個問題, 對應IR中的Value/Node/Use類型, LLVM重新設計了SDValue/SDNode/SDUse類型(all defined in include/llvm/CodeGen/SelectionDAGNodes.h).
// 類似於Value, SelectionDAG中每個操作數都使用一個SDValue對象來表示
// 但不同於Node, SDNode支持多個輸出, 因此SDValue不光要指明值來源於哪個SDNode, 還要指明值來源於第幾個輸出
class SDValue {
  SDNode *Node = nullptr; // 定義這個SDValue的SDNode
  unsigned ResNo = 0; // 記錄這個SDValue所屬於SDNode的輸出的序號
};

// 代表SelectionDAG中的一個節點, 一個節點包含了以下信息: 節點的操作類型, 輸入操作數, 輸出值
class SDNode : public FoldingSetNode, public ilist_node<SDNode> {
  // 節點操作符的枚舉, 枚舉值可以為ISD::NodeType或[arch]ISD::NodeType
  // 前者為架構無關的操作符, defined in include/llvm/CodeGen/ISDOpcodes.h
  // 后者為架構相關的操作符, defined in lib/Target/[arch]/[arch]ISelLowering.h
  int16_t NodeType;
  // 節點序號標記, 注意這個值在Selection過程中根據不同場景含義會發生變化
  int NodeId = -1;
  // 被該節點使用的SDValue(即輸入操作數)的鏈表, 根據參數順序排列
  SDUse *OperandList = nullptr;
  // 節點輸出值的類型, SDNode允許多個輸出所以指向的是個數組
  const EVT *ValueList;
  SDUse *UseList = nullptr; // 引用該節點的鏈表
  unsigned short NumOperands = 0; // 輸入操作數個數
  unsigned short NumValues; // 輸出值個數
public:SelectionDAGISel::SelectBasicBlock
  uint16_t PersistentId; // 節點唯一且不變的id, 用於debug打印
};

// 每個SDUse表示對一個SDNode的引用.
class SDUse {
  SDValue Val; // 該SDUse實際引用的SDValue
  SDNode *User = nullptr; // 指向引用該SDNode的節點(引用者)
  SDUse **Prev = nullptr; // 雙向鏈表記錄所有引用該SDNode的SDUse
  SDUse *Next = nullptr; // 雙向鏈表記錄所有引用該SDNode的SDUse
};

來看一個簡單的例子, t4是一個add操作, 它接受一個寄存器拷貝t2及常量t3.

Initial selection DAG: %bb.0 'test:entry'
SelectionDAG has 8 nodes:
  t0: ch = EntryToken
      t2: i32,ch = CopyFromReg t0, Register:i32 %0
    t4: i32 = add nsw t2, Constant:i32<1>
  t6: ch,glue = CopyToReg t0, Register:i32 $x10, t4
  t7: ch = RISCVISD::RET_FLAG t6, Register:i32 $x10, t6:1 

其數據框圖如下:

初始化SelectionDAG

LLVM使用SelectionDAGBuilder類來初始化SelectionDAG, SelectionDAGBuilder是以basic block為單位構建的. 這是基於對性能與復雜度的權衡, 在后面我們會看到由於SelectionDAG無法獲取跨basic block的信息, 丟失了生成復雜指令的機會(另一個導致無法獲取最優指令選擇的因素是貪婪的樹匹配方式只能保證局部最優).
在SelectionDAGISel::SelectAllBasicBlocks()中會對每個basic block調用SelectionDAGISel::SelectBasicBlock(), 后者會順序的對每條指令調用SelectionDAGBuilder::visit()接口將IR節點轉換為DAG節點, 轉換完成后將DAG的root傳遞給SelectionDAGISel的CurDAG. 代碼如下:

void SelectionDAGISel::SelectBasicBlock(BasicBlock::const_iterator Begin,
                                        BasicBlock::const_iterator End, 
                                        bool &HadTailCall) {
  // Allow creating illegal types during DAG building for the basic block.
  CurDAG->NewNodesMustHaveLegalTypes = false;

  // Lower the instructions. If a call is emitted as a tail call, cease emitting
  // nodes for this block.
  for (BasicBlock::const_iterator I = Begin; I != End && !SDB->HasTailCall; ++I) {
    if (!ElidedArgCopyInstrs.count(&*I))
      SDB->visit(*I);
  }

  // Make sure the root of the DAG is up-to-date.
  CurDAG->setRoot(SDB->getControlRoot());
  HadTailCall = SDB->HasTailCall;
  SDB->resolveOrClearDbgInfo();
  SDB->clear();

  // Final step, emit the lowered DAG as machine code.
  CodeGenAndEmitDAG();
}

void SelectionDAGBuilder::visit(const Instruction &I) {
  // Set up outgoing PHI node register values before emitting the terminator.
  if (I.isTerminator()) {
    HandlePHINodesInSuccessorBlocks(I.getParent());
  }

  // Increase the SDNodeOrder if dealing with a non-debug instruction.
  if (!isa<DbgInfoIntrinsic>(I))
    ++SDNodeOrder;

  CurInst = &I;

  visit(I.getOpcode(), I);

  if (auto *FPMO = dyn_cast<FPMathOperator>(&I)) {
    // Propagate the fast-math-flags of this IR instruction to the DAG node that
    // maps to this instruction.
    // TODO: We could handle all flags (nsw, etc) here.
    // TODO: If an IR instruction maps to >1 node, only the final node will have
    //       flags set.
    if (SDNode *Node = getNodeForIRValue(&I)) {
      SDNodeFlags IncomingFlags;
      IncomingFlags.copyFMF(*FPMO);
      if (!Node->getFlags().isDefined())
        Node->setFlags(IncomingFlags);
      else  
        Node->intersectFlagsWith(IncomingFlags);
    }
  }

  if (!I.isTerminator() && !HasTailCall &&
      !isStatepoint(&I)) // statepoints handle their exp![](https://img2020.cnblogs.com/blog/1335902/202005/1335902-20200505211443900-1675976763.jpg)
orts internally
    CopyToExportRegsIfNeeded(&I);

  CurInst = nullptr;
}

void SelectionDAGBuilder::visit(unsigned Opcode, const User &I) {
  // Note: this doesn't use InstVisitor, because it has to work with
  // ConstantExpr's in addition to instructions.
  switch (Opcode) {
  default: llvm_unreachable("Unknown instruction type encountered!");
    // Build the switch statement using the Instruction.def file.
#define HANDLE_INST(NUM, OPCODE, CLASS) \
    case Instruction::OPCODE: visit##OPCODE((const ./llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cppCLASS&)I); break;
#include "llvm/IR/Instruction.def"
  }
}

注意到visit()對每個IR節點都會調用visit##OPCODE(), 其中OPCODE為節點的Opcode. 我們以Add為例, visitAdd()會拆解Add操作的操作數, 然后調用SelectionDAG::getNode()創建一個SDNode.

void visitAdd(const User &I)  { visitBinary(I, ISD::ADD); }

void SelectionDAGBuilder::visitBinary(const User &I, unsigned Opcode) {
  SDNodeFlags Flags;
  if (auto *OFBinOp = dyn_cast<OverflowingBinaryOperator>(&I)) {
    Flags.setNoSignedWrap(OFBinOp->hasNoSignedWrap());
    Flags.setNoUnsignedWrap(OFBinOp->hasNoUnsignedWrap());
  }
  if (auto *ExactOp = dyn_cast<PossiblyExactOperator>(&I)) {
    Flags.setExact(ExactOp->isExact());
  }
  if (isVectorReductionOp(&I)) {
    Flags.setVectorReduction(true);
    LLVM_DEBUG(dbgs() << "Detected a reduction operation:" << I << "\n");
  }

  SDValue Op1 = getValue(I.getOperand(0));
  SDValue Op2 = getValue(I.getOperand(1));
  SDValue BinNodeValue = DAG.getNode(Opcode, getCurSDLoc(), Op1.getValueType(),
                                     Op1, Op2, Flags);
  setValue(&I, BinNodeValue);
}

SDValue SelectionDAG::getNode(unsigned Opcode, const SDLoc &DL, EVT VT) {
  FoldingSetNodeID ID;
  AddNodeIDNode(ID, Opcode, getVTList(VT), None);
  void *IP = nullptr;
  if (SDNode *E = FindNodeOrInsertPos(ID, DL, IP))
    return SDValue(E, 0);
  
  auto *N = newSDNode<SDNode>(Opcode, DL.getIROrder(), DL.getDebugLoc(),
                              getVTList(VT));
  CSEMap.InsertNode(N, IP);

  InsertNode(N);
  SDValue V = SDValue(N, 0);
  NewSDValueDbgMsg(V, "Creating new node: ", this);
  return V;
}

其它的IR節點也類似以上的方式lowering, 不再贅述. 這里我們再看兩種特殊IR的lowering, 它們分別會涉及架構相關的lowering與跨basic block的信息傳遞.
第一種是Call.

void SelectionDAGBuilder::visitCall(const CallInst &I) {
  ......

  SDValue Callee = getValue(I.getCalledValue());

  if (I.countOperandBundles./llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cppOfType(LLVMContext::OB_deopt))
    LowerCallSiteWithDeoptBundle(&I, Callee, nullptr);
  else
    // Check if we can potentially perform a tail call. More detailed checking
    // is be done within LowerCallTo, after more information about the call is
    // known.
    LowerCallTo(&I, Callee, I.isTailCall());
}

void SelectionDAGBuilder::LowerCallTo(ImmutableCallSite CS, SDValue Callee,
                                      bool isTailCall,
                                      const BasicBlock *EHPadBB) {
  ......

  TargetLowering::CallLoweringInfo CLI(DAG);
  CLI.setDebugLoc(getCurSDLoc())
      .setChain(getRoot())
      .setCallee(RetTy, FTy, Callee, std::move(Args), CS)
      .setTailCall(isTailCall)SelectionDAGISel::SelectAllBasicBlocks
      .setConvergent(CS.isConvergent());
  std::pair<SDValue, SDValue> Result = lowerInvokable(CLI, EHPadBB);

  ......
}

std::pair<SDValue, SDValue>
SelectionDAGBuilder::lowerInvokable(TargetLowering::CallLoweringInfo &CLI,
                                    const BasicBlock *EHPadBB) {
  ......

  const TargetLowering &TLI = DAG.getTargetLoweringInfo();
  std::pair<SDValue, SDValue> Result = TLI.LowerCallTo(CLI);

  ......

  return Result;
}

這里省略了無關的代碼, 注意到在lowerInvokable()會調用TargetLoweringInfo::LowerCallTo(), 即對於call/return等不同架構可能實現不一致的IR, 會通過TargetLoweringInfo的回調單獨處理.
第二種是PHI.

void SelectionDAGBuilder::visitPHI(const PHINode &) {
  llvm_unreachable("SelectionDAGBuilder shouldn't visit PHI nodes!");
}

注意到調用visitPHI()函數會直接報錯, 那么

  1. 為什么PHI節點不應該被lowering?
  2. 如果PHI節點沒有被lowering那么引用PHI節點的節點的operand該設置成什么呢?

對於第一點, 首先PHI是為滿足程序SSA格式而創建的虛擬節點, 在DAG中我們無需保證SSA格式(由於調用約定的存在, 實際也無法保證), 因此lowering PHI是沒有意義的.
對於第二點, 在SelectionDAGISel::SelectAllBasicBlocks()調用SelectionDAGISel::SelectBasicBlock()前會首先遍歷每個basic block, 為每個basic block計算register liveout, 並保存到FunctionLoweringInfo.ValueMap中. 當引用PHI的節點在獲取其Value時會首先檢查這個map, 如果找到就調用SelectionDAGBuilder::getCopyFromRegs()創建一個從虛擬寄存器的拷貝節點.

void SelectionDAGISel::SelectAllBasicBlocks(const Function &Fn) {
  ......

  for (const BasicBlock *LLVMBB : RPOT) {
    if (OptLevel != CodeGenOpt::None) {
      bool AllPredsVisited = true;
      for (const_pred_iterator PI = pred_begin(LLVMBB), PE = pred_end(LLVMBB);
           PI != PE; ++PI) {
        if (!FuncInfo->VisitedBBs.count(*PI)) {
          AllPredsVisited = false;
          break;
        }
      }

      if (AllPredsVisited) {
        for (const PHINode &PN : LLVMBB->phis())
          FuncInfo->ComputePHILiveOutRegInfo(&PN);
      } else {
        for (const PHINode &PN : LLVMBB->phis())
          FuncInfo->InvalidatePHILiveOutRegInfo(&PN);
      }

      FuncInfo->VisitedBBs.insert(LLVMBB);
    }
  }

  ......SelectionDAGBuilder::getCopyFromRegs
}

void FunctionLoweringInfo::ComputePHILiveOutRegInfo(const PHINode *PN) {
  Type *Ty = PN->getType();
  if (!Ty->isIntegerTy() || Ty->isVectorTy())
    return;

  SmallVector<EVT, 1> ValueVTs;
  ComputeValueVTs(*TLI, MF->getDataLayout(), Ty, ValueVTs);
  assert(ValueVTs.size() == 1 &&
         "PHIs with non-vector integer types should have a single VT.");
  EVT IntVT = ValueVTs[0];

  if (TLI->getNumRegisters(PN->getContext(), IntVT) != 1)
    return;
  IntVT = TLI->getTypeToTransformTo(PN->getContext(), IntVT);
  unsigned BitWidth = IntVT.getSizeInBits();

  unsigned DestReg = ValueMap[PN];
  if (!Register::isVirtualRegister(DestReg))
    return;
  LiveOutRegInfo.grow(DestReg);
  LiveOutInfo &DestLOI = LiveOutRegInfo[DestReg];

  Value *V = PN->getIncomingValue(0);
  if (isa<UndefValue>(V) || isa<ConstantExpr>(V)) {
    DestLOI.NumSignBits = 1;
    DestLOI.Known = KnownBits(BitWidth);
    return;
  }

  if (ConstantInt *CI = dyn_cast<ConstantInt>(V)) {
    APInt Val = CI->getValue().zextOrTrunc(BitWidth);
    DestLOI.NumSignBits = Val.getNumSignBits();
    DestLOI.Known.Zero = ~Val;
    DestLOI.Known.One = Val;
  } else {
    assert(ValueMap.count(V) && "V should have been placed in ValueMap when its"
                                "CopyToReg node was created.");
    unsigned SrcReg = ValueMap[V];
    if (!Register::isVirtualRegister(SrcReg)) {
      DestLOI.IsValid = false;
      return;
    }
    const LiveOutInfo *SrcLOI = GetLiveOutRegInfo(SrcReg, BitWidth);
    if (!SrcLOI) {
      DestLOI.IsValid = false;
      return;
    }
    DestLOI = *SrcLOI;
  }

  assert(DestLOI.Known.Zero.getBitWidth() == BitWidth &&
         DestLOI.Known.One.getBitWidth() == BitWidth &&
         "Masks should have the same bit width as the type.");

  for (unsigned i = 1, e = PN->getNumIncomingValues(); i != e; ++i) {
    Value *V = PN->getIncomingValue(i);
    if (isa<UndefValue>(V) || isa<ConstantExpr>(V)) {
      DestLOI.NumSignBits = 1;
      DestLOI.Known = KnownBits(BitWidth);
      return;
    }

    if (ConstantInt *CI = dyn_cast<ConstantInt>(V)) {
      APInt Val = CI->getValue().zextOrTrunc(BitWidth);
      DestLOI.NumSignBits = std::min(DestLOI.NumSignBits, Val.getNumSignBits());
      DestLOI.Known.Zero &= ~Val;
      DestLOI.Known.One &= Val;
      continue;
    }

    assert(ValueMap.count(V) && "V should have been placed in ValueMap when "
                                "its CopyToReg node was created.");
    unsigned SrcReg = ValueMap[V];
    if (!Register::isVirtualRegister(SrcReg)) {
      DestLOI.IsValid = false;
      return;
    }
    const LiveOutInfo *SrcLOI = GetLiveOutRegInfo(SrcReg, BitWidth);
    if (!SrcLOI) {
      DestLOI.IsValid = false;
      return;
    }
    DestLOI.NumSignBits = std::min(DestLOI.NumSignBits, SrcLOI->NumSignBits);
    DestLOI.Known.Zero &= SrcLOI->Known.Zero;
    DestLOI.Known.One &= SrcLOI->Known.One;
  }
}

SDValue SelectionDAGBuilder::getValue(const Value *V) {
  // If we already have an SDValue for this value, use it. It's important
  // to do this first, so that we don't create a CopyFromReg if we already
  // have a regular SDValue.
  SDValue &N = NodeMap[V];
  if (N.getNode()) return N;

  // If there's a virtual register allocated and initialized for this
  // value, use it.
  if (SDValue copyFromReg = getCopyFromRegs(V, V->getType()))
    return copyFromReg;

  // Otherwise create a new SDValue and remember it.
  SDValue Val = getValueImpl(V);
  NodeMap[V] = Val;
  resolveDanglingDebugInfo(V, Val);
  return Val;
}

SDValue SelectionDAGBuilder::getCopyFromRegs(const Value *V, Type *Ty) {
  DenseMap<const Value *, unsigned>::iterator It = FuncInfo.ValueMap.find(V);
  SDValue Result;

  if (It != FuncInfo.ValueMap.end()) {
    unsigned InReg = It->second;

    RegsForValue RFV(*DAG.getContext(), DAG.getTargetLoweringInfo(),
                     DAG.getDataLayout(), InReg, Ty,
                     None); // This is not an ABI copy.
    SDValue Chain = DAG.getEntryNode();
    Result = RFV.getCopyFromRegs(DAG, FuncInfo, getCurSDLoc(), Chain, nullptr,
                                 V);SelectionDAGBuilder::getCopyFromRegs
    resolveDanglingDebugInfo(V, Result);
  }

  return Result;
}

關於lowering的過程就介紹到這里, 總結:

  1. lowering的目的是將IR的數據結構轉換為DAG, 方便之后指令選擇做模式匹配. lowering的過程就是DAG建立的過程.
  2. lowering分為架構無關與架構相關的部分, 對於架構相關的部分通過TargetLoweringInfo實現.
  3. lowering是以baisic block為單位的, 跨basic block的信息通過FunctionLoweringInfo實現.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM