From 6d1093fdb9821c5289dbd7979ad95ec4414cdd9f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 12 Jul 2026 13:52:11 -0700 Subject: [PATCH] Avoid boxing in flag enum checks by using bitwise tests Enum.HasFlag boxes both the enum value and the flag argument on every call. The configuration option getters are queried heavily during generation, so this shows up as ~14.7 MB (~5%) of allocations on the terrafx d3d12 generation under Tier0/JIT. Replace HasFlag with a direct bitwise test. All of the affected flags are single-bit, so (value & flag) != 0 is exactly equivalent. The JIT elides HasFlag boxing via an intrinsic once the getters tier up, and NativeAOT is optimized throughout, so this is a no-op there and a win for local/JIT-hosted runs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Abstractions/ValueDesc.cs | 8 +- .../PInvokeGenerator.VisitDecl.cs | 6 +- .../PInvokeGeneratorConfiguration.cs | 92 +++++++++---------- sources/ClangSharpPInvokeGenerator/Program.cs | 6 +- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/Abstractions/ValueDesc.cs b/sources/ClangSharp.PInvokeGenerator/Abstractions/ValueDesc.cs index 60a96f2a..087db164 100644 --- a/sources/ClangSharp.PInvokeGenerator/Abstractions/ValueDesc.cs +++ b/sources/ClangSharp.PInvokeGenerator/Abstractions/ValueDesc.cs @@ -17,10 +17,10 @@ internal struct ValueDesc public ValueFlags Flags { get; set; } public CXSourceLocation? Location { get; set; } - public readonly bool HasInitializer => Flags.HasFlag(ValueFlags.Initializer); - public readonly bool IsArray => Flags.HasFlag(ValueFlags.Array); - public readonly bool IsConstant => Flags.HasFlag(ValueFlags.Constant); - public readonly bool IsCopy => Flags.HasFlag(ValueFlags.Copy); + public readonly bool HasInitializer => (Flags & ValueFlags.Initializer) != 0; + public readonly bool IsArray => (Flags & ValueFlags.Array) != 0; + public readonly bool IsConstant => (Flags & ValueFlags.Constant) != 0; + public readonly bool IsCopy => (Flags & ValueFlags.Copy) != 0; public Action WriteCustomAttrs { get; set; } public object CustomAttrGeneratorData { get; set; } } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index bd29843b..d1e93191 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -3563,7 +3563,7 @@ private void VisitVarDecl(VarDecl varDecl) case CX_SLK_Ordinary: case CX_SLK_UTF8: { - typeName = flags.HasFlag(ValueFlags.Constant) ? "ReadOnlySpan" : "byte[]"; + typeName = (flags & ValueFlags.Constant) != 0 ? "ReadOnlySpan" : "byte[]"; break; } @@ -3588,7 +3588,7 @@ private void VisitVarDecl(VarDecl varDecl) case CX_SLK_UTF32: { - typeName = (!_config.GenerateCompatibleCode && flags.HasFlag(ValueFlags.Constant)) ? "ReadOnlySpan" : "uint[]"; + typeName = (!_config.GenerateCompatibleCode && (flags & ValueFlags.Constant) != 0) ? "ReadOnlySpan" : "uint[]"; break; } @@ -3603,7 +3603,7 @@ private void VisitVarDecl(VarDecl varDecl) { kind = ValueKind.Primitive; - if (flags.HasFlag(ValueFlags.Constant) && !IsConstant(typeName, varDecl.Init)) + if ((flags & ValueFlags.Constant) != 0 && !IsConstant(typeName, varDecl.Init)) { flags |= ValueFlags.Copy; } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs index 426f85a2..c3a10e02 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs @@ -106,25 +106,25 @@ public PInvokeGeneratorConfiguration(string language, string languageStandard, s _withUsings = new Dictionary>(QualifiedNameComparer.Default); _withPackings = new Dictionary(QualifiedNameComparer.Default); - if ((outputMode == PInvokeGeneratorOutputMode.Xml) && !options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles) && (options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit) || options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit))) + if ((outputMode == PInvokeGeneratorOutputMode.Xml) && (options & PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles) == 0 && ((options & PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit) != 0 || (options & PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit) != 0)) { // we can't mix XML and C#! we're in XML mode, not generating multiple files, and generating tests; fail throw new ArgumentException("Can't generate tests in XML mode without multiple files.", nameof(options)); } - else if (options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode) && options.HasFlag(PInvokeGeneratorConfigurationOptions.GeneratePreviewCode)) + else if ((options & PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode) != 0 && (options & PInvokeGeneratorConfigurationOptions.GeneratePreviewCode) != 0) { throw new ArgumentOutOfRangeException(nameof(options)); } - else if (options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode) && options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateLatestCode)) + else if ((options & PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode) != 0 && (options & PInvokeGeneratorConfigurationOptions.GenerateLatestCode) != 0) { throw new ArgumentOutOfRangeException(nameof(options)); } - else if (options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateLatestCode) && options.HasFlag(PInvokeGeneratorConfigurationOptions.GeneratePreviewCode)) + else if ((options & PInvokeGeneratorConfigurationOptions.GenerateLatestCode) != 0 && (options & PInvokeGeneratorConfigurationOptions.GeneratePreviewCode) != 0) { throw new ArgumentOutOfRangeException(nameof(options)); } - if (options.HasFlag(PInvokeGeneratorConfigurationOptions.GeneratePreviewCode)) + if ((options & PInvokeGeneratorConfigurationOptions.GeneratePreviewCode) != 0) { // While users shouldn't have passed it in like this, we can simplify // our own downstream checks be having preview also opt into "latest". @@ -132,7 +132,7 @@ public PInvokeGeneratorConfiguration(string language, string languageStandard, s } _options = options; - if (!_options.HasFlag(PInvokeGeneratorConfigurationOptions.NoDefaultRemappings)) + if ((_options & PInvokeGeneratorConfigurationOptions.NoDefaultRemappings) == 0) { if (!ExcludeNIntCodegen) { @@ -165,21 +165,21 @@ public string DefaultClass } } - public bool DontUseUsingStaticsForEnums => _options.HasFlag(PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums); + public bool DontUseUsingStaticsForEnums => (_options & PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForEnums) != 0; - public bool ExcludeAnonymousFieldHelpers => _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeAnonymousFieldHelpers); + public bool ExcludeAnonymousFieldHelpers => (_options & PInvokeGeneratorConfigurationOptions.ExcludeAnonymousFieldHelpers) != 0; - public bool ExcludeComProxies => _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeComProxies); + public bool ExcludeComProxies => (_options & PInvokeGeneratorConfigurationOptions.ExcludeComProxies) != 0; - public bool ExcludeEmptyRecords => _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeEmptyRecords); + public bool ExcludeEmptyRecords => (_options & PInvokeGeneratorConfigurationOptions.ExcludeEmptyRecords) != 0; - public bool ExcludeEnumOperators => _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeEnumOperators); + public bool ExcludeEnumOperators => (_options & PInvokeGeneratorConfigurationOptions.ExcludeEnumOperators) != 0; public bool ExcludeFnptrCodegen { get { - return GenerateCompatibleCode || _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeFnptrCodegen); + return GenerateCompatibleCode || (_options & PInvokeGeneratorConfigurationOptions.ExcludeFnptrCodegen) != 0; } set @@ -195,7 +195,7 @@ public bool ExcludeFnptrCodegen } } - public bool ExcludeFunctionsWithBody => _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeFunctionsWithBody); + public bool ExcludeFunctionsWithBody => (_options & PInvokeGeneratorConfigurationOptions.ExcludeFunctionsWithBody) != 0; [AllowNull] public IReadOnlyCollection ExcludedNames @@ -211,65 +211,65 @@ public IReadOnlyCollection ExcludedNames } } - public bool ExcludeNIntCodegen => GenerateCompatibleCode || _options.HasFlag(PInvokeGeneratorConfigurationOptions.ExcludeNIntCodegen); + public bool ExcludeNIntCodegen => GenerateCompatibleCode || (_options & PInvokeGeneratorConfigurationOptions.ExcludeNIntCodegen) != 0; - public bool GenerateAggressiveInlining => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateAggressiveInlining); + public bool GenerateAggressiveInlining => (_options & PInvokeGeneratorConfigurationOptions.GenerateAggressiveInlining) != 0; - public bool GenerateCallConvMemberFunction => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateCallConvMemberFunction); + public bool GenerateCallConvMemberFunction => (_options & PInvokeGeneratorConfigurationOptions.GenerateCallConvMemberFunction) != 0; - public bool GenerateCompatibleCode => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode); + public bool GenerateCompatibleCode => (_options & PInvokeGeneratorConfigurationOptions.GenerateCompatibleCode) != 0; - public bool GenerateCppAttributes => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateCppAttributes); + public bool GenerateCppAttributes => (_options & PInvokeGeneratorConfigurationOptions.GenerateCppAttributes) != 0; - public bool GenerateDisableRuntimeMarshalling => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateDisableRuntimeMarshalling); + public bool GenerateDisableRuntimeMarshalling => (_options & PInvokeGeneratorConfigurationOptions.GenerateDisableRuntimeMarshalling) != 0; - public bool GenerateDocIncludes => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateDocIncludes); + public bool GenerateDocIncludes => (_options & PInvokeGeneratorConfigurationOptions.GenerateDocIncludes) != 0; - public bool GenerateExplicitVtbls => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateExplicitVtbls); + public bool GenerateExplicitVtbls => (_options & PInvokeGeneratorConfigurationOptions.GenerateExplicitVtbls) != 0; - public bool GenerateFileScopedNamespaces => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces); + public bool GenerateFileScopedNamespaces => (_options & PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces) != 0; - public bool GenerateGenericPointerWrapper => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateGenericPointerWrapper); + public bool GenerateGenericPointerWrapper => (_options & PInvokeGeneratorConfigurationOptions.GenerateGenericPointerWrapper) != 0; - public bool GenerateGuidMember => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateGuidMember); + public bool GenerateGuidMember => (_options & PInvokeGeneratorConfigurationOptions.GenerateGuidMember) != 0; - public bool GenerateHelperTypes => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateHelperTypes); + public bool GenerateHelperTypes => (_options & PInvokeGeneratorConfigurationOptions.GenerateHelperTypes) != 0; - public bool GenerateLatestCode => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateLatestCode); + public bool GenerateLatestCode => (_options & PInvokeGeneratorConfigurationOptions.GenerateLatestCode) != 0; - public bool GenerateMacroBindings => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateMacroBindings); + public bool GenerateMacroBindings => (_options & PInvokeGeneratorConfigurationOptions.GenerateMacroBindings) != 0; - public bool GenerateMarkerInterfaces => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateMarkerInterfaces); + public bool GenerateMarkerInterfaces => (_options & PInvokeGeneratorConfigurationOptions.GenerateMarkerInterfaces) != 0; - public bool GenerateMultipleFiles => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles); + public bool GenerateMultipleFiles => (_options & PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles) != 0; - public bool GenerateNativeBitfieldAttribute => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateNativeBitfieldAttribute); + public bool GenerateNativeBitfieldAttribute => (_options & PInvokeGeneratorConfigurationOptions.GenerateNativeBitfieldAttribute) != 0; - public bool GenerateNativeInheritanceAttribute => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateNativeInheritanceAttribute); + public bool GenerateNativeInheritanceAttribute => (_options & PInvokeGeneratorConfigurationOptions.GenerateNativeInheritanceAttribute) != 0; - public bool GeneratePreviewCode => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GeneratePreviewCode); + public bool GeneratePreviewCode => (_options & PInvokeGeneratorConfigurationOptions.GeneratePreviewCode) != 0; - public bool GenerateSetsLastSystemErrorAttribute => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateSetsLastSystemErrorAttribute); + public bool GenerateSetsLastSystemErrorAttribute => (_options & PInvokeGeneratorConfigurationOptions.GenerateSetsLastSystemErrorAttribute) != 0; - public bool GenerateSourceLocationAttribute => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateSourceLocationAttribute); + public bool GenerateSourceLocationAttribute => (_options & PInvokeGeneratorConfigurationOptions.GenerateSourceLocationAttribute) != 0; - public bool GenerateTemplateBindings => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTemplateBindings); + public bool GenerateTemplateBindings => (_options & PInvokeGeneratorConfigurationOptions.GenerateTemplateBindings) != 0; - public bool GenerateTestsNUnit => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit); + public bool GenerateTestsNUnit => (_options & PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit) != 0; - public bool GenerateTestsXUnit => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit); + public bool GenerateTestsXUnit => (_options & PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit) != 0; - public bool GenerateTrimmableVtbls => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTrimmableVtbls); + public bool GenerateTrimmableVtbls => (_options & PInvokeGeneratorConfigurationOptions.GenerateTrimmableVtbls) != 0; - public bool GenerateUnixTypes => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateUnixTypes); + public bool GenerateUnixTypes => (_options & PInvokeGeneratorConfigurationOptions.GenerateUnixTypes) != 0; - public bool GenerateUnmanagedConstants => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateUnmanagedConstants); + public bool GenerateUnmanagedConstants => (_options & PInvokeGeneratorConfigurationOptions.GenerateUnmanagedConstants) != 0; - public bool GenerateVtblIndexAttribute => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateVtblIndexAttribute); + public bool GenerateVtblIndexAttribute => (_options & PInvokeGeneratorConfigurationOptions.GenerateVtblIndexAttribute) != 0; - public bool StripEnumMemberTypeName => _options.HasFlag(PInvokeGeneratorConfigurationOptions.StripEnumMemberTypeName); + public bool StripEnumMemberTypeName => (_options & PInvokeGeneratorConfigurationOptions.StripEnumMemberTypeName) != 0; - public bool DontUseUsingStaticsForGuidMember => _options.HasFlag(PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForGuidMember); + public bool DontUseUsingStaticsForGuidMember => (_options & PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForGuidMember) != 0; public string HeaderText => _headerText; @@ -301,11 +301,11 @@ public string LibraryPath } } - public bool LogExclusions => _options.HasFlag(PInvokeGeneratorConfigurationOptions.LogExclusions); + public bool LogExclusions => (_options & PInvokeGeneratorConfigurationOptions.LogExclusions) != 0; - public bool LogPotentialTypedefRemappings => _options.HasFlag(PInvokeGeneratorConfigurationOptions.LogPotentialTypedefRemappings); + public bool LogPotentialTypedefRemappings => (_options & PInvokeGeneratorConfigurationOptions.LogPotentialTypedefRemappings) != 0; - public bool LogVisitedFiles => _options.HasFlag(PInvokeGeneratorConfigurationOptions.LogVisitedFiles); + public bool LogVisitedFiles => (_options & PInvokeGeneratorConfigurationOptions.LogVisitedFiles) != 0; [AllowNull] public string MethodPrefixToStrip diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index 7e644a62..e72dba86 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -314,7 +314,7 @@ public static void Run(InvocationContext context) errorList.Add("Error: No test output file location provided. Use --test-output or -to"); } - if (configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit)) + if ((configOptions & PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit) != 0) { errorList.Add("Cannot generate both NUnit and XUnit tests."); } @@ -329,7 +329,7 @@ public static void Run(InvocationContext context) errorList.Add("Error: No test output file location provided. Use --test-output or -to"); } - if (configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit)) + if ((configOptions & PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit) != 0) { errorList.Add("Cannot generate both NUnit and XUnit tests."); } @@ -485,7 +485,7 @@ public static void Run(InvocationContext context) } } - if (!string.IsNullOrWhiteSpace(testOutputLocation) && !configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit) && !configOptions.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit)) + if (!string.IsNullOrWhiteSpace(testOutputLocation) && (configOptions & PInvokeGeneratorConfigurationOptions.GenerateTestsNUnit) == 0 && (configOptions & PInvokeGeneratorConfigurationOptions.GenerateTestsXUnit) == 0) { errorList.Add("Error: No test format provided. Use --config generate-tests-nunit or --config generate-tests-xunit"); }