Skip to content
Merged
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ The Direct2D authoring projects were removed before the first release and restor
| Generated HLSL, a variable a static field initializer would have to declare | Writes an out argument into a call in a static field initializer as the author wrote it, declaration and all, so the generated HLSL declares a variable inside an argument list and the shader compiler rejects it while naming generated code. A discarded out argument answers the same way, the discard reaching the generated HLSL as an identifier nothing declares. The shader body declares the variable at the start of the body and passes it to the call, so where the author writes the call decides whether it works | Reports `CMPW0126`, or `CMPWD2D0098` on the Direct2D path, at the declaration and at the discard alike. The two reporting sites are the two places the rewriter for a body introduces such a variable, so what is refused is what that rewriter hoists rather than the shapes that happened to be measured. Giving the variable a global static of its own instead was rejected: one storage would be shared by every invocation of the shader, and HLSL leaves the order of global static initializers undefined, which is the hazard `CMPW0124` already refuses. The report names the source the author wrote, where the failure it replaces named the shader type and quoted a line of generated HLSL | `121de6e8` |
| Generated HLSL, a static field of the shader reaching itself | Rewrites the initializer of a static field of the shader without claiming an entry for it, the collection the other rewritings claim in holding the fields of other types, and leaves a static method of the shader for the generator to write out, which it does before it rewrites any initializer. An initializer that reaches the field it initializes therefore finds nothing on any of its routes: nothing is reported, the generator does not fault, and the shader compiler accepts generated HLSL that reads a global through a function reading it back, so the shader computes a value C# never produces. Reading the field by its own name and reading it through the shader type differ only in that the second writes the type name into the generated HLSL, which the compiler then rejects while naming generated code | Reports the read from a walk over what every initializer reaches, run once after the initializers are rewritten, which the row on a static field accessed before its initializer has run describes. Nothing is claimed for a field of the shader and nothing is reported as an initializer is rewritten: a rewriting does not pass through every declaration an initializer reaches, a declaration being imported once and a method of the shader never, which is what left routes unanswered while the read was reported where it was rewritten. Before the walk, the read was answered where it was written, by name alone in the shared rewriter and through the shader type in each of the two rewriters, and a call to a static method of the shader was walked from the rewriting site, which reached neither a declaration the body or an earlier initializer had imported first, nor a declaration in the middle of its own import, nor a field of another type the walk found. Two fields whose initializers reach each other through a static method are answered as a read C# performs too early rather than as a cycle, C# running the first initializer to completion before the second starts. The type symbol for the shader moves to the type both rewriters derive from, the shared logic having to tell a member of the shader from a member of any other type, and each of the two having held its own copy | `41027c2f`, `18e3c03d`, `6d4e2354` |
| Generated HLSL, a static field accessed before its initializer has run | Writes an access to a static field into the generated HLSL as it stands wherever the access is performed, and writes the static fields out in an order derived from what each initializer reached. C# runs the static field initializers of a type once, in the order the fields are declared, and a field of that type whose turn has not come yet holds the default value of its type: a read of it is that default, and a write to it is discarded when its initializer runs, whether the initializer reaches the field directly, through a method of the shader, through an imported declaration, or from the initializer of an imported field. The generated HLSL reproduces neither: the shader compiler folds the initializer the later field carries when it can, so the shader computes the initialized value where C# computes the default and keeps a write C# discards, leaves the read undefined otherwise, and does not compile the direct read at all, naming a global declared later in generated code. A field read through a function before its initializer has run was measured to compute 10 on DXC and FXC alike where C# computes 0, with neither compiler reporting anything, and a write through a function to a later field carrying a constant initializer was measured to stay on DXC where C# computes the initializer's value, FXC refusing that write while naming generated code. An initializer reaching back into the field it initializes is the same read, and the cycle rows above answered it only where a rewriting passed through the read while the field was claimed | Reports `CMPW0124`, or `CMPWD2D0096` on the Direct2D path, at the access, naming the field accessed and the initializer that performs the access too early. The report comes from one walk over every static field the generated HLSL declares, the shader's own and the imported ones alike, run once after the initializers are rewritten: from each initializer it follows the method, local function or constructor a call resolves to and the initializer of a static field of another type, whose initializers C# runs when that type is first touched, and reports a read or a write of a field of the initializer's own type whose turn has not come and that carries an initializer, the field being initialized among them for a read. A field carrying no initializer holds the default value of its type in C# and in the generated HLSL alike, and a write to the field being initialized is overwritten by its initializer in both, so neither is reported; both were measured on DXC. A write is a simple assignment to the field or an out argument, and every other reference reads the field, a compound assignment and a ref argument among them. A field of the same type is not walked into, its initializer having run already or not running until its own turn, a local function is walked only through a call, C# not running one that is never called, and an access two initializers both perform too early is reported once, for the first of the two in declaration order, so every access is one report. The walk does not follow the order of the statements it passes, so a read after a write in the same declaration is reported like any other. The same descriptor answers for an initializer reaching itself, that being the first field whose turn has not come, so the identifier the earlier rows introduced widens rather than a second one being added, and its message names both fields. The walk resolves semantic information only for the kinds an access, a call or a construction can be written as, which is the pre-filter the rewriting uses too | `6d4e2354` |
| Generated HLSL, a static field assigned by a static constructor | Imports a static field by rewriting its initializer alone, and writes no static constructor into the generated HLSL. C# runs the static constructor of a type when the type is first touched, after the initializers of its static fields, so a static field the constructor assigns holds that value from then on, whereas the generated HLSL declares the field with the value of its initializer, or with none, which both shader compilers read as zero without reporting anything. A `readonly` field assigned only by the constructor was measured to compute 5 in C# and 0 in the generated HLSL on DXC and FXC alike, and a mutable one the same | Reports `CMPW0130`, or `CMPWD2D0103` on the Direct2D path, at the write, naming the field and the type whose static constructor performs it. The report comes from a walk over the static constructor of every type the rewriting touched, which are the shader type and the types of every imported method, constructor and static field, each followed through the method, local function or constructor a call resolves to, the way the walk of the row above follows an initializer, the two sharing that walk. A write to any static field the generated HLSL declares is reported, whichever of those types the constructor belongs to, C# running that constructor when its type is touched and the generated HLSL never; a read is not, and neither is a write to a field the generated HLSL does not declare. A write is an assignment of any kind, an increment or a decrement, or a `ref` or `out` argument, a `ref` one counting because the callee is free to write through it. A write two constructors both reach is reported once, for the first of the two in the order the types were touched | `af839246`, `cf1905fe` |
| Generated HLSL, the requirements a shader raises | Keeps what a rewriting gathers on the rewriter that gathered it, and carries it out by hand wherever one rewriter creates another. Which of those places carry anything is left to a hook the specialized rewriter may implement, and a hook nothing implements is silent. On the Direct2D path the scene position requirement is raised inside the rewriter for a called method, an instance method, a constructor, the initializer of an external static field, an initializer of the shader itself, and a declaration imported by one, and reaches the generator from none of them, so `CMPWD2D0045` answers only for a call written in the shader body. The generated HLSL then reads the position without the define its header needs, and the author is left with the failure FXC raises against generated code, which names a line of generated source and not the attribute to add. On the compute path the three initializer routes lose the requirement that a dispatch cover whole thread groups, so a range that cuts a group in half is accepted for a shader that waits for one. Two of the hooks are declared and implemented with no call site anywhere in the tree | Holds the requirements in one instance per shader, handed to every rewriter created for it, so a requirement is raised where it is found and read where the shader is written, with no path in between having to carry it. Which requirements exist still differs between the two generators, so they are declared in a partial declaration each one carries, the way the rewriters themselves already are, and a rewriter that is not handed them does not compile. The two hooks with no call site are removed along with the carrying they existed for, and so is the suppression that hid them: measured over both generators, that rule had nothing else to report, so a hook declared and never called now stops the build. The other rule in that suppression is kept, one field being assigned and never read where the shared file compiles into the Direct2D generator, and what it hides is written beside it. The hook that raises a requirement from a mapped call moves to the type both rewriters derive from: what a call requires does not depend on whether it was written in a body or in an initializer, and declaring it twice was the shape that left one of the two uncalled. The sampler requirement is left where it is, a texture being refused as a static field and as a parameter alike, so only a rewriting of the shader's own members can raise it, which was measured rather than read. Measured on both paths over the six places a rewriter creates another one and the one each generator creates for a static field of the shader itself | `44f39c86`, `6b8f4af7`, `f937b857`, `33756710` |
| Generated HLSL, primary constructors | Refuses the construction of a type declaring a primary constructor, which is deliberate, the captures of such a constructor reaching the members of the type in ways the rewriting cannot follow. It reports the diagnostic for a constructor with no source to analyze, because the check that reaches it is whether a constructor declaration can be found, and a primary constructor has none of its own. The author reads a reason that is visibly not the one, the source being right there, and the same refusal covers a constructor from another assembly, for which the reason is correct | Reports `CMPW0120`, or `CMPWD2D0093` on the Direct2D path, when the constructor is declared in source, and keeps the older diagnostic for one that is not. The two are told apart by whether the constructor has any declaring syntax reference at all, a primary constructor having one on the type declaration. The refusal itself is unchanged, and so is the shader type's own primary constructor, whose captures become the shader fields | `00f95213` |
| Generated HLSL, generic local functions | Lifts a local function to a top level HLSL function whether or not it is called, carrying its type parameter list with it, and asks whether a method is generic only where a call is rewritten. A generic local function that is never called reaches the HLSL compiler with `<T>` on it, and one that is called is reported against the call rather than against the declaration the author has to change | The declaration is refused, so the report names it, and the lifted function, which still carries its type parameter list, never reaches the shader compiler. The call site leaves a local function alone, because the declaration has already answered and two reports would name two places for one cause | `e364c7e4` |
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` の診断113種類として報告します。対象は属性の位置、ホストとパイプラインメソッドの形、スロットの宣言、資源の契約、生成されるオーバーロードの衝突です。一部にはコード修正が付きます。
以上の宣言はアナライザーが検査し、接頭辞 `CMPW` の診断114種類として報告します。対象は属性の位置、ホストとパイプラインメソッドの形、スロットの宣言、資源の契約、生成されるオーバーロードの衝突です。一部にはコード修正が付きます。

---

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

これらの宣言は、`CMPWD2D` を接頭辞とする102件の診断を報告するアナライザーが検査します。シェーダーは Direct2D が受け付ける DXBC へ FXC でコンパイルします。`d3dcompiler_47.dll` は Windows に同梱されているため、このパッケージはコンパイラーを同梱しません。
これらの宣言は、`CMPWD2D` を接頭辞とする103件の診断を報告するアナライザーが検査します。シェーダーは 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 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.
The declarations above are checked by analyzers that report 114 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 102 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 103 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
Expand Up @@ -7,3 +7,4 @@ Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CMPWD2D0101 | ComputeWeave.D2D1.Shaders | Error | [Documentation](https://github.com/routersys/ComputeWeave)
CMPWD2D0102 | ComputeWeave.D2D1.Shaders | Error | [Documentation](https://github.com/routersys/ComputeWeave)
CMPWD2D0103 | ComputeWeave.D2D1.Shaders | Error | [Documentation](https://github.com/routersys/ComputeWeave)
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ private static ImmutableArray<HlslStaticField> GetStaticFields(
CancellationToken token)
{
using ImmutableArrayBuilder<HlslStaticField> declared = new();
List<IFieldSymbol> declaredFields = [];

foreach (ISymbol memberSymbol in structDeclarationSymbol.GetMembers())
{
Expand Down Expand Up @@ -378,6 +379,7 @@ private static ImmutableArray<HlslStaticField> GetStaticFields(
typeDeclaration,
assignmentExpression,
HlslDefinitionsSyntaxProcessor.GetStaticFieldOrder(staticFieldDefinitions)));
declaredFields.Add(fieldSymbol);

// An initializer may import a method that declares a local function, which HLSL cannot
// nest, so the ones lifted out of it are carried up to be written like any other
Expand All @@ -397,6 +399,18 @@ private static ImmutableArray<HlslStaticField> GetStaticFields(
diagnostics,
token);

// A static field a static constructor assigns is reported (same as in the DX12 generator)
HlslDefinitionsSyntaxProcessor.ReportStaticFieldsAssignedByAStaticConstructor(
structDeclarationSymbol,
declaredFields,
staticMethods,
instanceMethods,
constructors,
staticFieldDefinitions,
semanticModel,
diagnostics,
token);

// One sequence ordered by when each field finished (same as in the DX12 generator)
HlslStaticField[] fields = [.. declared.WrittenSpan, .. staticFieldDefinitions.Values];

Expand Down
Loading
Loading