From 67b8c0b4fd7af4581b2405e5adb7ae7fa9a40b30 Mon Sep 17 00:00:00 2001 From: routersys Date: Thu, 17 Sep 2026 09:44:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E7=94=BB=E7=B4=A0=E6=AE=B5=E9=9A=8E?= =?UTF-8?q?=E3=81=AE=E7=B5=84=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=AE=E6=96=AD?= =?UTF-8?q?=E3=82=8A=E3=82=92=E8=A7=A3=E6=9E=90=E5=99=A8=E3=81=8B=E3=82=89?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E3=81=B8=E7=A7=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SyntaxRewriters/ShaderSourceRewriter.cs | 9 +++ .../SyntaxRewriters/StaticFieldRewriter.cs | 6 ++ .../UnsupportedHlslIntrinsicAnalyzer.cs | 78 ------------------- .../Mappings/HlslKnownMethods.cs | 32 ++++++++ .../SyntaxRewriters/HlslSourceRewriter.cs | 34 ++++++++ 5 files changed, 81 insertions(+), 78 deletions(-) delete mode 100644 src/ComputeWeave.SourceGenerators/Diagnostics/Analyzers/UnsupportedHlslIntrinsicAnalyzer.cs diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs index 78861efea..056d29d68 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/ShaderSourceRewriter.cs @@ -711,6 +711,15 @@ public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyn TrackKnownMethodInvocation(metadataName); #if D3D12_SOURCE_GENERATOR + // Special case: the intrinsics of the pixel stage the shared Hlsl type declares are refused + // under cs_6_0, so the call is refused before the compiler is handed it. Direct2D shaders use + // them legitimately, so this is excluded from that generator by the compilation symbol, which + // is what keeps it away from a pixel shader in a project referencing both products. + if (ReportUnsupportedIntrinsic(node, metadataName, method)) + { + return updatedNode; + } + // Special case: an intrinsic that writes through an out parameter terminates DXC on two // shapes of matrix argument, so the call is refused before the compiler is handed it. // Direct2D shaders are compiled with FXC, which does not have the defect, so this is diff --git a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs index 084828996..fe3e3c4cb 100644 --- a/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs +++ b/src/ComputeWeave.SourceGeneration.Hlsl/SyntaxRewriters/StaticFieldRewriter.cs @@ -216,6 +216,12 @@ public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyn TrackKnownMethodInvocation(metadataName); #if D3D12_SOURCE_GENERATOR + // Refuse an intrinsic of the pixel stage (see ShaderSourceRewriter for more info) + if (ReportUnsupportedIntrinsic(node, metadataName, method)) + { + return updatedNode; + } + // Refuse a matrix on an intrinsic with an out parameter (see ShaderSourceRewriter for more info) if (ReportMatrixOnIntrinsicWithOutParameter(node, method)) { diff --git a/src/ComputeWeave.SourceGenerators/Diagnostics/Analyzers/UnsupportedHlslIntrinsicAnalyzer.cs b/src/ComputeWeave.SourceGenerators/Diagnostics/Analyzers/UnsupportedHlslIntrinsicAnalyzer.cs deleted file mode 100644 index e53e0e581..000000000 --- a/src/ComputeWeave.SourceGenerators/Diagnostics/Analyzers/UnsupportedHlslIntrinsicAnalyzer.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Operations; -using static ComputeWeave.SourceGeneration.Diagnostics.DiagnosticDescriptors; - -namespace ComputeWeave.SourceGenerators; - -/// -/// A diagnostic analyzer that generates an error whenever an intrinsic that a compute shader cannot use is invoked. -/// -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class UnsupportedHlslIntrinsicAnalyzer : DiagnosticAnalyzer -{ - /// - public override ImmutableArray SupportedDiagnostics { get; } = [UnsupportedHlslIntrinsicInvocation]; - - /// - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); - context.EnableConcurrentExecution(); - - context.RegisterCompilationStartAction(static context => - { - if (context.Compilation.GetTypeByMetadataName("ComputeWeave.Hlsl") is not { } hlslSymbol) - { - return; - } - - context.RegisterOperationAction(context => - { - if (context.Operation is not IInvocationOperation { TargetMethod: { IsStatic: true } methodSymbol }) - { - return; - } - - if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, hlslSymbol)) - { - return; - } - - if (GetUnsupportedReason(methodSymbol.Name) is not { } reason) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create( - UnsupportedHlslIntrinsicInvocation, - context.Operation.Syntax.GetLocation(), - methodSymbol.Name, - reason)); - }, OperationKind.Invocation); - }); - } - - /// - /// Gets the reason a given intrinsic cannot be used in a compute shader. - /// - /// The name of the intrinsic being invoked. - /// The reason the intrinsic cannot be used, or if it can. - private static string? GetUnsupportedReason(string name) - { - return name switch - { - "Abort" => "the abort intrinsic is not accepted by the DXC compiler", - "Clip" => "the clip intrinsic discards a pixel, and a compute shader has no pixel to discard", - "DerivativeOfDx" or - "DerivativeOfDxHighPrecision" or - "DerivativeOfDxLowPrecision" or - "DerivativeOfDy" or - "DerivativeOfDyHighPrecision" or - "DerivativeOfDyLowPrecision" or - "Fwidth" => "derivatives in a compute shader require shader model 6.6, while shaders are compiled as cs_6_0", - _ => null - }; - } -} diff --git a/src/ComputeWeave.SourceGenerators/Mappings/HlslKnownMethods.cs b/src/ComputeWeave.SourceGenerators/Mappings/HlslKnownMethods.cs index 080f23394..0e97d0981 100644 --- a/src/ComputeWeave.SourceGenerators/Mappings/HlslKnownMethods.cs +++ b/src/ComputeWeave.SourceGenerators/Mappings/HlslKnownMethods.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace ComputeWeave.SourceGeneration.Mappings; @@ -24,6 +25,37 @@ public static bool SynchronizesTheWholeThreadGroup(string name) "ComputeWeave.Hlsl.GroupMemoryBarrierWithGroupSync"; } + /// + /// Gets the reason a method name, previously matched with , + /// cannot be used in a compute shader, if it cannot. + /// + /// The fully qualified metadata name. + /// The reason the intrinsic cannot be used, when it cannot. + /// Whether the intrinsic cannot be used in a compute shader. + /// + /// The shared Hlsl type declares intrinsics of the pixel stage as well, a pixel shader being written with + /// the same type. The nine here were measured to fail under cs_6_0: DXC does not accept the abort, a compute + /// shader has no pixel to discard, and the derivatives need shader model 6.6. + /// + public static bool TryGetUnsupportedReason(string name, [NotNullWhen(true)] out string? reason) + { + reason = name switch + { + "ComputeWeave.Hlsl.Abort" => "the abort intrinsic is not accepted by the DXC compiler", + "ComputeWeave.Hlsl.Clip" => "the clip intrinsic discards a pixel, and a compute shader has no pixel to discard", + "ComputeWeave.Hlsl.DerivativeOfDx" or + "ComputeWeave.Hlsl.DerivativeOfDxHighPrecision" or + "ComputeWeave.Hlsl.DerivativeOfDxLowPrecision" or + "ComputeWeave.Hlsl.DerivativeOfDy" or + "ComputeWeave.Hlsl.DerivativeOfDyHighPrecision" or + "ComputeWeave.Hlsl.DerivativeOfDyLowPrecision" or + "ComputeWeave.Hlsl.Fwidth" => "derivatives in a compute shader require shader model 6.6, while shaders are compiled as cs_6_0", + _ => null + }; + + return reason is not null; + } + /// private static partial Dictionary BuildKnownResourceSamplers() { diff --git a/src/ComputeWeave.SourceGenerators/SyntaxRewriters/HlslSourceRewriter.cs b/src/ComputeWeave.SourceGenerators/SyntaxRewriters/HlslSourceRewriter.cs index ba41d5b39..ceaf5cb05 100644 --- a/src/ComputeWeave.SourceGenerators/SyntaxRewriters/HlslSourceRewriter.cs +++ b/src/ComputeWeave.SourceGenerators/SyntaxRewriters/HlslSourceRewriter.cs @@ -15,6 +15,40 @@ protected partial void TrackKnownMethodInvocation(string metadataName) Requirements.SynchronizesTheWholeThreadGroup |= HlslKnownMethods.SynchronizesTheWholeThreadGroup(metadataName); } + /// + /// Reports an intrinsic being invoked that a compute shader cannot use. + /// + /// The invocation that is about to be written out as it stands. + /// The fully qualified metadata name of the intrinsic being invoked. + /// The resolved target of . + /// Whether the invocation was reported, in which case the caller leaves it alone. + /// + /// + /// The shared Hlsl type declares the intrinsics of the pixel stage as well, and DXC refuses each of them + /// under cs_6_0 for a reason of its own, which the report names. Reporting here keeps the call from reaching + /// the compiler at all, the compilation step being skipped for a shader whose rewriting produced an error, so the + /// author sees one report at their own line rather than that report and the forwarded compiler error together. + /// + /// + /// This was an analyzer before, reading every invocation of the compilation whatever type held it. A project + /// referencing the Direct2D product as well loads that analyzer too, and a pixel shader there uses the clip and + /// the derivatives legitimately, so the refusal is on the rewriting the compute generator alone runs. The report + /// is on the type the two rewriters share, because a derivative returns a value and so can be written into a + /// static field initializer as well as into a body. + /// + /// + protected bool ReportUnsupportedIntrinsic(InvocationExpressionSyntax node, string metadataName, IMethodSymbol method) + { + if (!HlslKnownMethods.TryGetUnsupportedReason(metadataName, out string? reason)) + { + return false; + } + + Diagnostics.Add(UnsupportedHlslIntrinsicInvocation, node, method.Name, reason); + + return true; + } + /// /// Reports an intrinsic that writes through an out parameter being given a matrix the compiler terminates on. /// From aca4a827879c91d0537c1247935693ff60186a24 Mon Sep 17 00:00:00 2001 From: routersys Date: Thu, 17 Sep 2026 09:44:51 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E7=94=BB=E7=B4=A0=E6=AE=B5=E9=9A=8E?= =?UTF-8?q?=E3=81=AE=E7=B5=84=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=AE=E6=96=AD?= =?UTF-8?q?=E3=82=8A=E3=82=92=E7=94=9F=E6=88=90=E5=99=A8=E3=81=AE=E8=A9=A6?= =?UTF-8?q?=E9=A8=93=E3=81=A7=E5=9B=BA=E5=AE=9A=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComputeOnlyIntrinsicsTests.cs | 19 +- .../UnsupportedHlslIntrinsicAnalyzerTests.cs | 104 ---------- .../Shaders/UnsupportedIntrinsicTests.cs | 187 ++++++++++++++++++ 3 files changed, 196 insertions(+), 114 deletions(-) delete mode 100644 tests/ComputeWeave.Tests.SourceGenerators/Canonicalization/UnsupportedHlslIntrinsicAnalyzerTests.cs create mode 100644 tests/ComputeWeave.Tests.SourceGenerators/Shaders/UnsupportedIntrinsicTests.cs diff --git a/tests/ComputeWeave.D2D1.Tests/ComputeOnlyIntrinsicsTests.cs b/tests/ComputeWeave.D2D1.Tests/ComputeOnlyIntrinsicsTests.cs index 4e21b115d..bb55c20c2 100644 --- a/tests/ComputeWeave.D2D1.Tests/ComputeOnlyIntrinsicsTests.cs +++ b/tests/ComputeWeave.D2D1.Tests/ComputeOnlyIntrinsicsTests.cs @@ -11,10 +11,10 @@ namespace ComputeWeave.D2D1.Tests; /// /// /// -/// UnsupportedHlslIntrinsicAnalyzer rejects these nine with CMPW0112. It lives in -/// ComputeWeave.SourceGenerators, which no D2D1 consumer loads, so the rejection does not -/// reach the authoring path. A pixel shader has pixels to discard and a stage that computes -/// derivatives, so eight of the nine are ordinary here. +/// The compute generator rejects these nine with CMPW0112, from the rewriting it alone runs, +/// so the rejection does not reach the authoring path even in a project referencing both products. +/// A pixel shader has pixels to discard and a stage that computes derivatives, so eight of the nine +/// are ordinary here. /// /// /// Abort is the exception, and not for the compute-side reason. D2D1 compiles a shader twice @@ -26,12 +26,11 @@ namespace ComputeWeave.D2D1.Tests; /// the tests below pin both halves against controls that isolate the instruction from the options. /// /// -/// That this file compiles at all is half the measurement: this project loads -/// ComputeWeave.D2D1.SourceGenerators and ComputeWeave.Core.SourceGenerators as -/// analyzers, and a CMPW0112 reaching here would be an error. The other half is the -/// compute-side counterpart, UnsupportedHlslIntrinsicAnalyzerTests, which asserts that the -/// same nine names do produce CMPW0112. Without it, this test would also pass if the -/// analyzer had simply stopped working. +/// That this file compiles and its effects draw is half the measurement: the eight are usable here. +/// The other half is the compute-side counterpart, UnsupportedIntrinsicTests, which asserts +/// that the same nine names do produce CMPW0112 in a compute shader, and that a type which is +/// no compute shader is left alone. Without it, this test would also pass if the refusal had simply +/// stopped working. /// /// [TestClass] diff --git a/tests/ComputeWeave.Tests.SourceGenerators/Canonicalization/UnsupportedHlslIntrinsicAnalyzerTests.cs b/tests/ComputeWeave.Tests.SourceGenerators/Canonicalization/UnsupportedHlslIntrinsicAnalyzerTests.cs deleted file mode 100644 index 9cfbd3755..000000000 --- a/tests/ComputeWeave.Tests.SourceGenerators/Canonicalization/UnsupportedHlslIntrinsicAnalyzerTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -using ComputeWeave.SourceGenerators; -using ComputeWeave.Tests.SourceGenerators.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace ComputeWeave.Tests.SourceGenerators.Canonicalization; - -[TestClass] -public class UnsupportedHlslIntrinsicAnalyzerTests -{ - private static string Shader(string body) - { - return $$""" - using ComputeWeave; - - namespace Ukiyoe; - - public readonly partial struct Shader : IComputeShader - { - private readonly ReadWriteBuffer buffer; - - public void Execute() - { - {{body}} - } - } - """; - } - - [TestMethod] - public void DetectsTheIntrinsicsThatNoComputeShaderModelAllows() - { - AnalyzerHelper.AssertDiagnostics( - new UnsupportedHlslIntrinsicAnalyzer(), - [Shader(""" - Hlsl.Abort(); - Hlsl.Clip(this.buffer[0]); - """)], - "DetectsUnsupportedStageIntrinsics", - "CMPW0112", - "CMPW0112"); - } - - [TestMethod] - public void DetectsTheDerivativeIntrinsics() - { - AnalyzerHelper.AssertDiagnostics( - new UnsupportedHlslIntrinsicAnalyzer(), - [Shader(""" - this.buffer[0] = Hlsl.DerivativeOfDx(this.buffer[1]); - this.buffer[2] = Hlsl.DerivativeOfDxHighPrecision(this.buffer[3]); - this.buffer[4] = Hlsl.DerivativeOfDxLowPrecision(this.buffer[5]); - this.buffer[6] = Hlsl.DerivativeOfDy(this.buffer[7]); - this.buffer[8] = Hlsl.DerivativeOfDyHighPrecision(this.buffer[9]); - this.buffer[10] = Hlsl.DerivativeOfDyLowPrecision(this.buffer[11]); - this.buffer[12] = Hlsl.Fwidth(this.buffer[13]); - """)], - "DetectsDerivativeIntrinsics", - "CMPW0112", - "CMPW0112", - "CMPW0112", - "CMPW0112", - "CMPW0112", - "CMPW0112", - "CMPW0112"); - } - - [TestMethod] - public void AcceptsAnIntrinsicThatAComputeShaderCanUse() - { - AnalyzerHelper.AssertDiagnostics( - new UnsupportedHlslIntrinsicAnalyzer(), - [Shader(""" - this.buffer[0] = Hlsl.Sqrt(this.buffer[1]); - this.buffer[2] = Hlsl.SmoothStep(0, 1, this.buffer[3]); - """)], - "AcceptsSupportedIntrinsics"); - } - - [TestMethod] - public void AcceptsAMethodOfTheSameNameOnAnotherType() - { - AnalyzerHelper.AssertDiagnostics( - new UnsupportedHlslIntrinsicAnalyzer(), - [ - """ - namespace Ukiyoe; - - public static class Other - { - public static float Fwidth(float x) => x; - - public static void Abort() - { - } - } - """, - Shader(""" - this.buffer[0] = Other.Fwidth(this.buffer[1]); - Other.Abort(); - """) - ], - "AcceptsAnotherType"); - } -} diff --git a/tests/ComputeWeave.Tests.SourceGenerators/Shaders/UnsupportedIntrinsicTests.cs b/tests/ComputeWeave.Tests.SourceGenerators/Shaders/UnsupportedIntrinsicTests.cs new file mode 100644 index 000000000..ee2c618b2 --- /dev/null +++ b/tests/ComputeWeave.Tests.SourceGenerators/Shaders/UnsupportedIntrinsicTests.cs @@ -0,0 +1,187 @@ +using System.Collections.Immutable; +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; + +/// +/// What an intrinsic of the pixel stage is answered with in a compute shader. The shared Hlsl type declares +/// the nine for the pixel shaders written with the same type, and DXC refuses each of them under cs_6_0. +/// +/// +/// The refusal is the generator's, reported from the rewriting the compute generator alone runs. It was an +/// analyzer before, which read every invocation of the compilation whatever type held it, so a project +/// referencing the Direct2D product as well was refused a pixel shader using the clip or a derivative. The +/// last row here is that property: a type that is no compute shader is left alone. +/// +[TestClass] +public class UnsupportedIntrinsicTests +{ + [TestMethod] + [DataRow("AbortIntrinsicTests", "Hlsl.Abort();", "Hlsl.Abort()")] + [DataRow("ClipIntrinsicTests", "Hlsl.Clip(this.buffer[0]);", "Hlsl.Clip(this.buffer[0])")] + [DataRow("DerivativeOfDxIntrinsicTests", "this.buffer[0] = Hlsl.DerivativeOfDx(this.buffer[1]);", "Hlsl.DerivativeOfDx(this.buffer[1])")] + [DataRow("DerivativeOfDxHighPrecisionIntrinsicTests", "this.buffer[0] = Hlsl.DerivativeOfDxHighPrecision(this.buffer[1]);", "Hlsl.DerivativeOfDxHighPrecision(this.buffer[1])")] + [DataRow("DerivativeOfDxLowPrecisionIntrinsicTests", "this.buffer[0] = Hlsl.DerivativeOfDxLowPrecision(this.buffer[1]);", "Hlsl.DerivativeOfDxLowPrecision(this.buffer[1])")] + [DataRow("DerivativeOfDyIntrinsicTests", "this.buffer[0] = Hlsl.DerivativeOfDy(this.buffer[1]);", "Hlsl.DerivativeOfDy(this.buffer[1])")] + [DataRow("DerivativeOfDyHighPrecisionIntrinsicTests", "this.buffer[0] = Hlsl.DerivativeOfDyHighPrecision(this.buffer[1]);", "Hlsl.DerivativeOfDyHighPrecision(this.buffer[1])")] + [DataRow("DerivativeOfDyLowPrecisionIntrinsicTests", "this.buffer[0] = Hlsl.DerivativeOfDyLowPrecision(this.buffer[1]);", "Hlsl.DerivativeOfDyLowPrecision(this.buffer[1])")] + [DataRow("FwidthIntrinsicTests", "this.buffer[0] = Hlsl.Fwidth(this.buffer[1]);", "Hlsl.Fwidth(this.buffer[1])")] + public void AnIntrinsicOfThePixelStageIsDiagnosedAtTheCall(string assemblyName, string body, string expectedCall) + { + AssertReportsOnlyAt(Shader("", "", body), assemblyName, expectedCall); + } + + /// + /// A derivative in a static field initializer, the one route besides a body an intrinsic returning a value + /// can be written into, which the static field rewriter answers for. + /// + [TestMethod] + public void AnIntrinsicOfThePixelStageInAStaticFieldInitializerIsDiagnosedAtTheCall() + { + AssertReportsOnlyAt( + Shader("", "private static readonly float Slope = Hlsl.DerivativeOfDx(1.0f);", "this.buffer[0] = Slope;"), + "DerivativeInInitializerTests", + "Hlsl.DerivativeOfDx(1.0f)"); + } + + /// + /// The message names the intrinsic and the reason, so the author reads why rather than only that. + /// + [TestMethod] + public void TheMessageNamesTheIntrinsicAndTheReason() + { + Diagnostic diagnostic = Run(Shader("", "", "Hlsl.Clip(this.buffer[0]);"), "ClipMessageTests").Diagnostics.Single(); + string message = diagnostic.GetMessage(); + + Assert.IsTrue(message.Contains("Clip"), message); + Assert.IsTrue(message.Contains("no pixel to discard"), message); + } + + /// + /// Intrinsics a compute shader can use, left alone and handed to the compiler, so that the rows above answer + /// for the stage and not for the intrinsic being one of the Hlsl type. + /// + [TestMethod] + public void AnIntrinsicAComputeShaderCanUseIsNotDiagnosed() + { + GeneratorRunResult result = Run( + Shader("", "", "this.buffer[0] = Hlsl.Sqrt(this.buffer[1]) + Hlsl.SmoothStep(0, 1, this.buffer[2]);"), + "SupportedIntrinsicTests"); + + Assert.IsTrue(result.Diagnostics.IsEmpty, string.Join(", ", result.Diagnostics.Select(static diagnostic => diagnostic.Id))); + Assert.AreNotEqual(0, GeneratorHelper.GetGeneratedSource(result.GeneratedSources, "Shaders.Shader").Length); + } + + /// + /// A method of another type carrying the name of a refused intrinsic, which is a method with source the shader + /// imports and not an intrinsic, so the name alone decides nothing. + /// + [TestMethod] + public void AMethodOfTheSameNameOnAnotherTypeIsNotDiagnosed() + { + const string declarations = """ + internal static class Stage + { + public static float Clip(float value) => value; + } + """; + + GeneratorRunResult result = Run(Shader(declarations, "", "this.buffer[0] = Stage.Clip(this.buffer[1]);"), "SameNameOnAnotherTypeTests"); + + Assert.IsTrue(result.Diagnostics.IsEmpty, string.Join(", ", result.Diagnostics.Select(static diagnostic => diagnostic.Id))); + Assert.AreNotEqual(0, GeneratorHelper.GetGeneratedSource(result.GeneratedSources, "Shaders.Shader").Length); + } + + /// + /// A type that is no compute shader, using the intrinsics of the pixel stage the way a Direct2D pixel shader + /// does in a project referencing both products. The generator has no shader to write it into, so it is left + /// alone, which the analyzer this replaces did not do. + /// + [TestMethod] + public void AnIntrinsicOfThePixelStageOutsideAComputeShaderIsLeftAlone() + { + const string declarations = """ + public interface IPixelShaderLike + { + float Execute(); + } + + internal readonly partial struct Effect : IPixelShaderLike + { + private readonly float time; + + public float Execute() + { + Hlsl.Clip(this.time); + + return Hlsl.DerivativeOfDx(this.time); + } + } + """; + + GeneratorRunResult result = Run(Shader(declarations, "", "this.buffer[0] = 1.0f;"), "PixelStageIntrinsicOutsideAShaderTests"); + + Assert.IsTrue(result.Diagnostics.IsEmpty, string.Join(", ", result.Diagnostics.Select(static diagnostic => diagnostic.Id))); + Assert.AreNotEqual(0, GeneratorHelper.GetGeneratedSource(result.GeneratedSources, "Shaders.Shader").Length); + } + + private static string Shader(string declarations, string members, string body) + { + return $$""" + using ComputeWeave; + + namespace Shaders; + + {{declarations}} + + [ThreadGroupSize(DefaultThreadGroupSizes.X)] + [GeneratedComputeShaderDescriptor] + internal readonly partial struct Shader : IComputeShader + { + {{members}} + + private readonly ReadWriteBuffer buffer; + + public void Execute() + { + {{body}} + } + } + """; + } + + private static void AssertReportsOnlyAt(string source, string assemblyName, string expectedCall) + { + ImmutableArray diagnostics = Run(source, assemblyName).Diagnostics; + + // Not made distinct, so that one cause reported twice fails rather than reading as one report + string[] actualIds = [.. diagnostics.Select(static diagnostic => diagnostic.Id).Order()]; + + Assert.IsTrue(actualIds.SequenceEqual(["CMPW0112"]), $"CMPW0112 is not the only report: {string.Join(", ", actualIds)}"); + + // The location is read back through the tree, so a report bound to none fails here rather than reading as a match + Diagnostic diagnostic = diagnostics[0]; + + Assert.AreEqual(expectedCall, diagnostic.Location.SourceTree!.GetText().ToString(diagnostic.Location.SourceSpan)); + } + + private static GeneratorRunResult 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]; + + // A fault discards the output for the whole compilation unit, so it is read ahead of the reports + Assert.IsNull(result.Exception, result.Exception?.ToString()); + + return result; + } +} From ae1e15a873090a9a84383a26eec2eec03499f1cd Mon Sep 17 00:00:00 2001 From: routersys Date: Thu, 17 Sep 2026 09:57:53 +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=E7=94=BB=E7=B4=A0=E6=AE=B5=E9=9A=8E=E3=81=AE=E7=B5=84?= =?UTF-8?q?=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=AE=E8=A1=8C=E3=82=92=E8=B6=B3?= =?UTF-8?q?=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 da151816c..a1b7541ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -761,6 +761,7 @@ The Direct2D authoring projects were removed before the first release and restor | 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` | | Generated HLSL, a call leading back to the declaration it is written in | Writes every call out as it stands and imports a declaration once, claiming its entry before the rewriting so that a method reaching itself ends the import rather than the stack, and nothing reads what that claim just found. HLSL has no recursion, so the shader compiler refuses the function, and the failure lands on the shader type as the forwarded compiler error, naming a function of the generated HLSL, which for a local function or a method of a custom type is a name the generator gave it. Six shapes were measured on the compute path and two on the Direct2D path, each accepted by the generator and refused by the shader compiler | Records every call the generated HLSL holds as it is rewritten, from the declaration being rewritten to the one the call resolves to, into one collection every rewriter for a shader shares the way the requirements are shared, and reads it once every declaration is rewritten for the calls whose target reaches back to the declaration holding them. Each is reported as `CMPW0129`, or `CMPWD2D0101` on the Direct2D path, at the call as the author wrote it, naming the callee and the declaration in C# terms. The read comes after the rewriting rather than during it because no rewriting passes through every edge of a cycle: a method of the shader is written out by the generator's own path rather than imported, a local function is lifted where it is declared, and an imported declaration is rewritten once. Every call on a cycle is reported rather than the one a traversal happens to close it on, so removing any of them resolves the report and the set does not depend on the order the declarations were reached in. A partial declaration is recorded under its defining part, which is what a call to it resolves to; a call to a declaration with no source is left to the report it already had; a static field initializer is not a function and is not a node, while the declarations it imports are recorded by the rewriter importing them | `68ebbaed` | | Generated HLSL, a thread synchronization in a pixel shader | Maps the six memory barriers the shared `Hlsl` type declares for the compute shaders onto their HLSL names on the Direct2D path the way it maps every intrinsic, and writes the call out. A pixel shader runs with no thread group to synchronize, so FXC refuses five of the six under every pixel shader profile with `X3664`, and the failure lands on the shader type as the forwarded compiler error, naming the intrinsic but not the call. The compute path refuses the intrinsics of the pixel stage through an analyzer, so the two products did not mirror each other | Reports `CMPWD2D0102` at the call, from the shared rewriter under the Direct2D compilation symbol, the way the compute path refuses a matrix given to an intrinsic with an out parameter under its own symbol. The names are held by the Direct2D partial of the known methods, with `DeviceMemoryBarrier` left out because FXC accepts it, which was measured one name at a time. The report is on the shader rewriter alone: a barrier returns nothing, so no static field initializer can hold one, and a method an initializer imports is rewritten by the shader rewriter like any other. An analyzer was not chosen, because one would flag a compute shader in a project referencing both products, whereas the rewriter refuses only what the Direct2D generator writes out | `c45fe471`, `e982d57d` | +| Generated HLSL, the intrinsics of the pixel stage in a compute shader | Maps the nine intrinsics of the pixel stage the shared `Hlsl` type declares (`Abort`, `Clip`, the six derivatives and `Fwidth`) onto their HLSL names in a compute shader the way it maps every intrinsic, and writes the call out. DXC refuses each of them under `cs_6_0`, and the failure lands on the shader type as the forwarded compiler error | Reports `CMPW0112` at the call, from the shared rewriter under the compute compilation symbol, naming the intrinsic and the reason DXC refuses it, so the compilation step is skipped and the author sees one report at their own line. The names and reasons are held by the compute partial of the known methods. The report is on the type the two rewriters share, because a derivative returns a value and so can be written into a static field initializer. This replaces the analyzer the fork had placed on the compute product on 2026-08-22, which read every invocation of the compilation whatever type held it: a project referencing the Direct2D product as well loaded it too and was refused a pixel shader using the clip or a derivative, which is legitimate there, and a compute shader was given that report and the forwarded compiler error together. The rewriting branch runs in the compute generator alone, so neither happens | `67b8c0b4` | 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`.