Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,12 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
var currentSize = fieldDecl.Type.Handle.SizeOf;
var bitfieldName = "_bitfield";

// A `bool` bitfield cannot be shifted or masked directly and needs an integer
// backing store. It is emitted using the same unsigned-integer path as the
// equivalently-sized integer type, with the public accessor kept as `bool` and
// the conversion done at the get/set boundary.
var isBooleanBitfield = fieldDecl.Type.CanonicalType.Kind == CXType_Bool;

Type typeBacking;
string typeNameBacking;

Expand All @@ -1149,6 +1155,11 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
typeBacking = bitfieldDesc.TypeBacking;
typeNameBacking = GetRemappedTypeName(fieldDecl, context: null, typeBacking, out _);

if (isBooleanBitfield)
{
typeNameBacking = "byte";
}

if (parent == recordDecl)
{
var fieldDesc = new FieldDesc {
Expand Down Expand Up @@ -1201,6 +1212,11 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
var bitfieldDesc = (index > 0) ? bitfieldDescs[index - 1] : bitfieldDescs[0];
typeBacking = bitfieldDesc.TypeBacking;
typeNameBacking = GetRemappedTypeName(fieldDecl, context: null, typeBacking, out _);

if (isBooleanBitfield)
{
typeNameBacking = "byte";
}
}

var bitfieldOffset = (currentSize * 8) - remainingBits;
Expand All @@ -1216,6 +1232,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record

switch (builtinTypeBacking.Kind)
{
case CXType_Bool:
case CXType_Char_U:
case CXType_UChar:
case CXType_UShort:
Expand Down Expand Up @@ -1315,6 +1332,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record

switch (builtinType.Kind)
{
case CXType_Bool:
case CXType_Char_U:
case CXType_UChar:
case CXType_UShort:
Expand Down Expand Up @@ -1389,6 +1407,16 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
var name = GetRemappedCursorName(fieldDecl);
var escapedName = EscapeName(name);

// The public accessor keeps the original type (e.g. `bool`) while the shift/mask
// arithmetic is done against the integer backing type; the two only differ for a
// `bool` bitfield, which is converted at the get/set boundary below.
var fieldTypeName = typeName;

if (isBooleanBitfield)
{
typeName = typeNameBacking;
}

var desc = new FieldDesc {
AccessSpecifier = accessSpecifier,
NativeTypeName = nativeTypeName,
Expand All @@ -1409,7 +1437,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record

_outputBuilder.WriteDivider();
_outputBuilder.BeginField(in desc);
_outputBuilder.WriteRegularField(typeName, escapedName);
_outputBuilder.WriteRegularField(fieldTypeName, escapedName);
_outputBuilder.BeginBody();

var recordDeclName = GetCursorName(recordDecl);
Expand Down Expand Up @@ -1461,7 +1489,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
code.Write(")(");
}

if (needsCastToFinal)
if (needsCastToFinal && !isBooleanBitfield)
{
code.Write('(');
code.BeginMarker("typeName");
Expand Down Expand Up @@ -1565,6 +1593,11 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
code.Write(')');
}

if (isBooleanBitfield)
{
code.Write(" != 0");
}

code.WriteSemicolon();
code.WriteNewline();
_outputBuilder.EndCSharpCode(code);
Expand Down Expand Up @@ -1658,6 +1691,10 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
code.Write(typeNameBacking);
code.Write(")(value)");
}
else if (isBooleanBitfield)
{
code.Write("(value ? 1 : 0)");
}
else
{
code.Write("value");
Expand Down
140 changes: 140 additions & 0 deletions tests/ClangSharp.PInvokeGenerator.UnitTests/BooleanBitfieldTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// 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.Threading.Tasks;
using NUnit.Framework;

namespace ClangSharp.UnitTests;

public sealed class BooleanBitfieldTest : PInvokeGeneratorTest
{
// Regression test for https://github.com/dotnet/clangsharp/issues/508
// A `bool` bitfield previously generated invalid C# (a `bool` backing field plus
// `bool & int` arithmetic and `(bool)` casts). It should use an integer backing store
// with the public accessor kept as `bool`, converting at the get/set boundary. An
// adjacent non-bool bitfield must be unaffected.
private const string InputContents = @"struct MyStruct
{
bool a : 1;
bool b : 1;
int c : 2;
};
";

[Test]
public Task LatestTest()
{
var expectedOutputContents = @"namespace ClangSharp.Test
{
public partial struct MyStruct
{
public byte _bitfield1;

[NativeTypeName(""bool : 1"")]
public bool a
{
readonly get
{
return (_bitfield1 & 0x1u) != 0;
}

set
{
_bitfield1 = (byte)((_bitfield1 & ~0x1u) | ((value ? 1 : 0) & 0x1u));
}
}

[NativeTypeName(""bool : 1"")]
public bool b
{
readonly get
{
return ((_bitfield1 >> 1) & 0x1u) != 0;
}

set
{
_bitfield1 = (byte)((_bitfield1 & ~(0x1u << 1)) | (((value ? 1 : 0) & 0x1u) << 1));
}
}

public int _bitfield2;

[NativeTypeName(""int : 2"")]
public int c
{
readonly get
{
return (_bitfield2 << 30) >> 30;
}

set
{
_bitfield2 = (_bitfield2 & ~0x3) | (value & 0x3);
}
}
}
}
";

return ValidateGeneratedCSharpLatestWindowsBindingsAsync(InputContents, expectedOutputContents);
}

[Test]
public Task CompatibleTest()
{
var expectedOutputContents = @"namespace ClangSharp.Test
{
public partial struct MyStruct
{
public byte _bitfield1;

[NativeTypeName(""bool : 1"")]
public bool a
{
get
{
return (_bitfield1 & 0x1u) != 0;
}

set
{
_bitfield1 = (byte)((_bitfield1 & ~0x1u) | ((value ? 1 : 0) & 0x1u));
}
}

[NativeTypeName(""bool : 1"")]
public bool b
{
get
{
return ((_bitfield1 >> 1) & 0x1u) != 0;
}

set
{
_bitfield1 = (byte)((_bitfield1 & ~(0x1u << 1)) | (((value ? 1 : 0) & 0x1u) << 1));
}
}

public int _bitfield2;

[NativeTypeName(""int : 2"")]
public int c
{
get
{
return (_bitfield2 << 30) >> 30;
}

set
{
_bitfield2 = (_bitfield2 & ~0x3) | (value & 0x3);
}
}
}
}
";

return ValidateGeneratedCSharpCompatibleWindowsBindingsAsync(InputContents, expectedOutputContents);
}
}
Loading