使用 TableGen 表示法描述 DXIL 操作的规范

简介

DirectXShaderCompiler 封装了各种 DXIL 操作(以及其他信息)在 hctdb.py 中。DXIL 操作以以下两种方式之一表示 两种方式

  1. 使用 LLVM 指令。

  2. 使用 LLVM 外部函数。这些在 LLVM IR 中表示如下

    • “标准”LLVM 内联函数(例如,llvm.sin.*)和

    • HLSL 内联函数(在 llvm/include/llvm/IR/IntrinsicsDirectX.td 中定义为 LLVM 内联函数,例如,llvm.dx.*

    这些在此注释中统称为LLVM 内联函数

以下是 DXIL 操作属性的完整列表,以及在 hctdb.py 中使用的相应字段名称。DXIL 操作由一组关联的属性表示。这些属性在 DXIL 后端传递以及其他使用场景(如验证、DXIL 阅读器等)中被使用。

  1. DXIL 后端传递中使用的属性

    1. 操作名称(dxil_op

    2. 描述操作的字符串(doc) - 这不是严格必要的,但为了操作的可读性和文档而包含。

    3. 映射到操作的通用或 HLSL 特定内联函数(llvm_name)。

    4. 唯一整数 ID(dxil_opid

    5. 表示操作名称和函数签名的操作类(dxil_class)。此字符串是 DXIL 操作函数名称的组成部分,并以 dx.op.<class-name>.<overload-type> 格式构建。根据与驱动程序的现有约定,每个 DXIL 操作调用目标函数名称都必须符合此格式。

    6. 操作的有效重载类型列表(oload_types)。

    7. 支持操作所需的 DXIL 版本。

    8. 所需的最小着色器模型(shader_model)。

    9. 链接器进行转换所需的最小着色器模型(shader_model_translated

    10. 适用的着色器阶段列表(shader_stages),如果适用于所有阶段,则为空。

    11. 操作的内存访问属性(fn_attr)。

    12. 操作的布尔属性,指示它

      • 是否某种导数(is_derivative

      • 是否需要梯度计算(is_gradient

      • 是否为采样器反馈(is_feedback

      • 是否需要波内、跨车道功能(is_wave

      • 是否要求其所有输入在整个波中都是一致的(requires_uniform_inputs)。

      • 是否为屏障操作(is_barrier)。

动机

DXIL 后端传递依赖于 DXIL 操作的各种属性。例如,DXILOpLowering 传递将需要诸如 LLVM 内联函数要降低到的 DXIL 操作的信息,以及有效的重载和参数类型等。TableGen 文件 - llvm/lib/Target/DirectX/DXIL.td - 用于通过指定上面列出的属性来表示 DXIL 操作。DXIL.td 旨在成为 DXIL 操作的单一参考来源,主要用于 llvm-project 存储库中 DXIL 后端的传递实现 - 类比于 DirectXShadeCompiler 存储库中的 hctdb.py。但是,当前设计并不打算封装 hctdb.py 中存在的各种验证规则,但这些规则与 DXIL 操作无关。它需要具有 TableGen 后端(如 DXILEmitter)可以依赖的丰富的表示能力。此外,DXIL 操作规范应该易于阅读和理解。

本说明提供了 DXIL 操作作为 TableGen 类 DXILOp 规范的设计,通过指定上面确定的其属性。

DXIL 操作规范

DXIL 操作使用 TableGen 类 DXILOp 表示。DXIL 操作属性如下所述,指定为 DXILOp 类的字段。

  1. 每个 DXIL 操作都表示为一个 TableGen 记录。每个记录的名称表示操作名称。

  2. 操作的文档字符串。

  3. 映射到操作的 LLVM 内联函数表示为在 Intrinsics.td 中定义的 Intrinsic

  4. 唯一的操作 ID 由整数表示。

  5. DXIL 操作类表示如下

    // Abstraction of DXIL Operation class.
    class DXILOpClass;
    

    具体操作记录,如 unary,通过继承 DXILOpClass 来定义。

  6. 定义了一组表示返回类型和参数类型的类型名称,它们都继承自 DXILOpParamType。这些表示简单的类型,如 int32Ty,DXIL 类型,如 dx.types.Handle,以及一个特殊的 overloadTy,它可以是 Overloads 允许的任何类型,如下所述。

  7. 操作返回类型表示为 DXILOpParamType,参数表示为相同类型的列表。没有返回值的操作应将其返回类型指定为 VoidTy

  8. 基于 DXIL 版本预测的有效操作重载类型指定为 Overloads 记录列表。Overloads 类的表示在后面的章节中描述。

  9. 基于 DXIL 版本预测的有效着色器阶段指定为 Stages 记录列表。Stages 类的表示在后面的章节中描述。

  10. DXIL 操作的各种属性表示为 Attributes 类记录的 listAttributes 类的表示在后面的章节中描述。

DXIL 特定的类型

本文档中使用的类型表示法,即 <size>Ty,对应于 LLVM 类型 llvm_<size>_ty 的 TableGen 记录。除了上面描述的 overloadTy 之外,resRetF32Ty 用于表示资源返回类型,handleTy 用于表示句柄类型。

DXIL 操作的规范

DXIL 操作由以下 TableGen 类表示,该类封装了其上面描述的各种属性的 TableGen 表示。

// Abstraction DXIL Operation
class DXILOp<int opcode, DXILOpClass opclass> {
  // A short description of the operation
  string Doc = "";

  // Opcode of DXIL Operation
  int OpCode = opcode;

  // Class of DXIL Operation.
  DXILOpClass OpClass = opclass;

  // LLVM Intrinsic DXIL Operation maps to
  Intrinsic LLVMIntrinsic = ?;

  // Result type of the op.
  DXILOpParamType result;

  // List of argument types of the op. Default to 0 arguments.
  list<DXILOpParamType> arguments = [];

  // List of valid overload types predicated by DXIL version
  list<Overloads> overloads;

  // List of valid shader stages predicated by DXIL version
 list<Stages> stages;

  // List of valid attributes predicated by DXIL version
  list<Attributes> attributes = [];
}

版本规范

DXIL 版本用于指定各种依赖于版本的运算属性,而不是着色器模型版本。

一个封装了 MajorMinor 版本号的 Version 类定义如下

// Abstract class to represent major and minor version values
class Version<int major, int minor> {
  int Major = major;
  int Minor = minor;
}

有效 DXIL 版本的具体表示定义如下

// Definition of DXIL Version 1.0 - 1.8
foreach i = 0...8 in {
  def DXIL1_#i : Version<1, i>;
}

着色器阶段规范

各种着色器阶段,如 computepixelvertex 等,表示如下

// Shader stages
class DXILShaderStage;

def compute : DXILShaderStage;
def pixel : DXILShaderStage;
def vertex : DXILShaderStage;
...

着色器属性规范

各种操作内存访问和布尔属性,如 ReadNoneIsWave 等,表示如下

class DXILAttribute;

def ReadOnly : DXILOpAttributes;
def ReadNone : DXILOpAttributes;
def IsWave : DXILOpAttributes;
...

版本化属性规范

DXIL 操作属性,如有效重载类型、着色器阶段和属性,都基于 DXIL 版本。这些表示为版本化属性列表。

重载类型规范

overloads 字段的 class DXILOp 用于表示基于 DXIL 版本预测的有效操作重载,作为以下类的记录列表

class Overloads<Version minver, list<DXILOpParamType> ols> {
  Version dxil_version = minver;
  list<DXILOpParamType> overload_types = ols;
}

以下是如何为 DXIL1_0DXIL1_2 指定有效重载类型的示例。

overloads = [
              Overloads<DXIL1_0, [halfTy, floatTy]>,
              Overloads<DXIL1_2, [halfTy, floatTy, doubleTy]>
            ];

空列表表示该操作不支持任何重载类型。

阶段规范

stages 字段在 class DXILOp 中用于表示基于 DXIL 版本的有效操作阶段,以以下类的记录列表表示

class Stages<Version minver, list<DXILShaderStage> sts> {
  Version dxil_version = minver;
  list<DXILShaderStage> shader_stages = sts;
}

以下是 DXIL1_0DXIL1_2DXIL1_4DXIL1_6 的有效阶段规范示例。

stages = [
          Stages<DXIL1_0, [compute, pixel]>,
          Stages<DXIL1_2, [compute, pixel, mesh]>,
          Stages<DXIL1_4, [all_stages]>,
          Stages<DXIL1_6, [removed]>
         ];

除了标准着色器阶段外,还定义了以下两个伪阶段记录。

  1. all_stages 表示该操作对于指定 DXIL 版本及更高版本中的所有阶段均有效。

  2. removed 表示在指定 DXIL 版本及更高版本中删除了对该操作的支持。

需要指定一个非空的受支持阶段列表。如果某个操作在所有 DXIL 版本和所有阶段中都受支持,则需要将其指定为

stages = [Stages<DXIL1_0, [all_stages]>];

属性规范

attributes 字段在 class DXILOp 中用于表示基于 DXIL 版本的有效操作属性,以以下类的记录列表表示

class Attributes<MinVersion minver, list<DXILAttribute> attrs> {
  MinVersion dxil_version = ver;
  list<DXILAttribute> attributes = attrs;
}

以下是 DXIL1_0 的有效属性规范示例。

attributes = [Attributes<DXIL1_0, [ReadNone]];

一个空的 attributes 列表表示没有操作属性。

多版本属性的解释

每个版本化的属性都声明指定的重载类型、阶段或属性记录对于预测的 DXIL 版本有效。只有对应于最新最小 DXIL 版本的属性适用。请注意,如上例所示,在以后的 DXIL 版本中仍然有效的任何重载类型、阶段或属性都需要完整指定。例如,考虑以下有效重载类型的规范

overloads = [
             Overloads<DXIL1_0, [halfTy, floatTy]>,
             Overloads<DXIL1_2, [halfTy, floatTy, doubleTy]>
            ];

它指定重载类型 halfTyfloatTy 对于 DXIL 版本 1.0 及更高版本有效。它还指定 doubleTy 在 DXIL 版本 1.2 及更高版本中也得到支持。

这提供了灵活性,可以在列表中独立于其他版本化规范来指定属性。

DXIL 操作规范示例

以下示例说明了一些 DXIL 操作的规范。

Sin 操作 - 在所有 DXIL 版本和所有阶段中都有效的操作,并且具有基于 DXIL 版本的有效重载类型。

def Sin : DXILOp<13, unary> {
  let Doc = "Returns sine(theta) for theta in radians.";
  let LLVMIntrinsic = int_sin;
  let result = overloadTy;
  let arguments = [overloadTy];
  let overloads = [Overloads<DXIL1_0, [halfTy, floatTy]>];
  let stages = [Stages<DXIL1_0, [all_stages]>];
  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}

FlattenedThreadIdInGroup - 一个没有参数、没有重载类型并且有效阶段和属性由 DXIL 版本预测的操作。

def FlattenedThreadIdInGroup :  DXILOp<96, flattenedThreadIdInGroup> {
 let Doc = "Provides a flattened index for a given thread within a given "
           "group (SV_GroupIndex)";
 let LLVMIntrinsic = int_dx_flattened_thread_id_in_group;
 let result = i32Ty;
 let stages = [Stages<DXIL1_0, [compute, mesh, amplification, node]>];
 let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}

RawBufferStore - 一个返回类型为 void 的操作,有效重载类型由 DXIL 版本预测,并且在所有 DXIL 版本和阶段中都有效。

def RawBufferStore : DXILOp<140, rawBufferStore> {
  let Doc = "Writes to a RWByteAddressBuffer or RWStructuredBuffer.";
  let result = voidTy;
  let arguments = [dxil_resource_ty, i32Ty, i32Ty, overloadTy,
                   overloadTy, overloadTy, overloadTy, i8Ty, i32Ty];
  let overloads = [
                   Overloads<DXIL1_2, [halfTy, floatTy, i16Ty, i32Ty]>,
                   Overloads<DXIL1_3>,[halfTy, floatTy, doubleTy,
                                                i16Ty, i32Ty, i64Ty]>
                  ];
   let stages = [Stages<DXIL1_2, all_stages>];
   let attributes = [Attributes<DXIL1_0, [ReadOnly]>];
}

DerivCoarseX - 一个没有重载类型和阶段的操作,由 DXIL 版本预测。

def DerivCoarseX : DXILOp<83, unary> {
 let doc = "Computes the rate of change per stamp in x direction.";
 let LLVMIntrinsic = int_dx_ddx;
 let result = overloadTy;
 let arguments = [overloadTy];
 let stages = [
                Stages<DXIL1_0, [library, pixel]>,
                Stages<DXIL1_6, [library, pixel, amplification, compute, mesh]>
              ];
 let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}

CreateHandle - 一个没有重载类型、没有关联的 LLVMIntrinsic 并且阶段由 DXIL 版本预测的操作。

def CreateHandle : DXILOp<57, createHandle> {
  let doc = "Creates the handle to a resource";
  let result = i32Ty;
  let arguments = [i8Ty, i32Ty, i32Ty, i1Ty];
  let stages = [
                Stages<DXIL1_0, [all_stages]>,
                Stages<DXIL1_6, [removed]
               ];
  let attributes = [Attributes<DXIL1_0, [ReadOnly]>];
}

Sample - 一个具有有效重载类型、阶段和属性的操作,由 DXIL 版本预测。

def Sample : DXILOp<60, sample> {
  let Doc = "Samples a texture";
  let LLVMIntrinsic = int_dx_sample;
  let result = resRetF32Ty;
  let arguments = [handleTy, handleTy, floatTy, floatTy, floatTy, floatTy,
                   i32Ty, i32Ty, i32Ty, floatTy];
  let overloads = [Overloads<DXIL1_0, [halfTy, floatTy, i16Ty, i32Ty]>];
  let stages = [
                Stages<DXIL1_0, [library, pixel]>,
                Stages<DXIL1_6, [library, pixel, amplification, compute, mesh]>
               ];
  let attributes = [Attributes<DXIL1_0, [ReadOnly]>];
}

总结

本说明概述了在 DXIL.td 中可读且易于维护的 DXIL 操作 TableGen 规范的设计,旨在作为 TableGen 后端(例如 DXILEmitter)的单一参考源,这些后端生成 DXIL 后端传递中使用的 C++ 表示。