Repository navigation
[Bug] nint と nuint の定数を左辺に置いた演算が、診断なく 32 ビットで計算される #251
Description
領域
シェーダーのHLSLへの変換
パッケージの版数
main の c050699d(PR #248 の併合直後)
診断ID
無し。構築は警告0件、エラー0件で通ります。
期待した挙動
nint と nuint は生成器が写す HLSL の型集合に無く、局所変数に書けば CMPW0050 が断ります。定数に書いた場合も同じ経路で断ることを期待します。PR #248 が long と ulong の定数に対してそうしました。
実際の挙動
const nint か const nuint のフィールドは、値の桁だけで #define に書き出され、診断は出ません。シェーダーはその定数を32ビットの int として計算しますが、C# は x64 の工程で nint を64ビットで計算します。結果が32ビットに収まらないとき、値が食い違います。
生成器を直に動かして読んだ結果です。土台は c050699d です。
| 書いた式 | 診断 | 生成 HLSL |
|---|---|---|
const nint Own = 5; と (float)(Own + this.signed) |
無し | (float)(__Shaders_Shader__Own + __reserved__signed)、定義は #define __Shaders_Shader__Own 5 |
const nuint UnsignedOwn = 5; と (float)(UnsignedOwn + this.unsigned) |
無し | 同じ形 |
別の型の const nint をメンバーアクセスで読む |
無し | 同じ形 |
静的フィールドの初期化子の中の const nint |
無し | static const float Seeded = __Shaders_Helper__Native + 1 |
(float)Own |
無し | (float)__Shaders_Shader__Own |
(float)(this.signed + Own)(対照、右辺) |
CMPW0125 |
断られる |
nint wide = this.signed;(対照、局所変数) |
CMPW0050 |
断られる |
再現手順
using ComputeWeave;
namespace Shaders;
[ThreadGroupSize(DefaultThreadGroupSizes.X)]
[GeneratedComputeShaderDescriptor]
internal readonly partial struct Shader : IComputeShader
{
private const nint Own = 5;
private const nuint UnsignedOwn = 5;
private readonly ReadWriteBuffer<float> buffer;
private readonly int signed;
private readonly uint unsigned;
public void Execute()
{
this.buffer[0] = (float)(Own + this.signed);
this.buffer[1] = (float)(Own * this.signed);
this.buffer[2] = (float)(UnsignedOwn + this.unsigned);
}
}signed が int.MaxValue、unsigned が uint.MaxValue のとき、x64 の C# では buffer[0] が 2147483652、buffer[1] が 10737418235、buffer[2] が 4294967300 を単精度へ丸めた値です。
測ったこと
上の3行を実機と WARP で走らせました。両方の装置で同じ値です。
| 式 | C# の値(x64) | 装置が返した値 |
|---|---|---|
(float)(Own + signed) |
2147483648(2147483652 を丸めた値) | -2147483648(-2147483644 を丸めた値) |
(float)(Own * signed) |
1.0737418E+10(10737418235 を丸めた値) | 2147483648(2147483643 を丸めた値) |
(float)(UnsignedOwn + unsigned) |
4294967296(4294967300 を丸めた値) | 4 |
装置の値は32ビットで折り返した整数を単精度へ丸めたものです。
なぜ既存の断りが捕まえないか
定数を #define へ書き出す補助メソッドは、定数の値から綴りを決めます。long と ulong の値は64ビットの整数として箱詰めされるので、PR #248 はそこで断ちました。nint と nuint の定数は、C# が定数の値を32ビットの範囲に限るため、32ビットの整数として箱詰めされます。値だけを見る判定は int の定数と見分けられず、桁をそのまま書き出します。
CMPW0125 は左辺の変換だけを読みます。定数を左辺に置くと変換が無く、右辺の変換は読まれません。右辺に置いた形は左辺の int から nint への変換で断られます。
どこまで広がるか
定数の型のうち、生成器が値から書き出す型と、断つ型はこう分かれます。bool、char、8ビットと16ビットの整数、int、uint、float、double、decimal は書き出します。8ビットと16ビットの整数と char は C# が演算のたびに int へ広げるので、32ビットで計算しても値は同じです。decimal は浮動小数点の綴りへ書き出す既存の扱いです。long、ulong、文字列は PR #248 が断ちました。残るのは nint と nuint だけで、これを断つと定数の型の族は閉じます。列挙型のメンバーは生成物に名前のまま残り、シェーダーコンパイラが落とします(CMPW0046)。
対応の案
定数を #define へ書き出す補助メソッドで、フィールドの型がネイティブ整数なら値を見ずに型を追跡します。報告は局所変数と long の定数と同じ CMPW0050(Direct2D 側は CMPWD2D0041)です。値では見分けられないので、この1つだけは型で見分けます。
測っていないこと
Direct2D 側の生成器で同じ定数を建てていません。書き換え器は共有なので同じ経路で書き出されると読んでいます。利用者のシェーダーにこの形がどれだけ在るかは分かりません。
デバイス
統合GPU, WARPソフトウェアアダプター
アダプターとドライバー
Intel(R) Iris(R) Xe Graphics, 32.0.101.7085
Windowsの版数
Windows 11 Pro 24H2, 26200
.NET SDKの版数
10.0.400
提出の前に
- 最新の公開版を使っています。
- 既存の課題を検索し、重複でないことを確認しました。課題 [Bug] nint と nuint が診断なくHLSLへ書き出される #48 は
nintの型名が生成物へ残る件、課題 [Bug] long のリテラルか定数を左辺に置いた演算が、CMPW0125 で断たれず 32 ビットで計算される #242 はlongとulongのリテラルと定数で、nintの定数は扱っていません。 - 独自の ID3D12MemoryAllocator を設定していません。
Activity