如何使用指令映射

簡介

本文檔包含關於為目標新增指令映射支援的資訊。此功能背後的動機來自於在各種最佳化期間切換不同指令格式的需求。一種方法可能是使用 switch case,其中列出所有指令以及它們可以轉換成的格式。然而,由於硬編碼的指令名稱,它具有龐大的維護成本。此外,每當在 .td 檔案中新增一個新指令時,所有相關的 switch case 都應相應地修改。相反地,相同的功能可以使用 TableGen 以及來自 .td 檔案的一些支援來實現,且維護成本僅為一小部分。

InstrMapping 類別概述

TableGen 使用關係模型來將指令彼此映射。這些模型使用 InstrMapping 類別作為基礎來描述。每個模型都設定 InstrMapping 類別的各個欄位,以便它們可以使用該模型唯一地描述所有指令。TableGen 解析所有關係模型,並使用這些資訊來建構將指令彼此關聯的關係表。這些表會連同查詢它們的函式一起發射到 XXXInstrInfo.inc 檔案中。以下是在 Target.td 檔案中定義的 InstrMapping 類別的定義

class InstrMapping {
  // Used to reduce search space only to the instructions using this
  // relation model.
  string FilterClass;

  // List of fields/attributes that should be same for all the instructions in
  // a row of the relation table. Think of this as a set of properties shared
  // by all the instructions related by this relationship.
  list<string> RowFields = [];

  // List of fields/attributes that are same for all the instructions
  // in a column of the relation table.
  list<string> ColFields = [];

  // Values for the fields/attributes listed in 'ColFields' corresponding to
  // the key instruction. This is the instruction that will be transformed
  // using this relation model.
  list<string> KeyCol = [];

  // List of values for the fields/attributes listed in 'ColFields', one for
  // each column in the relation table. These are the instructions a key
  // instruction will be transformed into.
  list<list<string> > ValueCols = [];
}

範例

假設我們想要有一個函式 int getPredOpcode(uint16_t Opcode, enum PredSense inPredSense),它接受一個非述詞指令,並根據一些輸入標誌 inPredSense 傳回其述詞真或假形式。此過程的第一步是定義一個關係模型,透過將適當的值指派給 InstrMapping 欄位,將述詞指令與其非述詞形式相關聯。對於這種關係,非述詞指令被視為關鍵指令,因為它們是用於查詢介面函式的指令。

def getPredOpcode : InstrMapping {
  // Choose a FilterClass that is used as a base class for all the
  // instructions modeling this relationship. This is done to reduce the
  // search space only to these set of instructions.
  let FilterClass = "PredRel";

  // Instructions with same values for all the fields in RowFields form a
  // row in the resulting relation table.
  // For example, if we want to relate 'ADD' (non-predicated) with 'Add_pt'
  // (predicated true) and 'Add_pf' (predicated false), then all 3
  // instructions need to have same value for BaseOpcode field. It can be any
  // unique value (Ex: XYZ) and should not be shared with any other
  // instruction not related to 'add'.
  let RowFields = ["BaseOpcode"];

  // List of attributes that can be used to define key and column instructions
  // for a relation. Key instruction is passed as an argument
  // to the function used for querying relation tables. Column instructions
  // are the instructions they (key) can transform into.
  //
  // Here, we choose 'PredSense' as ColFields since this is the unique
  // attribute of the key (non-predicated) and column (true/false)
  // instructions involved in this relationship model.
  let ColFields = ["PredSense"];

  // The key column contains non-predicated instructions.
  let KeyCol = ["none"];

  // Two value columns - first column contains instructions with
  // PredSense=true while second column has instructions with PredSense=false.
  let ValueCols = [["true"], ["false"]];
}

TableGen 使用上述關係模型來發射關係表,該表將非述詞指令與其述詞形式映射。它還輸出介面函式 int getPredOpcode(uint16_t Opcode, enum PredSense inPredSense) 以查詢該表。在這裡,函式 getPredOpcode 接受兩個引數:目前指令的運算碼和所需指令的 PredSense,如果關係表中找到,則傳回指令的述詞形式。為了將指令新增到關係表中,它需要在其定義中包含相關資訊。例如,考慮以下 ADD、ADD_pt (true) 和 ADD_pf (false) 指令的目前定義

def ADD : ALU32_rr<(outs IntRegs:$dst), (ins IntRegs:$a, IntRegs:$b),
            "$dst = add($a, $b)",
            [(set (i32 IntRegs:$dst), (add (i32 IntRegs:$a),
                                           (i32 IntRegs:$b)))]>;

def ADD_Pt : ALU32_rr<(outs IntRegs:$dst),
                       (ins PredRegs:$p, IntRegs:$a, IntRegs:$b),
            "if ($p) $dst = add($a, $b)",
            []>;

def ADD_Pf : ALU32_rr<(outs IntRegs:$dst),
                       (ins PredRegs:$p, IntRegs:$a, IntRegs:$b),
            "if (!$p) $dst = add($a, $b)",
            []>;

在此步驟中,我們修改這些指令以包含關係模型 <tt>getPredOpcode</tt> 所需的資訊,以便它們可以相關聯。

def ADD : PredRel, ALU32_rr<(outs IntRegs:$dst), (ins IntRegs:$a, IntRegs:$b),
            "$dst = add($a, $b)",
            [(set (i32 IntRegs:$dst), (add (i32 IntRegs:$a),
                                           (i32 IntRegs:$b)))]> {
  let BaseOpcode = "ADD";
  let PredSense = "none";
}

def ADD_Pt : PredRel, ALU32_rr<(outs IntRegs:$dst),
                       (ins PredRegs:$p, IntRegs:$a, IntRegs:$b),
            "if ($p) $dst = add($a, $b)",
            []> {
  let BaseOpcode = "ADD";
  let PredSense = "true";
}

def ADD_Pf : PredRel, ALU32_rr<(outs IntRegs:$dst),
                       (ins PredRegs:$p, IntRegs:$a, IntRegs:$b),
            "if (!$p) $dst = add($a, $b)",
            []> {
  let BaseOpcode = "ADD";
  let PredSense = "false";
}

請注意,以上所有指令都使用 PredRel 作為基底類別。這非常重要,因為 TableGen 使用它作為過濾器,為 getPredOpcode 模型選擇指令。任何未從 PredRel 衍生的指令都會從分析中排除。BaseOpcode 是另一個重要的欄位。由於它被選為模型的 RowFields,因此所有 3 個指令都需要具有相同的值才能相關聯。接下來,PredSense 用於透過將其值與 KeyColValueCols 進行比較來決定它們的欄位置。如果指令將其 PredSense 值設定為關係模型中未使用的值,則不會在關係表中為其指派欄位。