Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ The Direct2D authoring projects were removed before the first release and restor
| 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` |
| 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` |

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`.

Expand Down
4 changes: 2 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ GPUが書いたバッファを、以降のシェーダーへ読み取り専用

### コンパイル時の検証

以上の宣言はアナライザーが検査し、接頭辞 `CMPW` の診断112種類として報告します。対象は属性の位置、ホストとパイプラインメソッドの形、スロットの宣言、資源の契約、生成されるオーバーロードの衝突です。一部にはコード修正が付きます。
以上の宣言はアナライザーが検査し、接頭辞 `CMPW` の診断113種類として報告します。対象は属性の位置、ホストとパイプラインメソッドの形、スロットの宣言、資源の契約、生成されるオーバーロードの衝突です。一部にはコード修正が付きます。

---

Expand Down Expand Up @@ -297,7 +297,7 @@ ReadOnlyMemory<byte> bytecode = D2D1PixelShader.LoadBytecode<DifferenceEffect>()
ReadOnlyMemory<byte> buffer = D2D1PixelShader.GetConstantBuffer(new DifferenceEffect(1));
```

これらの宣言は、`CMPWD2D` を接頭辞とする100件の診断を報告するアナライザーが検査します。シェーダーは Direct2D が受け付ける DXBC へ FXC でコンパイルします。`d3dcompiler_47.dll` は Windows に同梱されているため、このパッケージはコンパイラーを同梱しません。
これらの宣言は、`CMPWD2D` を接頭辞とする101件の診断を報告するアナライザーが検査します。シェーダーは Direct2D が受け付ける DXBC へ FXC でコンパイルします。`d3dcompiler_47.dll` は Windows に同梱されているため、このパッケージはコンパイラーを同梱しません。

---

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ The budget covers the resources the device creates itself. A device using an all

### Compile-time validation

The declarations above are checked by analyzers that report 112 diagnostics with the `CMPW` prefix, covering attribute placement, host and pipeline method shape, slot declaration, resource contracts and generated overload conflicts. Some carry a code fix.
The declarations above are checked by analyzers that report 113 diagnostics with the `CMPW` prefix, covering attribute placement, host and pipeline method shape, slot declaration, resource contracts and generated overload conflicts. Some carry a code fix.

---

Expand Down Expand Up @@ -297,7 +297,7 @@ ReadOnlyMemory<byte> bytecode = D2D1PixelShader.LoadBytecode<DifferenceEffect>()
ReadOnlyMemory<byte> buffer = D2D1PixelShader.GetConstantBuffer(new DifferenceEffect(1));
```

The declarations are checked by analyzers that report 100 diagnostics with the `CMPWD2D` prefix. Shaders are compiled to DXBC with FXC, which is what Direct2D accepts; `d3dcompiler_47.dll` ships with Windows, so the package bundles no compiler of its own.
The declarations are checked by analyzers that report 101 diagnostics with the `CMPWD2D` prefix. Shaders are compiled to DXBC with FXC, which is what Direct2D accepts; `d3dcompiler_47.dll` ships with Windows, so the package bundles no compiler of its own.

---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CMPWD2D0101 | ComputeWeave.D2D1.Shaders | Error | [Documentation](https://github.com/routersys/ComputeWeave)
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ public static string GetHlslSource(
// is read here whichever declaration it was found in and however that declaration was reached
HlslShaderRequirements requirements = new();

// Every rewriter records the calls it writes out into this one collection, so a call leading back
// to the declaration it is written in is read here once every declaration is rewritten
List<HlslCall> calls = [];

// Explore the syntax tree and extract the processed info
(string entryPoint, ImmutableArray<HlslMethod> processedMethods) = GetProcessedMethods(
diagnostics,
Expand All @@ -88,6 +92,7 @@ public static string GetHlslSource(
constantDefinitions,
staticFieldDefinitions,
requirements,
calls,
token);

token.ThrowIfCancellationRequested();
Expand All @@ -105,11 +110,18 @@ public static string GetHlslSource(
constantDefinitions,
staticFieldDefinitions,
requirements,
calls,
initializerLocalFunctions,
token);

token.ThrowIfCancellationRequested();

// HLSL has no recursion, and whether a call leads back is a property of every declaration the
// generated HLSL holds, so the calls are read once every declaration is rewritten
HlslDefinitionsSyntaxProcessor.ReportRecursiveCalls(diagnostics, calls);

token.ThrowIfCancellationRequested();

// The static methods are gathered after the static fields, because an initializer can import one
// of its own. Where they land among the methods does not matter: every forward declaration is
// written before any of the definitions, and before the static fields that call them.
Expand Down Expand Up @@ -314,6 +326,7 @@ private static ImmutableArray<HlslMethod> AppendImportedMethods(
/// <param name="constantDefinitions">The collection of discovered constant definitions.</param>
/// <param name="staticFieldDefinitions">The collection of discovered static field definitions.</param>
/// <param name="requirements">The requirements gathered for the shader being rewritten.</param>
/// <param name="calls">The collection of calls the generated HLSL holds, recorded from the declarations they are written in.</param>
/// <param name="localFunctions">The collection to receive the local functions lifted out of imported methods.</param>
/// <param name="token">The <see cref="CancellationToken"/> used to cancel the operation, if needed.</param>
/// <returns>A sequence of static constant fields in <paramref name="structDeclarationSymbol"/>.</returns>
Expand All @@ -328,6 +341,7 @@ private static ImmutableArray<HlslStaticField> GetStaticFields(
IDictionary<IFieldSymbol, string> constantDefinitions,
IDictionary<IFieldSymbol, HlslStaticField> staticFieldDefinitions,
HlslShaderRequirements requirements,
ICollection<HlslCall> calls,
IDictionary<IMethodSymbol, LocalFunctionStatementSyntax> localFunctions,
CancellationToken token)
{
Expand All @@ -351,6 +365,7 @@ private static ImmutableArray<HlslStaticField> GetStaticFields(
constantDefinitions,
staticFieldDefinitions,
requirements,
calls,
diagnostics,
token,
out string? name,
Expand Down Expand Up @@ -402,6 +417,7 @@ private static ImmutableArray<HlslStaticField> GetStaticFields(
/// <param name="constantDefinitions">The collection of discovered constant definitions.</param>
/// <param name="staticFieldDefinitions">The collection of discovered static field definitions.</param>
/// <param name="requirements">The requirements gathered for the shader being rewritten.</param>
/// <param name="calls">The collection of calls the generated HLSL holds, recorded from the declarations they are written in.</param>
/// <param name="token">The <see cref="CancellationToken"/> used to cancel the operation, if needed.</param>
/// <returns>A sequence of processed methods in <paramref name="structDeclarationSymbol"/>, and the entry point.</returns>
private static (string EntryPoint, ImmutableArray<HlslMethod> Methods) GetProcessedMethods(
Expand All @@ -416,6 +432,7 @@ private static (string EntryPoint, ImmutableArray<HlslMethod> Methods) GetProces
IDictionary<IFieldSymbol, string> constantDefinitions,
IDictionary<IFieldSymbol, HlslStaticField> staticFieldDefinitions,
HlslShaderRequirements requirements,
ICollection<HlslCall> calls,
CancellationToken token)
{
using ImmutableArrayBuilder<HlslMethod> methods = new();
Expand Down Expand Up @@ -462,6 +479,7 @@ private static (string EntryPoint, ImmutableArray<HlslMethod> Methods) GetProces
constantDefinitions,
staticFieldDefinitions,
requirements,
calls,
diagnostics,
token,
isShaderEntryPoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1537,4 +1537,24 @@ partial class DiagnosticDescriptors
isEnabledByDefault: true,
description: "HLSL needs a custom type declared ahead of every declaration naming it, and the generated HLSL declares the custom types in an order that satisfies the fields and the member method prototypes each of them holds. A field cannot name a type declared later, as C# reports a cycle through fields, so an order always exists for fields alone. Member signatures can name each other in a cycle, and then no order puts every type ahead of the prototypes naming it. The compute generator resolves that with a type forward declaration, which DXC accepts, but FXC has no such declaration, so a pixel shader with such a cycle would be written into HLSL that cannot build, and the failure would name generated code the author never wrote. The member reported is the prototype read before the type it names is declared; moving it out of its type, for instance into a static method taking both types, removes the cycle.",
helpLinkUri: "https://github.com/routersys/ComputeWeave");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a call leading back to the declaration it is written in.
/// <para>
/// Format: <c>"The call to {0} cannot be used in a D2D1 pixel shader (it leads back to {1}, the declaration it is written in, and HLSL has no recursion)"</c>.
/// </para>
/// </summary>
/// <remarks>
/// This is the Direct2D counterpart of the compute diagnostic. The rewriting that records the calls is
/// shared, so leaving one of the two out would refuse the same call on one path and not the other.
/// </remarks>
public static readonly DiagnosticDescriptor RecursiveCall = new(
id: "CMPWD2D0101",
title: "Recursive call",
messageFormat: "The call to {0} cannot be used in a D2D1 pixel shader (it leads back to {1}, the declaration it is written in, and HLSL has no recursion)",
category: "ComputeWeave.D2D1.Shaders",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "HLSL has no recursion, so the shader compiler refuses a function that reaches itself through the functions it calls. Without this the call is written out as it stands and the shader compiler answers at the shader type, naming the function in the generated HLSL, which for a local function or a method of a custom type is a name the generator gave it. What is reported is every call on a cycle, at the call as the author wrote it, so a cycle through two declarations is reported at both of the calls closing it and removing either one resolves both. The calls followed are the ones the generated HLSL holds: a method of the shader, a method or constructor of another type the shader reaches, and a local function, which is written out whether or not it is called.",
helpLinkUri: "https://github.com/routersys/ComputeWeave");
}
6 changes: 5 additions & 1 deletion src/ComputeWeave.SourceGeneration.Hlsl/Models/TypeAliases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@
// after every field its initializer reads, so writing them by it puts each after the ones it names
global using HlslStaticField = (string Name, string? TypeDeclaration, string? Assignment, int Order);
global using HlslSharedBuffer = (string Name, string Type, int? Count);
global using HlslMethod = (string Signature, string Declaration);
global using HlslMethod = (string Signature, string Declaration);

// A call the generated HLSL holds, from the declaration it is written in to the one it resolves to,
// with the call as the author wrote it. Every rewriter for one shader records into the same collection
global using HlslCall = (Microsoft.CodeAnalysis.IMethodSymbol Caller, Microsoft.CodeAnalysis.IMethodSymbol Callee, Microsoft.CodeAnalysis.SyntaxNode Site);
Loading
Loading