如何使用指令映射

簡介

本文檔包含有關為目標添加指令映射支援的信息。此功能背後的動機來自於在各種優化過程中需要在不同指令格式之間切換的需求。一種方法是使用 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 返回其預測的 true 或 false 形式。此過程的第一步是通過為 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(真)和 ADD_pf(假)指令的當前定義

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)",
            []>;

在這個步驟中,我們修改這些指令以包含關係模型 getPredOpcode 所需的信息,以便它們可以關聯。

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 值設置為關係模型中未使用的值,則不會在關係表中分配列。