TableGen表現を使用したDXILオペレーションの仕様¶
はじめに¶
DirectXShaderCompilerは、特に、hctdb.pyに様々なDXILオペレーションをカプセル化しています。DXILオペレーションは、以下の2つの方法のいずれかで表現されます。
LLVM命令を使用する。
LLVM外部関数を使用する。これらはLLVM IRで次のように表現されます。
「標準」LLVM組み込み関数 (例:
llvm.sin.*
) およびHLSL組み込み関数 (
llvm/include/llvm/IR/IntrinsicsDirectX.td
でLLVM組み込み関数として定義されています。例:llvm.dx.*
)
これらはまとめてこのノートではLLVM組み込み関数と呼びます。
以下は、hctdb.py
で使用されている対応するフィールド名とともに、DXIL Opのプロパティの完全なリストです。DXIL Opは、関連するプロパティのセットで表されます。これらは、DXILバックエンドパスだけでなく、検証、DXILリーダーなどの他の使用シナリオでも消費されます。
DXILバックエンドパスで消費されるプロパティ
オペレーションの名前 (
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バックエンドパスは、DXILオペレーションの様々なプロパティに依存しています。たとえば、DXILOpLowering
パスでは、LLVM組み込み関数がどのDXILオペレーションに下げられるか、有効なオーバーロードと引数の型などの情報が必要になります。TableGenファイル - llvm/lib/Target/DirectX/DXIL.td
- は、上記のプロパティを指定することにより、DXILオペレーションを表すために使用されます。DXIL.td
は、主にllvm-project
リポジトリのDXILバックエンドでのパスの実装のためのDXILオペレーションの単一の参照ソースとして設計されています。これは、DirectXShadeCompiler
リポジトリのhctdb.py
に類似しています。ただし、現在の設計では、hctdb.py
に存在するがDXILオペレーションに関係しない様々な検証ルールをカプセル化することは意図していません。TableGenバックエンド (DXILEmitter
など) が依存できる豊富な表現能力が必要です。さらに、DXIL Opの仕様は読みやすく、理解しやすい必要があります。
このノートでは、上記のプロパティを指定することにより、TableGenクラスDXILOp
としてDXIL Opの仕様を設計します。
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
のような単純な型、dx.types.Handle
のようなDXIL型、および後述のOverloads
で許可される任意の型にできる特別なoverloadTy
を表します。オペレーションの戻り値の型は
DXILOpParamType
として表され、引数は同じ型のリストとして表されます。戻り値がないオペレーションは、戻り値としてVoidTy
を指定する必要があります。DXILバージョンに基づいて予測される有効なオペレーションオーバーロード型は、
Overloads
レコードのリストとして指定されます。Overloads
クラスの表現については、後のセクションで説明します。DXILバージョンに基づいて予測される有効なシェーダーステージは、
Stages
レコードのリストとして指定されます。Stages
クラスの表現については、後のセクションで説明します。DXILオペレーションのさまざまな属性は、
Attributes
クラスレコードのlist
として表されます。Attributes
クラスの表現については、後のセクションで説明します。
DXIL固有の型¶
このドキュメントで使用されている型表記 (例: <size>Ty
) は、LLVM型のTableGenレコードであるllvm_<size>_ty
に対応します。上記の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]>
];
標準のシェーダーステージに加えて、次の 2 つの擬似ステージレコードが定義されています。
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
の null リストは、操作属性がないことを意味します。
複数のバージョン付きプロパティの解釈¶
バージョン付きプロパティのそれぞれは、指定されたオーバーロード型、ステージ、または属性レコードが、指定された DXIL バージョンに対して有効であることを示します。最新の最小 DXIL バージョンに対応するプロパティのみが適用されます。上記の例のように、後の DXIL バージョンで有効なままのオーバーロード型、ステージ、または属性は、完全に指定する必要があります。たとえば、有効なオーバーロード型の次の仕様を考えてみましょう。
overloads = [
Overloads<DXIL1_0, [halfTy, floatTy]>,
Overloads<DXIL1_2, [halfTy, floatTy, doubleTy]>
];
これは、オーバーロード型 halfTy
および floatTy
が 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 仕様の設計について概説します。これは、DXIL バックエンドパスで使用される C++ 表現を生成する TableGen バックエンド (DXILEmitter
など) の単一の参照元として機能することを目的としています。