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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Reflection;
using ComputeSharp.Core.Intrinsics.Attributes;

#pragma warning disable IDE0055

namespace ComputeSharp.SourceGeneration.Mappings;

/// <summary>
Expand All @@ -22,8 +24,8 @@ internal static partial class HlslKnownKeywords
private static IReadOnlyCollection<string> BuildKnownKeywordsMap()
{
// HLSL keywords
HashSet<string> knownKeywords = new(new[]
{
HashSet<string> knownKeywords = new(
[
"asm", "asm_fragment", "cbuffer", "buffer", "texture", "centroid",
"column_major", "compile", "discard", "dword", "export", "fxgroup",
"groupshared", "half", "inline", "inout", "line", "lineadj", "linear",
Expand All @@ -40,9 +42,9 @@ private static IReadOnlyCollection<string> BuildKnownKeywordsMap()
"Texture3D", "Texture2DMS", "Texture2DMSArray", "TextureCube", "TextureCubeArray",
"SV_DispatchThreadID", "SV_DomainLocation", "SV_GroupID", "SV_GroupIndex", "SV_GroupThreadID",
"SV_GSInstanceID", "SV_InsideTessFactor", "SV_OutputControlPointID", "SV_TessFactor",
"SV_InnerCoverage", "SV_StencilRef"
"SV_InnerCoverage", "SV_StencilRef", "globallycoherent"

});
]);

// HLSL primitive names
foreach (Type? type in HlslKnownTypes.KnownVectorTypes.Concat(HlslKnownTypes.KnownMatrixTypes))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ CMPS0054 | ComputeSharp.Shaders | Error | [Documentation](https://github.com/Ser
CMPS0055 | ComputeSharp.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPS0056 | ComputeSharp.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPS0057 | ComputeSharp.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPS0058 | ComputeSharp.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ private static (
continue;
}

AttributeData? attribute = fieldSymbol.GetAttributes().FirstOrDefault(static a => a.AttributeClass?.ToDisplayString() == "ComputeSharp.GroupSharedAttribute");
AttributeData? groupSharedAttribute = fieldSymbol.GetAttributes().FirstOrDefault(static a => a.AttributeClass?.ToDisplayString() == "ComputeSharp.GroupSharedAttribute");

// Group shared fields must be static
if (attribute is not null)
if (groupSharedAttribute is not null)
{
diagnostics.Add(InvalidGroupSharedFieldDeclaration, fieldSymbol, structDeclarationSymbol, fieldName);
}
Expand Down Expand Up @@ -169,6 +169,13 @@ private static (
types.Add((INamedTypeSymbol)typeSymbol.TypeArguments[0]);
}

// Check whether the resource is a globallycoherent writeable buffer
if (HlslKnownTypes.IsReadWriteBufferType(metadataName) &&
fieldSymbol.TryGetAttributeWithFullyQualifiedMetadataName("ComputeSharp.GloballyCoherentAttribute", out _))
{
typeName = "globallycoherent " + typeName;
}

// Add the current mapping for the name (if the name used a reserved keyword)
resources.Add((metadataName, mapping ?? fieldName, typeName));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Immutable;
using ComputeSharp.SourceGeneration.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using static ComputeSharp.SourceGeneration.Diagnostics.DiagnosticDescriptors;

namespace ComputeSharp.SourceGenerators;

/// <summary>
/// A diagnostic analyzer that generates an error whenever the [GloballyCoherent] attribute is used on an invalid target field.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class InvalidGloballyCoherentFieldDeclarationAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidGloballyCoherentFieldDeclaration);

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

context.RegisterCompilationStartAction(static context =>
{
// Get the IComputeShader, IComputeShader<TPixel>, ReadWriteBuffer<T> and [GloballyCoherent] symbols
if (context.Compilation.GetTypeByMetadataName("ComputeSharp.IComputeShader") is not { } computeShaderSymbol ||
context.Compilation.GetTypeByMetadataName("ComputeSharp.IComputeShader`1") is not { } pixelShaderSymbol ||
context.Compilation.GetTypeByMetadataName("ComputeSharp.ReadWriteBuffer`1") is not { } readWriteBufferSymbol ||
context.Compilation.GetTypeByMetadataName("ComputeSharp.GloballyCoherentAttribute") is not { } globallyCoherentAttributeSymbol)
{
return;
}

context.RegisterSymbolAction(context =>
{
if (context.Symbol is not IFieldSymbol { ContainingType: { } typeSymbol } fieldSymbol)
{
return;
}

// If the current type does not have [GloballyCoherent], there is nothing to do
if (!fieldSymbol.TryGetAttributeWithType(globallyCoherentAttributeSymbol, out AttributeData? attribute))
{
return;
}

// Emit a diagnostic if the field is not valid (either static, not of ReadWriteBuffer<T> type, or not within a compute shader type)
if (fieldSymbol.IsStatic ||
fieldSymbol.Type is not INamedTypeSymbol { IsGenericType: true } fieldTypeSymbol ||
!SymbolEqualityComparer.Default.Equals(fieldTypeSymbol.ConstructedFrom, readWriteBufferSymbol) ||
!MissingComputeShaderDescriptorOnComputeShaderAnalyzer.IsComputeShaderType(typeSymbol, computeShaderSymbol, pixelShaderSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(
InvalidGloballyCoherentFieldDeclaration,
attribute.GetLocation(),
fieldSymbol));
}
}, SymbolKind.Field);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -814,4 +814,20 @@ partial class DiagnosticDescriptors
isEnabledByDefault: true,
description: "Shader types should be readonly (shaders cannot mutate their instance state while running, so shader types not being readonly makes them error prone).",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for when a field annotated with <c>[GloballyCoherent]</c> is not valid.
/// <para>
/// Format: <c>"The field "{0}" is annotated with [GloballyCoherent], but is not a valid target for it (only ReadWriteBuffer&lt;T&gt; instance fields in compute shader types can be annotated with [GloballyCoherent])"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidGloballyCoherentFieldDeclaration = new(
id: "CMPS0058",
title: "Invalid [GloballyCoherent] field declaration",
messageFormat: """The field "{0}" is annotated with [GloballyCoherent], but is not a valid target for it (only ReadWriteBuffer<T> instance fields in compute shader types can be annotated with [GloballyCoherent])""",
category: "ComputeSharp.Shaders",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The [GloballyCoherent] attribute is only valid on ReadWriteBuffer<T> instance fields in compute shader types.",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
}
10 changes: 10 additions & 0 deletions src/ComputeSharp.SourceGenerators/Mappings/HlslKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public static bool IsConstantBufferType(string typeName)
return typeName == "ComputeSharp.ConstantBuffer`1";
}

/// <summary>
/// Checks whether or not a given type name matches a read write buffer type.
/// </summary>
/// <param name="typeName">The input type name to check.</param>
/// <returns>Whether or not <paramref name="typeName"/> represents a read write buffer type.</returns>
public static bool IsReadWriteBufferType(string typeName)
{
return typeName == "ComputeSharp.ReadWriteBuffer`1";
}

/// <summary>
/// Checks whether or not a given type name matches a structured buffer type.
/// </summary>
Expand Down
26 changes: 26 additions & 0 deletions src/ComputeSharp/Core/Attributes/GloballyCoherentAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace ComputeSharp;

/// <summary>
/// An attribute that indicates that a <see cref="ReadWriteBuffer{T}"/> field in
/// a shader type should use the <see langword="globallycoherent"/> modifier.
/// </summary>
/// <remarks>
/// <para>
/// The <see langword="globallycoherent"/> modifier causes memory barriers and syncs to
/// flush data across the entire GPU such that other groups can see writes. Without this
/// modifier, a memory barrier or sync will only flush a UAV within the current thread group.
/// </para>
/// <para>
/// In other words, if compute shader thread in a given thread group needs to perform loads of
/// data that was written by atomics or stores in another thread group, the UAV slot where the
/// data resides must be tagged as <see langword="globallycoherent"/>, so the implementation
/// can ignore the local cache. Otherwise, this form of cross-thread group data sharing will
/// produce undefined results.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class GloballyCoherentAttribute : Attribute
{
}
2 changes: 1 addition & 1 deletion src/ComputeSharp/Core/Attributes/GroupSharedAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace ComputeSharp;
public sealed class GroupSharedAttribute : Attribute
{
/// <summary>
/// Creates a new <see cref="GroupSharedAttribute"/> instance with the specified parameters.
/// Creates a new <see cref="GroupSharedAttribute"/> instance.
/// </summary>
public GroupSharedAttribute()
{
Expand Down
84 changes: 84 additions & 0 deletions tests/ComputeSharp.Tests.SourceGenerators/Test_Analyzers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,88 @@ public void Execute()

await CSharpAnalyzerWithLanguageVersionTest<NotReadOnlyComputeShaderTypeWithFieldsAnalyzer>.VerifyAnalyzerAsync(source);
}

[TestMethod]
public async Task GloballyCoherentAttribute_NotWithinShaderType()
{
const string source = """
using ComputeSharp;

namespace MyFancyApp.Sample;

internal partial struct MyType
{
[{|CMPS0058:GloballyCoherent|}]
public ReadWriteBuffer<float> buffer;
}
""";

await CSharpAnalyzerWithLanguageVersionTest<InvalidGloballyCoherentFieldDeclarationAnalyzer>.VerifyAnalyzerAsync(source);
}

[TestMethod]
public async Task GloballyCoherentAttribute_IncorrectType()
{
const string source = """
using ComputeSharp;

namespace MyFancyApp.Sample;

internal partial struct MyType : IComputeShader
{
[{|CMPS0058:GloballyCoherent|}]
public ReadOnlyBuffer<float> buffer;

public void Execute()
{
}
}
""";

await CSharpAnalyzerWithLanguageVersionTest<InvalidGloballyCoherentFieldDeclarationAnalyzer>.VerifyAnalyzerAsync(source);
}

[TestMethod]
public async Task GloballyCoherentAttribute_StaticField()
{
const string source = """
using ComputeSharp;

namespace MyFancyApp.Sample;

internal partial struct MyType : IComputeShader
{
[{|CMPS0058:GloballyCoherent|}]
public static ReadWriteBuffer<float> buffer;

public void Execute()
{
}
}
""";

await CSharpAnalyzerWithLanguageVersionTest<InvalidGloballyCoherentFieldDeclarationAnalyzer>.VerifyAnalyzerAsync(source);
}

[TestMethod]
public async Task GloballyCoherentAttribute_ValidField_DoesNotWarn()
{
const string source = """
using ComputeSharp;

namespace MyFancyApp.Sample;

internal partial struct MyType : IComputeShader
{
[GloballyCoherent]
public ReadWriteBuffer<float> buffer;

public void Execute()
{
}
}
""";

await CSharpAnalyzerWithLanguageVersionTest<InvalidGloballyCoherentFieldDeclarationAnalyzer>.VerifyAnalyzerAsync(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,7 @@ private static async Task VerifyGeneratedDiagnosticsAsync(string source, (string
// Also validate all analyzers
await CSharpAnalyzerWithLanguageVersionTest<InvalidComputeContextCopyAnalyzer>.VerifyAnalyzerAsync(source);
await CSharpAnalyzerWithLanguageVersionTest<InvalidGeneratedComputeShaderDescriptorAttributeTargetAnalyzer>.VerifyAnalyzerAsync(source);
await CSharpAnalyzerWithLanguageVersionTest<InvalidGloballyCoherentFieldDeclarationAnalyzer>.VerifyAnalyzerAsync(source);
await CSharpAnalyzerWithLanguageVersionTest<MissingAllowUnsafeBlocksCompilationOptionAnalyzer>.VerifyAnalyzerAsync(source);
await CSharpAnalyzerWithLanguageVersionTest<MissingComputeShaderDescriptorOnComputeShaderAnalyzer>.VerifyAnalyzerAsync(source);
await CSharpAnalyzerWithLanguageVersionTest<NotAccessibleFieldTypeInGeneratedShaderDescriptorAttributeTargetAnalyzer>.VerifyAnalyzerAsync(source);
Expand Down
52 changes: 52 additions & 0 deletions tests/ComputeSharp.Tests/ShaderCompilerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,58 @@ public void Execute()
this.buffer[ThreadIds.X] = ThreadIds.X;
}
}

[TestMethod]
public void GloballyCoherentBuffers()
{
ShaderInfo shaderInfo = ReflectionServices.GetShaderInfo<GloballyCoherentBufferShader>();

Assert.AreEqual(
"""
// ================================================
// AUTO GENERATED
// ================================================
// This shader was created by ComputeSharp.
// See: https://github.com/Sergio0694/ComputeSharp.

#define __GroupSize__get_X 64
#define __GroupSize__get_Y 1
#define __GroupSize__get_Z 1

cbuffer _ : register(b0)
{
uint __x;
uint __y;
uint __z;
}

globallycoherent RWStructuredBuffer<int> __reserved__buffer : register(u0);

[NumThreads(__GroupSize__get_X, __GroupSize__get_Y, __GroupSize__get_Z)]
void Execute(uint3 ThreadIds : SV_DispatchThreadID)
{
if (ThreadIds.x < __x && ThreadIds.y < __y && ThreadIds.z < __z)
{
InterlockedAdd(__reserved__buffer[0], 1);
}
}
""",
shaderInfo.HlslSource);
}

[AutoConstructor]
[ThreadGroupSize(DefaultThreadGroupSizes.X)]
[GeneratedComputeShaderDescriptor]
internal readonly partial struct GloballyCoherentBufferShader : IComputeShader
{
[GloballyCoherent]
private readonly ReadWriteBuffer<int> buffer;

public void Execute()
{
Hlsl.InterlockedAdd(ref this.buffer[0], 1);
}
}
}
}

Expand Down