From 1c3c0ecfe4dd2d9115189001c4b9d5e534d063eb Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 10:44:13 +0900 Subject: [PATCH 1/5] =?UTF-8?q?64=E3=83=93=E3=83=83=E3=83=88=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E3=83=AA=E3=83=86=E3=83=A9=E3=83=AB=E3=81=A8?= =?UTF-8?q?=E5=AE=9A=E6=95=B0=E3=82=92=E7=84=A1=E5=8A=B9=E3=81=AA=E5=9E=8B?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=A6=E6=96=AD=E3=81=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SyntaxRewriters/HlslSourceRewriter.cs | 53 ++++++++++++++++--- .../SyntaxRewriters/ShaderSourceRewriter.cs | 9 +--- .../SyntaxRewriters/StaticFieldRewriter.cs | 9 +--- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs index d76cd9306..fcb6eb99a 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.cs @@ -332,6 +332,12 @@ public sealed override SyntaxNode VisitDefaultExpression(DefaultExpressionSyntax return updatedNode.WithToken(Literal(literal, 0)); } + else if (!HlslKnownTypes.IsKnownHlslType(type.GetFullyQualifiedMetadataName())) + { + // A literal of a type outside the set is refused like a local of that type: the shader compiler + // would read a 64 bit integer as a 32 bit one, and a decimal has no literal at all + _ = TrackType(type); + } } else if (updatedNode.IsKind(SyntaxKind.CharacterLiteralExpression) && updatedNode.Token.Value is char character) @@ -518,14 +524,9 @@ private ExpressionSyntax VisitConvertedConditionalArm(ExpressionSyntax node, Exp { if (operation.Field.IsConst && operation.Type!.TypeKind != TypeKind.Enum && - TryGetConstantLiteral(operation.Field.ConstantValue, out string? constantLiteral)) + TryRewriteConstantReference(operation.Field, out IdentifierNameSyntax? constantName)) { - ConstantDefinitions[operation.Field] = constantLiteral!; - - string ownerTypeName = ((INamedTypeSymbol)operation.Field.ContainingSymbol).ToDisplayString().ToHlslIdentifierName(); - string constantName = $"__{ownerTypeName}__{operation.Field.Name}"; - - return IdentifierName(constantName); + return constantName; } // A field of the shader written by its name alone reaches no other reporting site: the rewriter for @@ -587,6 +588,36 @@ protected static ExpressionSyntax ParseMappedExpression(string mapping) return ParseExpression(mapping).AsPrimaryExpression(); } + /// + /// Rewrites a reference to a constant field into the name of the HLSL definition written for it. + /// + /// The constant field being referenced. + /// 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. + /// + protected bool TryRewriteConstantReference(IFieldSymbol field, [NotNullWhen(true)] out IdentifierNameSyntax? constantName) + { + if (!TryGetConstantLiteral(field.ConstantValue, out string? constantLiteral)) + { + _ = TrackType(field.Type); + + constantName = null; + + return false; + } + + ConstantDefinitions[field] = constantLiteral!; + + string ownerTypeName = ((INamedTypeSymbol)field.ContainingSymbol).ToDisplayString().ToHlslIdentifierName(); + + constantName = IdentifierName($"__{ownerTypeName}__{field.Name}"); + + return true; + } + protected static unsafe bool TryGetConstantLiteral(object? value, out string? literal) { if (value is bool flag) @@ -648,6 +679,14 @@ protected static unsafe bool TryGetConstantLiteral(object? value, out string? li return true; } + // A 64 bit integer has no HLSL literal to write: the digits alone are read as a 32 bit value + if (value is long or ulong) + { + literal = null; + + return false; + } + if (value is IFormattable formattable) { literal = formattable.ToString(null, CultureInfo.InvariantCulture); diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs index fd9d6e597..4f3d6ab62 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs @@ -499,14 +499,9 @@ public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyn return ParseMappedExpression(constantLiteral!); } - if (TryGetConstantLiteral(fieldOperation.Field.ConstantValue, out string? constantValue)) + if (TryRewriteConstantReference(fieldOperation.Field, out IdentifierNameSyntax? constantName)) { - ConstantDefinitions[fieldOperation.Field] = constantValue!; - - string ownerTypeName = ((INamedTypeSymbol)fieldOperation.Field.ContainingSymbol).ToDisplayString().ToHlslIdentifierName(); - string constantName = $"__{ownerTypeName}__{fieldOperation.Field.Name}"; - - return IdentifierName(constantName); + return constantName; } } diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs index 83ee632c9..d25fc305d 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs @@ -96,14 +96,9 @@ public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyn return ParseMappedExpression(constantLiteral!); } - if (TryGetConstantLiteral(fieldOperation.Field.ConstantValue, out string? constantValue)) + if (TryRewriteConstantReference(fieldOperation.Field, out IdentifierNameSyntax? constantName)) { - ConstantDefinitions[fieldOperation.Field] = constantValue!; - - string ownerTypeName = ((INamedTypeSymbol)fieldOperation.Field.ContainingSymbol).ToDisplayString().ToHlslIdentifierName(); - string constantName = $"__{ownerTypeName}__{fieldOperation.Field.Name}"; - - return IdentifierName(constantName); + return constantName; } } From 1b057e9ab2d45e4409c0ffe72982287c6694c4d6 Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 13:04:43 +0900 Subject: [PATCH 2/5] =?UTF-8?q?64=E3=83=93=E3=83=83=E3=83=88=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E3=83=AA=E3=83=86=E3=83=A9=E3=83=AB=E3=81=A8?= =?UTF-8?q?=E5=AE=9A=E6=95=B0=E3=81=AE=E6=96=AD=E3=82=8A=E3=81=AB=E5=9B=9E?= =?UTF-8?q?=E5=B8=B0=E8=A9=A6=E9=A8=93=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 | 69 +++++++ .../Shaders/ConstantAndLiteralTypeTests.cs | 172 ++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 tests/ComputeWeave.Tests.SourceGenerators/Shaders/ConstantAndLiteralTypeTests.cs diff --git a/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs b/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs index 263ea9317..d2f4f868a 100644 --- a/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs +++ b/tests/ComputeWeave.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Diagnostics.cs @@ -188,6 +188,75 @@ public float4 Execute() CSharpGeneratorTest.VerifyDiagnostics(source, "CMPWD2D0097"); } + + /// + /// A 64 bit integer literal, which has no HLSL literal the generator maps. The refusal comes from the type + /// tracking both generators share, so what this pins is that the pixel shader generator answers with its own + /// identifier for an invalid discovered type. The constant is a row of its own, being refused at another site. + /// + [TestMethod] + public void AWideIntegerLiteralIsDiagnosed() + { + 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 readonly uint unsigned; + + public float4 Execute() + { + float value = 5L < this.unsigned ? 1.0f : 0.0f; + + return value; + } + } + """; + + CSharpGeneratorTest.VerifyDiagnostics(source, "CMPWD2D0041"); + } + + /// + /// A 64 bit integer constant, whose value has no HLSL literal to write it as. + /// + [TestMethod] + public void AWideIntegerConstantIsDiagnosed() + { + 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 long Big = 5; + + private readonly int signed; + + public float4 Execute() + { + float value = (float)(Big + 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 new file mode 100644 index 000000000..c14953d39 --- /dev/null +++ b/tests/ComputeWeave.Tests.SourceGenerators/Shaders/ConstantAndLiteralTypeTests.cs @@ -0,0 +1,172 @@ +using System.Linq; +using ComputeWeave.SourceGenerators; +using ComputeWeave.Tests.SourceGenerators.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ComputeWeave.Tests.SourceGenerators.Shaders; + +/// +/// Which literal and constant types the rewriting refuses. A 64 bit integer has no HLSL literal the generator +/// maps, so a literal or a constant of that type used to reach the shader compiler as its digits, which are read +/// as a 32 bit value: a negative literal on the left of a comparison with an unsigned value was compared as +/// unsigned, and a value past the 32 bit range was truncated, with no diagnostic either way. +/// +/// +/// +/// 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. +/// +/// +/// The last rows are the controls. A constant of a type the set does have is still written as a definition, a +/// decimal constant among them, since its floating point spelling is the deliberate mapping. +/// +/// +[TestClass] +public class ConstantAndLiteralTypeTests +{ + /// + /// The shader the rows are written over. The values are read from the constant buffer, so nothing is folded + /// before the rewriting sees it. + /// + private const string Template = """ + using ComputeWeave; + + namespace Shaders; + + internal static class Helper + { + public const long Wide = 5; + + public const uint Narrow = 5; + } + + [ThreadGroupSize(DefaultThreadGroupSizes.X)] + [GeneratedComputeShaderDescriptor] + internal readonly partial struct Shader : IComputeShader + { + private const long Big = 5; + + private const int Small = 5; + + private const decimal Half = 0.5m; + + private const string Name = "x"; + + private const string Other = "y"; + + private static readonly float Seeded = {{INITIALIZER}}; + + private readonly ReadWriteBuffer buffer; + + private readonly int signed; + + private readonly uint unsigned; + + public void Execute() + { + {{BODY}} + } + } + """; + + [TestMethod] + public void ALongLiteralIsRefused() + { + AssertReports(Shader("this.buffer[0] = 5L < this.unsigned ? 1.0f : 0.0f;"), "LongLiteralTests", "CMPW0050"); + } + + [TestMethod] + public void AnUnsignedLongLiteralIsRefused() + { + AssertReports(Shader("this.buffer[0] = (float)(5UL + this.unsigned);"), "UnsignedLongLiteralTests", "CMPW0050"); + } + + [TestMethod] + public void ALongConstantReadByNameIsRefused() + { + AssertReports(Shader("this.buffer[0] = (float)(Big + this.signed);"), "LongConstantByNameTests", "CMPW0050"); + } + + [TestMethod] + public void ALongConstantReadThroughAMemberAccessIsRefused() + { + AssertReports(Shader("this.buffer[0] = (float)(Helper.Wide + this.signed);"), "LongConstantMemberAccessTests", "CMPW0050"); + } + + [TestMethod] + public void ALongConstantInAStaticFieldInitializerIsRefused() + { + AssertReports(Shader("this.buffer[0] = Seeded;", "Helper.Wide + 1"), "LongConstantInitializerTests", "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 + /// string literal adds a report of its own. + /// + [TestMethod] + public void AStringConstantIsRefused() + { + AssertReports(Shader("this.buffer[0] = Name == Other ? 1.0f : 0.0f;"), "StringConstantTests", "CMPW0050"); + } + + [TestMethod] + public void AnIntegerConstantReadByNameIsAccepted() + { + AssertNoDiagnostics(Shader("this.buffer[0] = (float)(Small + this.signed);"), "IntegerConstantByNameTests"); + } + + [TestMethod] + public void AnUnsignedConstantReadThroughAMemberAccessIsAccepted() + { + AssertNoDiagnostics(Shader("this.buffer[0] = (float)(Helper.Narrow + this.unsigned);"), "UnsignedConstantMemberAccessTests"); + } + + [TestMethod] + public void ADecimalConstantIsAccepted() + { + AssertNoDiagnostics(Shader("this.buffer[0] = (float)Half;"), "DecimalConstantTests"); + } + + [TestMethod] + public void AnIntegerLiteralIsAccepted() + { + AssertNoDiagnostics(Shader("this.buffer[0] = (float)(5 + this.signed);"), "IntegerLiteralTests"); + } + + private static string Shader(string body, string initializer = "1.0f") + { + return Template.Replace("{{INITIALIZER}}", initializer).Replace("{{BODY}}", body); + } + + private static void AssertReports(string source, string assemblyName, string expectedId) + { + string[] actualIds = Run(source, assemblyName); + + Assert.IsTrue(actualIds.Contains(expectedId), $"{expectedId} is not reported: {string.Join(", ", actualIds)}"); + } + + private static void AssertNoDiagnostics(string source, string assemblyName) + { + string[] actualIds = Run(source, assemblyName); + + Assert.AreEqual(0, actualIds.Length, string.Join(", ", actualIds)); + } + + private static string[] Run(string source, string assemblyName) + { + CSharpCompilation compilation = CompilationHelper.CreateCompilation( + [source], + assemblyName, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); + GeneratorDriver driver = GeneratorHelper.CreateDriver(new ComputeShaderDescriptorGenerator()); + GeneratorRunResult result = driver.RunGenerators(compilation).GetRunResult().Results[0]; + + Assert.IsNull(result.Exception, result.Exception?.ToString()); + + return [.. result.Diagnostics.Select(static diagnostic => diagnostic.Id).Distinct().Order()]; + } +} From cd6b8ab64596acfe5917ff93d39f65971d83a470 Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 13:04:44 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E4=B9=96=E9=9B=A2=E5=8F=B0=E5=B8=B3?= =?UTF-8?q?=E3=81=B864=E3=83=93=E3=83=83=E3=83=88=E6=95=B4=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E3=83=AA=E3=83=86=E3=83=A9=E3=83=AB=E3=81=A8=E5=AE=9A?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E8=A1=8C=E3=82=92=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 c800193df..82df5a4ac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -756,6 +756,7 @@ The Direct2D authoring projects were removed before the first release and restor | Generated HLSL, custom type declarations | Orders the custom type definitions by their fields alone, forward declares every custom type ahead of them on the compute path, and forward declares none on the Direct2D path. DXC accepts the forward declarations, so the shipped path is unaffected, but FXC has no type forward declaration and refuses each one with `X3000`, so a caller who takes the generated HLSL of a shader using a custom type and builds it for Direct3D 11 has to delete those lines by hand. The order by fields alone leaves a member method prototype free to name a type defined after it, which the forward declarations mask on the compute path, while on the Direct2D path such a prototype reaches FXC naming an undeclared type, with nothing that names the cause at the declaration. The order is found by dequeuing a type until none of its fields waits on another, which never comes for a type in a struct layout cycle, so on source C# has already refused with `CS0523` the generator never returns, and no cancellation reaches the loop | Orders the definitions by the fields and the member method prototypes each of them holds, so a prototype naming another type finds it declared, and forward declares a type only where the prototypes name each other in a cycle that no order resolves. Such a cycle cannot run through fields, which are refused first: a type reached again through its own fields is reported as an invalid discovered type, with every other type on that cycle, because the shader compiler runs out of stack on a use of such a type rather than reporting it, and the refusal is what keeps its bytecode from being compiled. The type written first is then the first in line whose fields are declared, and the types its prototypes still name are the ones forward declared. The Direct2D generator, whose compiler has no such declaration, reports `CMPWD2D0100` at each prototype naming such a type instead of writing HLSL that cannot build, that prototype being the place to change. Of the 22 generated compute shaders in this repository that carried forward declarations, 21 lose them all and compile to byte identical DXIL under `cs_6_0`, and they build under FXC at `cs_5_0` as a result; the one whose two custom types name each other keeps the single forward declaration it needs, which DXC accepts and FXC cannot, that shape having no spelling FXC takes | `e00057fb`, `6ac88e40` | | 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` | 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`. From 8c5db0ab4f92d7404a8e5be44e81dd2d0ce7ae06 Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 15:46:46 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=E5=BA=83=E3=81=8C=E3=82=8A=E3=81=AE?= =?UTF-8?q?=E5=A0=B1=E5=91=8A=E3=81=AE=E6=B3=A8=E8=A8=98=E3=81=B864?= =?UTF-8?q?=E3=83=93=E3=83=83=E3=83=88=E3=81=AE=E3=83=AA=E3=83=86=E3=83=A9?= =?UTF-8?q?=E3=83=AB=E3=81=A8=E5=AE=9A=E6=95=B0=E3=81=AE=E6=89=B1=E3=81=84?= =?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 --- .../SyntaxRewriters/HlslSourceRewriter.Diagnostics.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.Diagnostics.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.Diagnostics.cs index 50d921c2b..a98244dcc 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.Diagnostics.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/HlslSourceRewriter.Diagnostics.cs @@ -372,7 +372,9 @@ protected bool ReportUnmappedExtensionMemberCall(InvocationExpressionSyntax node /// type set, which a signed and an unsigned integer in one operation do. That widening cannot be written /// into the generated code, so the operands reach the shader compiler as they stand and the operation is /// resolved over them instead, where the unsigned kind wins: a comparison answers the other way and an - /// arithmetic result wraps at 32 bits, with neither compiler reporting anything. + /// arithmetic result wraps at 32 bits, with neither compiler reporting anything. The widening is read from + /// the left operand, so a 64 bit literal or constant on that side, being no conversion at all, is not + /// reported here: the rewriting tracks its type instead, and the type set refuses it as it does a local. /// /// /// The walk is over the resolved operations rather than the syntax, because an implicit conversion has no From bf32530e8f3139b0997c49adefa0b1e4c124560e Mon Sep 17 00:00:00 2001 From: routersys Date: Tue, 15 Sep 2026 15:48:05 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=E4=B9=96=E9=9B=A2=E5=8F=B0=E5=B8=B3?= =?UTF-8?q?=E3=81=AE=E8=A1=8C=E3=81=B8=E6=B3=A8=E8=A8=98=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=9F=E3=83=83=E3=83=88=E3=82=92=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82df5a4ac..63fab837c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -756,7 +756,7 @@ The Direct2D authoring projects were removed before the first release and restor | Generated HLSL, custom type declarations | Orders the custom type definitions by their fields alone, forward declares every custom type ahead of them on the compute path, and forward declares none on the Direct2D path. DXC accepts the forward declarations, so the shipped path is unaffected, but FXC has no type forward declaration and refuses each one with `X3000`, so a caller who takes the generated HLSL of a shader using a custom type and builds it for Direct3D 11 has to delete those lines by hand. The order by fields alone leaves a member method prototype free to name a type defined after it, which the forward declarations mask on the compute path, while on the Direct2D path such a prototype reaches FXC naming an undeclared type, with nothing that names the cause at the declaration. The order is found by dequeuing a type until none of its fields waits on another, which never comes for a type in a struct layout cycle, so on source C# has already refused with `CS0523` the generator never returns, and no cancellation reaches the loop | Orders the definitions by the fields and the member method prototypes each of them holds, so a prototype naming another type finds it declared, and forward declares a type only where the prototypes name each other in a cycle that no order resolves. Such a cycle cannot run through fields, which are refused first: a type reached again through its own fields is reported as an invalid discovered type, with every other type on that cycle, because the shader compiler runs out of stack on a use of such a type rather than reporting it, and the refusal is what keeps its bytecode from being compiled. The type written first is then the first in line whose fields are declared, and the types its prototypes still name are the ones forward declared. The Direct2D generator, whose compiler has no such declaration, reports `CMPWD2D0100` at each prototype naming such a type instead of writing HLSL that cannot build, that prototype being the place to change. Of the 22 generated compute shaders in this repository that carried forward declarations, 21 lose them all and compile to byte identical DXIL under `cs_6_0`, and they build under FXC at `cs_5_0` as a result; the one whose two custom types name each other keeps the single forward declaration it needs, which DXC accepts and FXC cannot, that shape having no spelling FXC takes | `e00057fb`, `6ac88e40` | | 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` | +| 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` | 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`.