Skip to content

Commit

Permalink
Merge pull request #874 from Sergio0694/dev/d2d-intrinsic-analyzer
Browse files Browse the repository at this point in the history
Add analyzer for input arguments for all 'D2D' intrinsics
  • Loading branch information
Sergio0694 authored Nov 24, 2024
2 parents ca22550 + 6386c4c commit ebef42b
Show file tree
Hide file tree
Showing 35 changed files with 485 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Diagnostics;

namespace ComputeSharp.Core.Intrinsics.Attributes;
namespace ComputeSharp.Core.Intrinsics;

/// <summary>
/// An attribute indicating the native member name of a given HLSL intrinsic.
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.Casts.g.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.Casts.tt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".g.cs"#>
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.Void.g.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.Void.tt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<#@include file="Hlsl.Void.ttinclude" #>
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.g.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Intrinsics/Hlsl.tt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<#@include file="Hlsl.ttinclude" #>
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable IDE0022

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Float/Float2.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#nullable enable
#pragma warning disable CS0660, CS0661
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Float/Float3.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#nullable enable
#pragma warning disable CS0660, CS0661
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Float/Float4.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#nullable enable
#pragma warning disable CS0660, CS0661
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Float/FloatMxN.g.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable CS0660, CS0661

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Int/Int2.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#nullable enable
#pragma warning disable CS0660, CS0661
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Int/Int3.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#nullable enable
#pragma warning disable CS0660, CS0661
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Int/Int4.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#nullable enable
#pragma warning disable CS0660, CS0661
Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/Int/IntMxN.g.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;

#pragma warning disable CS0660, CS0661

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/MatrixType.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
<#
if (typeName == "Float" || typeName == "Int")
{
WriteLine("using ComputeSharp.Core.Intrinsics.Attributes;");
WriteLine("using ComputeSharp.Core.Intrinsics;");
}
#>

Expand Down
2 changes: 1 addition & 1 deletion src/ComputeSharp.Core/Primitives/VectorType.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void GenerateVectorProperties(string typeName, int elementSize)

if (elementTypeName == "Float" || elementTypeName == "Int")
{
WriteLine("using ComputeSharp.Core.Intrinsics.Attributes;");
WriteLine("using ComputeSharp.Core.Intrinsics;");
}
#>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,5 @@ CMPSD2D0079 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://gith
CMPSD2D0080 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0081 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0082 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0083 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0084 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@
<Compile Include="..\ComputeSharp.D2D1\Attributes\Enums\D2D1Filter.cs" Link="ComputeSharp.D2D1\Attributes\Enums\D2D1Filter.cs" />
<Compile Include="..\ComputeSharp.D2D1\Attributes\Enums\D2D1PixelOptions.cs" Link="ComputeSharp.D2D1\Attributes\Enums\D2D1PixelOptions.cs" />
<Compile Include="..\ComputeSharp.D2D1\Intrinsics\D2D.cs" Link="ComputeSharp.D2D1\Intrinsics\D2D.cs" />
<Compile Include="..\ComputeSharp.D2D1\Intrinsics\Attributes\HlslD2DIntrinsicInputTypeAttribute.cs" Link="ComputeSharp.D2D1\Intrinsics\Attributes\HlslD2DIntrinsicInputTypeAttribute.cs" />
<Compile Include="..\ComputeSharp.D2D1\Shaders\Exceptions\FxcCompilationException.cs" Link="ComputeSharp.D2D1\Shaders\Exceptions\FxcCompilationException.cs" />
<Compile Include="..\ComputeSharp.D2D1\Shaders\Interop\D2D1PixelShaderInputType.cs" Link="ComputeSharp.D2D1\Shaders\Interop\D2D1PixelShaderInputType.cs" />
<Compile Include="..\ComputeSharp.D2D1\Shaders\Interop\Helpers\D2D1AssemblyAssociatedMemory.cs" Link="ComputeSharp.D2D1\Shaders\Interop\Helpers\D2D1AssemblyAssociatedMemory.cs" />
<Compile Include="..\ComputeSharp.D2D1\Shaders\Translation\ASCII.cs" Link="ComputeSharp.D2D1\Shaders\Translation\ASCII.cs" />
<Compile Include="..\ComputeSharp.D2D1\Shaders\Translation\D3DCompiler.cs" Link="ComputeSharp.D2D1\Shaders\Translation\D3DCompiler.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ partial class D2DPixelShaderDescriptorGenerator
/// <summary>
/// A helper with all logic to generate the <c>InputTypes</c> properties.
/// </summary>
private static partial class InputTypes
internal static partial class InputTypes
{
/// <summary>
/// Extracts the input info for the current shader.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ComputeSharp.D2D1.Interop;
using ComputeSharp.D2D1.Intrinsics;
using ComputeSharp.SourceGeneration.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using static ComputeSharp.SourceGeneration.Diagnostics.DiagnosticDescriptors;

namespace ComputeSharp.D2D1.SourceGenerators;

/// <summary>
/// A diagnostic analyzer that generates an error whenever an invocation to a D2D intrinsic references an out of range or invalid shader input.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class InvalidD2DInputArgumentAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
[
IndexOutOfRangeForD2DIntrinsic,
InvalidInputTypeForD2DIntrinsic
];

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(static context =>
{
// If we can't get the D2D methods map, we have to stop right away
if (!TryBuildMethodSymbolMap(context.Compilation, out ImmutableDictionary<IMethodSymbol, D2D1PixelShaderInputType?>? methodSymbols))
{
return;
}
context.CancellationToken.ThrowIfCancellationRequested();
// Get the '[D2DInputCount]' symbol, which we need to validate the source input arguments
if (context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DInputCountAttribute") is not { } d2DInputCountAttributeSymbol)
{
return;
}
// We want to register a callback for each method invocation, as all 'D2D' intrinsics would map to one
context.RegisterOperationAction(context =>
{
IInvocationOperation operation = (IInvocationOperation)context.Operation;
// Cheap initial filter: we only care about static methods from the 'D2D' type
if (operation.TargetMethod is not { IsStatic: true, ContainingType.Name: "D2D" } targetMethodSymbol)
{
return;
}
// Second cheap inital filter: we only care about invocations with a constant 'int' argument in first position.
// While we're validating this, let's also get the 'index' parameter, since we need to validate it anyway.
if (operation.Arguments is not [{ Value.ConstantValue: { HasValue: true, Value: int index } }, ..])
{
return;
}
// Validate that the target method is one of the ones we care about, and get the target input type
if (!methodSymbols.TryGetValue(targetMethodSymbol, out D2D1PixelShaderInputType? targetInputType))
{
return;
}
// We have matched a target symbol, so let's try to get the parent shader
if (context.ContainingSymbol.FirstAncestorOrSelf<INamedTypeSymbol>() is not { TypeKind: TypeKind.Struct } typeSymbol)
{
return;
}
// We found a containing type, make sure it has '[D2DInputCount]'
if (!typeSymbol.HasAttributeWithType(d2DInputCountAttributeSymbol))
{
return;
}
// At this point we can assume the shader type is mostly valid: let's get the actual input counts and types
D2DPixelShaderDescriptorGenerator.InputTypes.GetInfo(
typeSymbol,
out int inputCount,
out _,
out _,
out ImmutableArray<uint> inputTypes);
// First validation: the index must be in range
if ((uint)index >= (uint)inputCount)
{
context.ReportDiagnostic(Diagnostic.Create(
IndexOutOfRangeForD2DIntrinsic,
operation.Syntax.GetLocation(),
targetMethodSymbol.Name,
index,
typeSymbol,
inputCount));
}
else if (targetInputType is not null && (D2D1PixelShaderInputType)inputTypes[index] != targetInputType)
{
// Second validation: the input type must match
context.ReportDiagnostic(Diagnostic.Create(
InvalidInputTypeForD2DIntrinsic,
operation.Syntax.GetLocation(),
targetMethodSymbol.Name,
index));
}
}, OperationKind.Invocation);
});
}

/// <summary>
/// Tries to build a map of <see cref="IMethodSymbol"/> instances for all D2D intrinsics and their associated D2D input type.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="methodSymbols">The resulting mapping of resolved <see cref="IMethodSymbol"/> instances.</param>
/// <returns>Whether all requested <see cref="IMethodSymbol"/> instances could be resolved.</returns>
private static bool TryBuildMethodSymbolMap(Compilation compilation, [NotNullWhen(true)] out ImmutableDictionary<IMethodSymbol, D2D1PixelShaderInputType?>? methodSymbols)
{
// Get the 'D2D' symbol, to get methods from it
if (compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2D") is not { } d2DSymbol)
{
methodSymbols = null;

return false;
}

// Get the symbols for all relevant 'D2D' methods
IMethodSymbol?[] d2DMethodSymbols =
[
d2DSymbol.GetMethod(nameof(D2D.GetInput)),
d2DSymbol.GetMethod(nameof(D2D.GetInputCoordinate)),
d2DSymbol.GetMethod(nameof(D2D.SampleInput)),
d2DSymbol.GetMethod(nameof(D2D.SampleInputAtOffset)),
d2DSymbol.GetMethod(nameof(D2D.SampleInputAtPosition))
];

ImmutableDictionary<IMethodSymbol, D2D1PixelShaderInputType?>.Builder inputTypeMethodMap = ImmutableDictionary.CreateBuilder<IMethodSymbol, D2D1PixelShaderInputType?>(SymbolEqualityComparer.Default);

// Validate all methods and build the map
foreach (IMethodSymbol? d2DMethodSymbol in d2DMethodSymbols)
{
// We failed to find a symbol (shouldn't really ever happen), just stop here
if (d2DMethodSymbol is null)
{
methodSymbols = null;

return false;
}

// Lookup the attribute to get the D2D input type (the attribute only exists on the 'D2D' type loaded in the analyzer)
if (typeof(D2D).GetMethod(d2DMethodSymbol.Name).GetCustomAttribute<HlslD2DIntrinsicInputTypeAttribute>() is { } hlslD2DIntrinsicInputTypeAttribute)
{
inputTypeMethodMap.Add(d2DMethodSymbol, hlslD2DIntrinsicInputTypeAttribute.InputType);
}
else
{
// If the method is not annotated, we stil track it, but we will not indicate any exclusive input type.
// This means that the input index validation logic will still work, but we'll skip the input type checks.
inputTypeMethodMap.Add(d2DMethodSymbol, null);
}
}

methodSymbols = inputTypeMethodMap.ToImmutable();

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1237,4 +1237,36 @@ partial class DiagnosticDescriptors
isEnabledByDefault: true,
description: "Shaders can only be annotated with [D2DRequiresDoublePrecisionSupport] to perform validation for use of double precision operations if they are precompiled.",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a D2D intrinsic call with an out of range input.
/// <para>
/// Format: <c>"The D2D intrinsic '{0}' cannot be used for input {1}, as the containing shader {2} only declares {3} input(s) (use a valid argument for the D2D intrinsic, or adjust the number of inputs for the shader using [D2DInputCount])"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor IndexOutOfRangeForD2DIntrinsic = new(
id: "CMPSD2D0083",
title: "Index out of range for D2D intrinsic",
messageFormat: "The D2D intrinsic '{0}' cannot be used for input {1}, as the containing shader {2} only declares {3} input(s) (use a valid argument for the D2D intrinsic, or adjust the number of inputs for the shader using [D2DInputCount])",
category: "ComputeSharp.D2D1.Shaders",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "D2D intrinsics must be used with valid input indices (the number of inputs for a shader can be configured using [D2DInputCount]).",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a D2D intrinsic call with an invalid input type.
/// <para>
/// Format: <c>"The D2D intrinsic '{0}' cannot be used for input {1}, as it does not have the right input type (you can configure the input type of each input using [D2DInputSimple] and [D2DInputComplex] on the shader type)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidInputTypeForD2DIntrinsic = new(
id: "CMPSD2D0084",
title: "Invalid input type for D2D intrinsic",
messageFormat: "The D2D intrinsic '{0}' cannot be used for input {1}, as it does not have the right input type (you can configure the input type of each input using [D2DInputSimple] and [D2DInputComplex] on the shader type)",
category: "ComputeSharp.D2D1.Shaders",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "D2D intrinsics must be used with compatible input types (the input type of each shader input can be configured using [D2DInputSimple] and [D2DInputComplex] on the shader type).",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Reflection;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;
using ComputeSharp.D2D1;

namespace ComputeSharp.SourceGeneration.Mappings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ComputeSharp.Core.Intrinsics.Attributes;
using ComputeSharp.Core.Intrinsics;
using ComputeSharp.D2D1;

namespace ComputeSharp.SourceGeneration.Mappings;
Expand Down
Loading

0 comments on commit ebef42b

Please sign in to comment.