Skip to content

[Bug] 後ろで宣言された静的フィールドへ書く初期化子の呼び先の書きが、生成HLSLでは残る #245

Closed

Description

@routersys

領域

シェーダーのHLSLへの変換

パッケージの版数

ComputeWeave 2.6.0(既定ブランチの a8245449、v2.6.0 のタグと同じ木)

診断ID

無し。関数を1つ挟む形では診断が1件も出ません。

期待した挙動

静的フィールドの初期化子が呼ぶメソッドが、同じ型の後ろで宣言され初期化子を持つ静的フィールドへ書く形は、断られること。C# は静的フィールドの初期化子を宣言の順に一度ずつ走らせるので、その書きは後で走る初期化子が捨てます。生成HLSLはその書きを残します。

実際の挙動

診断なく通り、生成HLSLは C# と違う値を計算します。課題 #240 の読みと同じ根で、向きが逆です。読みは C# が既定値を読み HLSL が初期化後の値を読む形、書きは C# が捨て HLSL が残す形です。

形 診断 C# 生成HLSL
後ろで宣言され定数の初期化子を持つフィールドへ、初期化子が呼ぶメソッドが書く 無し 2 5
同じ形を out 引数で書く 無し 2 5
後ろで宣言され実行時の初期化子を持つフィールドへ書く 無し 初期化子の値 初期化子の値
後ろで宣言され初期化子を持たないフィールドへ書く 無し 5 5
自分自身へ、自分の初期化子が呼ぶメソッドが書く 無し 1 1
対照。前で宣言されたフィールドへ書く 無し 5 5

生成HLSLの列は、生成物と同じ形の手書きの HLSL を同梱の DXC(1.7.2308.7)へ当て、書き出し先へ格納される値を DXIL で読んだものです。実機では読んでいません。違うのは最初の2形だけで、残りは一致します。

再現手順

using ComputeWeave;

namespace Shaders;

[ThreadGroupSize(DefaultThreadGroupSizes.X)]
[GeneratedComputeShaderDescriptor]
internal readonly partial struct Shader : IComputeShader
{
    private static readonly float Value = Init();

    private static float Base = 2.0f;

    private readonly ReadWriteBuffer<float> buffer;

    private static float Init()
    {
        Base = 5.0f;

        return 1.0f;
    }

    public void Execute()
    {
        this.buffer[0] = Value;
        this.buffer[1] = Base;
    }
}

C# では Value の初期化子が Init を呼び、Init が Base へ 5 を書きますが、その後に Base の初期化子が走って 2 に戻すので、Base は 2 です。生成HLSLは次の形で、Base の初期化子が定数なのでシェーダーコンパイラが宣言へ畳み、順に走る格納にはなりません。Init の書きがそのまま残り、Base は 5 になります。

static float Init();

static const float Value = Init();

static float Base = 2.0;

static float Init()
{
    Base = 5.0;
    return 1.0;
}

切り分け

HLSL 側の意味を、ライブラリを経由せず同梱の DXC と SDK 10.0.26100.0 の FXC へ直接当てて測りました。

当てた形 DXC FXC
後ろで宣言し定数で初期化する変数へ関数経由で書く 書きが残る(5) X3691 で断る
同じ変数へ out 引数で書く 書きが残る(5) X3691 で断る
後ろで宣言し実行時の値で初期化する変数へ関数経由で書く 初期化子が上書きする X3691 で断る
後ろで宣言し初期化しない変数へ関数経由で書く 書きが残る(5、C# と同じ) X3691 で断る
自分自身へ関数経由で書く 初期化子が上書きする(1、C# と同じ) 初期化子が上書きする(1)
前で宣言した変数へ関数経由で書く 書きが残る(5、C# と同じ) 書きが残る(5)
後ろで宣言し初期化しない変数を関数経由で読む 0(C# と同じ) X3691 で断る

DXC は畳める初期化子を全域の宣言の初期値として置き、畳めない初期化子だけを宣言の順に走らせます。C# はどの初期化子も宣言の順に走らせます。書きが初期化子より前に来るとき、C# は必ず捨て、DXC は畳んだときだけ残します。どの初期化子を DXC が畳むかを生成器は知り得ないので、初期化子を持つ後ろのフィールドへの書きは全て断るのが筋です。初期化子を持たないフィールドは、C# の既定値と HLSL の静的変数の零が一致するので、読みも書きも同じ値になります。自分自身への書きは、C# でも DXC でも初期化子の値が上書きします。

Direct2D 側は FXC が生成物を名指して断つので、値が違うことはありませんが、利用者の書いていない行を指す誤りが届きます。

生成器の側は、PR #243 の歩きが読みを報告しますが、書きは報告しません。歩きは書きの位置も通るので、同じ歩きで報告するのが筋です。

何を保証すべきか

C# が計算する値と HLSL が計算する値が食い違う形は、断るか、写せる形へ変えるかのどちらかであるべきです。後で走る初期化子が捨てる書きは生成HLSLでは写せないので、断るのが筋です。断る位置は書きで、読みの報告と同じ識別子です。書きと見るのは単純代入の左辺と out 引数で、複合代入・増減・ref 引数は値を読むので読みとして報告します。

測っていないこと

実機で書きの形の値は読んでいません。DXIL の格納値で判定しました。Direct2D 側は FXC が断つことを手書きの HLSL で見たにとどまります。

デバイス

統合GPU

アダプターとドライバー

Intel(R) Iris(R) Xe Graphics, 32.0.101.7085

Windowsの版数

Windows 11 Pro 24H2, 26200

.NET SDKの版数

10.0.400

デバッグレイヤーの出力

No response

その他

課題 #240 の読みと同じ根で、PR #243 の中で同じ歩きに書きを足す形で直します。

提出の前に

  • 最新の公開版を使っています。
  • 既存の課題を検索し、重複でないことを確認しました。
  • 独自の ID3D12MemoryAllocator を設定していないことを確認しました。

Activity

  1. added
    bugSomething isn't working / 不具合が起きている
    analyzer or generatorAnalyzer or generator / アナライザーまたはジェネレーター
    on Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    analyzer or generatorAnalyzer or generator / アナライザーまたはジェネレーターbugSomething isn't working / 不具合が起きている

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions