From 40307925088a84e0b9477992e2846cd76401af4d Mon Sep 17 00:00:00 2001 From: William Chen Date: Sat, 26 Jul 2025 21:18:42 -0400 Subject: [PATCH 01/15] Add test cases for bitfield property type casting when using remapped types --- .../CTest.cs | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index c700b595..691ace1b 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. +using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading.Tasks; using NUnit.Framework; @@ -450,4 +451,133 @@ public static partial class Methods return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + + [Test] + public Task BitfieldEnumPropertyTypeCastTest() + { + var inputContents = @" +typedef enum Flags { + Member = 0x7FFFFFFF +} Flags; + +typedef struct Bitfield { + unsigned int bits : 8; + Flags flags : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + [NativeTypeName(""unsigned int"")] + public enum Flags : uint + { + Member = 0x7FFFFFFF, + } + + public partial struct Bitfield + { + public uint _bitfield; + + [NativeTypeName(""unsigned int : 8"")] + public uint bits + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + + [NativeTypeName(""Flags : 8"")] + public Flags flags + { + readonly get + { + return (Flags)((_bitfield >> 8) & 0xFFu); + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 8)) | (((uint)(value) & 0xFFu) << 8); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + + + [Test] + public Task BitfieldEnumPropertyTypeCastWithRemappingTest() + { + var inputContents = @" +typedef enum FlagBits { + Member = 0x7FFFFFFF +} FlagBits; +typedef unsigned int Flags; + +typedef struct Bitfield { + unsigned int bits : 8; + Flags flags : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + [NativeTypeName(""unsigned int"")] + public enum FlagBits : uint + { + Member = 0x7FFFFFFF, + } + + public partial struct Bitfield + { + public uint _bitfield; + + [NativeTypeName(""unsigned int : 8"")] + public uint bits + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + + [NativeTypeName(""Flags : 8"")] + public FlagBits flags + { + readonly get + { + return (FlagBits)((_bitfield >> 8) & 0xFFu); + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 8)) | (((uint)(value) & 0xFFu) << 8); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, + commandLineArgs: DefaultCClangCommandLineArgs, + language: "c", + languageStandard: DefaultCStandard, + remappedNames: new Dictionary() + { + { "Flags", "FlagBits" } + }); + } } \ No newline at end of file From 2aa428505af81f08aba142fbc3cefb8ff1b61c9b Mon Sep 17 00:00:00 2001 From: William Chen Date: Wed, 20 Aug 2025 21:58:01 -0400 Subject: [PATCH 02/15] Add test to capture current bitfield behavior --- .../CTest.cs | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 691ace1b..dfc5fc74 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -452,6 +452,124 @@ public static partial class Methods return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + [Test] + public Task BitfieldEnumPropertyBackingTypeTestUnix() + { + var inputContents = @" +typedef struct IntBitfield { + int bits : 8; + unsigned int bits2 : 8; +} IntBitfield; + +typedef struct UIntBitfield { + unsigned int bits1 : 8; + int bits2 : 8; + unsigned char bits3 : 8; + char bits4 : 8; +} UIntBitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public partial struct IntBitfield + { + public int _bitfield; + + [NativeTypeName(""int : 8"")] + public int bits + { + readonly get + { + return (_bitfield << 24) >> 24; + } + + set + { + _bitfield = (_bitfield & ~0xFF) | (value & 0xFF); + } + } + + [NativeTypeName(""unsigned int : 8"")] + public uint bits2 + { + readonly get + { + return (uint)((_bitfield >> 8) & 0xFF); + } + + set + { + _bitfield = (_bitfield & ~(0xFF << 8)) | (int)((value & 0xFFu) << 8); + } + } + } + + public partial struct UIntBitfield + { + public uint _bitfield; + + [NativeTypeName(""unsigned int : 8"")] + public uint bits1 + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + + [NativeTypeName(""int : 8"")] + public int bits2 + { + readonly get + { + return (int)(_bitfield << 16) >> 24; + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 8)) | (uint)((value & 0xFF) << 8); + } + } + + [NativeTypeName(""unsigned char : 8"")] + public byte bits3 + { + readonly get + { + return (byte)((_bitfield >> 16) & 0xFFu); + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 16)) | (uint)((value & 0xFFu) << 16); + } + } + + [NativeTypeName(""char : 8"")] + public sbyte bits4 + { + readonly get + { + return (sbyte)(_bitfield << 0) >> 24; + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 24)) | (uint)((value & 0xFF) << 24); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + [Test] public Task BitfieldEnumPropertyTypeCastTest() { @@ -580,4 +698,4 @@ readonly get { "Flags", "FlagBits" } }); } -} \ No newline at end of file +} From 9cb49d8e3f87ac93837ad9542c8266f0346c2ee7 Mon Sep 17 00:00:00 2001 From: William Chen Date: Wed, 20 Aug 2025 22:03:26 -0400 Subject: [PATCH 03/15] Add test case for remapping a bitfield type to itself --- .../CTest.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index dfc5fc74..c056517f 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -698,4 +698,63 @@ readonly get { "Flags", "FlagBits" } }); } + + [Test] + public Task BitfieldEnumPropertyTypeCastWithSelfRemappingTest() + { + var inputContents = @" +typedef unsigned int Flags; + +typedef struct Bitfield { + unsigned int bits : 8; + Flags flags : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public partial struct Bitfield + { + public uint _bitfield; + + [NativeTypeName(""unsigned int : 8"")] + public uint bits + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + + [NativeTypeName(""Flags : 8"")] + public Flags flags + { + readonly get + { + return (Flags)((_bitfield >> 8) & 0xFFu); + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 8)) | (((uint)(value) & 0xFFu) << 8); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, + commandLineArgs: DefaultCClangCommandLineArgs, + language: "c", + languageStandard: DefaultCStandard, + remappedNames: new Dictionary() + { + { "Flags", "Flags" } + }); + } } From 4dcd036516bd0f74506e0f5a217430306d4df400 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 01:26:49 -0400 Subject: [PATCH 04/15] Change expected result of sbyte with uint backing test case This case doesn't compile nor does it match the comments in VisitBitfieldDecl. However this means the test case now fails. --- tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index c056517f..eb35b8d5 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -555,7 +555,7 @@ public sbyte bits4 { readonly get { - return (sbyte)(_bitfield << 0) >> 24; + return (sbyte)((_bitfield << 0) >> 24); } set From 86f65c0c5e930fd39e1170994a40de2e2ece9130 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 01:47:07 -0400 Subject: [PATCH 05/15] Reorder and comment code for readability --- .../PInvokeGenerator.VisitDecl.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 1a874965..01da28ac 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2714,10 +2714,6 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record _outputBuilder.BeginField(in desc); _outputBuilder.WriteRegularField(typeName, escapedName); _outputBuilder.BeginBody(); - _outputBuilder.BeginGetter(_config.GenerateAggressiveInlining, isReadOnly: !Config.GenerateCompatibleCode); - var code = _outputBuilder.BeginCSharpCode(); - - code.WriteIndented("return "); var recordDeclName = GetCursorName(recordDecl); @@ -2739,6 +2735,12 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record // backing uint, current byte (byte)((value >> cns) & msk) // backing uint, current sbyte (sbyte)((value << cns) >> cns) + // Getter + _outputBuilder.BeginGetter(_config.GenerateAggressiveInlining, isReadOnly: !Config.GenerateCompatibleCode); + var code = _outputBuilder.BeginCSharpCode(); + + code.WriteIndented("return "); + if (needsCast) { code.Write('('); @@ -2805,6 +2807,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record _outputBuilder.EndCSharpCode(code); _outputBuilder.EndGetter(); + // Setter _outputBuilder.BeginSetter(_config.GenerateAggressiveInlining); code = _outputBuilder.BeginCSharpCode(); code.WriteIndentation(); From a54e5fc4e5e34748d06115f61235368f44da666b Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 12:01:07 -0400 Subject: [PATCH 06/15] Add another test and change expected output for sbyte case --- .../CTest.cs | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index eb35b8d5..df750c2a 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -452,6 +452,75 @@ public static partial class Methods return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + [Test] + public Task BitfieldEnumPropertySmallBackingTypeTest() + { + var inputContents = @" +typedef struct Bitfield { + unsigned char bits1 : 8; + char bits2 : 8; + unsigned char bits3 : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public partial struct Bitfield + { + public byte _bitfield1; + + [NativeTypeName(""unsigned char : 8"")] + public byte bits1 + { + readonly get + { + return (byte)(_bitfield1 & 0xFFu); + } + + set + { + _bitfield1 = (byte)((_bitfield1 & ~0xFFu) | (value & 0xFFu)); + } + } + + public sbyte _bitfield2; + + [NativeTypeName(""char : 8"")] + public sbyte bits2 + { + readonly get + { + return (sbyte)((_bitfield2 << 0) >> 0); + } + + set + { + _bitfield2 = (sbyte)((_bitfield2 & ~0xFF) | (value & 0xFF)); + } + } + + public byte _bitfield3; + + [NativeTypeName(""unsigned char : 8"")] + public byte bits3 + { + readonly get + { + return (byte)(_bitfield3 & 0xFFu); + } + + set + { + _bitfield3 = (byte)((_bitfield3 & ~0xFFu) | (value & 0xFFu)); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + [Test] public Task BitfieldEnumPropertyBackingTypeTestUnix() { @@ -555,7 +624,7 @@ public sbyte bits4 { readonly get { - return (sbyte)((_bitfield << 0) >> 24); + return (sbyte)((sbyte)(_bitfield << 0) >> 24); } set From 7272a7e2d874ca5916787a674129497ee73ca857 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 12:01:54 -0400 Subject: [PATCH 07/15] Implement potential solution Not completely verified yet and the conditions are named a bit inconsistently. --- .../PInvokeGenerator.VisitDecl.cs | 372 +++++++++++------- 1 file changed, 224 insertions(+), 148 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 01da28ac..31c8153a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2717,204 +2717,280 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record var recordDeclName = GetCursorName(recordDecl); - var isSmallType = currentSize < 4; - var isRemappedToSelf = _config.RemappedNames.TryGetValue(typeName, out var remappedTypeName) && typeName.Equals(remappedTypeName, StringComparison.Ordinal); - var isTypeMismatch = type != builtinTypeBacking; - var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned; + // Small types become uint32/int32s after shifting + var isSmallType = fieldDecl.Type.Handle.SizeOf < 4; + var isSmallTypeBacking = currentSize < 4; - var needsCast = isSmallType || isRemappedToSelf || isTypeMismatch || isUnsignedToSigned; - var needsParenFirst = !isSmallType && isUnsignedToSigned; - var needsParenSecond = !needsParenFirst || isRemappedToSelf; + // Check if input/output types mismatch + var isTypeMismatch = type != typeBacking; + // Signed types are sign extended when shifted + var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned; + + // Check if type is directly shiftable/maskable + var isTypeLikelyRemapped = type == typeBacking && typeName != typeNameBacking; + var isTypeAnEnum = IsType(fieldDecl); + var isTypeShiftableAndMaskable = !isTypeLikelyRemapped && !isTypeAnEnum; + var isTypeShiftable = isTypeShiftableAndMaskable; + var isTypeMaskable = !isTypeLikelyRemapped || isTypeAnEnum; + + // Main cases: // backing int, current int (value << cns) >> cns // backing int, current uint (uint)((value >> cns) & msk) - // backing uint, current int ((int)value << cns) >> cns + // backing uint, current int (int)(value << cns) >> cns // backing uint, current uint (value >> cns) & msk // backing uint, current byte (byte)((value >> cns) & msk) - // backing uint, current sbyte (sbyte)((value << cns) >> cns) + // backing uint, current sbyte (sbyte)((sbyte)(value << cns) >> cns) // Getter - _outputBuilder.BeginGetter(_config.GenerateAggressiveInlining, isReadOnly: !Config.GenerateCompatibleCode); - var code = _outputBuilder.BeginCSharpCode(); + { + _outputBuilder.BeginGetter(_config.GenerateAggressiveInlining, + isReadOnly: !Config.GenerateCompatibleCode); + var code = _outputBuilder.BeginCSharpCode(); - code.WriteIndented("return "); + code.WriteIndented("return "); - if (needsCast) - { - code.Write('('); - code.BeginMarker("typeName"); - code.Write(typeName); - code.EndMarker("typeName"); - code.Write(")("); - } + var needsCastToFinal = (isSmallType || isTypeMismatch || isUnsignedToSigned) && !isTypeAnEnum; - if ((!needsParenFirst && (bitfieldOffset != 0)) || (!isUnsignedToSigned && isTypeSigned)) - { - code.Write('('); - } + // This is to handle the "backing uint, current sbyte" case + var needsCastToFinalAgain = isUnsignedToSigned && isSmallType; + if (needsCastToFinalAgain) + { + code.Write('('); + code.BeginMarker("typeName"); + code.Write(typeName); + code.EndMarker("typeName"); + code.Write(")("); + } - if (!string.IsNullOrWhiteSpace(contextName)) - { - code.BeginMarker("contextName"); - code.Write(contextName); - code.EndMarker("contextName"); - code.Write('.'); - } + if (needsCastToFinal) + { + code.Write('('); + code.BeginMarker("typeName"); + code.Write(typeName); + code.EndMarker("typeName"); + code.Write(")"); + } - code.BeginMarker("bitfieldName"); - code.Write(bitfieldName); - code.EndMarker("bitfieldName"); + var needsCastToFinalParen = needsCastToFinal && !isUnsignedToSigned; + if (needsCastToFinalParen) + { + code.Write('('); + } - if (isTypeSigned) - { - code.Write(" << "); - code.BeginMarker("remainingBitsMinusBitWidth"); - code.Write(remainingBits - fieldDecl.BitWidthValue); - code.EndMarker("remainingBitsMinusBitWidth"); - code.Write(')'); + var needsCastBeforeOp = !isTypeShiftableAndMaskable; + if (needsCastBeforeOp) + { + code.Write('('); + code.BeginMarker("typeName"); + code.Write(typeName); + code.EndMarker("typeName"); + code.Write(")("); + } - code.Write(" >> "); - code.BeginMarker("currentSizeMinusBitWidth"); - code.Write((currentSize * 8) - fieldDecl.BitWidthValue); - code.EndMarker("currentSizeMinusBitWidth"); - } - else - { - if (bitfieldOffset != 0) + if (isTypeSigned) { - code.Write(" >> "); - code.BeginMarker("bitfieldOffset"); - code.Write(bitfieldOffset); - code.EndMarker("bitfieldOffset"); + code.Write('('); + + if (!string.IsNullOrWhiteSpace(contextName)) + { + code.BeginMarker("contextName"); + code.Write(contextName); + code.EndMarker("contextName"); + code.Write('.'); + } + + code.BeginMarker("bitfieldName"); + code.Write(bitfieldName); + code.EndMarker("bitfieldName"); + + code.Write(" << "); + code.BeginMarker("remainingBitsMinusBitWidth"); + code.Write(remainingBits - fieldDecl.BitWidthValue); + code.EndMarker("remainingBitsMinusBitWidth"); + code.Write(')'); + + code.Write(" >> "); + code.BeginMarker("currentSizeMinusBitWidth"); + code.Write((currentSize * 8) - fieldDecl.BitWidthValue); + code.EndMarker("currentSizeMinusBitWidth"); } + else + { + var needsOffset = bitfieldOffset != 0; + if (needsOffset) + { + code.Write('('); + } - code.Write(" & 0x"); - code.BeginMarker("bitwidthHexStringBacking"); - code.Write(bitwidthHexStringBacking); - code.EndMarker("bitwidthHexStringBacking"); - } + if (!string.IsNullOrWhiteSpace(contextName)) + { + code.BeginMarker("contextName"); + code.Write(contextName); + code.EndMarker("contextName"); + code.Write('.'); + } - if (needsCast && needsParenSecond) - { - code.Write(')'); - } + code.BeginMarker("bitfieldName"); + code.Write(bitfieldName); + code.EndMarker("bitfieldName"); - code.WriteSemicolon(); - code.WriteNewline(); - _outputBuilder.EndCSharpCode(code); - _outputBuilder.EndGetter(); + if (needsOffset) + { + code.Write(" >> "); + code.BeginMarker("bitfieldOffset"); + code.Write(bitfieldOffset); + code.EndMarker("bitfieldOffset"); - // Setter - _outputBuilder.BeginSetter(_config.GenerateAggressiveInlining); - code = _outputBuilder.BeginCSharpCode(); - code.WriteIndentation(); + code.Write(')'); + } - if (!string.IsNullOrWhiteSpace(contextName)) - { - code.BeginMarker("contextName"); - code.Write(contextName); - code.EndMarker("contextName"); - code.Write('.'); - } + code.Write(" & 0x"); + code.BeginMarker("bitwidthHexStringBacking"); + code.Write(bitwidthHexStringBacking); + code.EndMarker("bitwidthHexStringBacking"); + } - code.BeginMarker("bitfieldName"); - code.Write(bitfieldName); - code.EndMarker("bitfieldName"); + if (needsCastBeforeOp) + { + code.Write(')'); + } - code.Write(" = "); + if (needsCastToFinalParen) + { + code.Write(')'); + } - if (currentSize < 4) - { - code.Write('('); - code.BeginMarker("typeNameBacking"); - code.Write(typeNameBacking); - code.EndMarker("typeNameBacking"); - code.Write(")("); - } + if (needsCastToFinalAgain) + { + code.Write(')'); + } - code.Write('('); + code.WriteSemicolon(); + code.WriteNewline(); + _outputBuilder.EndCSharpCode(code); + _outputBuilder.EndGetter(); + } - if (!string.IsNullOrWhiteSpace(contextName)) + // Setter { - code.Write(contextName); - code.Write('.'); - } + _outputBuilder.BeginSetter(_config.GenerateAggressiveInlining); + var code = _outputBuilder.BeginCSharpCode(); + code.WriteIndentation(); - code.Write(bitfieldName); + if (!string.IsNullOrWhiteSpace(contextName)) + { + code.BeginMarker("contextName"); + code.Write(contextName); + code.EndMarker("contextName"); + code.Write('.'); + } - code.Write(" & ~"); + code.BeginMarker("bitfieldName"); + code.Write(bitfieldName); + code.EndMarker("bitfieldName"); - if (bitfieldOffset != 0) - { + code.Write(" = "); + + var needsCastToFinal = isSmallTypeBacking; + if (needsCastToFinal) + { + code.Write('('); + code.BeginMarker("typeNameBacking"); + code.Write(typeNameBacking); + code.EndMarker("typeNameBacking"); + code.Write(")("); + } + + // Zero out target bits code.Write('('); - } - code.Write("0x"); - code.BeginMarker("bitwidthHexStringBacking"); - code.Write(bitwidthHexStringBacking); - code.EndMarker("bitwidthHexStringBacking"); + if (!string.IsNullOrWhiteSpace(contextName)) + { + code.Write(contextName); + code.Write('.'); + } - if (bitfieldOffset != 0) - { - code.Write(" << "); - code.BeginMarker("bitfieldOffset"); - code.Write(bitfieldOffset); - code.EndMarker("bitfieldOffset"); - code.Write(')'); - } + code.Write(bitfieldName); - code.Write(") | "); + code.Write(" & ~"); - if ((builtinType != builtinTypeBacking) && !IsType(fieldDecl)) - { - code.Write('('); - code.Write(typeNameBacking); - code.Write(')'); - } + if (bitfieldOffset != 0) + { + code.Write('('); + } - code.Write('('); + code.Write("0x"); + code.BeginMarker("bitwidthHexStringBacking"); + code.Write(bitwidthHexStringBacking); + code.EndMarker("bitwidthHexStringBacking"); - if (bitfieldOffset != 0) - { - code.Write('('); - } + if (bitfieldOffset != 0) + { + code.Write(" << "); + code.BeginMarker("bitfieldOffset"); + code.Write(bitfieldOffset); + code.EndMarker("bitfieldOffset"); + code.Write(')'); + } + + // Write to target bits + code.Write(") | "); + + var needsCastBeforeLogicalOr = isTypeMismatch && !isTypeAnEnum; + if (needsCastBeforeLogicalOr) + { + code.Write('('); + code.Write(typeNameBacking); + code.Write(")"); + } - if (IsType(fieldDecl) || isRemappedToSelf) - { code.Write('('); - code.Write(typeNameBacking); - code.Write(")(value)"); - } - else - { - code.Write("value"); - } - code.Write(" & 0x"); - code.BeginMarker("bitwidthHexString"); - code.Write(bitwidthHexString); - code.EndMarker("bitwidthHexString"); + if (bitfieldOffset != 0) + { + code.Write('('); + } - if (bitfieldOffset != 0) - { - code.Write(") << "); - code.Write(bitfieldOffset); - } + var needsCastBeforeOp = !isTypeShiftableAndMaskable; + if (needsCastBeforeOp) + { + code.Write('('); + code.Write(typeNameBacking); + code.Write(")(value)"); + } + else + { + code.Write("value"); + } - code.Write(')'); + code.Write(" & 0x"); + code.BeginMarker("bitwidthHexString"); + code.Write(bitwidthHexString); + code.EndMarker("bitwidthHexString"); + + if (bitfieldOffset != 0) + { + code.Write(") << "); + code.Write(bitfieldOffset); + } - if (currentSize < 4) - { code.Write(')'); + + if (needsCastToFinal) + { + code.Write(')'); + } + + code.WriteSemicolon(); + code.WriteNewline(); + _outputBuilder.EndCSharpCode(code); + _outputBuilder.EndSetter(); } - code.WriteSemicolon(); - code.WriteNewline(); - _outputBuilder.EndCSharpCode(code); - _outputBuilder.EndSetter(); _outputBuilder.EndBody(); _outputBuilder.EndField(in desc); _outputBuilder.WriteDivider(); From 2959b42da5006669e1fb0935117e640d3496bbc0 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 13:50:50 -0400 Subject: [PATCH 08/15] Cleanup --- .../PInvokeGenerator.VisitDecl.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 31c8153a..d91021d2 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2728,11 +2728,10 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned; // Check if type is directly shiftable/maskable + // Remapped types are not guaranteed to be shiftable or maskable + // Enums are maskable, but not shiftable var isTypeLikelyRemapped = type == typeBacking && typeName != typeNameBacking; var isTypeAnEnum = IsType(fieldDecl); - var isTypeShiftableAndMaskable = !isTypeLikelyRemapped && !isTypeAnEnum; - var isTypeShiftable = isTypeShiftableAndMaskable; - var isTypeMaskable = !isTypeLikelyRemapped || isTypeAnEnum; // Main cases: // backing int, current int (value << cns) >> cns @@ -2780,7 +2779,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record code.Write('('); } - var needsCastBeforeOp = !isTypeShiftableAndMaskable; + var needsCastBeforeOp = isTypeLikelyRemapped || isTypeAnEnum; if (needsCastBeforeOp) { code.Write('('); @@ -2955,7 +2954,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record code.Write('('); } - var needsCastBeforeOp = !isTypeShiftableAndMaskable; + var needsCastBeforeOp = isTypeLikelyRemapped || isTypeAnEnum; if (needsCastBeforeOp) { code.Write('('); From dcb1d931ab62e3b277e6e7af669d09b4779b9bb3 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 22:06:55 -0400 Subject: [PATCH 09/15] Change the non-enum related bitfield tests I added to be Unix x64 only --- .../CTest.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index df750c2a..de502eec 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -453,8 +453,15 @@ public static partial class Methods } [Test] - public Task BitfieldEnumPropertySmallBackingTypeTest() + [Platform("unix")] + public Task BitfieldEnumPropertySmallBackingTypeTestUnix() { + // This test is here mainly to ensure that the sbyte case gets coverage + if (RuntimeInformation.ProcessArchitecture != Architecture.X64) + { + Assert.Ignore("This test is only valid for Unix x64"); + } + var inputContents = @" typedef struct Bitfield { unsigned char bits1 : 8; @@ -518,12 +525,19 @@ readonly get } "; - return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } [Test] + [Platform("unix")] public Task BitfieldEnumPropertyBackingTypeTestUnix() { + // This test is here mainly to ensure that the sbyte case gets coverage + if (RuntimeInformation.ProcessArchitecture != Architecture.X64) + { + Assert.Ignore("This test is only valid for Unix x64"); + } + var inputContents = @" typedef struct IntBitfield { int bits : 8; From 1f5a9ccf51f27be84f77d46e4f8352d3ad1180a4 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 22:13:39 -0400 Subject: [PATCH 10/15] Limit added test cases to Unix only Will add a few variants for Windows to ensure that enum signedness is handled properly. --- .../CTest.cs | 74 +++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index de502eec..5b09aa15 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -654,6 +654,7 @@ readonly get } [Test] + [Platform("unix")] // This test has slight platform-specific differences public Task BitfieldEnumPropertyTypeCastTest() { var inputContents = @" @@ -710,12 +711,74 @@ readonly get } "; - return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + + [Test] + [Platform("unix")] // This test has slight platform-specific differences + public Task BitfieldEnumTypeDefPropertyTypeCast() + { + var inputContents = @" +typedef enum FlagBits { + Member = 0x7FFFFFFF +} FlagBits; +typedef unsigned int Flags; + +typedef struct Bitfield { + unsigned int bits : 8; + Flags flags : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + [NativeTypeName(""unsigned int"")] + public enum FlagBits : uint + { + Member = 0x7FFFFFFF, + } + + public partial struct Bitfield + { + public uint _bitfield; + + [NativeTypeName(""unsigned int : 8"")] + public uint bits + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + + [NativeTypeName(""Flags : 8"")] + public uint flags + { + readonly get + { + return (_bitfield >> 8) & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 8)) | ((value & 0xFFu) << 8); + } + } } +} +"; + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } [Test] - public Task BitfieldEnumPropertyTypeCastWithRemappingTest() + [Platform("unix")] // This test has slight platform-specific differences + public Task BitfieldEnumTypeDefPropertyTypeCastWithRemappingTest() { var inputContents = @" typedef enum FlagBits { @@ -772,7 +835,7 @@ readonly get } "; - return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard, @@ -783,7 +846,8 @@ readonly get } [Test] - public Task BitfieldEnumPropertyTypeCastWithSelfRemappingTest() + [Platform("unix")] // This test has slight platform-specific differences + public Task BitfieldEnumTypeDefPropertyTypeCastWithSelfRemappingTest() { var inputContents = @" typedef unsigned int Flags; @@ -831,7 +895,7 @@ readonly get } "; - return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard, From ae029f736394ad65a71e4996317e360ea527f0e2 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 21 Aug 2025 23:29:47 -0400 Subject: [PATCH 11/15] Add variant of BitfieldEnumPropertyTypeCastTest for Windows --- .../CTest.cs | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 5b09aa15..bdb3b14f 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -654,8 +654,8 @@ readonly get } [Test] - [Platform("unix")] // This test has slight platform-specific differences - public Task BitfieldEnumPropertyTypeCastTest() + [Platform("unix")] + public Task BitfieldEnumPropertyTypeCastTestUnix() { var inputContents = @" typedef enum Flags { @@ -714,6 +714,66 @@ readonly get return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + [Test] + [Platform("win")] // This test has slight platform-specific differences + public Task BitfieldEnumPropertyTypeCastTestWindows() + { + var inputContents = @" +typedef enum Flags { + Member = 0x7FFFFFFF +} Flags; + +typedef struct Bitfield { + unsigned int bits : 8; + Flags flags : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public enum Flags + { + Member = 0x7FFFFFFF, + } + + public partial struct Bitfield + { + public uint _bitfield; + + [NativeTypeName(""unsigned int : 8"")] + public uint bits + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + + [NativeTypeName(""Flags : 8"")] + public Flags flags + { + readonly get + { + return (Flags)((_bitfield << 16) >> 24); + } + + set + { + _bitfield = (_bitfield & ~(0xFFu << 8)) | (((uint)(value) & 0xFF) << 8); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + [Test] [Platform("unix")] // This test has slight platform-specific differences public Task BitfieldEnumTypeDefPropertyTypeCast() From 2a10939830ab91edfb9911a162a887621a9dbf1f Mon Sep 17 00:00:00 2001 From: William Chen Date: Fri, 22 Aug 2025 18:34:06 -0400 Subject: [PATCH 12/15] Edit comment --- .../ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index a35cb7fe..04e53c7a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2721,7 +2721,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record var isSmallType = fieldDecl.Type.Handle.SizeOf < 4; var isSmallTypeBacking = currentSize < 4; - // Check if input/output types mismatch + // Check if field/backing types match var isTypeMismatch = type != typeBacking; // Signed types are sign extended when shifted From 266f93a6f834555be4a07c33bbc77ac1f1552bdd Mon Sep 17 00:00:00 2001 From: William Chen Date: Fri, 5 Sep 2025 20:06:01 -0400 Subject: [PATCH 13/15] Add parentheses for clarity Co-authored-by: Tanner Gooding --- .../ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index dc9b8890..197d111a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2764,7 +2764,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record // Check if type is directly shiftable/maskable // Remapped types are not guaranteed to be shiftable or maskable // Enums are maskable, but not shiftable - var isTypeLikelyRemapped = type == typeBacking && typeName != typeNameBacking; + var isTypeLikelyRemapped = (type == typeBacking) && (typeName != typeNameBacking); var isTypeAnEnum = IsType(fieldDecl); // Main cases: From e95c3b375e576490e5304d29943e8bc8789a73a6 Mon Sep 17 00:00:00 2001 From: William Chen Date: Fri, 5 Sep 2025 21:46:20 -0400 Subject: [PATCH 14/15] Add BitfieldTypeDefTypeCastTestUnix to cover the typedef case This case is when typeBacking is not the same as builtinTypeBacking --- .../CTest.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 5d933451..22d264a7 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -714,6 +714,44 @@ readonly get return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + [Test] + [Platform("unix")] + public Task BitfieldTypeDefTypeCastTestUnix() + { + var inputContents = @" +typedef unsigned int Number; + +typedef struct Bitfield { + Number bits : 8; +} Bitfield; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public partial struct Bitfield + { + public uint _bitfield; + + [NativeTypeName(""Number : 8"")] + public uint bits + { + readonly get + { + return _bitfield & 0xFFu; + } + + set + { + _bitfield = (_bitfield & ~0xFFu) | (value & 0xFFu); + } + } + } +} +"; + + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + [Test] [Platform("win")] // This test has slight platform-specific differences public Task BitfieldEnumPropertyTypeCastTestWindows() From 5a53aea27102c47adc4b22ef4a82ab541e16edd5 Mon Sep 17 00:00:00 2001 From: William Chen Date: Fri, 5 Sep 2025 21:47:27 -0400 Subject: [PATCH 15/15] Use builtinTypeBacking instead of typeBacking to correctly handle typedefs and similar cases --- .../ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 197d111a..88992629 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2756,7 +2756,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record var isSmallTypeBacking = currentSize < 4; // Check if field/backing types match - var isTypeMismatch = type != typeBacking; + var isTypeMismatch = type != builtinTypeBacking; // Signed types are sign extended when shifted var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned; @@ -2764,7 +2764,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record // Check if type is directly shiftable/maskable // Remapped types are not guaranteed to be shiftable or maskable // Enums are maskable, but not shiftable - var isTypeLikelyRemapped = (type == typeBacking) && (typeName != typeNameBacking); + var isTypeLikelyRemapped = !isTypeMismatch && (typeName != typeNameBacking); var isTypeAnEnum = IsType(fieldDecl); // Main cases: