如何使用指令映射

简介

本文档包含有关为目标添加指令映射支持的信息。此功能背后的动机源于在各种优化过程中在不同指令格式之间切换的需求。一种方法可以使用 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(真)和 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)",
            []>;

在此步骤中,我们修改这些指令以包含关系模型 <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 值设置为关系模型中未使用的内容,则不会在关系表中为其分配列。