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 @@ -757,6 +757,7 @@ The Direct2D authoring projects were removed before the first release and restor
| Constant buffer layout, a captured struct in a layout cycle | Walks the captured fields into every nested unmanaged struct to lay the constant buffer out, with nothing that notices a type it is already inside. A struct whose fields lead back to itself, which C# refuses with `CS0523` but which the generator and the dispatch data size analyzers run over all the same while the author is in the middle of writing it, is followed around the cycle until the stack runs out, and the process hosting the generator ends with it | Skips a field whose type is already being walked, in the walk that lays the fields out and in the one that only sizes them alike, so the walk ends. The type is then refused by the discovered type check as one HLSL cannot lay out, which reports `CMPW0050` or `CMPWD2D0041` and keeps the shader compiler from being run over it; the C# compiler already names the field, so nothing further is reported here | `245fe8ab` |
| Custom type discovery, a field of a type with no name | Casts the type of every field of a discovered custom type to a named type while exploring the nested types. A pointer field, or a fixed size buffer whose field type is a pointer as well, is valid C# in an unsafe struct and ends the generator with `InvalidCastException`, which Roslyn reports as `CS8785` while discarding the descriptors of every shader in the compilation unit | Refuses a custom type holding such a field as an invalid discovered type, reporting `CMPW0050` or `CMPWD2D0041` for the type the way a `bool` field is reported, and stops exploring it. HLSL has no pointer, and while a struct in HLSL does hold a fixed size array, the generator writes no mapping for a fixed size buffer, neither the field itself nor the constant buffer layout an array in one would need, so there is nothing to write for either as things stand; a shader whose own field is one of these was already refused by the field check, and stays so | `c8cc970f` |
| Generated HLSL, 64 bit integer literals and constants | Writes a `long` or a `ulong` literal with the suffix the author wrote and a constant of either type as its digits, and leaves a `string` constant as an identifier. The shader compiler reads the digits as a 32 bit value, so a negative literal on the left of a comparison with an unsigned value is compared as unsigned, and a value past the 32 bit range is truncated, with no diagnostic from either compiler; the refusal of a widened operand reads the left operand alone, which a literal is not a conversion of, so a 64 bit literal on that side escapes it as well | Refuses a literal or a constant whose type the generator does not map by tracking that type, so the shader is reported with `CMPW0050`, or `CMPWD2D0041` on the Direct2D path, the way a local of that type already is. The three sites that turn a constant into an HLSL definition share one helper whose failure path is that refusal, so a string constant is refused the same way rather than left as an identifier the shader compiler cannot resolve. No shader in this repository carries such a literal or constant, so the generated files are unchanged, which was compared in full | `1c3c0ecf`, `8c5db0ab` |
| Generated HLSL, native integer constants | Writes a `nint` or a `nuint` constant as its digits, the value being boxed as a 32 bit one, so the shader computes with it at 32 bits while C# computes with it at the width of the platform: a result past 32 bits wraps, and the refusal of a widened operand reads the left operand alone, so the constant on that side escapes it as a 64 bit one does | Refuses a native integer constant by tracking its type, the way a local of that type and a 64 bit constant already are, so the shader is reported with `CMPW0050`, or `CMPWD2D0041` on the Direct2D path. The one function that decides which constants have an HLSL literal takes the type of the constant as well as its value, since the value cannot tell a native integer apart. No shader in this repository carries such a constant, so the generated files are unchanged | `e33998ba` |

The ledger is kept current by derivation, not by memory: `build/audit-upstream-divergence.ps1` lists every commit after the marker below that modifies an inherited `.cs` file under `src/` and is not cited by a row. That is the whole of what it reads. Inherited project files, documents and test sources are outside it, so a divergence in one of those reaches the ledger only because someone wrote the row by hand. Run it before a release. When the queue is not empty, either add a row or decide that the commit is not a divergence, and then move the marker. The ledger is audited through commit `c8cc970f`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,12 @@ protected static ExpressionSyntax ParseMappedExpression(string mapping)
/// <param name="constantName">The name of the HLSL definition, if the constant can be written as one.</param>
/// <returns>Whether <paramref name="field"/> can be written as an HLSL definition.</returns>
/// <remarks>
/// A constant whose value has no HLSL literal is refused by tracking its type instead, the way a local of
/// that type is: the shader compiler reads a 64 bit integer as a 32 bit one, and a string has no HLSL form.
/// A constant with no HLSL literal is refused by tracking its type instead, the way a local of that type is:
/// the shader compiler reads a 64 bit integer as a 32 bit one, and a string has no HLSL form.
/// </remarks>
protected bool TryRewriteConstantReference(IFieldSymbol field, [NotNullWhen(true)] out IdentifierNameSyntax? constantName)
{
if (!TryGetConstantLiteral(field.ConstantValue, out string? constantLiteral))
if (!TryGetConstantLiteral(field.Type, field.ConstantValue, out string? constantLiteral))
{
_ = TrackType(field.Type);

Expand All @@ -618,8 +618,29 @@ protected bool TryRewriteConstantReference(IFieldSymbol field, [NotNullWhen(true
return true;
}

protected static unsafe bool TryGetConstantLiteral(object? value, out string? literal)
/// <summary>
/// Gets the HLSL literal a constant is written as, if its type and value have one.
/// </summary>
/// <param name="type">The type of the constant.</param>
/// <param name="value">The value of the constant.</param>
/// <param name="literal">The resulting HLSL literal, if the constant has one.</param>
/// <returns>Whether the constant has an HLSL literal.</returns>
/// <remarks>
/// The shader compiler reads the digits of an integer as a 32 bit value, so a 64 bit integer has no literal,
/// and neither has a native integer, which C# computes with at the width of the platform. The former is told
/// by its value and the latter by its type, since the value of a native integer constant is boxed as a 32 bit
/// one. A string has no HLSL form at all.
/// </remarks>
protected static unsafe bool TryGetConstantLiteral(ITypeSymbol type, object? value, out string? literal)
{
// The value of a native integer constant is boxed as a 32 bit one, so its type is what tells it apart
if (type.IsNativeIntegerType)
{
literal = null;

return false;
}

if (value is bool flag)
{
literal = flag ? "true" : "false";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,41 @@ public float4 Execute()
CSharpGeneratorTest<D2DPixelShaderDescriptorGenerator>.VerifyDiagnostics(source, "CMPWD2D0041");
}

/// <summary>
/// A native integer constant, whose value is boxed as a 32 bit one while C# computes with it at the width of
/// the platform, so it is told by its type.
/// </summary>
[TestMethod]
public void ANativeIntegerConstantIsDiagnosed()
{
const string source = """
using ComputeWeave;
using ComputeWeave.D2D1;
using float4 = global::ComputeWeave.Float4;

namespace MyNamespace;

[D2DInputCount(0)]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
internal readonly partial struct MyShader : ID2D1PixelShader
{
private const nint Native = 5;

private readonly int signed;

public float4 Execute()
{
float value = (float)(Native + this.signed);

return value;
}
}
""";

CSharpGeneratorTest<D2DPixelShaderDescriptorGenerator>.VerifyDiagnostics(source, "CMPWD2D0041");
}

/// <summary>
/// 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace ComputeWeave.Tests.SourceGenerators.Shaders;
/// Both are refused by tracking the type, the way a local of that type already is, so the report is the one for
/// an invalid discovered type. A constant is reached by its name alone, through a member access in the body, and
/// through a member access in a static field initializer, which are three rewriting sites, so each has a row.
/// A native integer constant is boxed as a 32 bit value while C# computes with it at the width of the platform,
/// so it is told by its type rather than by its value: it has a row of each sign.
/// </para>
/// <para>
/// The last rows are the controls. A constant of a type the set does have is still written as a definition, a
Expand All @@ -41,6 +43,8 @@ internal static class Helper
public const long Wide = 5;

public const uint Narrow = 5;

public const nuint UnsignedNative = 5;
}

[ThreadGroupSize(DefaultThreadGroupSizes.X)]
Expand All @@ -49,6 +53,8 @@ internal static class Helper
{
private const long Big = 5;

private const nint Native = 5;

private const int Small = 5;

private const decimal Half = 0.5m;
Expand Down Expand Up @@ -102,6 +108,18 @@ public void ALongConstantInAStaticFieldInitializerIsRefused()
AssertReports(Shader("this.buffer[0] = Seeded;", "Helper.Wide + 1"), "LongConstantInitializerTests", "CMPW0050");
}

[TestMethod]
public void ANativeIntegerConstantIsRefused()
{
AssertReports(Shader("this.buffer[0] = (float)(Native + this.signed);"), "NativeIntegerConstantTests", "CMPW0050");
}

[TestMethod]
public void AnUnsignedNativeIntegerConstantReadThroughAMemberAccessIsRefused()
{
AssertReports(Shader("this.buffer[0] = (float)(Helper.UnsignedNative + this.unsigned);"), "UnsignedNativeIntegerConstantTests", "CMPW0050");
}

/// <summary>
/// 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
Expand Down
Loading