使用 TableGen 表示的 DXIL 操作规范¶
简介¶
DirectXShaderCompiler 除其他信息外,还在 hctdb.py 中封装了各种 DXIL 操作。DXIL 操作以以下 两种方式之一表示
使用 LLVM 指令。
使用 LLVM 外部函数。这些在 LLVM IR 中表示如下
“标准” LLVM 内联函数(例如,
llvm.sin.*)和HLSL 内联函数(在
llvm/include/llvm/IR/IntrinsicsDirectX.td中定义为 LLVM 内联函数,例如,llvm.dx.*)
在本文档中,这些统称为 LLVM 内联函数。
以下是 DXIL Ops 属性的完整列表,以及在 hctdb.py 中使用的相应字段名称。DXIL Op 由一组关联的属性表示。这些属性在 DXIL 后端 Pass 以及其他使用场景(如验证、DXIL 读取器等)中使用。
DXIL 后端 Pass 中使用的属性
操作名称 (
dxil_op)记录操作的字符串 (
doc) - 这不是严格必需的,但包含在内是为了提高可读性和操作文档的可读性。映射到操作的通用或 HLSL 特定的内联函数 (
llvm_name)。唯一整数 ID (
dxil_opid)操作类,表示操作的名称和函数签名 (
dxil_class)。此字符串是 DXIL Op 函数名称的组成部分,并以格式dx.op.<class-name>.<overload-type>构建。根据与驱动程序的现有约定,每个 DXIL Op 调用目标函数名称都必须符合此格式。操作的有效重载类型列表 (
oload_types)。支持操作所需的 DXIL 版本。
所需的最低着色器模型 (
shader_model)。链接器转换所需的最低着色器模型 (
shader_model_translated)适用于的着色器阶段列表 (
shader_stages),如果适用于所有阶段,则为空。操作的内存访问属性 (
fn_attr)。指示操作是否为以下类型的布尔属性
某种导数 (
is_derivative)需要梯度计算 (
is_gradient)是采样器反馈 (
is_feedback)需要在波内、跨通道功能中使用 (
is_wave)要求其所有输入在波中都是统一的 (
requires_uniform_inputs)。是屏障操作 (
is_barrier)。
动机¶
DXIL 后端 Pass 依赖于 DXIL 操作的各种属性。例如,DXILOpLowering Pass 将需要诸如 LLVM 内联函数要降低到的 DXIL 操作的信息,以及有效的重载和参数类型等。TableGen 文件 - llvm/lib/Target/DirectX/DXIL.td - 用于通过指定上面列出的属性来表示 DXIL 操作。DXIL.td 旨在成为 DXIL 操作的单一参考源,主要用于在 llvm-project 仓库中实现 DXIL 后端中的 Pass - 类似于 DirectXShadeCompiler 仓库的 hctdb.py。但是,当前设计不打算封装 hctdb.py 中存在的各种验证规则,但这些规则与 DXIL 操作无关。它需要具有丰富的表示能力,TableGen 后端(如 DXILEmitter)可以依赖这些能力。此外,DXIL Op 规范应该易于阅读和理解。
本文档提供了将 DXIL Ops 规范设计为 TableGen 类 DXILOp 的方案,方法是指定上面确定的属性。
DXIL 操作规范¶
DXIL 操作使用 TableGen 类 DXILOp 表示。DXIL 操作属性指定为 DXILOp 类的字段,如下所述。
每个 DXIL 操作都表示为 TableGen 记录。每个记录的名称表示操作名称。
操作的文档字符串。
映射到操作的 LLVM 内联函数表示为在 Intrinsics.td 中定义的
Intrinsic。唯一操作 ID 由整数表示。
DXIL 操作类表示如下
// Abstraction of DXIL Operation class. class DXILOpClass;
具体的操作记录,例如
unary,通过从DXILOpClass继承来定义。定义了一组类型名称,用于表示返回类型和参数类型,所有类型名称都从
DXILOpParamType继承。这些类型名称表示简单类型(如int32Ty)、DXIL 类型(如dx.types.Handle)以及特殊的overloadTy类型,该类型可以是Overloads允许的任何类型,如下所述。操作返回类型表示为
DXILOpParamType,参数表示为相同的列表。没有返回值的操作应将VoidTy指定为其返回类型。基于 DXIL 版本的有效操作重载类型指定为
Overloads记录的列表。Overloads类的表示将在后面的章节中描述。基于 DXIL 版本的有效着色器阶段指定为
Stages记录的列表。Stages类的表示将在后面的章节中描述。DXIL 操作的各种属性表示为
Attributes类记录的list。Attributes类的表示将在后面的章节中描述。
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 版本用于指定各种依赖于版本的操作属性,而不是着色器模型版本。
封装 Major 和 Minor 版本号的 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>;
}
着色器阶段规范¶
各种着色器阶段,如 compute、pixel、vertex 等,表示如下
// Shader stages
class DXILShaderStage;
def compute : DXILShaderStage;
def pixel : DXILShaderStage;
def vertex : DXILShaderStage;
...
着色器属性规范¶
各种操作内存访问和布尔属性,如 ReadNone、IsWave 等,表示如下
class DXILAttribute;
def ReadOnly : DXILOpAttributes;
def ReadNone : DXILOpAttributes;
def IsWave : DXILOpAttributes;
...
版本化属性规范¶
DXIL 操作属性,如有效的重载类型、着色器阶段和属性,都依赖于 DXIL 版本。这些属性表示为版本化属性的列表。
重载类型规范¶
class DXILOp 的 overloads 字段用于表示基于 DXIL 版本的有效操作重载,表示为以下类的记录列表
class Overloads<Version minver, list<DXILOpParamType> ols> {
Version dxil_version = minver;
list<DXILOpParamType> overload_types = ols;
}
以下是 DXIL1_0 和 DXIL1_2 的有效重载类型的规范示例。
overloads = [
Overloads<DXIL1_0, [halfTy, floatTy]>,
Overloads<DXIL1_2, [halfTy, floatTy, doubleTy]>
];
空列表表示操作不支持任何重载类型。
阶段规范¶
class DXILOp 的 stages 字段用于表示基于 DXIL 版本的有效操作阶段,表示为以下类的记录列表
class Stages<Version minver, list<DXILShaderStage> sts> {
Version dxil_version = minver;
list<DXILShaderStage> shader_stages = sts;
}
以下是 DXIL1_0、DXIL1_2、DXIL1_4 和 DXIL1_6 的有效阶段的规范示例。
stages = [
Stages<DXIL1_0, [compute, pixel]>,
Stages<DXIL1_2, [compute, pixel, mesh]>,
Stages<DXIL1_4, [all_stages]>,
Stages<DXIL1_6, [removed]>
];
除了标准着色器阶段之外,还定义了以下两个伪阶段记录。
all_stages表示该操作在指定的 DXIL 版本及更高版本中的所有阶段均有效。removed表示在指定的 DXIL 版本及更高版本中取消对该操作的支持。
需要指定非空的支持阶段列表。如果某个操作在所有 DXIL 版本和所有阶段都受支持,则需要将其指定为
stages = [Stages<DXIL1_0, [all_stages]>];
属性规范¶
class DXILOp 的 attributes 字段用于表示基于 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]>
];
它指定重载类型 halfTy 和 floatTy 对于 DXIL 版本 1.0 及更高版本有效。它还指定在 DXIL 版本 1.2 及更高版本中额外支持 doubleTy。
这提供了独立于列表中其他版本化规范指定属性的灵活性。
DXIL 操作规范示例¶
以下示例说明了一些 DXIL Ops 的规范。
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 Ops 的可读且可维护的 TableGen 规范的设计,旨在作为 TableGen 后端(如 DXILEmitter)的单一参考源,这些后端生成 DXIL 后端 Pass 中使用的 C++ 表示形式。
