TableGen表現を使用したDXILオペレーションの仕様

はじめに

DirectXShaderCompilerは、特に、hctdb.pyに様々なDXILオペレーションをカプセル化しています。DXILオペレーションは、以下の2つの方法のいずれかで表現されます。

  1. LLVM命令を使用する。

  2. 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リーダーなどの他の使用シナリオでも消費されます。

  1. DXILバックエンドパスで消費されるプロパティ

    1. オペレーションの名前 (dxil_op)

    2. オペレーションを説明する文字列 (doc) - これは厳密には必須ではありませんが、読みやすさとオペレーションの説明のために含まれています。

    3. オペレーションにマップされる汎用またはHLSL固有の組み込み関数 (llvm_name)。

    4. 一意の整数ID (dxil_opid)

    5. オペレーションの名前と関数シグネチャを示すオペレーションクラス (dxil_class)。この文字列はDXIL Op関数名の不可欠な部分であり、dx.op.<class-name>.<overload-type>の形式で構築されます。各DXIL Op呼び出しターゲット関数名は、ドライバーとの既存の契約に従ってこの形式に準拠する必要があります。

    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は、主にllvm-projectリポジトリのDXILバックエンドでのパスの実装のためのDXILオペレーションの単一の参照ソースとして設計されています。これは、DirectXShadeCompilerリポジトリのhctdb.pyに類似しています。ただし、現在の設計では、hctdb.pyに存在するがDXILオペレーションに関係しない様々な検証ルールをカプセル化することは意図していません。TableGenバックエンド (DXILEmitterなど) が依存できる豊富な表現能力が必要です。さらに、DXIL Opの仕様は読みやすく、理解しやすい必要があります。

このノートでは、上記のプロパティを指定することにより、TableGenクラスDXILOpとしてDXIL Opの仕様を設計します。

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のような単純な型、dx.types.HandleのようなDXIL型、および後述のOverloadsで許可される任意の型にできる特別なoverloadTyを表します。

  7. オペレーションの戻り値の型はDXILOpParamTypeとして表され、引数は同じ型のリストとして表されます。戻り値がないオペレーションは、戻り値としてVoidTyを指定する必要があります。

  8. DXILバージョンに基づいて予測される有効なオペレーションオーバーロード型は、Overloadsレコードのリストとして指定されます。Overloadsクラスの表現については、後のセクションで説明します。

  9. DXILバージョンに基づいて予測される有効なシェーダーステージは、Stagesレコードのリストとして指定されます。Stagesクラスの表現については、後のセクションで説明します。

  10. 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>;
}

シェーダーステージ仕様

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 バージョンに基づいて決定されます。これらは、バージョン付きプロパティのリストとして表されます。

オーバーロード型仕様

class DXILOpoverloads フィールドは、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 DXILOpstages フィールドは、DXIL バージョンに基づいて決定される有効な操作ステージを、次のクラスのレコードのリストとして表すために使用されます。

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

以下は、DXIL1_0DXIL1_2DXIL1_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 つの擬似ステージレコードが定義されています。

  1. all_stages は、その操作が指定された DXIL バージョン以降のすべてのステージで有効であることを意味します。

  2. removed は、指定された DXIL バージョン以降での操作のサポートが削除されたことを意味します。

サポートされているステージの空ではないリストを指定する必要があります。操作がすべての DXIL バージョンおよびすべてのステージでサポートされている場合は、次のように指定する必要があります。

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

属性仕様

class DXILOpattributes フィールドは、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 など) の単一の参照元として機能することを目的としています。