Skip to content

Commit 31f6964

Browse files
committed
Bitfields on GCC
1 parent 62b2f65 commit 31f6964

2 files changed

Lines changed: 134 additions & 28 deletions

File tree

src/core/IronPython.Modules/_ctypes/StructType.cs

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Diagnostics.CodeAnalysis;
1313
using System.Numerics;
1414
using System.Reflection.Emit;
15+
using System.Runtime.InteropServices;
1516
using System.Text;
1617

1718
using Microsoft.Scripting;
@@ -266,7 +267,7 @@ private void SetFields(object? fields) {
266267
foreach (object fieldDef in fieldDefList) {
267268
GetFieldInfo(this, fieldDef, out string fieldName, out INativeType cdata, out bitCount);
268269

269-
int prevSize = UpdateSizeAndAlignment(cdata, bitCount, lastType, ref size, ref alignment, ref curBitCount);
270+
int prevSize = UpdateSizeAndAlignment(cdata, bitCount, ref lastType, ref size, ref alignment, ref curBitCount);
270271

271272
var newField = new Field(fieldName, cdata, prevSize, allFields.Count, bitCount, curBitCount - bitCount);
272273
allFields.Add(newField);
@@ -275,8 +276,6 @@ private void SetFields(object? fields) {
275276
if (anonFields != null && anonFields.Contains(fieldName)) {
276277
AddAnonymousFields(this, allFields, cdata, newField);
277278
}
278-
279-
lastType = cdata;
280279
}
281280

282281
CheckAnonymousFields(allFields, anonFields);
@@ -359,63 +358,126 @@ private List<Field> GetBaseSizeAlignmentAndFields(out int size, out int alignmen
359358
st.EnsureFinal();
360359
foreach (Field f in st._fields) {
361360
allFields.Add(f);
362-
UpdateSizeAndAlignment(f.NativeType, f.BitCount, lastType, ref size, ref alignment, ref totalBitCount);
361+
UpdateSizeAndAlignment(f.NativeType, f.BitCount, ref lastType, ref size, ref alignment, ref totalBitCount);
363362

364363
if (f.NativeType == this) {
365364
throw StructureCannotContainSelf();
366365
}
367-
368-
lastType = f.NativeType;
369366
}
370367
}
371368
}
372369
return allFields;
373370
}
374371

375-
private int UpdateSizeAndAlignment(INativeType cdata, int? bitCount, INativeType? lastType, ref int size, ref int alignment, ref int? totalBitCount) {
376-
Debug.Assert(totalBitCount == null || lastType != null); // lastType is null only on the first iteration, when totalBitCount is null as well
377-
int prevSize = size;
378-
if (bitCount != null) {
379-
if (lastType != null && lastType.Size != cdata.Size) {
380-
totalBitCount = null;
381-
prevSize = size += lastType.Size;
382-
}
383372

384-
size = PythonStruct.Align(size, cdata.Alignment);
385-
386-
if (totalBitCount != null) {
387-
if ((bitCount + totalBitCount + 7) / 8 <= cdata.Size) {
388-
totalBitCount = bitCount + totalBitCount;
373+
/// <summary>
374+
/// Processes one field definition and allocates its data payload within the struct.
375+
/// </summary>
376+
/// <param name="cdata">
377+
/// The type of the field to process.</param>
378+
/// <param name="bitCount">
379+
/// Width of the bitfield in bits. If the fields to process is not a bitfield, this value is null</param>
380+
/// <param name="lastType">
381+
/// The type of the last field (or container unit) processed in the struct. If processing the first field in the struct, this value is null.
382+
/// On return, this value is updated with the processed field's type, or, if the processed field was a bitfield, with its container unit type.</param>
383+
/// <param name="size">
384+
/// The total size of the struct in bytes excluding an open bitfield container, if any.
385+
/// On input, the size of the struct before the field was allocated.
386+
/// On return, the size of the struct after the field has been processed.
387+
/// If the processed field was a bitfield, the size may not have been increased yet, depending whether the bitfield fit in the current container unit.
388+
/// So the full (current) struct size in bits is size * 8 + totalBitCount. </param>
389+
/// <param name="alignment">
390+
/// The total alignment of the struct (the common denominator of all fields).
391+
/// This value is being updated as necessary with the alignment of the processed field.</param>
392+
/// <param name="totalBitCount">
393+
/// The number of already occupied bits in the currently open containment unit for bitfields.
394+
/// If the previous field is not a bitfield, this value is null.
395+
/// On return, the count is updated with the number of occupied bits.</param>
396+
/// <returns>
397+
/// The offset of the processed field within the struct. If the processed field was a bitfield, this is the offset of its container unit.</returns>
398+
private int UpdateSizeAndAlignment(INativeType cdata, int? bitCount, ref INativeType? lastType, ref int size, ref int alignment, ref int? totalBitCount) {
399+
int fieldOffset;
400+
if (bitCount != null) {
401+
// process a bitfield
402+
Debug.Assert(bitCount <= cdata.Size * 8);
403+
Debug.Assert(totalBitCount == null || lastType != null);
404+
405+
if (_pack != null) throw new NotImplementedException("pack with bitfields"); // TODO: implement
406+
407+
if (UseMsvcBitfieldAlignmentRules) {
408+
if (totalBitCount != null) {
409+
// this bitfield is not the first field in the struct
410+
if (totalBitCount != null) { // there is already a bitfield container open
411+
// under the MSVC rules, only bitfields of type that has the same size/alignment, are packed into the same container unit
412+
if (lastType!.Size != cdata.Size || lastType.Alignment != cdata.Alignment) {
413+
// if the bitfield type is not compatible with the type of the previous container unit, close the previous container unit
414+
size += lastType.Size;
415+
fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); // TODO: _pack
416+
totalBitCount = null;
417+
}
418+
}
419+
}
420+
if (totalBitCount != null) {
421+
// container unit open
422+
if ((bitCount + totalBitCount + 7) / 8 <= cdata.Size) {
423+
// new bitfield fits into the container unit
424+
fieldOffset = size;
425+
totalBitCount = bitCount + totalBitCount;
426+
} else {
427+
// new bitfield does not fit into the container unit, close it
428+
size += lastType!.Size;
429+
// and open a new container unit for the bitfield
430+
fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); // TODO: _pack
431+
totalBitCount = bitCount;
432+
lastType = cdata;
433+
}
389434
} else {
390-
size += lastType!.Size;
391-
prevSize = size;
435+
// open a new container unit for the bitfield
436+
fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); // TODO: _pack
392437
totalBitCount = bitCount;
438+
lastType = cdata;
393439
}
394-
} else {
395-
totalBitCount = bitCount;
440+
} else { // GCC bitfield alignment rules
441+
// under the GCC rules, all bitfields are packed into the same container unit or an overlapping container unit of a different type,
442+
// as long as they fit and match the alignment
443+
int containerOffset = AlignBack(size, cdata.Alignment); // TODO: _pack
444+
int containerBitCount = (totalBitCount ?? 0) + (size - containerOffset) * 8;
445+
while (containerBitCount + bitCount > cdata.Size * 8) {
446+
// the bitfield does not fit into the container unit at this offset, try the next allowed offset
447+
int deltaOffset = cdata.Alignment; // TODO: _pack
448+
containerOffset += deltaOffset;
449+
containerBitCount = Math.Max(0, containerBitCount - deltaOffset * 8);
450+
}
451+
// the bitfield now fits into the container unit at this offset
452+
fieldOffset = size = containerOffset;
453+
totalBitCount = containerBitCount + bitCount;
454+
lastType = cdata;
396455
}
456+
alignment = Math.Max(alignment, lastType!.Alignment); // TODO: _pack
397457
} else {
458+
// process a regular field
398459
if (totalBitCount != null) {
460+
// last field was a bitfield; close its container unit to prepare for the next regular field
399461
size += lastType!.Size;
400-
prevSize = size;
401462
totalBitCount = null;
402463
}
403464

404465
if (_pack != null) {
405466
alignment = _pack.Value;
406-
prevSize = size = PythonStruct.Align(size, _pack.Value);
407-
467+
fieldOffset = size = PythonStruct.Align(size, _pack.Value);
408468
size += cdata.Size;
409469
} else {
410470
alignment = Math.Max(alignment, cdata.Alignment);
411-
prevSize = size = PythonStruct.Align(size, cdata.Alignment);
471+
fieldOffset = size = PythonStruct.Align(size, cdata.Alignment);
412472
size += cdata.Size;
413473
}
474+
lastType = cdata;
414475
}
415476

416-
return prevSize;
477+
return fieldOffset; // this is the offset of the field or the container unit for the bitfield
417478
}
418479

480+
419481
[MemberNotNull(nameof(_fields), nameof(_size), nameof(_alignment))]
420482
internal void EnsureFinal() {
421483
if (_fields == null) {
@@ -452,6 +514,12 @@ private void EnsureSizeAndAlignment() {
452514
throw new InvalidOperationException("size and alignment should always be initialized together");
453515
}
454516
}
517+
518+
private static int AlignBack(int length, int size)
519+
=> length & ~(size - 1);
520+
521+
private static bool UseMsvcBitfieldAlignmentRules
522+
=> RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
455523
}
456524
}
457525
}

tests/suite/modules/type_related/test_ctypes.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ class Test(Structure):
217217
self.assertEqual((Test.y.offset, Test.y.size), (0, (16 << 16) + 16))
218218
self.assertEqual((Test.z.offset, Test.z.size), (4, (32 << 16) + 0))
219219

220+
220221
def test_bitfield_longlong(self):
221222
"""Tests for bitfields of type c_longlong"""
222223

@@ -236,6 +237,43 @@ class TestU(Structure):
236237
self.assertEqual(TestU(-(1 << 64)).x, 0)
237238
self.assertEqual(TestU(-(1 << 64) - 1).x, 0x7fffffffffffffff)
238239

240+
def test_bitfield_mixed1(self):
241+
class Test(Structure):
242+
_fields_ = [
243+
("a", c_int),
244+
("b1", c_short, 3),
245+
("b2", c_short, 3),
246+
("c", c_int, 3),
247+
]
248+
249+
250+
instance = Test()
251+
instance.a = 1
252+
instance.b1 = 5 # equals -3 in 2-complement on 3 bits
253+
instance.b2 = 7 # equals -1 in 2-complement on 3 bits
254+
instance.c = 3
255+
self.assertEqual(instance.a, 1)
256+
self.assertEqual(instance.b1, -3)
257+
self.assertEqual(instance.b2, -1)
258+
self.assertEqual(instance.c, 3)
259+
260+
self.assertTrue(isinstance(instance.a, int))
261+
self.assertEqual(Test.a.offset, 0)
262+
self.assertEqual(Test.a.size, 4)
263+
264+
self.assertEqual(Test.b1.offset, 4)
265+
self.assertEqual(Test.b1.size & 0xffff, 0) # bit offset
266+
self.assertEqual(Test.b1.size >> 16, 3) # num bits
267+
268+
self.assertEqual(Test.b2.offset, 4)
269+
self.assertEqual(Test.b2.size & 0xffff, 3) # bit offset
270+
self.assertEqual(Test.b2.size >> 16, 3) # num bits
271+
272+
self.assertEqual(Test.c.offset, 8)
273+
self.assertEqual(Test.c.size & 0xffff, 0) # bit offset
274+
self.assertEqual(Test.c.size >> 16, 3) # num bits
275+
276+
239277
@unittest.skipIf(is_posix, 'Windows specific test')
240278
def test_loadlibrary_error(self):
241279
with self.assertRaises(OSError) as cm:

0 commit comments

Comments
 (0)