From e33998ba72a06974f2d86f7409b87bc0e8791b5f Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 20:39:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=83=8D=E3=82=A4=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=96=E6=95=B4=E6=95=B0=E3=81=AE=E5=AE=9A=E6=95=B0=E3=82=92?= =?UTF-8?q?=E7=84=A1=E5=8A=B9=E3=81=AA=E5=9E=8B=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E6=96=AD=E3=81=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SyntaxRewriters/HlslSourceRewriter.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs index fcb6eb99a..8ff60c5cd 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs @@ -595,12 +595,12 @@ protected static ExpressionSyntax ParseMappedExpression(string mapping) /// The name of the HLSL definition, if the constant can be written as one. /// Whether can be written as an HLSL definition. /// - /// A constant whose value has no HLSL literal is refused by tracking its type instead, the way a local of - /// that type is: the shader compiler reads a 64 bit integer as a 32 bit one, and a string has no HLSL form. + /// A constant with no HLSL literal is refused by tracking its type instead, the way a local of that type is: + /// the shader compiler reads a 64 bit integer as a 32 bit one, and a string has no HLSL form. /// protected bool TryRewriteConstantReference(IFieldSymbol field, [NotNullWhen(true)] out IdentifierNameSyntax? constantName) { - if (!TryGetConstantLiteral(field.ConstantValue, out string? constantLiteral)) + if (!TryGetConstantLiteral(field.Type, field.ConstantValue, out string? constantLiteral)) { _ = TrackType(field.Type); @@ -618,8 +618,29 @@ protected bool TryRewriteConstantReference(IFieldSymbol field, [NotNullWhen(true return true; } - protected static unsafe bool TryGetConstantLiteral(object? value, out string? literal) + /// + /// Gets the HLSL literal a constant is written as, if its type and value have one. + /// + /// The type of the constant. + /// The value of the constant. + /// The resulting HLSL literal, if the constant has one. + /// Whether the constant has an HLSL literal. + /// + /// The shader compiler reads the digits of an integer as a 32 bit value, so a 64 bit integer has no literal, + /// and neither has a native integer, which C# computes with at the width of the platform. The former is told + /// by its value and the latter by its type, since the value of a native integer constant is boxed as a 32 bit + /// one. A string has no HLSL form at all. + /// + protected static unsafe bool TryGetConstantLiteral(ITypeSymbol type, object? value, out string? literal) { + // The value of a native integer constant is boxed as a 32 bit one, so its type is what tells it apart + if (type.IsNativeIntegerType) + { + literal = null; + + return false; + } + if (value is bool flag) { literal = flag ? "true" : "false"; From 08c7db291d288dd51e69b509593b9135527a07f7 Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 20:39:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E3=83=8D=E3=82=A4=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=96=E6=95=B4=E6=95=B0=E3=81=AE=E5=AE=9A=E6=95=B0=E3=81=AE?= =?UTF-8?q?=E6=96=AD=E3=82=8A=E3=81=AB=E5=9B=9E=E5=B8=B0=E8=A9=A6=E9=A8=93?= =?UTF-8?q?=E3=82=92=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...elShaderDescriptorGenerator_Diagnostics.cs | 35 +++++++++++++++++++ .../Shaders/ConstantAndLiteralTypeTests.cs | 18 ++++++++++ 2 files changed, 53 insertions(+) diff --git a/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs b/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs index d2f4f868a..e239e6d02 100644 --- a/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs +++ b/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs @@ -257,6 +257,41 @@ public float4 Execute() CSharpGeneratorTest.VerifyDiagnostics(source, "CMPWD2D0041"); } + /// + /// A native integer constant, whose value is boxed as a 32 bit one while C# computes with it at the width of + /// the platform, so it is told by its type. + /// + [TestMethod] + public void ANativeIntegerConstantIsDiagnosed() + { + const string source = """ + using ComputeWeave; + using ComputeWeave.D2D1; + using float4 = global::ComputeWeave.Float4; + + namespace MyNamespace; + + [D2DInputCount(0)] + [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] + [D2DGeneratedPixelShaderDescriptor] + internal readonly partial struct MyShader : ID2D1PixelShader + { + private const nint Native = 5; + + private readonly int signed; + + public float4 Execute() + { + float value = (float)(Native + this.signed); + + return value; + } + } + """; + + CSharpGeneratorTest.VerifyDiagnostics(source, "CMPWD2D0041"); + } + /// /// An indexer declared on a custom type. The rewriters are shared with the compute generator, so what /// this pins is that the pixel shader generator answers with its own identifier. diff --git a/tests/ComputeWeave.Tests.SourceGenerators/Shaders/ConstantAndLiteralTypeTests.cs b/tests/ComputeWeave.Tests.SourceGenerators/Shaders/ConstantAndLiteralTypeTests.cs index c14953d39..75741e968 100644 --- a/tests/ComputeWeave.Tests.SourceGenerators/Shaders/ConstantAndLiteralTypeTests.cs +++ b/tests/ComputeWeave.Tests.SourceGenerators/Shaders/ConstantAndLiteralTypeTests.cs @@ -18,6 +18,8 @@ namespace ComputeWeave.Tests.SourceGenerators.Shaders; /// Both are refused by tracking the type, the way a local of that type already is, so the report is the one for /// an invalid discovered type. A constant is reached by its name alone, through a member access in the body, and /// through a member access in a static field initializer, which are three rewriting sites, so each has a row. +/// A native integer constant is boxed as a 32 bit value while C# computes with it at the width of the platform, +/// so it is told by its type rather than by its value: it has a row of each sign. /// /// /// The last rows are the controls. A constant of a type the set does have is still written as a definition, a @@ -41,6 +43,8 @@ internal static class Helper public const long Wide = 5; public const uint Narrow = 5; + + public const nuint UnsignedNative = 5; } [ThreadGroupSize(DefaultThreadGroupSizes.X)] @@ -49,6 +53,8 @@ internal static class Helper { private const long Big = 5; + private const nint Native = 5; + private const int Small = 5; private const decimal Half = 0.5m; @@ -102,6 +108,18 @@ public void ALongConstantInAStaticFieldInitializerIsRefused() AssertReports(Shader("this.buffer[0] = Seeded;", "Helper.Wide + 1"), "LongConstantInitializerTests", "CMPW0050"); } + [TestMethod] + public void ANativeIntegerConstantIsRefused() + { + AssertReports(Shader("this.buffer[0] = (float)(Native + this.signed);"), "NativeIntegerConstantTests", "CMPW0050"); + } + + [TestMethod] + public void AnUnsignedNativeIntegerConstantReadThroughAMemberAccessIsRefused() + { + AssertReports(Shader("this.buffer[0] = (float)(Helper.UnsignedNative + this.unsigned);"), "UnsignedNativeIntegerConstantTests", "CMPW0050"); + } + /// /// A string constant has no HLSL form either, and used to be left as an identifier the shader compiler could /// not resolve. It goes through the same refusal now. The comparison is between two constants so that no From 577ee38f606100691a87e984fb88fb6255a228c0 Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 20:39:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E4=B9=96=E9=9B=A2=E5=8F=B0=E5=B8=B3?= =?UTF-8?q?=E3=81=B8=E3=83=8D=E3=82=A4=E3=83=86=E3=82=A3=E3=83=96=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E5=AE=9A=E6=95=B0=E3=81=AE=E8=A1=8C=E3=82=92?= =?UTF-8?q?=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 450cb95cd..a57ed48f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -757,6 +757,7 @@ The Direct2D authoring projects were removed before the first release and restor | Constant buffer layout, a captured struct in a layout cycle | Walks the captured fields into every nested unmanaged struct to lay the constant buffer out, with nothing that notices a type it is already inside. A struct whose fields lead back to itself, which C# refuses with `CS0523` but which the generator and the dispatch data size analyzers run over all the same while the author is in the middle of writing it, is followed around the cycle until the stack runs out, and the process hosting the generator ends with it | Skips a field whose type is already being walked, in the walk that lays the fields out and in the one that only sizes them alike, so the walk ends. The type is then refused by the discovered type check as one HLSL cannot lay out, which reports `CMPW0050` or `CMPWD2D0041` and keeps the shader compiler from being run over it; the C# compiler already names the field, so nothing further is reported here | `245fe8ab` | | Custom type discovery, a field of a type with no name | Casts the type of every field of a discovered custom type to a named type while exploring the nested types. A pointer field, or a fixed size buffer whose field type is a pointer as well, is valid C# in an unsafe struct and ends the generator with `InvalidCastException`, which Roslyn reports as `CS8785` while discarding the descriptors of every shader in the compilation unit | Refuses a custom type holding such a field as an invalid discovered type, reporting `CMPW0050` or `CMPWD2D0041` for the type the way a `bool` field is reported, and stops exploring it. HLSL has no pointer, and while a struct in HLSL does hold a fixed size array, the generator writes no mapping for a fixed size buffer, neither the field itself nor the constant buffer layout an array in one would need, so there is nothing to write for either as things stand; a shader whose own field is one of these was already refused by the field check, and stays so | `c8cc970f` | | Generated HLSL, 64 bit integer literals and constants | Writes a `long` or a `ulong` literal with the suffix the author wrote and a constant of either type as its digits, and leaves a `string` constant as an identifier. The shader compiler reads the digits as a 32 bit value, so a negative literal on the left of a comparison with an unsigned value is compared as unsigned, and a value past the 32 bit range is truncated, with no diagnostic from either compiler; the refusal of a widened operand reads the left operand alone, which a literal is not a conversion of, so a 64 bit literal on that side escapes it as well | Refuses a literal or a constant whose type the generator does not map by tracking that type, so the shader is reported with `CMPW0050`, or `CMPWD2D0041` on the Direct2D path, the way a local of that type already is. The three sites that turn a constant into an HLSL definition share one helper whose failure path is that refusal, so a string constant is refused the same way rather than left as an identifier the shader compiler cannot resolve. No shader in this repository carries such a literal or constant, so the generated files are unchanged, which was compared in full | `1c3c0ecf`, `8c5db0ab` | +| Generated HLSL, native integer constants | Writes a `nint` or a `nuint` constant as its digits, the value being boxed as a 32 bit one, so the shader computes with it at 32 bits while C# computes with it at the width of the platform: a result past 32 bits wraps, and the refusal of a widened operand reads the left operand alone, so the constant on that side escapes it as a 64 bit one does | Refuses a native integer constant by tracking its type, the way a local of that type and a 64 bit constant already are, so the shader is reported with `CMPW0050`, or `CMPWD2D0041` on the Direct2D path. The one function that decides which constants have an HLSL literal takes the type of the constant as well as its value, since the value cannot tell a native integer apart. No shader in this repository carries such a constant, so the generated files are unchanged | `e33998ba` | The ledger is kept current by derivation, not by memory: `build/audit-upstream-divergence.ps1` lists every commit after the marker below that modifies an inherited `.cs` file under `src/` and is not cited by a row. That is the whole of what it reads. Inherited project files, documents and test sources are outside it, so a divergence in one of those reaches the ledger only because someone wrote the row by hand. Run it before a release. When the queue is not empty, either add a row or decide that the commit is not a divergence, and then move the marker. The ledger is audited through commit `c8cc970f`.