Repository navigation
初期化子がまだ走っていない静的フィールドの読み書きを歩きで報告する - #243
Conversation
PRレビュー / PR review変更 13 ファイル、コミット 4 件。 / 13 files, 4 commits. 検証の要否 / Verification required以下を走らせ、失敗した試験の名前と失敗の様子を変更前と突き合わせてください。総数だけで判断しないでください。 dotnet build ComputeWeave.sln -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.Tests.SourceGenerators/ComputeWeave.Tests.SourceGenerators.csproj -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.Tests.Internals/ComputeWeave.Tests.Internals.csproj -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.Tests/ComputeWeave.Tests.csproj -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.Tests.DeviceLost/ComputeWeave.Tests.DeviceLost.csproj -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.D2D1.Tests.SourceGenerators/ComputeWeave.D2D1.Tests.SourceGenerators.csproj -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.D2D1.Tests/ComputeWeave.D2D1.Tests.csproj -c Release -p:Platform=x64
dotnet test tests/ComputeWeave.D2D1.Tests.AssemblyLevelAttributes/ComputeWeave.D2D1.Tests.AssemblyLevelAttributes.csproj -c Release -p:Platform=x64ビルドエラーを出す診断を変えた場合は、アナライザーの試験に加えて解全体の検証が要ります。 慎重を要する箇所 / Guarded areas touched
寿命の追跡と危険の追跡は別々の保証です。一方を保っても他方が保たれるとは限りません。 公開APIまたは生成される記述子を変えた場合は、該当部分の互換性、生成の決定性、基準データの検査を走らせてください。 確認できた点 / Confirmed
これは自動の確認で、査読の代わりにはなりません。指摘は判断の材料であって、従う義務はありません。 |
cb3094e to
a894665
Compare
a894665 to
1466b44
Compare
1466b44 to
5cce9d1
Compare
…icFieldReadBeforeInitialization # Conflicts: # src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs
Summary / 概要
静的フィールドの初期化子が、C# ではまだ初期化子の走っていない静的フィールドを読む形と書く形を、初期化子ごとの1度の歩きで報告します。課題 #239、#240、#245 を直します。
C# は静的フィールドの初期化子を宣言の順に一度ずつ走らせます。順番のまだ来ていない同じ型のフィールドは型の既定値を持ち、その読みは既定値に、その書きは後で走る初期化子が捨てます。初期化子が自分自身へ戻る環も読みの1つです。生成HLSLはどちらも写せません。シェーダーコンパイラは後ろのフィールドの初期化子を畳めるなら畳んで宣言へ置くので、読みは初期化後の値になり、書きは残ります。畳めなければ読みは未定義の値になり、直に読む形は宣言前の識別子として建ちません。
これまで環は、初期化子の書き換え中に席を予約して読みが通ったときに報告する仕組みと、シェーダー自身の静的メソッドを呼んだときにその先を歩く仕組みの2つで断っていました。書き換えは宣言を一度しか通らないので、本文や前のフィールドが先に取り込んだ宣言を経る環は前者に掛からず、後者はシェーダー自身の静的メソッドからしか入りませんでした。後ろのフィールドの読み書きはどちらの対象でもありませんでした。
2つの仕組みを、書き換えのあとに初期化子ごとに到達する宣言を歩く1つの処理へ置き換えます。歩きは、呼び出しが解決するメソッド、局所関数、構築子、そして他の型の静的フィールドの初期化子を辿り、その型の順番がまだ来ていない、初期化子を持つフィールドの読み書きを
CMPW0124、Direct2D ではCMPWD2D0096で報告します。識別子はそのままで、意味を「初期化子がまだ走っていない静的フィールドへのアクセス」へ広げ、本文が触れられるフィールドと触れる側の初期化子の両方を名指しします。Linked issue / 関連する課題
Closes #239
Closes #240
Closes #245
Kind / 種別
Behavior change / 挙動の変更
これまで診断なく通っていた次の形が、
CMPW0124/CMPWD2D0096の誤りになります。いずれも C# と違う値を計算していた形です。直に読む形はこれまで
CMPW0046(シェーダーコンパイラの誤りの転送)で、生成物の行を名指ししていました。これからは書いた読みを名指しします。次の形は報告しません。初期化子を持たない後ろのフィールドの読み書きは、C# の既定値と HLSL の静的変数の零が一致し、書きも両者で残ります。自分自身への書きは、C# でも生成HLSLでも初期化子の値が上書きします。呼ばれない局所関数の中の読み書きは、C# がその関数を走らせません。
課題 #188 の形(2つのフィールドが自身の静的メソッドを挟んで互いに届く)は、環ではなく後ろのフィールドの読みとして報告されます。C# は最初の初期化子を最後まで走らせてから次へ進むので、2つ目の初期化子が1つ目を読み返す時点では1つ目は初期化済みです。報告は1件のままで、位置が2つ目の初期化子の中の読みから、1つ目の初期化子がメソッド経由で2つ目を読む箇所へ移ります。シェーダーのフィールドと外の型のフィールドが互いに届く形は、どちらの型が先に初期化されるかで早すぎる読みが変わるので、2つの読みが両方報告されます。
CMPW0124/CMPWD2D0096の題名と本文が変わります。識別子と重大度は変わらず、同梱する文書が述べる診断の件数も変わりません。上のどの形も持たない入力の生成物は変わりません。Verification / 検証
土台は
887d5851、実装は6d4e2354、試験は8a4be231、台帳は5cce9d1fです。計算側の生成器は土台の557件に対して578件、Direct2D側の生成器は232件に対して234件で、いずれも失敗0件です。土台の値は v2.6.0 のa8245449で他の並列のセッションが測ったもので、そこから土台までに生成器の試験の変更はありません。計算側で増える21件は、足した行19本から他の試験へ移した行1本を引き、行を持たない試験3本を足した数と一致します。Direct2D側の2件は足した試験2本です。表の検査は21文書407行が描画で欠けず、件数の検査は4文書とも一致、出荷の記録の検査は6 file の214項目がそれぞれ1つの履歴を持つと述べ、乖離の監査は待ち行列が空です。直す前の形を測りました
土台
a8245449で、生成器へ直接通した形と、統合GPU(Intel Iris Xe)で読み戻した値です。NaNCMPW0046環は他に4形を生成器へ通しました。前の静的フィールドが先に取り込んだ静的メソッドを呼ぶ形、本文が先に取り込んだ構築子を使う形、本文が先に取り込んだインスタンスメソッドを呼ぶ形、そして外の型のフィールドの初期化子が取り込みの途中のメソッドを呼ぶ形で、いずれも診断は無く、値は読んでいません。
HLSL 側の意味は、ライブラリを経由せず同梱の DXC(1.7.2308.7)と SDK 10.0.26100.0 の FXC へ手書きの HLSL を当てて確かめました。読みは、後ろで宣言した定数を関数経由で読む形は両方が 10 を格納し、直に読む形は両方が宣言前の識別子で断り、自分自身を関数経由で読む環は DXC が未定義の値を格納して FXC が
X4000で断ります。DXC は全域の静的変数を宣言の順に初期化しますが、まだ初期化されていない変数の読みは 0 ではなく未定義です。書きは、生成物と同じ形の手書きの HLSL を当てて DXIL の格納値で読み、C# の列は同じ形をコンソールで走らせて読みました。実機では読んでいません。
X3691で断るX3691で断るX3691で断るX3691で断るX3691で断るDXC は畳める初期化子を宣言の初期値として置き、畳めない初期化子だけを宣言の順に走らせます。書きが初期化子より前に来るとき、C# は必ず捨て、DXC は畳んだときだけ残します。どの初期化子を DXC が畳むかを生成器は知り得ないので、初期化子を持つ後ろのフィールドへの書きは全て報告します。初期化しない変数は読みも書きも C# と一致し、自分自身への書きも一致するので、どちらも報告しません。
変異
実装をコミットしてから、歩きの判断を1つずつ壊しました。当てた木は
5cce9d1fで、変異を当てない対照では2つのスイートとも失敗0件です。説明文と試験の表明を直す前のコミットで一度回した際は、並列に走っていた別の作業で記憶が尽き、6件は構築か実行が落ちたので、最終のコミットで17件すべてを回し直しました。狙った試験は計算側65件、Direct2D側9件です。訪問済みの集合を外すと歩きが環を回り続け、計算側の工程が
0xC00000FDで落ちます。Direct2D側の9件には宣言を2度訪れる形がありません。同じ型のフィールドの初期化子へも入る変異が何も落とさないのは、次の構造によります。順番の来ていない同じ型のフィールドは報告されて、その初期化子へは入りません。前のフィールドの初期化子は、そのフィールド自身の歩きが先に終えており、同じアクセスを1件にする集合が吸います。この条件はどの報告も変えず、前の歩きが済ませたことを繰り返さないためのものです。本文の2つの名前を入れ替える変異は、本文を読む試験1本だけが落とします。生成物を全数で突き合わせました
解の11 project を
-t:Rebuildと-p:EmitCompilerGeneratedFiles=trueで1本ずつ建て直し、書き出された file を v2.6.0 のa8245449で他の並列のセッションが同じ道具で採った値と突き合わせました。11 project とも誤り0件で建ちます。両方に在る file は1080で、そのうち1078が1バイト同一、内容が違うものが2、片方にしか無いものは0です。違う2件は Direct2D の記述子で、同じコミットを2回建てても動く DXBC の検査値です。この木に既に在るシェーダーの生成物は1件も変わらず、新しい報告に当たるものもありません。origin/main の併合
この枝を出したあとに PR #238、#241、#247、#248 が既定ブランチへ入ったので、
c050699dの origin/main を併合しました(ff4a6bff)。衝突はHlslSourceRewriter.VisitIdentifierNameの1箇所で、#248 が定数の書き換えを1つの補助へまとめた呼び出しを、この枝が #187 の前の形へ戻した条件式に当てる形で解き、ReportCyclicStaticFieldInitializerの呼び出しはこの枝が消したまま残していません。歩き、記述子、呼び出し箇所、診断の試験は併合の前後で1バイトも変わりません。併合の先端で生成器の試験2本は計算側588件と Direct2D側236件がいずれも失敗0件で、生成物の全数調査は origin/main と突き合わせて1080 file のうち1078が1バイト同一、違う2件は同じ既知の DXBC の検査値、片方にしか無い file は0です。走らせていないもの
ComputeWeave.Tests、ComputeWeave.D2D1.Tests、ComputeWeave.Tests.Internals、ComputeWeave.Tests.DeviceLost、ComputeWeave.Tests.DebugLayer、ComputeWeave.Tests.GlobalStatements、ComputeWeave.D2D1.Tests.AssemblyLevelAttributesは手元では走らせていません。変更は、生成器が書き換えを終えたあとの1つの歩きと、診断の本文に閉じており、上の全数比較のとおり生成物が1件も変わらないので、生成物を照合する試験と実行時の面に届く経路がありません。CI は枝の3コミットの先端5cce9d1fと、origin/main を併合した先端ff4a6bffの両方でビルドとテストの工程を通しました。併合の先端で走った試験はComputeWeave.Tests4189、ComputeWeave.Tests.Internals1085、ComputeWeave.Tests.DeviceLost43、ComputeWeave.D2D1.Tests206、ComputeWeave.D2D1.Tests.AssemblyLevelAttributes4で、生成器の2本は588と236で、いずれも失敗0件です。Adapter and driver tested / 試したアダプターとドライバー: Intel(R) Iris(R) Xe Graphics, 32.0.101.7085(直す前の読みの形の値を読むためだけに使いました)
Checklist / 確認
If this touches a guarded area / 慎重を要する箇所に触れる場合
Notes for the reviewer / 査読者への注記
2つの仕組みを1つに置き換えた理由
予約に基づく報告は、書き換えが読みを通ることを前提にしていました。取り込みは宣言ごとに一度なので、その前提は本文か前のフィールドが同じ宣言を先に取り込んでいると崩れます。PR #187 と #204 の歩きはその穴を塞ぐために足されましたが、入口がシェーダー自身の静的メソッドの呼び出しに限られていました。足りない入口を足すよりも、書き換えのあとに初期化子ごとに1度歩く形にすれば、取り込みの状態に依らず同じ読みが同じ場所で見えます。2つの仕組みが重なる部分を持たなくなるので、1つの読みが2件になる形も起きません。
歩きの置き場は初期化子を処理する側の処理器です。両生成器の静的フィールドを集める処理が同じ形で呼び、書き換え器の側には予約を残すだけにしました。予約は生成器の異常終了を止めるためのもので(課題 #153)、そのまま要ります。
読みと書きを同じ識別子にした理由
C# にとってはどちらも「初期化子がまだ走っていないフィールドへのアクセス」で、読みは既定値になり、書きは後で走る初期化子が捨てます。生成HLSLはどちらも写せません。環は読みの特別な場合で、初期化中のフィールド自身が最初の「まだ走っていない」フィールドです。1つの規則で1つの識別子から出し、本文で触れられるフィールドと触れる側の初期化子を名指す形にしました。識別子を増やさないので同梱する文書の件数は動きません。
書きと見るのは単純代入の左辺と out 引数です。複合代入、増減、ref 引数は値を読むので読みとして報告します。歩きは文の順を追わないので、同じ宣言の中で書いてから読む形の読みも報告します。読みの値は C# と生成HLSLで同じですが、初期化子がまだ走っていないフィールドを読んでいることに変わりはなく、順に依存した形を通す理由がありません。
報告しない2つの形
どちらも C# と生成HLSLの値が一致することを手書きの HLSL で測りました。初期化子を持たないフィールドは C# の既定値と HLSL の静的変数の零が一致し、書きも両者で残ります。自分自身への書きは、自分の初期化子が呼ぶ宣言の中で行われるので、C# でも生成HLSLでも初期化子の値がその後に上書きします。報告する条件を「初期化子を持つ後ろのフィールド、自分自身は読みだけ」と書くと、測った表とちょうど一致します。
局所関数の扱い
生成HLSLは局所関数を持ち上げて書き出すので、呼ばれない局所関数も生成物に現れますが、C# は呼ばれない局所関数を走らせません。メソッドと同じく、呼び出しが解決する局所関数へ入り、宣言されている本体からは入らないようにしました。呼ばれる形と呼ばれない形の両方を試験に置き、それぞれを壊す変異で落ちることを見ました。
課題 #188 の形の報告が移る理由
2つのフィールドが自身の静的メソッドを挟んで互いに届く形は、C# では環になりません。最初の初期化子が最後まで走ってから2つ目が走るので、早すぎる読みは最初の初期化子がメソッド経由で2つ目を読む1箇所だけです。これまでは2つ目の初期化子の中の読みを環として報告していましたが、その読みは C# では初期化済みの値を読んでおり、直す場所として正しくありませんでした。報告の件数は変わらず、位置がその1箇所へ移ります。
同じアクセスが2つの初期化子から早すぎる場合
前のフィールドと後のフィールドが同じメソッドを呼び、そのメソッドが後のフィールドを読む形は、C# ではどちらの初期化子からも早すぎる読みです。読みは1箇所なので1件とし、宣言の順で先の初期化子を名指します。取り込んだフィールドも、その型の宣言の順で歩きます。試験の表明は件数に加えて位置が互いに異なることも見るようにしました。1つの読みが2件になる形は、これで件数からも見えます。
触らなかったもの
外の型のフィールドの予約は残しました。書き換えの途中で同じフィールドへ戻ると同じ鍵を2度足して生成器が終了する経路で、報告とは別の役です。
CMPW0124/CMPWD2D0096の識別子と重大度は変えていません。題名と本文と説明を広げた意味へ合わせました。型名を付けてシェーダー自身の静的メソッドを呼ぶ形(
Shader.Twice())は、本文でも初期化子でも取り込んだ宣言でも型名が生成HLSLへそのまま出てCMPW0046になることを測りましたが、この変更の範囲ではありません。課題 #244 として出してあります。歩きの模型「他の型の初期化子は、その型が最初に触れられたときに走る」の裏を取る中で、別の族を2つ測りました。外の型2つの初期化子が互いの型を読む環は、C# の値が本文の触れる順で変わり、生成HLSLは取り込みが終わった順で1つの値を計算します(課題 #249)。静的構築子が値を入れる静的フィールドは、初期化子を持たない静的変数として書き出され、生成HLSLは零を計算します(課題 #250)。どちらも同じ型の中の順序とは別の軸で、この変更の範囲ではありません。
初期化子を持たない後ろのフィールドを直に読む形(
Value = Base * 2; static float Base;)は、この歩きの対象に含めていません。これまでどおり生成HLSLが宣言前の識別子で建たず、CMPW0046になります。C# と値が食い違う形ではなく、生成物で宣言が並ぶ順の問題なので、ここでは触りません。前提が崩れたときの戻し方
歩きは1つの処理で、両生成器から1行ずつ呼ばれています。呼び出しを外せば報告は消え、予約と書き出しは変わりません。誤検出が出るなら、辺と規則ごとに切り分けられます。呼び出しの辺、局所関数の辺、構築子の辺、他の型のフィールドの辺がそれぞれ1つの
caseで、書きの判定と自分自身の除外がそれぞれ1つの式です。測っていないこと
部分型で宣言が file をまたぐときの順は、生成器が読む
GetMembers()の順と C# の型初期化子の順が同じであることを 2 file の部分型で確かめました。3 file 以上は測っていません。書きの形は DXIL の格納値で判定し、実機では読んでいません。Direct2D 側で実機の値は読んでいません。Direct2D 側では FXC が環を
X4000で、後ろのフィールドへの書きと初期化子を持たない後ろのフィールドの読みをX3691で断つので、生成物を名指す転送の診断で止まっていました。後ろで宣言した定数を関数経由で読む形だけは FXC も 10 を格納します。歩きの費用は測っていません。生成器のスイートの所要時間は土台と同じ桁で、条件を揃えた比較はしていません。