diff --git a/EfficientUnionGenerator.slnx b/EfficientUnionGenerator.slnx index f01582a..96bb81e 100644 --- a/EfficientUnionGenerator.slnx +++ b/EfficientUnionGenerator.slnx @@ -1,10 +1,16 @@ + + + + + + diff --git a/src/EfficientUnionGenerator.GeneratorBenchmark/EfficientUnionGenerator.GeneratorBenchmark.csproj b/src/EfficientUnionGenerator.GeneratorBenchmark/EfficientUnionGenerator.GeneratorBenchmark.csproj new file mode 100644 index 0000000..6e036a1 --- /dev/null +++ b/src/EfficientUnionGenerator.GeneratorBenchmark/EfficientUnionGenerator.GeneratorBenchmark.csproj @@ -0,0 +1,28 @@ + + + + net10.0 + enable + enable + Exe + Debug;Release;Profiling + + + + + + + + + + + + + + + + + + + + diff --git a/src/EfficientUnionGenerator.GeneratorBenchmark/GeneratorBenchmarkContext.cs b/src/EfficientUnionGenerator.GeneratorBenchmark/GeneratorBenchmarkContext.cs new file mode 100644 index 0000000..b4e326c --- /dev/null +++ b/src/EfficientUnionGenerator.GeneratorBenchmark/GeneratorBenchmarkContext.cs @@ -0,0 +1,131 @@ +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using BenchmarkDotNet.Attributes; + +namespace EfficientUnionGenerator.GeneratorBenchmark; + +[MemoryDiagnoser] +public partial class GeneratorBenchmarkContext +{ + [Params([ + "Int32OrString", + "SignedInteger", + "PositiveOnlyIntOrFloat", + "ElfHeader", + ])] + public string? TypeName { get; set; } = null!; + + private IIncrementalGenerator _generator = null!; + private IIncrementalGenerator _negativeControlGenerator = null!; + private Compilation _baseCompilation = null!; + private Compilation _changedCompilation = null!; + + [Params("Experimental", "NegativeControl")] + public string Target { get; set; } = null!; + private GeneratorDriver TargetDriver { get; set; } = null!; + + public string? SourceCode { get; private set; } + + [GlobalSetup] + public void Setup() + { + if(TypeName is null) + { + return; + } + var resourceName = $"EfficientUnionGenerator.GeneratorBenchmark.resources.{TypeName}.cs"; + var executingAssembly = Assembly.GetExecutingAssembly(); + using (var stream = executingAssembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + throw new InvalidOperationException($"Resource '{resourceName}' not found."); + } + using var reader = new StreamReader(stream); + SourceCode = reader.ReadToEnd(); + } + + _generator = new Generator(); + _negativeControlGenerator = new NegativeControlGenerator(); + + var tree = CSharpSyntaxTree.ParseText(SourceCode); + _baseCompilation = CSharpCompilation.Create( + assemblyName: "BenchmarkDummy", + syntaxTrees: [tree], + references: [ + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + ]); + + var modifiedTree = tree.WithRootAndOptions( + new Visitor(TypeName).Visit(tree.GetRoot()), + tree.Options); + _changedCompilation = _baseCompilation.ReplaceSyntaxTree(tree, modifiedTree); + } + + [IterationSetup] + public void ResetDriver() + { + TargetDriver = Target switch + { + "Experimental" => CSharpGeneratorDriver + .Create(_generator), + "NegativeControl" => CSharpGeneratorDriver + .Create(_negativeControlGenerator), + _ => throw new InvalidOperationException($"Unknown target: {Target}"), + }; + } + + [Benchmark] + public void RunGenerator() + { + TargetDriver = TargetDriver + .RunGenerators(_baseCompilation); + } + + [Benchmark] + public void IncrementalUpdate_NoChanges() + { + TargetDriver = TargetDriver + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_baseCompilation); + } + + [Benchmark] + public void IncrementalUpdate_Regenerate() + { + TargetDriver = TargetDriver + .RunGenerators(_baseCompilation) + .RunGenerators(_changedCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_changedCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_changedCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_changedCompilation) + .RunGenerators(_baseCompilation) + .RunGenerators(_changedCompilation); + } +} + + +file class Visitor(string typeName) : CSharpSyntaxRewriter +{ + public override SyntaxToken VisitToken(SyntaxToken token) + { + if(token.IsKind(SyntaxKind.IdentifierToken) && token.Text == typeName) + { + return SyntaxFactory.Identifier(typeName + "_Modified"); + } + return base.VisitToken(token); + } +} \ No newline at end of file diff --git a/src/EfficientUnionGenerator.GeneratorBenchmark/NegativeControlGenerator.cs b/src/EfficientUnionGenerator.GeneratorBenchmark/NegativeControlGenerator.cs new file mode 100644 index 0000000..2e5f792 --- /dev/null +++ b/src/EfficientUnionGenerator.GeneratorBenchmark/NegativeControlGenerator.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EfficientUnionGenerator.GeneratorBenchmark; + +using static Constants; + +internal class NegativeControlGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(static cxt => + { + cxt.AddSource( + $"{AttributeNamespace}.{EfficientUnionAttributeName}.g.cs", + EfficientUnionAttributeSource); + }); + + var sourceAttr = context.CompilationProvider + .Select(static (compilation, token) => compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.UnionAttribute") is { }); + context.RegisterSourceOutput(sourceAttr, (context, isAvailable) => + { + if (!isAvailable) + { + context.AddSource( + $"{AttributeNamespace}.UnionAttribute.g.cs", + CompilerServicesUnionAttributeSource); + } + }); + + var sourceInterface = context.CompilationProvider + .Select(static (compilation, token) => compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.IUnion") is { }); + context.RegisterSourceOutput(sourceInterface, (context, isAvailable) => + { + if (!isAvailable) + { + context.AddSource( + $"{AttributeNamespace}.IUnion.g.cs", + CompilerServicesIUnionSource); + } + }); + + var sourceUnion = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{EfficientUnionAttributeName}", + static (node, token) => node is ClassDeclarationSyntax or StructDeclarationSyntax, + static (context, token) => context) + .Where(static _ => false); + context.RegisterSourceOutput(sourceUnion, EmitUnionType); + } + + private void EmitUnionType(SourceProductionContext context, GeneratorAttributeSyntaxContext source) + { + } +} diff --git a/src/EfficientUnionGenerator.GeneratorBenchmark/Program.cs b/src/EfficientUnionGenerator.GeneratorBenchmark/Program.cs new file mode 100644 index 0000000..67d8214 --- /dev/null +++ b/src/EfficientUnionGenerator.GeneratorBenchmark/Program.cs @@ -0,0 +1,49 @@ +// #define PROFILING + +using BenchmarkDotNet.Running; +using EfficientUnionGenerator; +using EfficientUnionGenerator.GeneratorBenchmark; + +#if DEBUG + +var defaultConsoleColor = Console.ForegroundColor; +Console.WriteLine("This application is running in DEBUG mode."); +Console.WriteLine("This build mode is only for debugging benchmark context."); +Console.WriteLine("Rebuild release mode for benchmarking."); +Console.WriteLine(); + +var context = new GeneratorBenchmarkContext() +{ + TypeName = "Int32OrString", +}; +context.Setup(); +context.ResetDriver(); +context.IncrementalUpdate_NoChanges(); + +Console.WriteLine("[loaded source code]:"); +Console.ForegroundColor = ConsoleColor.DarkGray; +Console.WriteLine(context.SourceCode); +Console.ForegroundColor = defaultConsoleColor; + +#elif PROFILING + +var context = new GeneratorBenchmarkContext() +{ + TypeName = "Int32OrString", +}; +context.Setup(); +for(var i = 0; i < (1 << 16); ++i) +{ + context.ResetDriver(); + context.IncrementalUpdate_NoChanges(); +} + +var reports = Generator.Profiler.CreateProfileReports(); +var report = reports["PredicateUnionType"]; +Console.WriteLine(report); + +#else + +BenchmarkRunner.Run(); + +#endif diff --git a/src/EfficientUnionGenerator.SampleApp/EfficientUnionGenerator.SampleApp.csproj b/src/EfficientUnionGenerator.SampleApp/EfficientUnionGenerator.SampleApp.csproj index 814aed4..e08efa0 100644 --- a/src/EfficientUnionGenerator.SampleApp/EfficientUnionGenerator.SampleApp.csproj +++ b/src/EfficientUnionGenerator.SampleApp/EfficientUnionGenerator.SampleApp.csproj @@ -9,6 +9,7 @@ enable true True + Debug;Release;Profiling diff --git a/src/EfficientUnionGenerator.SampleApp/ElfHeader/ElfHeader.cs b/src/EfficientUnionGenerator.SampleApp/ElfHeader/ElfHeader.cs new file mode 100644 index 0000000..1eac5d8 --- /dev/null +++ b/src/EfficientUnionGenerator.SampleApp/ElfHeader/ElfHeader.cs @@ -0,0 +1,188 @@ +using System.Runtime.InteropServices; +using EfficientUnion; + +namespace EfficientUnionGenerator.SampleApp.ElfHeader; + +#pragma warning restore format + +public enum EI_CLASS : byte +{ + ELFCLASSNONE = 0, + ELFCLASS32 = 1, + ELFCLASS64 = 2, +} + +public enum EI_DATA : byte +{ + ELFDATANONE = 0, + ELFDATA2LSB = 1, + ELFDATA2MSB = 2, +} + +public enum EI_VERSION : byte +{ + EV_NONE = 0, + EV_CURRENT = 1, +} + +public enum E_TYPE : ushort +{ + ET_NONE = 0, + ET_REL = 1, + ET_EXEC = 2, + ET_DYN = 3, + ET_CORE = 4, +} + +public enum E_MACHINE : ushort +{ + EM_NONE = 0, + EM_M32 = 1, + EM_SPARC = 2, + EM_386 = 3, + EM_68K = 4, + EM_88K = 5, + EM_860 = 7, + EM_MIPS = 8, + EM_ARM = 40, + EM_X86_64 = 62, +} + +public enum E_VERSION : uint +{ + EV_NONE = 0, + EV_CURRENT = 1, +} + +public enum E_SHSTRNDX : ushort +{ + SHN_UNDEF = 0, + SHN_LORESERVE = 0xFF00, + SHN_LOPROC = 0xFF00, + SHN_HIPROC = 0xFF1F, + SHN_ABS = 0xFFF1, + SHN_COMMON = 0xFFF2, + SHN_HIRESERVE = 0xFFFF, +} + +#pragma warning disable format +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public unsafe struct Elf32Header +{ + private fixed byte e_ident[16]; + public E_TYPE e_type; + public E_MACHINE e_machine; + public E_VERSION e_version; + public uint e_entry; + public uint e_phoff; + public uint e_shoff; + public uint e_flags; + public ushort e_ehsize; + public ushort e_phentsize; + public ushort e_phnum; + public ushort e_shentsize; + public ushort e_shnum; + public E_SHSTRNDX e_shstrndx; + + public byte E_Ident(int offset) => e_ident[offset]; + public byte EI_MAG0 { get => e_ident[0]; init => e_ident[0] = value; } + public byte EI_MAG1 { get => e_ident[1]; init => e_ident[1] = value; } + public byte EI_MAG2 { get => e_ident[2]; init => e_ident[2] = value; } + public byte EI_MAG3 { get => e_ident[3]; init => e_ident[3] = value; } + public EI_CLASS EI_CLASS { get => (EI_CLASS) e_ident[4]; init => e_ident[4] = (byte)value; } + public EI_DATA EI_DATA { get => (EI_DATA) e_ident[5]; init => e_ident[5] = (byte)value; } + public EI_VERSION EI_VERSION { get => (EI_VERSION)e_ident[6]; init => e_ident[6] = (byte)value; } + public byte EI_OSABI { get => e_ident[7]; init => e_ident[7] = value; } + public byte EI_ABIVERSION { get => e_ident[8]; init => e_ident[8] = value; } + + public bool IsValid => + EI_MAG0 == 0x7F && + EI_MAG1 == (byte)'E' && + EI_MAG2 == (byte)'L' && + EI_MAG3 == (byte)'F' && + EI_CLASS == EI_CLASS.ELFCLASS32 && + EI_VERSION == EI_VERSION.EV_CURRENT && + e_version == E_VERSION.EV_CURRENT && + e_ehsize == 52; + + public Elf32Header(EI_DATA ei_data) + { + EI_MAG0 = 0x7F; + EI_MAG1 = (byte)'E'; + EI_MAG2 = (byte)'L'; + EI_MAG3 = (byte)'F'; + EI_CLASS = EI_CLASS.ELFCLASS32; + EI_DATA = ei_data; + EI_VERSION = EI_VERSION.EV_CURRENT; + e_version = E_VERSION.EV_CURRENT; + e_ehsize = 52; + } +} + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public unsafe struct Elf64Header +{ + private fixed byte e_ident[16]; + public E_TYPE e_type; + public E_MACHINE e_machine; + public E_VERSION e_version; + public ulong e_entry; + public ulong e_phoff; + public ulong e_shoff; + public uint e_flags; + public ushort e_ehsize; + public ushort e_phentsize; + public ushort e_phnum; + public ushort e_shentsize; + public ushort e_shnum; + public E_SHSTRNDX e_shstrndx; + + public byte E_Ident(int offset) => e_ident[offset]; + public byte EI_MAG0 { get => e_ident[0]; init => e_ident[0] = value; } + public byte EI_MAG1 { get => e_ident[1]; init => e_ident[1] = value; } + public byte EI_MAG2 { get => e_ident[2]; init => e_ident[2] = value; } + public byte EI_MAG3 { get => e_ident[3]; init => e_ident[3] = value; } + public EI_CLASS EI_CLASS { get => (EI_CLASS) e_ident[4]; init => e_ident[4] = (byte)value; } + public EI_DATA EI_DATA { get => (EI_DATA) e_ident[5]; init => e_ident[5] = (byte)value; } + public EI_VERSION EI_VERSION { get => (EI_VERSION)e_ident[6]; init => e_ident[6] = (byte)value; } + public byte EI_OSABI { get => e_ident[7]; init => e_ident[7] = value; } + public byte EI_ABIVERSION { get => e_ident[8]; init => e_ident[8] = value; } + + public bool IsValid => + EI_MAG0 == 0x7F && + EI_MAG1 == (byte)'E' && + EI_MAG2 == (byte)'L' && + EI_MAG3 == (byte)'F' && + EI_CLASS == EI_CLASS.ELFCLASS64 && + EI_VERSION == EI_VERSION.EV_CURRENT && + e_version == E_VERSION.EV_CURRENT && + e_ehsize == 64; + + public Elf64Header(EI_DATA ei_data) + { + EI_MAG0 = 0x7F; + EI_MAG1 = (byte)'E'; + EI_MAG2 = (byte)'L'; + EI_MAG3 = (byte)'F'; + EI_CLASS = EI_CLASS.ELFCLASS64; + EI_DATA = ei_data; + EI_VERSION = EI_VERSION.EV_CURRENT; + e_version = E_VERSION.EV_CURRENT; + e_ehsize = 64; + } +} + +// 7 6 5 4 3 2 1 0 e_ident +[EfficientUnion(Mode, unmanagedFieldMask: 0x00_00_00_FF_00_00_00_00uL)] +public readonly partial struct ElfHeader +{ + private const TypeIdentifierValueMode Mode = + TypeIdentifierValueMode.ExplicitAssign + | TypeIdentifierValueMode.LeaveWhenCreate + | TypeIdentifierValueMode.LeaveWhenGet; + + // 7 6 5 4 3 2 1 0 e_ident + [EnumBitPattern(0x00_00_00_01_00_00_00_00uL)] public partial ElfHeader(Elf32Header x); + // 7 6 5 4 3 2 1 0 e_ident + [EnumBitPattern(0x00_00_00_02_00_00_00_00uL)] public partial ElfHeader(Elf64Header x); +} diff --git a/src/EfficientUnionGenerator.SampleApp/ElfHeader/Sample.cs b/src/EfficientUnionGenerator.SampleApp/ElfHeader/Sample.cs index d60eb1e..b7d493c 100644 --- a/src/EfficientUnionGenerator.SampleApp/ElfHeader/Sample.cs +++ b/src/EfficientUnionGenerator.SampleApp/ElfHeader/Sample.cs @@ -1,6 +1,5 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using EfficientUnion; namespace EfficientUnionGenerator.SampleApp.ElfHeader; @@ -42,187 +41,3 @@ private static void ShowValue(string name, ElfHeader x) #endif } } - - -public enum EI_CLASS : byte -{ - ELFCLASSNONE = 0, - ELFCLASS32 = 1, - ELFCLASS64 = 2, -} - -public enum EI_DATA : byte -{ - ELFDATANONE = 0, - ELFDATA2LSB = 1, - ELFDATA2MSB = 2, -} - -public enum EI_VERSION : byte -{ - EV_NONE = 0, - EV_CURRENT = 1, -} - -public enum E_TYPE : ushort -{ - ET_NONE = 0, - ET_REL = 1, - ET_EXEC = 2, - ET_DYN = 3, - ET_CORE = 4, -} - -public enum E_MACHINE : ushort -{ - EM_NONE = 0, - EM_M32 = 1, - EM_SPARC = 2, - EM_386 = 3, - EM_68K = 4, - EM_88K = 5, - EM_860 = 7, - EM_MIPS = 8, - EM_ARM = 40, - EM_X86_64 = 62, -} - -public enum E_VERSION : uint -{ - EV_NONE = 0, - EV_CURRENT = 1, -} - -public enum E_SHSTRNDX : ushort -{ - SHN_UNDEF = 0, - SHN_LORESERVE = 0xFF00, - SHN_LOPROC = 0xFF00, - SHN_HIPROC = 0xFF1F, - SHN_ABS = 0xFFF1, - SHN_COMMON = 0xFFF2, - SHN_HIRESERVE = 0xFFFF, -} - -#pragma warning disable format -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public unsafe struct Elf32Header -{ - private fixed byte e_ident[16]; - public E_TYPE e_type; - public E_MACHINE e_machine; - public E_VERSION e_version; - public uint e_entry; - public uint e_phoff; - public uint e_shoff; - public uint e_flags; - public ushort e_ehsize; - public ushort e_phentsize; - public ushort e_phnum; - public ushort e_shentsize; - public ushort e_shnum; - public E_SHSTRNDX e_shstrndx; - - public byte E_Ident(int offset) => e_ident[offset]; - public byte EI_MAG0 { get => e_ident[0]; init => e_ident[0] = value; } - public byte EI_MAG1 { get => e_ident[1]; init => e_ident[1] = value; } - public byte EI_MAG2 { get => e_ident[2]; init => e_ident[2] = value; } - public byte EI_MAG3 { get => e_ident[3]; init => e_ident[3] = value; } - public EI_CLASS EI_CLASS { get => (EI_CLASS) e_ident[4]; init => e_ident[4] = (byte)value; } - public EI_DATA EI_DATA { get => (EI_DATA) e_ident[5]; init => e_ident[5] = (byte)value; } - public EI_VERSION EI_VERSION { get => (EI_VERSION)e_ident[6]; init => e_ident[6] = (byte)value; } - public byte EI_OSABI { get => e_ident[7]; init => e_ident[7] = value; } - public byte EI_ABIVERSION { get => e_ident[8]; init => e_ident[8] = value; } - - public bool IsValid => - EI_MAG0 == 0x7F && - EI_MAG1 == (byte)'E' && - EI_MAG2 == (byte)'L' && - EI_MAG3 == (byte)'F' && - EI_CLASS == EI_CLASS.ELFCLASS32 && - EI_VERSION == EI_VERSION.EV_CURRENT && - e_version == E_VERSION.EV_CURRENT && - e_ehsize == 52; - - public Elf32Header(EI_DATA ei_data) - { - EI_MAG0 = 0x7F; - EI_MAG1 = (byte)'E'; - EI_MAG2 = (byte)'L'; - EI_MAG3 = (byte)'F'; - EI_CLASS = EI_CLASS.ELFCLASS32; - EI_DATA = ei_data; - EI_VERSION = EI_VERSION.EV_CURRENT; - e_version = E_VERSION.EV_CURRENT; - e_ehsize = 52; - } -} - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public unsafe struct Elf64Header -{ - private fixed byte e_ident[16]; - public E_TYPE e_type; - public E_MACHINE e_machine; - public E_VERSION e_version; - public ulong e_entry; - public ulong e_phoff; - public ulong e_shoff; - public uint e_flags; - public ushort e_ehsize; - public ushort e_phentsize; - public ushort e_phnum; - public ushort e_shentsize; - public ushort e_shnum; - public E_SHSTRNDX e_shstrndx; - - public byte E_Ident(int offset) => e_ident[offset]; - public byte EI_MAG0 { get => e_ident[0]; init => e_ident[0] = value; } - public byte EI_MAG1 { get => e_ident[1]; init => e_ident[1] = value; } - public byte EI_MAG2 { get => e_ident[2]; init => e_ident[2] = value; } - public byte EI_MAG3 { get => e_ident[3]; init => e_ident[3] = value; } - public EI_CLASS EI_CLASS { get => (EI_CLASS) e_ident[4]; init => e_ident[4] = (byte)value; } - public EI_DATA EI_DATA { get => (EI_DATA) e_ident[5]; init => e_ident[5] = (byte)value; } - public EI_VERSION EI_VERSION { get => (EI_VERSION)e_ident[6]; init => e_ident[6] = (byte)value; } - public byte EI_OSABI { get => e_ident[7]; init => e_ident[7] = value; } - public byte EI_ABIVERSION { get => e_ident[8]; init => e_ident[8] = value; } - - public bool IsValid => - EI_MAG0 == 0x7F && - EI_MAG1 == (byte)'E' && - EI_MAG2 == (byte)'L' && - EI_MAG3 == (byte)'F' && - EI_CLASS == EI_CLASS.ELFCLASS64 && - EI_VERSION == EI_VERSION.EV_CURRENT && - e_version == E_VERSION.EV_CURRENT && - e_ehsize == 64; - - public Elf64Header(EI_DATA ei_data) - { - EI_MAG0 = 0x7F; - EI_MAG1 = (byte)'E'; - EI_MAG2 = (byte)'L'; - EI_MAG3 = (byte)'F'; - EI_CLASS = EI_CLASS.ELFCLASS64; - EI_DATA = ei_data; - EI_VERSION = EI_VERSION.EV_CURRENT; - e_version = E_VERSION.EV_CURRENT; - e_ehsize = 64; - } -} -#pragma warning restore format - -// 7 6 5 4 3 2 1 0 e_ident -[EfficientUnion(Mode, unmanagedFieldMask: 0x00_00_00_FF_00_00_00_00uL)] -public readonly partial struct ElfHeader -{ - private const TypeIdentifierValueMode Mode = - TypeIdentifierValueMode.ExplicitAssign - | TypeIdentifierValueMode.LeaveWhenCreate - | TypeIdentifierValueMode.LeaveWhenGet; - - // 7 6 5 4 3 2 1 0 e_ident - [EnumBitPattern(0x00_00_00_01_00_00_00_00uL)] public partial ElfHeader(Elf32Header x); - // 7 6 5 4 3 2 1 0 e_ident - [EnumBitPattern(0x00_00_00_02_00_00_00_00uL)] public partial ElfHeader(Elf64Header x); -} diff --git a/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/PositiveOnlyIntOrFloat.cs b/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/PositiveOnlyIntOrFloat.cs new file mode 100644 index 0000000..9003291 --- /dev/null +++ b/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/PositiveOnlyIntOrFloat.cs @@ -0,0 +1,10 @@ +using EfficientUnion; + +namespace EfficientUnionGenerator.SampleApp.PositiveOnlyIntOrFloat; + +[EfficientUnion(unmanagedFieldMask: 0x80_00_00_00u)] +public readonly partial struct PositiveOnlyIntOrFloat +{ + public partial PositiveOnlyIntOrFloat(int value); + public partial PositiveOnlyIntOrFloat(float value); +} \ No newline at end of file diff --git a/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/Sample.cs b/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/Sample.cs index 247bd9a..c86193e 100644 --- a/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/Sample.cs +++ b/src/EfficientUnionGenerator.SampleApp/PositiveOnlyIntOrFloat/Sample.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using EfficientUnion; namespace EfficientUnionGenerator.SampleApp.PositiveOnlyIntOrFloat; @@ -41,10 +40,3 @@ private static void ShowValue(string name, PositiveOnlyIntOrFloat x) #endif } } - -[EfficientUnion(unmanagedFieldMask: 0x80_00_00_00u)] -public readonly partial struct PositiveOnlyIntOrFloat -{ - public partial PositiveOnlyIntOrFloat(int value); - public partial PositiveOnlyIntOrFloat(float value); -} \ No newline at end of file diff --git a/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Int32OrString.cs b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Int32OrString.cs new file mode 100644 index 0000000..ac75fd1 --- /dev/null +++ b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Int32OrString.cs @@ -0,0 +1,10 @@ +using EfficientUnion; + +namespace EfficientUnionGenerator.SampleApp.SimpleUnionOfInt32OrString; + +[EfficientUnion] +public readonly partial struct Int32OrString +{ + public partial Int32OrString(int x); + public partial Int32OrString(string x); +} diff --git a/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Sample.cs b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Sample.cs index 475e20d..86f6d7d 100644 --- a/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Sample.cs +++ b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfInt32OrString/Sample.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using EfficientUnion; namespace EfficientUnionGenerator.SampleApp.SimpleUnionOfInt32OrString; @@ -41,11 +40,3 @@ private static void ShowValue(string name, Int32OrString x) #endif } } - - -[EfficientUnion] -public readonly partial struct Int32OrString -{ - public partial Int32OrString(int x); - public partial Int32OrString(string x); -} diff --git a/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/Sample.cs b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/Sample.cs index de88a2e..8011f02 100644 --- a/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/Sample.cs +++ b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/Sample.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using EfficientUnion; namespace EfficientUnionGenerator.SampleApp.SimpleUnionOfSignedInteger; @@ -59,12 +58,3 @@ private static void ShowValue(string name, SignedInteger x) #endif } } - -[EfficientUnion] -public readonly partial struct SignedInteger -{ - public partial SignedInteger(sbyte x); - public partial SignedInteger(short x); - public partial SignedInteger(int x); - public partial SignedInteger(long x); -} \ No newline at end of file diff --git a/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/SignedInteger.cs b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/SignedInteger.cs new file mode 100644 index 0000000..9491632 --- /dev/null +++ b/src/EfficientUnionGenerator.SampleApp/SimpleUnionOfSignedInteger/SignedInteger.cs @@ -0,0 +1,12 @@ +using EfficientUnion; + +namespace EfficientUnionGenerator.SampleApp.SimpleUnionOfSignedInteger; + +[EfficientUnion] +public readonly partial struct SignedInteger +{ + public partial SignedInteger(sbyte x); + public partial SignedInteger(short x); + public partial SignedInteger(int x); + public partial SignedInteger(long x); +} \ No newline at end of file diff --git a/src/EfficientUnionGenerator.Test/EfficientUnionGenerator.Test.csproj b/src/EfficientUnionGenerator.Test/EfficientUnionGenerator.Test.csproj index 3c5d2b3..483a439 100644 --- a/src/EfficientUnionGenerator.Test/EfficientUnionGenerator.Test.csproj +++ b/src/EfficientUnionGenerator.Test/EfficientUnionGenerator.Test.csproj @@ -5,6 +5,7 @@ enable enable false + Debug;Release;Profiling diff --git a/src/EfficientUnionGenerator.Test/GeneratorTest.Logics.cs b/src/EfficientUnionGenerator.Test/GeneratorTest.Logics.cs new file mode 100644 index 0000000..6ef68d9 --- /dev/null +++ b/src/EfficientUnionGenerator.Test/GeneratorTest.Logics.cs @@ -0,0 +1,60 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EfficientUnionGenerator.Test; + +public partial class GeneratorTest +{ + private static AttributeData GetAttributeData(string source, CancellationToken canceller) + { + var tree = CSharpSyntaxTree.ParseText(source, cancellationToken: canceller); + var structDecl = tree + .GetRoot(canceller) + .DescendantNodes() + .OfType() + .First(); + var comp = CSharpCompilation.Create( + "TestAssembly", + [tree,], + [ + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), + ], + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); + var typeSym = comp + .GetSemanticModel(tree) + .GetDeclaredSymbol(structDecl, canceller)!; + return typeSym.GetAttributes().First(); + } + + [Fact] + public void IsEfficientUnionType_ShouldReturnFalseForUnrelatedTypes() + { + const string source = $$""" + internal class UnrelatedAttribute : System.Attribute; + + [UnrelatedAttribute] + public partial struct S; + """; + var attrData = GetAttributeData(source, CancellationToken); + Assert.False(Generator.IsEfficientUnionAttribute(attrData)); + } + + [Fact] + public void IsEfficientUnionType_ShouldReturnTrueForEfficientUnionType() + { + const string source = $$""" + namespace {{Constants.AttributeNamespace}} + { + internal class {{Constants.EfficientUnionAttributeName}} : System.Attribute; + } + + [{{Constants.EfficientUnionAttributeFullName}}] + public partial struct S; + """; + var attrData = GetAttributeData(source, CancellationToken); + Assert.True(Generator.IsEfficientUnionAttribute(attrData)); + } +} diff --git a/src/EfficientUnionGenerator.Test/GeneratorTest.cs b/src/EfficientUnionGenerator.Test/GeneratorTest.cs new file mode 100644 index 0000000..0261ad8 --- /dev/null +++ b/src/EfficientUnionGenerator.Test/GeneratorTest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EfficientUnionGenerator.Test; + +public partial class GeneratorTest(ITestContextAccessor accessor) +{ + private readonly ITestContextAccessor _accessor = accessor; + + private ITestContext TestContext => _accessor.Current; + private CancellationToken CancellationToken => TestContext.CancellationToken; +} diff --git a/src/EfficientUnionGenerator/AssemblyInfo.cs b/src/EfficientUnionGenerator/AssemblyInfo.cs new file mode 100644 index 0000000..4c6f29f --- /dev/null +++ b/src/EfficientUnionGenerator/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("EfficientUnionGenerator.Test")] diff --git a/src/EfficientUnionGenerator/CodePathProfiler.Dummy.cs b/src/EfficientUnionGenerator/CodePathProfiler.Dummy.cs new file mode 100644 index 0000000..e528a0c --- /dev/null +++ b/src/EfficientUnionGenerator/CodePathProfiler.Dummy.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +#pragma warning disable IDE0130 +namespace PathBench; +#pragma warning restore IDE0130 + +#if !PROFILING + +public class CodePathProfiler +{ + private static readonly CodePathProfiler _instance = new(); + + private CodePathProfiler() { } + + public static CodePathProfiler Create() => _instance; + + internal CodePathProfileScope StartMeasurement() => new(); + + internal ref struct CodePathProfileScope + { + public readonly void MarkCheckpoint(string _) { } + public readonly void Dispose() { } + } +} + +#endif diff --git a/src/EfficientUnionGenerator/Constants.cs b/src/EfficientUnionGenerator/Constants.cs index 51c5669..b4a1b12 100644 --- a/src/EfficientUnionGenerator/Constants.cs +++ b/src/EfficientUnionGenerator/Constants.cs @@ -1,23 +1,39 @@ -using System; -using System.Collections.Generic; -using System.Text; - namespace EfficientUnionGenerator; -internal static class Constants +public static class Constants { public const string AttributeNamespace = "EfficientUnion"; public const string EfficientUnionAttributeName = "EfficientUnionAttribute"; + public const string EfficientUnionAttributeFullName = $"{AttributeNamespace}.{EfficientUnionAttributeName}"; + public const string EnumBitPatternAttributeName = "EnumBitPatternAttribute"; public const string TypeIdentifierValueModeEnumName = nameof(TypeIdentifierValueMode); + public const string CompilerServicesUnionAttributeSource = """ + namespace System.Runtime.CompilerServices; + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)] + internal sealed class UnionAttribute : Attribute; + """; + + public const string CompilerServicesIUnionSource = """ + #nullable enable + namespace System.Runtime.CompilerServices; + + internal interface IUnion + { + object? Value { get; } + } + """; + public static readonly string EfficientUnionAttributeSource = $$""" using System; namespace {{AttributeNamespace}}; + /// /// Indicates how to treat the fields corresponding to the set bits in the unmanaged field bit mask. /// @@ -60,6 +76,18 @@ internal enum {{TypeIdentifierValueModeEnumName}} : int {{nameof(TypeIdentifierValueMode.LeaveWhenGet)}} = {{(int)TypeIdentifierValueMode.LeaveWhenGet}}, } + +[AttributeUsage(AttributeTargets.Constructor, Inherited = false, AllowMultiple = false)] +internal sealed class {{EnumBitPatternAttributeName}} : Attribute +{ + public ulong Flag { get; } + + public {{EnumBitPatternAttributeName}}(ulong flag) + { + } +} + + /// /// Generates an efficient union struct implementation for the attributed struct. /// The struct must be a partial struct. @@ -107,33 +135,5 @@ internal sealed class {{EfficientUnionAttributeName}} : Attribute UnmanagedFieldMask = unmanagedFieldMask; } } - - -[AttributeUsage(AttributeTargets.Constructor, Inherited = false, AllowMultiple = false)] -internal sealed class {{EnumBitPatternAttributeName}} : Attribute -{ - public ulong Flag { get; } - - public {{EnumBitPatternAttributeName}}(ulong flag) - { - } -} """; - - public static readonly string CompilerServicesUnionAttributeSource = """ - namespace System.Runtime.CompilerServices; - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)] - internal sealed class UnionAttribute : Attribute; - """; - - public static readonly string CompilerServicesIUnionSource = """ - #nullable enable - namespace System.Runtime.CompilerServices; - - internal interface IUnion - { - object? Value { get; } - } - """; } diff --git a/src/EfficientUnionGenerator/EfficientUnionGenerator.csproj b/src/EfficientUnionGenerator/EfficientUnionGenerator.csproj index 74d2fcf..a6eb773 100644 --- a/src/EfficientUnionGenerator/EfficientUnionGenerator.csproj +++ b/src/EfficientUnionGenerator/EfficientUnionGenerator.csproj @@ -1,31 +1,32 @@  - - netstandard2.0 - 14 - enable - enable + + netstandard2.0 + 14 + enable + enable - true - cs - false - true - false - true - true + true + cs + false + true + false + true + true - akanse.$(AssemblyName) - EfficientUnionGenerator - 0.1.0.1 - aka-nse - - LICENSE.txt - README.md - https://github.com/aka-nse/EfficientUnionGenerator - https://github.com/aka-nse/EfficientUnionGenerator - Generator - True - + akanse.$(AssemblyName) + EfficientUnionGenerator + 0.1.0.1 + aka-nse + + LICENSE.txt + README.md + https://github.com/aka-nse/EfficientUnionGenerator + https://github.com/aka-nse/EfficientUnionGenerator + Generator + True + Debug;Release;Profiling + @@ -34,6 +35,10 @@ + + + + diff --git a/src/EfficientUnionGenerator/Generator.cs b/src/EfficientUnionGenerator/Generator.cs index 1d40ba6..c3a82a1 100644 --- a/src/EfficientUnionGenerator/Generator.cs +++ b/src/EfficientUnionGenerator/Generator.cs @@ -1,13 +1,24 @@ using System.Collections.Immutable; +using System.Diagnostics; using Microsoft.CodeAnalysis; using SourceGeneratorToolkit; +using PathBench; +using Microsoft.CodeAnalysis.CSharp.Syntax; + namespace EfficientUnionGenerator; + using static Constants; [Generator(LanguageNames.CSharp)] public partial class Generator : IIncrementalGenerator { + public static CodePathProfiler Profiler = + CodePathProfiler.Create(); + + private static IEqualityComparer AttributeSyntaxContextComparer { get; } = + new GeneratorAttributeSyntaxContextComparer(Profiler); + public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(static cxt => @@ -42,67 +53,101 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); var sourceUnion = context.SyntaxProvider.ForAttributeWithMetadataName( - $"{AttributeNamespace}.{EfficientUnionAttributeName}", - static (node, token) => true, - PredicateUnionType); + $"{AttributeNamespace}.{EfficientUnionAttributeName}", + static (node, token) => node is ClassDeclarationSyntax or StructDeclarationSyntax, + static (context, token) => context) + .WithComparer(AttributeSyntaxContextComparer) + .Select(PredicateUnionType) + .WithComparer(UnitTypeGenerationInfoComparer.Instance); context.RegisterSourceOutput(sourceUnion, EmitUnionType); } - private static UnionTypeDefinition PredicateUnionType(GeneratorAttributeSyntaxContext context, CancellationToken token) + private static UnitTypeGenerationInfo? PredicateUnionType( + GeneratorAttributeSyntaxContext context, + CancellationToken token) { + using var counter = Profiler.StartMeasurement(); + + ((TypeDeclarationSyntax)context.TargetNode).ChildNodes().OfType().ToList(); + var symbol = (INamedTypeSymbol)context.TargetSymbol; + + counter.MarkCheckpoint("After symbol retrieval"); + var fullName = symbol.ToDisplayString(); - var candidateTypes = symbol - .GetMembers() - .OfType() + + counter.MarkCheckpoint("After full name retrieval"); + + var unionTypeDefinitionAttributeData = context.Attributes + .FirstOrDefault(IsEfficientUnionAttribute); + if (unionTypeDefinitionAttributeData is null) + { + return null; + } + + counter.MarkCheckpoint("After attribute data retrieval"); + + var members = symbol.Constructors; + + counter.MarkCheckpoint("After members retrieval"); + + var candidateTypes = members .Select(TypeCandidateDefinition.Create) .OfType(); + + counter.MarkCheckpoint("After candidate type creation"); + var candidateUnmanagedTypes = ImmutableArray.CreateBuilder(); var candidateManagedTypes = ImmutableArray.CreateBuilder(); foreach (var type in candidateTypes) { (type.IsUnmanaged ? candidateUnmanagedTypes : candidateManagedTypes).Add(type); } - TypeIdentifierValueMode mode; - ulong bitMask; - if (context.Attributes[0].ConstructorArguments.Length == 0) - { - mode = TypeIdentifierValueMode.AutoAssign; - bitMask = 0; - } - else if (context.Attributes[0].ConstructorArguments.Length == 2) - { - mode = (TypeIdentifierValueMode)(int)context.Attributes[0].ConstructorArguments[0].Value!; - bitMask = (ulong)context.Attributes[0].ConstructorArguments[1].Value!; - } - else + + counter.MarkCheckpoint("After candidate type separation"); + + var (mode, bitMask) = ReadAttributeParameter(unionTypeDefinitionAttributeData); + + counter.MarkCheckpoint("After attribute parameter reading"); + + return new( + new UnionTypeDefinition( + fullName, + candidateUnmanagedTypes.ToImmutable(), + candidateManagedTypes.ToImmutable(), + bitMask, + mode), + new SourceBuilder(context, false)); + } + + + private static void EmitUnionType(SourceProductionContext context, UnitTypeGenerationInfo? info) + { + if (info is null) { - throw new InvalidOperationException($"Unsupported bit mask type"); + return; } - return new UnionTypeDefinition( - fullName, - candidateUnmanagedTypes.ToImmutable(), - candidateManagedTypes.ToImmutable(), - bitMask, - mode - ) { SourceBuilder = new SourceBuilder(context, false) }; + var (source, sb) = info; + var (hintName, sourceCode) = GenerateImplementationSource(source, sb); + context.AddSource( + hintName, + sourceCode + ); } - private static void EmitUnionType(SourceProductionContext context, UnionTypeDefinition source) + internal static (string hintName, string sourceCode) GenerateImplementationSource(UnionTypeDefinition source, SourceBuilder sb) { - var sb = source.SourceBuilder; var mode = source.TypeIdentifierValueMode; sb.AppendAutoGeneratedComment(); sb.AppendLine("#nullable enable"); sb.AppendNamespaceDeclaration(); - using (var typeDecl = sb.BeginTargetTypeDeclare()) { typeDecl.AddAttribute($"System.Runtime.CompilerServices.Union"); - GenerateTypeSpecifierEnumDeclaration(source); + GenerateTypeSpecifierEnumDeclaration(source, sb); if (source.CandidateUnmanagedTypes.Length > 0) { @@ -215,7 +260,7 @@ private object? UnmanagedValue """); } - if(source.BitMask == 0 || mode.IsLeaveWhenGet) + if (source.BitMask == 0 || mode.IsLeaveWhenGet) { sb.AppendLine($$""" public bool TryGetValue(out {{type.TypeName}} value) @@ -291,7 +336,7 @@ public bool TryGetValue([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out } } - switch((source.CandidateUnmanagedTypes.Length > 0, source.CandidateManagedTypes.Length > 0)) + switch ((source.CandidateUnmanagedTypes.Length > 0, source.CandidateManagedTypes.Length > 0)) { case (true, true): sb.AppendLine($$""" @@ -324,16 +369,12 @@ public bool TryGetValue([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out var hintName = sb.GetPreferHintName(prefix: "EfficientUnionGenerator-", suffix: ".g"); var sourceCode = sb.Build(); - context.AddSource( - hintName, - sourceCode - ); + return (hintName, sourceCode); } - private static void GenerateTypeSpecifierEnumDeclaration(UnionTypeDefinition source) + internal static void GenerateTypeSpecifierEnumDeclaration(UnionTypeDefinition source, ISourceBuilder sb) { - var sb = source.SourceBuilder; var baseType = source.TypeIdentifierBaseType; var mode = source.TypeIdentifierValueMode; var bitMask = source.BitMask; @@ -357,7 +398,7 @@ private enum __TypeSpecifier{{baseType}} } } } - else if(bitMask == 0) + else if (bitMask == 0) { var i = 1; foreach (var type in types) @@ -391,4 +432,115 @@ private enum __TypeSpecifier{{baseType}} } + internal static bool IsEfficientUnionAttribute(AttributeData attrData) => + attrData.AttributeClass?.ToDisplayString() == EfficientUnionAttributeFullName; + + + internal static (TypeIdentifierValueMode mode, ulong bitMask) ReadAttributeParameter(AttributeData attrData) + { + Debug.Assert(IsEfficientUnionAttribute(attrData)); + + TypeIdentifierValueMode mode; + ulong bitMask; + if (attrData.ConstructorArguments.Length == 0) + { + mode = TypeIdentifierValueMode.AutoAssign; + bitMask = 0; + } + else if (attrData.ConstructorArguments.Length == 2) + { + mode = (TypeIdentifierValueMode)(int)attrData.ConstructorArguments[0].Value!; + bitMask = (ulong)attrData.ConstructorArguments[1].Value!; + } + else + { + throw new InvalidOperationException($"Unsupported bit mask type"); + } + return (mode, bitMask); + } +} + + +internal record UnitTypeGenerationInfo(UnionTypeDefinition Source, SourceBuilder SourceBuilder); + + +// determines whether re-generation is needed based on the equality of the source and source builder. +file class GeneratorAttributeSyntaxContextComparer(CodePathProfiler profiler) + : IEqualityComparer +{ + public bool Equals(GeneratorAttributeSyntaxContext x, GeneratorAttributeSyntaxContext y) + { + using var counter = profiler.StartMeasurement(); + + if ((x.TargetNode, y.TargetNode) is not (TypeDeclarationSyntax xType, TypeDeclarationSyntax yType)) + { + throw new InvalidOperationException(); + } + + if (xType.Identifier.ValueText != yType.Identifier.ValueText) + { + return false; + } + + counter.MarkCheckpoint("Identifier check"); + + var ctorsY = new HashSet(SyntaxNodeComparer.Instance); + foreach (var member in yType.Members) + { + if (member is ConstructorDeclarationSyntax ctor) + { + ctorsY.Add(ctor); + } + } + + counter.MarkCheckpoint("Collect ctors of y"); + + foreach (var member in xType.Members) + { + if (member is ConstructorDeclarationSyntax ctor) + { + if (!ctorsY.Contains(ctor)) + { + return false; + } + } + } + return true; + } + + public int GetHashCode(GeneratorAttributeSyntaxContext obj) + { + if (obj.TargetNode is not TypeDeclarationSyntax typeDecl) + { + throw new InvalidOperationException(); + } + + var hash = (uint)typeDecl.Identifier.ValueText.GetHashCode(); + foreach (var member in typeDecl.Members) + { + if (member is not ConstructorDeclarationSyntax ctor) + { + continue; + } + hash ^= (uint)ctor.WithoutTrivia().ToFullString().GetHashCode(); + hash = (hash << 13) | (hash >> 19); + } + return (int)hash; + } } + + +file class UnitTypeGenerationInfoComparer : IEqualityComparer +{ + // SourceBuilder is a string builder for the generated source code. + // It contains information about the generation target type, but is not part of the type's logical state. + // Therefore, it should not be considered for equality comparison or hash code generation. + + public static UnitTypeGenerationInfoComparer Instance { get; } = new(); + + public bool Equals(UnitTypeGenerationInfo? x, UnitTypeGenerationInfo? y) => + UnionTypeDefinitionEqualityComparer.Equals(x?.Source, y?.Source); + + public int GetHashCode(UnitTypeGenerationInfo? obj) => + UnionTypeDefinitionEqualityComparer.GetHashCode(obj?.Source); +} \ No newline at end of file diff --git a/src/EfficientUnionGenerator/SyntaxNodeComparer.cs b/src/EfficientUnionGenerator/SyntaxNodeComparer.cs new file mode 100644 index 0000000..bc3c2c6 --- /dev/null +++ b/src/EfficientUnionGenerator/SyntaxNodeComparer.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; + +namespace EfficientUnionGenerator; + +internal class SyntaxNodeComparer : IEqualityComparer +{ + public static SyntaxNodeComparer Instance { get; } = new(); + + public bool Equals(SyntaxNode x, SyntaxNode y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + switch ((x, y)) + { + case (null, null): + return true; + case (null, _): + case (_, null): + return false; + default: + break; + } + return x.IsEquivalentTo(y); + } + + public int GetHashCode(SyntaxNode obj) + { + return obj.ToFullString().GetHashCode(); + } +} diff --git a/src/EfficientUnionGenerator/UnionTypeDefinition.cs b/src/EfficientUnionGenerator/UnionTypeDefinition.cs index 91904cd..d5d8ff5 100644 --- a/src/EfficientUnionGenerator/UnionTypeDefinition.cs +++ b/src/EfficientUnionGenerator/UnionTypeDefinition.cs @@ -10,9 +10,6 @@ public record UnionTypeDefinition( ulong BitMask, TypeIdentifierValueMode TypeIdentifierValueMode) { - internal SourceBuilder SourceBuilder { get; init; } = default!; - - public string TypeIdentifierBaseType => BitMask switch { <= byte.MaxValue => " : byte", @@ -21,6 +18,7 @@ public record UnionTypeDefinition( _ => " : ulong", }; + public IEnumerable GetUnmanagedFieldDecl() { foreach (var type in CandidateUnmanagedTypes) @@ -31,71 +29,11 @@ public IEnumerable GetUnmanagedFieldDecl() } } - public override int GetHashCode() - { - var hash = (uint)TypeName.GetHashCode(); - foreach (var candidate in CandidateUnmanagedTypes) - { - hash = (hash << 13) | (hash >> 19); - hash ^= (uint)candidate.GetHashCode(); - } - foreach (var candidate in CandidateManagedTypes) - { - hash = (hash << 13) | (hash >> 19); - hash ^= (uint)candidate.GetHashCode(); - } - return (int)hash; - } - public virtual bool Equals(UnionTypeDefinition? other) - { - if (other is null) - { - return false; - } - - if (TypeName != other.TypeName) - { - return false; - } + public override int GetHashCode() => + UnionTypeDefinitionEqualityComparer.GetHashCode(this); - if (CandidateUnmanagedTypes.Length != other.CandidateUnmanagedTypes.Length) - { - return false; - } - for (int i = 0; i < CandidateUnmanagedTypes.Length; i++) - { - if (CandidateUnmanagedTypes[i] != other.CandidateUnmanagedTypes[i]) - { - return false; - } - } - - if (CandidateManagedTypes.Length != other.CandidateManagedTypes.Length) - { - return false; - } - - for (int i = 0; i < CandidateManagedTypes.Length; i++) - { - if (CandidateManagedTypes[i] != other.CandidateManagedTypes[i]) - { - return false; - } - } - - if (BitMask != other.BitMask) - { - return false; - } - - if (TypeIdentifierValueMode != other.TypeIdentifierValueMode) - { - return false; - } - - // SourceBuilder is not considered for equality as it is used for code generation and does not affect the identity of the union type definition. - return true; - } + public virtual bool Equals(UnionTypeDefinition? other) => + UnionTypeDefinitionEqualityComparer.Equals(this, other); } diff --git a/src/EfficientUnionGenerator/UnionTypeDefinitionEqualityComparer.cs b/src/EfficientUnionGenerator/UnionTypeDefinitionEqualityComparer.cs new file mode 100644 index 0000000..ff75ecb --- /dev/null +++ b/src/EfficientUnionGenerator/UnionTypeDefinitionEqualityComparer.cs @@ -0,0 +1,71 @@ +namespace EfficientUnionGenerator; + +public sealed class UnionTypeDefinitionEqualityComparer + : IEqualityComparer +{ + public static UnionTypeDefinitionEqualityComparer Instance { get; } = new (); + + bool IEqualityComparer.Equals(UnionTypeDefinition? x, UnionTypeDefinition? y) => + Equals(x, y); + + int IEqualityComparer.GetHashCode(UnionTypeDefinition? obj) => + GetHashCode(obj); + + public static bool Equals(UnionTypeDefinition? x, UnionTypeDefinition? y) + { + switch ((x, y)) + { + case (null, null): + return true; + case (null, _): + case (_, null): + return false; + default: + break; + } + + if (x.TypeName != y.TypeName) + { + return false; + } + + if (!Enumerable.SequenceEqual(x.CandidateUnmanagedTypes, y.CandidateUnmanagedTypes)) + { + return false; + } + if (!Enumerable.SequenceEqual(x.CandidateManagedTypes, y.CandidateManagedTypes)) + { + return false; + } + if (x.BitMask != y.BitMask) + { + return false; + } + if (x.TypeIdentifierValueMode != y.TypeIdentifierValueMode) + { + return false; + } + return true; + } + + public static int GetHashCode(UnionTypeDefinition? obj) + { + if (obj is null) + { + return 0; + } + + var hash = (uint)obj.TypeName.GetHashCode(); + foreach (var candidate in obj.CandidateUnmanagedTypes) + { + hash = (hash << 13) | (hash >> 19); + hash ^= (uint)candidate.GetHashCode(); + } + foreach (var candidate in obj.CandidateManagedTypes) + { + hash = (hash << 13) | (hash >> 19); + hash ^= (uint)candidate.GetHashCode(); + } + return (int)hash; + } +} \ No newline at end of file