diff --git a/Samples/AdjustableSharedMemory/Program.cs b/Samples/AdjustableSharedMemory/Program.cs index 5e669ccf4..f070ea5e3 100644 --- a/Samples/AdjustableSharedMemory/Program.cs +++ b/Samples/AdjustableSharedMemory/Program.cs @@ -1,6 +1,6 @@ // --------------------------------------------------------------------------------------- // ILGPU Samples -// Copyright (c) 2021 ILGPU Project +// Copyright (c) 2021-2024 ILGPU Project // www.ilgpu.net // // File: Program.cs @@ -31,7 +31,7 @@ interface ISharedAllocationSize struct SharedArray32 : ISharedAllocationSize { /// - /// Returns a + /// Returns a /// public int ArraySize => 32; } @@ -49,7 +49,7 @@ struct SharedArray64 : ISharedAllocationSize /// Implicit shared-memory parameter that is handled by the runtime. static void SharedMemoryKernel( ArrayView outputView) // A view to a chunk of memory (1D in this case) - where TSharedAllocationSize : struct, ISharedAllocationSize + where TSharedAllocationSize : unmanaged, ISharedAllocationSize { // Compute the global 1D index for accessing the data view var globalIndex = Grid.GlobalIndex.X; @@ -66,7 +66,7 @@ static void SharedMemoryKernel( } static void ExecuteSample(Context context) - where TSharedAllocationSize : struct, ISharedAllocationSize + where TSharedAllocationSize : unmanaged, ISharedAllocationSize { // For each available device... foreach (var device in context) diff --git a/Samples/GenericKernel/Program.cs b/Samples/GenericKernel/Program.cs index 9e66aed14..f3fe7313a 100644 --- a/Samples/GenericKernel/Program.cs +++ b/Samples/GenericKernel/Program.cs @@ -1,6 +1,6 @@ // --------------------------------------------------------------------------------------- // ILGPU Samples -// Copyright (c) 2021 ILGPU Project +// Copyright (c) 2021-2024 ILGPU Project // www.ilgpu.net // // File: Program.cs @@ -72,7 +72,7 @@ static void Kernel( ArrayView data, int value, TKernelFunction function) - where TKernelFunction : struct, IKernelFunction + where TKernelFunction : unmanaged, IKernelFunction where T : unmanaged { // Invoke the custom "lambda function" diff --git a/Samples/ILGPU.Samples.sln b/Samples/ILGPU.Samples.sln index ce9098eb9..ba07fe3fd 100644 --- a/Samples/ILGPU.Samples.sln +++ b/Samples/ILGPU.Samples.sln @@ -135,6 +135,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILGPU.Analyzers", "..\Src\I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterleaveFields", "InterleaveFields\InterleaveFields.csproj", "{1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ILGPU.Analyzers", "ILGPU.Analyzers", "{6D111E09-49A1-492E-B4CE-E18CE27B56A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedTypeAnalyzer", "ManagedTypeAnalyzer\ManagedTypeAnalyzer.csproj", "{6176F0D0-4A34-42DB-BDFB-2D8ED80868C3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -381,6 +385,10 @@ Global {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}.Release|Any CPU.Build.0 = Release|Any CPU + {6176F0D0-4A34-42DB-BDFB-2D8ED80868C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6176F0D0-4A34-42DB-BDFB-2D8ED80868C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6176F0D0-4A34-42DB-BDFB-2D8ED80868C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6176F0D0-4A34-42DB-BDFB-2D8ED80868C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -449,6 +457,7 @@ Global {70B69CE3-24A9-463C-B14C-E2934988BBEE} = {25BA2234-5778-40BC-9386-9CE87AB87D1F} {1C5E9E39-3C14-4B52-8D97-04555D5F6331} = {03FCC663-945D-4982-90D8-B14BE52D8FCD} {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A} = {C1D99632-ED4A-4B08-A14D-4C8DB375934F} + {6176F0D0-4A34-42DB-BDFB-2D8ED80868C3} = {6D111E09-49A1-492E-B4CE-E18CE27B56A8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {30E502BD-3826-417F-888F-1CE19CF5C6DA} diff --git a/Samples/ManagedTypeAnalyzer/ManagedTypeAnalyzer.csproj b/Samples/ManagedTypeAnalyzer/ManagedTypeAnalyzer.csproj new file mode 100644 index 000000000..4e6d0c8c8 --- /dev/null +++ b/Samples/ManagedTypeAnalyzer/ManagedTypeAnalyzer.csproj @@ -0,0 +1,17 @@ + + + Exe + $(LibrarySamplesTargetFrameworks) + enable + enable + ManagedTypeAnalyzer + + + + + + + + diff --git a/Samples/ManagedTypeAnalyzer/Program.cs b/Samples/ManagedTypeAnalyzer/Program.cs new file mode 100644 index 000000000..b717799ba --- /dev/null +++ b/Samples/ManagedTypeAnalyzer/Program.cs @@ -0,0 +1,88 @@ +// --------------------------------------------------------------------------------------- +// ILGPU Samples +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: Program.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU; +using ILGPU.Runtime; +using System.Diagnostics.CodeAnalysis; + +namespace ManagedTypeAnalyzer; + +// Disable warnings for build +[SuppressMessage("Usage", "ILA003:Managed type in kernel")] +[SuppressMessage("Usage", "ILA004:Array of managed types in kernel")] +class Program +{ + class RefType + { + public int Hello => 42; + } + + struct ValueType + { + public int Hello; + + public ValueType() + { + Hello = 42; + } + } + + static int AnotherFunction() + { + return new RefType().Hello; + } + + static void Kernel(Index1D index, ArrayView input, ArrayView output) + { + // This is disallowed, since MyRefType is a reference type + var refType = new RefType(); + output[index] = input[index] + refType.Hello; + + // Allocating arrays of unmanaged types is fine + ValueType[] array = + { + new ValueType() + }; + + int[] ints = + { + 0, 1, 2 + }; + + // But arrays of reference types are still disallowed + RefType[] other = + { + new RefType(), + }; + + // Any functions that may be called are also analyzed + int result = AnotherFunction(); + } + + static void Main(string[] args) + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + using var output = accelerator.Allocate1D(1024); + + var kernel = + accelerator + .LoadAutoGroupedStreamKernel, ArrayView>( + Kernel); + + kernel(input.IntExtent, input.View, output.View); + + accelerator.Synchronize(); + } +} diff --git a/Samples/StaticAbstractInterfaceMembers/Program.cs b/Samples/StaticAbstractInterfaceMembers/Program.cs index f11a3e8a8..6980eafb7 100644 --- a/Samples/StaticAbstractInterfaceMembers/Program.cs +++ b/Samples/StaticAbstractInterfaceMembers/Program.cs @@ -1,6 +1,6 @@ // --------------------------------------------------------------------------------------- // ILGPU Samples -// Copyright (c) 2023 ILGPU Project +// Copyright (c) 2023-2024 ILGPU Project // www.ilgpu.net // // File: Program.cs @@ -66,13 +66,13 @@ public interface ICalculatorOperation { T Calculate(T left, T right); } - + // Interface must be implemented by a 'struct'. public struct AdditionOp : ICalculatorOperation { public int Calculate(int left, int right) => left + right; } - + // Interface must be implemented by a 'struct'. public struct MultiplyOp : ICalculatorOperation { @@ -84,7 +84,7 @@ public static void CalculatorKernel( ArrayView1D input, ArrayView1D output) where T : unmanaged - where TOp : struct, ICalculatorOperation + where TOp : unmanaged, ICalculatorOperation { // Creates a new instance of the struct, and calls the method. output[index] = default(TOp).Calculate(input[index], input[index]); @@ -98,7 +98,7 @@ public static void UsingAbstractFunction(Accelerator accelerator) where TOp : ICalculatorOperation #else where T : unmanaged - where TOp : struct, ICalculatorOperation + where TOp : unmanaged, ICalculatorOperation #endif { var values = diff --git a/Src/ILGPU.Analyzers.Tests/Generic/DiagnosticAnalyzerVerifier.cs b/Src/ILGPU.Analyzers.Tests/Generic/DiagnosticAnalyzerVerifier.cs new file mode 100644 index 000000000..99da0ff8d --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Generic/DiagnosticAnalyzerVerifier.cs @@ -0,0 +1,68 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: DiagnosticAnalyzerVerifier.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Threading.Tasks; +using ILGPU.CodeGeneration; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using VerifyTests; +using VerifyXunit; + +namespace ILGPU.Analyzers.Tests.Generic; + +/// +/// Verifies the output of running TDiagnosticAnalyzer on C# source. +/// +/// The diagnostic analyzer to run. +public static class DiagnosticAnalyzerVerifier + where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() +{ + /// + /// Verifies the output of running TDiagnosticAnalyzer on source using + /// snapshots. + /// + /// The source to run the analyzer on. + /// + /// Optional action to configure verification settings. + /// + public static async Task Verify(string source, + Action configure = null) + { + var ilgpuAssemblies = + new[] + { + typeof(InterleaveFieldsAttribute).Assembly, + typeof(TDiagnosticAnalyzer).Assembly + }; + + var compilation = + SourceCompiler.CreateCompilationWithAssemblies("Tests", source, + ilgpuAssemblies); + + var array = ImmutableArray.Create(new TDiagnosticAnalyzer()); + var options = new AnalyzerOptions(ImmutableArray.Empty); + var analyzerCompilation = + new CompilationWithAnalyzers(compilation, array, options); + + var diagnostics = await analyzerCompilation.GetAnalyzerDiagnosticsAsync(); + + var settings = new VerifySettings(); + settings.UseDirectory(Path.Combine("..", "Snapshots")); + + if (configure is not null) + configure(settings); + + await Verifier.Verify(diagnostics, settings); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Generic/IncrementalGeneratorVerifier.cs b/Src/ILGPU.Analyzers.Tests/Generic/IncrementalGeneratorVerifier.cs index 9b4771ee0..88bec1a6c 100644 --- a/Src/ILGPU.Analyzers.Tests/Generic/IncrementalGeneratorVerifier.cs +++ b/Src/ILGPU.Analyzers.Tests/Generic/IncrementalGeneratorVerifier.cs @@ -12,46 +12,40 @@ using ILGPU.CodeGeneration; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using System; using System.IO; -using System.Linq; using System.Threading.Tasks; using VerifyTests; using VerifyXunit; -namespace ILGPU.Analyzers.Tests +namespace ILGPU.Analyzers.Tests.Generic { + /// + /// Verifies the output source from the source generator TIncrementalGenerator + /// using snapshots. + /// + /// + /// The generator to run on source text. + /// public static class IncrementalGeneratorVerifier where TIncrementalGenerator : IIncrementalGenerator, new() { + /// + /// Verifies the C# source generated by TIncrementalGenerator. + /// + /// The source text to run the generator on. + /// A task that runs the generator and verifies the output. public static Task Verify(string source) { - // Parse syntax tree. - var syntaxTree = CSharpSyntaxTree.ParseText(source); - - // Add system references. - var trustedAssembliesPaths = - (string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"); - var systemReferences = - trustedAssembliesPaths - .Split(Path.PathSeparator) - .Select(x => MetadataReference.CreateFromFile(x)) - .ToArray(); - - var ilgpuReferences = + var ilgpuAssemblies = new[] { - typeof(InterleaveFieldsAttribute), - typeof(TIncrementalGenerator) - } - .Select(x => MetadataReference.CreateFromFile(x.Assembly.Location)) - .ToArray(); + typeof(InterleaveFieldsAttribute).Assembly, + typeof(TIncrementalGenerator).Assembly + }; - // Create a roslyn compilation for the syntax tree. - var compilation = CSharpCompilation.Create( - "Tests", - new[] { syntaxTree }, - references: systemReferences.Concat(ilgpuReferences)); + var compilation = + SourceCompiler.CreateCompilationWithAssemblies("Tests", source, + ilgpuAssemblies); // Create an instance of the incremental source generator. var generator = new TIncrementalGenerator(); @@ -65,4 +59,4 @@ public static Task Verify(string source) return Verifier.Verify(driver, settings); } } -} +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Generic/SourceCompiler.cs b/Src/ILGPU.Analyzers.Tests/Generic/SourceCompiler.cs new file mode 100644 index 000000000..816dff341 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Generic/SourceCompiler.cs @@ -0,0 +1,66 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: SourceCompiler.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ILGPU.Analyzers.Tests.Generic; + +/// +/// Contains utility functions for compiling C# source files for analyzer testing. +/// +public static class SourceCompiler +{ + /// + /// Compiles the source text source into an assembly with the name + /// assemblyName and includes the given additionalAssemblies. + /// + /// The name of the output assembly. + /// The source text to compile. + /// + /// The additional assembly references to include in the compilation. + /// + /// The resulting compilation. + public static CSharpCompilation CreateCompilationWithAssemblies( + string assemblyName, + string source, + Assembly[] additionalAssemblies) + { + // Parse syntax tree. + var syntaxTree = CSharpSyntaxTree.ParseText(source); + + // Add system references. + var trustedAssembliesPaths = + (string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"); + var systemReferences = + trustedAssembliesPaths! + .Split(Path.PathSeparator) + .Select(x => MetadataReference.CreateFromFile(x)) + .ToArray(); + + var additionalReferences = + additionalAssemblies + .Select(x => MetadataReference.CreateFromFile(x.Location)) + .ToArray(); + + // Create a roslyn compilation for the syntax tree. + var compilation = CSharpCompilation.Create( + assemblyName, + new[] { syntaxTree }, + references: systemReferences.Concat(additionalReferences)); + + return compilation; + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/ILGPU.Analyzers.Tests.csproj b/Src/ILGPU.Analyzers.Tests/ILGPU.Analyzers.Tests.csproj index 9971c843c..69479bfac 100644 --- a/Src/ILGPU.Analyzers.Tests/ILGPU.Analyzers.Tests.csproj +++ b/Src/ILGPU.Analyzers.Tests/ILGPU.Analyzers.Tests.csproj @@ -23,8 +23,16 @@ + + + + + Always + + + diff --git a/Src/ILGPU.Analyzers.Tests/InterleaveFields.cs b/Src/ILGPU.Analyzers.Tests/InterleaveFields.cs index 5cc1b135a..36d9a3bda 100644 --- a/Src/ILGPU.Analyzers.Tests/InterleaveFields.cs +++ b/Src/ILGPU.Analyzers.Tests/InterleaveFields.cs @@ -10,10 +10,9 @@ // --------------------------------------------------------------------------------------- using System.Threading.Tasks; -using VerifyXunit; using Xunit; using VerifyCS = - ILGPU.Analyzers.Tests.IncrementalGeneratorVerifier< + ILGPU.Analyzers.Tests.Generic.IncrementalGeneratorVerifier< ILGPU.Analyzers.InterleaveFieldsGenerator>; namespace ILGPU.Analyzers.Tests diff --git a/Src/ILGPU.Analyzers.Tests/ManagedTypeAnalyzer.cs b/Src/ILGPU.Analyzers.Tests/ManagedTypeAnalyzer.cs new file mode 100644 index 000000000..4714b945c --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/ManagedTypeAnalyzer.cs @@ -0,0 +1,41 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: ManagedTypeAnalyzer.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using System.IO; +using System.Threading.Tasks; +using Xunit; +using VerifyCS = + ILGPU.Analyzers.Tests.Generic.DiagnosticAnalyzerVerifier< + ILGPU.Analyzers.ManagedTypeAnalyzer>; + +namespace ILGPU.Analyzers.Tests; + +public class ManagedTypeAnalyzer +{ + [Theory] + [InlineData("Simple")] + [InlineData("Complex")] + [InlineData("Arrays")] + [InlineData("Functions")] + [InlineData("PartialMethods")] + [InlineData("Constructors")] + [InlineData("LoadDiscovery")] + [InlineData("ILGPUTypesIntrinsics")] + public async Task FileTests(string file) + { + // In build, we copy all programs to output directory. + // See ILGPU.Analyzers.Tests.csproj + var code = await File.ReadAllTextAsync( + $"Programs/ManagedType/{file}.cs" + ); + await VerifyCS.Verify(code, settings => settings.UseParameters(file)); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Arrays.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Arrays.cs new file mode 100644 index 000000000..26618b9ba --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Arrays.cs @@ -0,0 +1,56 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: Arrays.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class Arrays +{ + class RefType + { + public int Hello => 42; + } + + struct ValueType + { + public int Hello; + + public ValueType() + { + Hello = 42; + } + } + + static void Kernel(Index1D index, ArrayView input) + { + ValueType[] array = [new ValueType()]; + int[] ints = [0, 1, 2]; + + RefType[] refs = [new RefType()]; + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Complex.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Complex.cs new file mode 100644 index 000000000..d0193d2d5 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Complex.cs @@ -0,0 +1,65 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: Complex.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class Complex +{ + class RefTypeEmpty + { + } + + struct Unmanaged + { + private int a; + private int b; + } + + struct Managed + { + private int a; + private RefTypeEmpty r; + } + + struct ValueType + { + public int Hello; + + public ValueType() + { + Hello = 42; + } + } + + static void Kernel(Index1D index, ArrayView input) + { + var unmanaged = new Unmanaged(); + var managed = new Managed(); + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Constructors.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Constructors.cs new file mode 100644 index 000000000..e64bfe1f7 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Constructors.cs @@ -0,0 +1,53 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: Constructors.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class Constructors +{ + class RefType + { + public int Hello => 42; + } + + struct ValueType + { + public int Hello; + + public ValueType() + { + Hello = new RefType().Hello; + } + } + + static void Kernel(Index1D index, ArrayView input) + { + ValueType value = new ValueType(); + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Functions.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Functions.cs new file mode 100644 index 000000000..a1f1abc9f --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Functions.cs @@ -0,0 +1,69 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: Functions.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class Functions +{ + class RefType + { + public int Hello => 42; + } + + // This wouldn't compile anyways, but the analyzer shouldn't hang + static int Recursion(int i) + { + if (i == 0) return new RefType().Hello; + return Recursion(i - 1); + } + + static int MutualRecursion1(int i) + { + if (i == 0) return new RefType().Hello; + return MutualRecursion2(i - 1); + } + + static int MutualRecursion2(int i) + { + if (i == 0) return 0; + return MutualRecursion1(i - 1); + } + + static int AnotherFunction() + { + return new RefType().Hello; + } + + static void Kernel(Index1D index, ArrayView input) + { + int result = AnotherFunction(); + int rec1 = Recursion(10); + int rec2 = MutualRecursion1(10); + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/ILGPUTypesIntrinsics.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/ILGPUTypesIntrinsics.cs new file mode 100644 index 000000000..357aaaa37 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/ILGPUTypesIntrinsics.cs @@ -0,0 +1,52 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: ILGPUTypesIntrinsics.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; +using ILGPU.Runtime.Cuda; +using ILGPU.Algorithms.Random; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class ILGPUTypesIntrinsics +{ + static void Kernel(Index1D index, ArrayView input) + { + var a = input.SubView(0, 10); + int b = a[index]; + int c = Warp.WarpIdx; + Group.Barrier(); + + // Strings should be allowed for this and debug. + // We will be conservative in our analysis and ignore all strings. + CudaAsm.Emit(""); + + // Algorithms should also be allowed. + // RNGView is just an example of a managed type in Algorithms. + var rngView = new RNGView(); + rngView.Next(); + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/LoadDiscovery.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/LoadDiscovery.cs new file mode 100644 index 000000000..4edab3c20 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/LoadDiscovery.cs @@ -0,0 +1,75 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: LoadDiscovery.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class LoadDiscovery +{ + class RefType + { + public int Hello => 42; + } + + // Separate kernels so we can observe multiple analyses + static void Kernel1(Index1D index, ArrayView input) + { + int result = new RefType().Hello; + } + + static void Kernel2(Index1D index, ArrayView input) + { + int result = new RefType().Hello; + } + + static void Kernel3(Index1D index, ArrayView input) + { + int result = new RefType().Hello; + } + + static void Kernel4(Index1D index, ArrayView input) + { + int result = new RefType().Hello; + } + + static void Kernel5(Index1D index, ArrayView input) + { + int result = new RefType().Hello; + } + + static void Kernel6(Index1D index, ArrayView input) + { + int result = new RefType().Hello; + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + // Also make sure kernels loaded twice aren't analyzed twice + var kernel1 = accelerator.LoadStreamKernel>(Kernel1); + var twice = accelerator.LoadStreamKernel>(Kernel1); + + var kernel2 = + accelerator.LoadAutoGroupedStreamKernel>(Kernel2); + var kernel3 = + accelerator.LoadImplicitlyGroupedStreamKernel>( + Kernel3, 32); + + var kernel4 = accelerator.LoadKernel>(Kernel4); + var kernel5 = accelerator.LoadAutoGroupedKernel>(Kernel5); + var kernel6 = + accelerator.LoadImplicitlyGroupedKernel>(Kernel6, 32); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/PartialMethods.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/PartialMethods.cs new file mode 100644 index 000000000..d7d895305 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/PartialMethods.cs @@ -0,0 +1,57 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: PartialMethods.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType.PartialMethods; + +class RefType +{ + public int Hello => 42; +} + +partial class Foo +{ + public static partial int Bar(int i); +} + +partial class Foo +{ + public static partial int Bar(int i) + { + RefType r = new RefType(); + return r.Hello + i; + } +} + +class PartialMethods +{ + static void Kernel(Index1D index, ArrayView view) + { + view[index] = Foo.Bar(10); + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Simple.cs b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Simple.cs new file mode 100644 index 000000000..ec225ac00 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Programs/ManagedType/Simple.cs @@ -0,0 +1,44 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: Simple.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU.Runtime; + +namespace ILGPU.Analyzers.Tests.Programs.ManagedType; + +class Simple +{ + class RefType + { + public int Hello => 42; + } + + static void Kernel(Index1D index, ArrayView input) + { + var refType = new RefType(); + input[index] = input[index] + refType.Hello; + } + + static void Run() + { + using var context = Context.CreateDefault(); + var device = context.GetPreferredDevice(false); + using var accelerator = device.CreateAccelerator(context); + + using var input = accelerator.Allocate1D(1024); + + var kernel = + accelerator.LoadAutoGroupedStreamKernel>(Kernel); + + kernel(input.IntExtent, input.View); + + accelerator.Synchronize(); + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Arrays.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Arrays.verified.txt new file mode 100644 index 000000000..fb4ff8b6e --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Arrays.verified.txt @@ -0,0 +1,22 @@ +[ + { + Id: ILA004, + Title: Array of managed types in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (37,25)-(37,40), + MessageFormat: Type '{0}' is an array of type '{1}', which is a managed type. Arrays of managed types cannot be used in kernels., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Arrays.RefType[]' is an array of type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Arrays.RefType', which is a managed type. Arrays of managed types cannot be used in kernels., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (37,26)-(37,39), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Arrays.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Complex.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Complex.verified.txt new file mode 100644 index 000000000..bad99fa9b --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Complex.verified.txt @@ -0,0 +1,12 @@ +[ + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (46,22)-(46,35), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Complex.Managed' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Constructors.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Constructors.verified.txt new file mode 100644 index 000000000..70fe334d2 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Constructors.verified.txt @@ -0,0 +1,12 @@ +[ + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (28,20)-(28,33), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Constructors.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Functions.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Functions.verified.txt new file mode 100644 index 000000000..25d483611 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Functions.verified.txt @@ -0,0 +1,32 @@ +[ + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (31,27)-(31,40), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Functions.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (25,27)-(25,40), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Functions.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (43,15)-(43,28), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Functions.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=ILGPUTypesIntrinsics.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=ILGPUTypesIntrinsics.verified.txt new file mode 100644 index 000000000..ad47dbb93 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=ILGPUTypesIntrinsics.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=LoadDiscovery.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=LoadDiscovery.verified.txt new file mode 100644 index 000000000..09eeec3d6 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=LoadDiscovery.verified.txt @@ -0,0 +1,62 @@ +[ + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (25,21)-(25,34), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.LoadDiscovery.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (30,21)-(30,34), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.LoadDiscovery.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (35,21)-(35,34), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.LoadDiscovery.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (40,21)-(40,34), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.LoadDiscovery.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (45,21)-(45,34), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.LoadDiscovery.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (50,21)-(50,34), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.LoadDiscovery.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=PartialMethods.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=PartialMethods.verified.txt new file mode 100644 index 000000000..e7a029638 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=PartialMethods.verified.txt @@ -0,0 +1,22 @@ +[ + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (29,20)-(29,33), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.PartialMethods.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (30,15)-(30,16), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.PartialMethods.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Simple.verified.txt b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Simple.verified.txt new file mode 100644 index 000000000..adf2a8458 --- /dev/null +++ b/Src/ILGPU.Analyzers.Tests/Snapshots/ManagedTypeAnalyzer.FileTests_file=Simple.verified.txt @@ -0,0 +1,22 @@ +[ + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (24,22)-(24,35), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Simple.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + }, + { + Id: ILA003, + Title: Managed type in kernel, + Severity: Warning, + WarningLevel: 1, + Location: : (25,38)-(25,45), + MessageFormat: Type '{0}' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Message: Type 'ILGPU.Analyzers.Tests.Programs.ManagedType.Simple.RefType' is a managed type. Managed types like classes cannot be used in kernels, except for arrays of unmanaged types., + Category: Usage + } +] \ No newline at end of file diff --git a/Src/ILGPU.Analyzers/AnalyzerReleases.Unshipped.md b/Src/ILGPU.Analyzers/AnalyzerReleases.Unshipped.md index ddc574b58..3d29a0ce6 100644 --- a/Src/ILGPU.Analyzers/AnalyzerReleases.Unshipped.md +++ b/Src/ILGPU.Analyzers/AnalyzerReleases.Unshipped.md @@ -6,4 +6,6 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- ILA001 | CodeGeneration | Error | SourceGenerator -ILA002 | CodeGeneration | Error | SourceGenerator \ No newline at end of file +ILA002 | CodeGeneration | Error | SourceGenerator +ILA003 | Usage | Warning | Analyzer +ILA004 | Usage | Warning | Analyzer diff --git a/Src/ILGPU.Analyzers/DiagnosticCategory.cs b/Src/ILGPU.Analyzers/DiagnosticCategory.cs new file mode 100644 index 000000000..d89d0099b --- /dev/null +++ b/Src/ILGPU.Analyzers/DiagnosticCategory.cs @@ -0,0 +1,18 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2023-2024 ILGPU Project +// www.ilgpu.net +// +// File: DiagnosticCategory.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +namespace ILGPU.Analyzers +{ + public static class DiagnosticCategory + { + public static string Usage => "Usage"; + } +} diff --git a/Src/ILGPU.Analyzers/ILGPU.Analyzers.csproj b/Src/ILGPU.Analyzers/ILGPU.Analyzers.csproj index c6e94e3ff..239e33f12 100644 --- a/Src/ILGPU.Analyzers/ILGPU.Analyzers.csproj +++ b/Src/ILGPU.Analyzers/ILGPU.Analyzers.csproj @@ -36,6 +36,16 @@ + + True + True + ILA003_ManagedTypeInKernel.Designer.cs + + + True + True + ILA004_ManagedTypeArrayInKernel.Designer.cs + True True diff --git a/Src/ILGPU.Analyzers/InterleaveFieldsGenerator.cs b/Src/ILGPU.Analyzers/InterleaveFieldsGenerator.cs index 0506a69a4..9ed076aa9 100644 --- a/Src/ILGPU.Analyzers/InterleaveFieldsGenerator.cs +++ b/Src/ILGPU.Analyzers/InterleaveFieldsGenerator.cs @@ -34,7 +34,7 @@ public class InterleaveFieldsGenerator : IIncrementalGenerator id: "ILA001", title: ErrorMessages.StructMustBePartial_Title, messageFormat: ErrorMessages.StructMustBePartial_Message, - category: ErrorMessages.Usage_Category, + category: DiagnosticCategory.Usage, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); @@ -42,7 +42,7 @@ public class InterleaveFieldsGenerator : IIncrementalGenerator id: "ILA002", title: ErrorMessages.ContainingTypeMustBePartial_Title, messageFormat: ErrorMessages.ContainingTypeMustBePartial_Message, - category: ErrorMessages.Usage_Category, + category: DiagnosticCategory.Usage, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); diff --git a/Src/ILGPU.Analyzers/KernelAnalyzer.cs b/Src/ILGPU.Analyzers/KernelAnalyzer.cs new file mode 100644 index 000000000..a306e75ed --- /dev/null +++ b/Src/ILGPU.Analyzers/KernelAnalyzer.cs @@ -0,0 +1,110 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2023-2024 ILGPU Project +// www.ilgpu.net +// +// File: KernelAnalyzer.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using System.Collections.Concurrent; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using System.Collections.Immutable; +using System.Linq; + +namespace ILGPU.Analyzers +{ + /// + /// Base analyzer that can be used to implement diagnostic analyzers that operate on + /// kernel method bodies. + /// + public abstract class KernelAnalyzer : DiagnosticAnalyzer + { + private readonly ImmutableHashSet kernelLoadNames = + ImmutableHashSet.Create( + "LoadKernel", + "LoadAutoGroupedKernel", + "LoadImplicitlyGroupedKernel", + "LoadStreamKernel", + "LoadAutoGroupedStreamKernel", + "LoadImplicitlyGroupedStreamKernel" + ); + + private readonly ConcurrentDictionary seen = new(); + + /// + /// Called for every kernel body. + /// + /// + /// The analysis context used to report diagnostics. + /// + /// The operation. + protected abstract void AnalyzeKernelBody( + OperationAnalysisContext context, + IOperation bodyOp); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + seen.Clear(); + + // Subscribe to semantic (compile time) action invocation + // Subscribe only to method invocations (we want to find the kernel load call) + context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation); + } + + private void AnalyzeOperation(OperationAnalysisContext context) + { + if (seen.ContainsKey(context.Operation)) return; + + if (context.Operation is not IInvocationOperation invocationOperation || + context.Operation.Syntax is not InvocationExpressionSyntax) + return; + + var methodSymbol = invocationOperation.TargetMethod; + + if (methodSymbol.MethodKind != MethodKind.Ordinary + || !kernelLoadNames.Contains(methodSymbol.Name)) + return; + + var kernelArg = invocationOperation.Arguments.FirstOrDefault(x => + x.Parameter?.Type.TypeKind == TypeKind.Delegate); + + // TODO: support expressions that return delegate (probably requires dataflow) + if (kernelArg?.Value is + IDelegateCreationOperation + delegateOp) + { + // We should always have a semantic model since we subscribed + // to semantic analysis + var semanticModel = context.Operation.SemanticModel!; + + var bodyOp = delegateOp.Target switch + { + IMethodReferenceOperation refOp => MethodUtil.GetMethodBody( + semanticModel, + refOp.Method), + IAnonymousFunctionOperation anonymousOp => anonymousOp.Body, + _ => null, + }; + + if (bodyOp is not null) + { + // If we couldn't add it, another thread got here first. + bool added = seen.TryAdd(bodyOp, true); + if (added) + { + AnalyzeKernelBody(context, bodyOp); + } + } + } + } + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers/ManagedTypeAnalyzer.cs b/Src/ILGPU.Analyzers/ManagedTypeAnalyzer.cs new file mode 100644 index 000000000..8f999de1a --- /dev/null +++ b/Src/ILGPU.Analyzers/ManagedTypeAnalyzer.cs @@ -0,0 +1,169 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: ManagedTypeAnalyzer.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Operations; +using ILGPU.Analyzers.Resources; + +namespace ILGPU.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ManagedTypeAnalyzer : KernelAnalyzer + { + private static readonly DiagnosticDescriptor GeneralDiagnosticRule = new( + id: "ILA003", + title: ErrorMessages.ManagedTypeInKernel_Title, + messageFormat: ErrorMessages.ManagedTypeInKernel_Message, + category: DiagnosticCategory.Usage, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + + private static readonly DiagnosticDescriptor ArrayDiagnosticRule = new( + id: "ILA004", + title: ErrorMessages.ManagedTypeArrayInKernel_Title, + messageFormat: ErrorMessages.ManagedTypeArrayInKernel_Message, + category: DiagnosticCategory.Usage, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + + private const string ILGPUAssemblyName = "ILGPU"; + private const string AlgorithmsAssemblyName = "ILGPU.Algorithms"; + + public override ImmutableArray + SupportedDiagnostics { get; } = + ImmutableArray.Create(GeneralDiagnosticRule, ArrayDiagnosticRule); + + protected override void AnalyzeKernelBody(OperationAnalysisContext context, + IOperation bodyOp) + { + Stack bodies = new Stack(); + + // Prevents infinite looping when functions are recursive + // Also prevents analyzing syntax (like collection expressions) that appear + // twice in descendants for some unknown reason. + // Costs us more memory than I would like, but not sure if we have a choice. + HashSet seenLocations = new HashSet(); + + bodies.Push(bodyOp); + + // We will always have a semantic model because KernelAnalyzer subscribes + // to semantic analysis + var semanticModel = context.Operation.SemanticModel!; + + while (bodies.Count != 0) + { + var op = bodies.Pop(); + + foreach (var descendant in op.DescendantsAndSelf()) + { + var descendantLocation = descendant.Syntax.GetLocation(); + if (!seenLocations.Add(descendantLocation)) continue; + + AnalyzeKernelOperation(context, descendant); + + var invokedSymbol = GetInvokedSymbolIfExists(descendant); + if (invokedSymbol is null) continue; + + if (IsILGPUSymbol(invokedSymbol)) continue; + + var methodBodyOp = + MethodUtil.GetMethodBody(semanticModel, invokedSymbol); + if (methodBodyOp is null) continue; + + var methodBodyLocation = methodBodyOp.Syntax.GetLocation(); + if (!seenLocations.Contains(methodBodyLocation)) + { + bodies.Push(methodBodyOp); + } + } + } + } + + private void AnalyzeKernelOperation(OperationAnalysisContext context, + IOperation op) + { + if (op.Type is null) + return; + + if (op.Type.IsUnmanagedType) + return; + + if (IsILGPUSymbol(op.Type)) + return; + + if (op.Type.SpecialType == SpecialType.System_String) + return; + + if (op.Type is IArrayTypeSymbol arrayTypeSymbol) + { + if (arrayTypeSymbol.ElementType.IsUnmanagedType) + return; + + string first = arrayTypeSymbol.ToDisplayString(); + string second = arrayTypeSymbol.ElementType.ToDisplayString(); + + var arrayDiagnostic = + Diagnostic.Create(ArrayDiagnosticRule, + op.Syntax.GetLocation(), first, second); + context.ReportDiagnostic(arrayDiagnostic); + } + else + { + var generalDiagnostic = + Diagnostic.Create(GeneralDiagnosticRule, + op.Syntax.GetLocation(), + op.Type.ToDisplayString()); + context.ReportDiagnostic(generalDiagnostic); + } + } + + /// + /// Gets the symbol for a method a given operation invokes, if the operation + /// invokes anything at all. Only looks at direct invocations, meaning descendants + /// will not be explored. + /// + /// The operation to analyze. + /// + /// The method symbol representing the method op invokes. This could be a + /// regular method or a constructor. Null if op doesn't invoke anything + /// directly. + /// + private IMethodSymbol? GetInvokedSymbolIfExists(IOperation op) => + op switch + { + IInvocationOperation invocationOperation => invocationOperation + .TargetMethod, + IObjectCreationOperation + { + Constructor: not null + } creationOperation => creationOperation.Constructor, + _ => null + }; + + /// + /// Whether a given symbol is a symbol from an ILGPU assembly. + /// + /// The symbol to check. + /// + /// Whether symbol is in the ILGPU or ILGPU.Algorithms assemblies. + /// + private static bool IsILGPUSymbol(ISymbol symbol) + { + return symbol.ContainingAssembly?.Name is ILGPUAssemblyName + or AlgorithmsAssemblyName; + } + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers/MethodUtil.cs b/Src/ILGPU.Analyzers/MethodUtil.cs new file mode 100644 index 000000000..16d111c1a --- /dev/null +++ b/Src/ILGPU.Analyzers/MethodUtil.cs @@ -0,0 +1,56 @@ +// --------------------------------------------------------------------------------------- +// ILGPU +// Copyright (c) 2024 ILGPU Project +// www.ilgpu.net +// +// File: MethodUtil.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using Microsoft.CodeAnalysis; + +namespace ILGPU.Analyzers +{ + public static class MethodUtil + { + /// + /// Gets the body operation of a given method. + /// + /// + /// The semantic model for the compilation that includes symbol. + /// + /// The method symbol to get the body of. + /// + /// The body of the method represented by symbol. If symbol is a + /// partial method, the operation representing the implementation is returned. + /// Null if the operation could not be resolved, for example, if symbol + /// is partial and there is no implementation part. + /// + public static IOperation? GetMethodBody(SemanticModel model, IMethodSymbol symbol) + { + return symbol switch + { + { + IsPartialDefinition: false, + DeclaringSyntaxReferences: { Length: > 0 } refs + } => GetOperationIfInTree(model, refs[0].GetSyntax()), + { + PartialImplementationPart: + { + DeclaringSyntaxReferences: { Length: > 0 } refs + }, + } => GetOperationIfInTree(model, refs[0].GetSyntax()), + _ => null + }; + } + + private static IOperation? GetOperationIfInTree(SemanticModel model, + SyntaxNode node) + { + var root = model.SyntaxTree.GetRoot(); + return root.Contains(node) ? model.GetOperation(node) : null; + } + } +} \ No newline at end of file diff --git a/Src/ILGPU.Analyzers/Properties/launchSettings.json b/Src/ILGPU.Analyzers/Properties/launchSettings.json index 54dcc924f..0b83fbc0c 100644 --- a/Src/ILGPU.Analyzers/Properties/launchSettings.json +++ b/Src/ILGPU.Analyzers/Properties/launchSettings.json @@ -1,8 +1,12 @@ { "profiles": { - "ILGPU.Analyzers": { + "InterleaveFields": { "commandName": "DebugRoslynComponent", "targetProject": "..\\..\\Samples\\InterleaveFields\\InterleaveFields.csproj" + }, + "RefType": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\Samples\\RefTypeAnalyzer\\RefTypeAnalyzer.csproj" } } } \ No newline at end of file diff --git a/Src/ILGPU.Analyzers/Resources/ErrorMessages.Designer.cs b/Src/ILGPU.Analyzers/Resources/ErrorMessages.Designer.cs index fcf31f0c6..d182e6d95 100644 --- a/Src/ILGPU.Analyzers/Resources/ErrorMessages.Designer.cs +++ b/Src/ILGPU.Analyzers/Resources/ErrorMessages.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,46 +11,32 @@ namespace ILGPU.Analyzers.Resources { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ErrorMessages { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal ErrorMessages() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ILGPU.Analyzers.Resources.ErrorMessages", typeof(ErrorMessages).Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ILGPU.Analyzers.Resources.ErrorMessages", typeof(ErrorMessages).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -60,48 +45,51 @@ internal ErrorMessages() { } } - /// - /// Looks up a localized string similar to The type '{0}' containing '{1}' must be partial. - /// + internal static string StructMustBePartial_Message { + get { + return ResourceManager.GetString("StructMustBePartial_Message", resourceCulture); + } + } + + internal static string StructMustBePartial_Title { + get { + return ResourceManager.GetString("StructMustBePartial_Title", resourceCulture); + } + } + internal static string ContainingTypeMustBePartial_Message { get { return ResourceManager.GetString("ContainingTypeMustBePartial_Message", resourceCulture); } } - /// - /// Looks up a localized string similar to Containing type must be partial. - /// internal static string ContainingTypeMustBePartial_Title { get { return ResourceManager.GetString("ContainingTypeMustBePartial_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to The struct '{0}' must be partial. - /// - internal static string StructMustBePartial_Message { + internal static string ManagedTypeInKernel_Message { get { - return ResourceManager.GetString("StructMustBePartial_Message", resourceCulture); + return ResourceManager.GetString("ManagedTypeInKernel_Message", resourceCulture); } } - /// - /// Looks up a localized string similar to Struct must be partial. - /// - internal static string StructMustBePartial_Title { + internal static string ManagedTypeInKernel_Title { get { - return ResourceManager.GetString("StructMustBePartial_Title", resourceCulture); + return ResourceManager.GetString("ManagedTypeInKernel_Title", resourceCulture); + } + } + + internal static string ManagedTypeArrayInKernel_Message { + get { + return ResourceManager.GetString("ManagedTypeArrayInKernel_Message", resourceCulture); } } - /// - /// Looks up a localized string similar to Usage. - /// - internal static string Usage_Category { + internal static string ManagedTypeArrayInKernel_Title { get { - return ResourceManager.GetString("Usage_Category", resourceCulture); + return ResourceManager.GetString("ManagedTypeArrayInKernel_Title", resourceCulture); } } } diff --git a/Src/ILGPU.Analyzers/Resources/ErrorMessages.resx b/Src/ILGPU.Analyzers/Resources/ErrorMessages.resx index 6603f4ca7..f6f460cc5 100644 --- a/Src/ILGPU.Analyzers/Resources/ErrorMessages.resx +++ b/Src/ILGPU.Analyzers/Resources/ErrorMessages.resx @@ -1,135 +1,45 @@ - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The type '{0}' containing '{1}' must be partial - - - Containing type must be partial - - - The struct '{0}' must be partial - - - Struct must be partial - - - Usage - \ No newline at end of file