diff --git a/src/core/IronPython.Modules/_ctypes/StructType.cs b/src/core/IronPython.Modules/_ctypes/StructType.cs index b37878ec2..0af28c7be 100644 --- a/src/core/IronPython.Modules/_ctypes/StructType.cs +++ b/src/core/IronPython.Modules/_ctypes/StructType.cs @@ -12,6 +12,7 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Reflection.Emit; +using System.Runtime.InteropServices; using System.Text; using Microsoft.Scripting; @@ -261,22 +262,20 @@ private void SetFields(object? fields) { INativeType? lastType = null; List allFields = GetBaseSizeAlignmentAndFields(out int size, out int alignment); - IList? anonFields = GetAnonymousFields(this); + IList? anonFields = GetAnonymousFields(this); foreach (object fieldDef in fieldDefList) { GetFieldInfo(this, fieldDef, out string fieldName, out INativeType cdata, out bitCount); - int prevSize = UpdateSizeAndAlignment(cdata, bitCount, lastType, ref size, ref alignment, ref curBitCount); + int fieldOffset = UpdateSizeAndAlignment(cdata, bitCount, ref lastType, ref size, ref alignment, ref curBitCount); - var newField = new Field(fieldName, cdata, prevSize, allFields.Count, bitCount, curBitCount - bitCount); + var newField = new Field(fieldName, cdata, fieldOffset, allFields.Count, bitCount, curBitCount - bitCount); allFields.Add(newField); AddSlot(fieldName, newField); if (anonFields != null && anonFields.Contains(fieldName)) { AddAnonymousFields(this, allFields, cdata, newField); } - - lastType = cdata; } CheckAnonymousFields(allFields, anonFields); @@ -293,7 +292,7 @@ private void SetFields(object? fields) { } } - internal static void CheckAnonymousFields(List allFields, IList? anonFields) { + internal static void CheckAnonymousFields(List allFields, IList? anonFields) { if (anonFields != null) { foreach (string s in anonFields) { bool found = false; @@ -311,18 +310,26 @@ internal static void CheckAnonymousFields(List allFields, IList? } } - internal static IList? GetAnonymousFields(PythonType type) { - object anonymous; - IList? anonFields = null; - if (type.TryGetBoundAttr(type.Context.SharedContext, type, "_anonymous_", out anonymous)) { - anonFields = anonymous as IList; - if (anonFields == null) { + + internal static IList? GetAnonymousFields(PythonType type) { + IList? anonFieldNames = null; + if (type.TryGetBoundAttr(type.Context.SharedContext, type, "_anonymous_", out object anonymous)) { + if (anonymous is not IList anonFields) { throw PythonOps.TypeError("_anonymous_ must be a sequence"); } + anonFieldNames = []; + foreach (object anonField in anonFields) { + if (Converter.TryConvertToString(anonField, out string? anonFieldStr)) { + anonFieldNames.Add(anonFieldStr); + } else { + throw PythonOps.TypeErrorForBadInstance("anonymous field must be a string, not '{0}'", anonField); + } + } } - return anonFields; + return anonFieldNames; } + internal static void AddAnonymousFields(PythonType type, List allFields, INativeType cdata, Field newField) { Field[] childFields; if (cdata is StructType st) { @@ -348,6 +355,7 @@ internal static void AddAnonymousFields(PythonType type, List allFields, } } + private List GetBaseSizeAlignmentAndFields(out int size, out int alignment) { size = 0; alignment = 1; @@ -359,63 +367,125 @@ private List GetBaseSizeAlignmentAndFields(out int size, out int alignmen st.EnsureFinal(); foreach (Field f in st._fields) { allFields.Add(f); - UpdateSizeAndAlignment(f.NativeType, f.BitCount, lastType, ref size, ref alignment, ref totalBitCount); + UpdateSizeAndAlignment(f.NativeType, f.BitCount, ref lastType, ref size, ref alignment, ref totalBitCount); if (f.NativeType == this) { throw StructureCannotContainSelf(); } - - lastType = f.NativeType; } } } return allFields; } - private int UpdateSizeAndAlignment(INativeType cdata, int? bitCount, INativeType? lastType, ref int size, ref int alignment, ref int? totalBitCount) { - Debug.Assert(totalBitCount == null || lastType != null); // lastType is null only on the first iteration, when totalBitCount is null as well - int prevSize = size; - if (bitCount != null) { - if (lastType != null && lastType.Size != cdata.Size) { - totalBitCount = null; - prevSize = size += lastType.Size; - } - - size = PythonStruct.Align(size, cdata.Alignment); - if (totalBitCount != null) { - if ((bitCount + totalBitCount + 7) / 8 <= cdata.Size) { - totalBitCount = bitCount + totalBitCount; + /// + /// Processes one field definition and allocates its data payload within the struct. + /// + /// + /// The type of the field to process. + /// + /// Width of the bitfield in bits. If the fields to process is not a bitfield, this value is null + /// + /// 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. + /// 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. + /// + /// The total size of the struct in bytes excluding an open bitfield container, if any. + /// On input, the size of the struct before the field was allocated. + /// On return, the size of the struct after the field has been processed. + /// 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. + /// So the full (current) struct size in bits is size * 8 + totalBitCount. + /// + /// The total alignment of the struct (the common denominator of all fields). + /// This value is being updated as necessary with the alignment of the processed field. + /// + /// The number of already occupied bits in the currently open containment unit for bitfields. + /// If the previous field is not a bitfield, this value is null. + /// On return, the count is updated with the number of occupied bits. + /// + /// The offset of the processed field within the struct. If the processed field was a bitfield, this is the offset of its container unit. + private int UpdateSizeAndAlignment(INativeType cdata, int? bitCount, ref INativeType? lastType, ref int size, ref int alignment, ref int? totalBitCount) { + int fieldOffset; + if (bitCount != null) { + // process a bitfield + Debug.Assert(bitCount <= cdata.Size * 8); + Debug.Assert(totalBitCount == null || lastType != null); + + if (_pack != null) throw new NotImplementedException("pack with bitfields"); // TODO: implement + + if (UseMsvcBitfieldAlignmentRules) { + if (totalBitCount != null) { // there is already a bitfield container open + // under the MSVC rules, only bitfields of type that has the same size/alignment, are packed into the same container unit + if (lastType!.Size != cdata.Size || lastType.Alignment != cdata.Alignment) { + // if the bitfield type is not compatible with the type of the previous container unit, close the previous container unit + size += lastType.Size; + fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); // TODO: _pack + totalBitCount = null; + } + } + if (totalBitCount != null) { + // container unit open + if ((bitCount + totalBitCount + 7) / 8 <= cdata.Size) { + // new bitfield fits into the container unit + fieldOffset = size; + totalBitCount += bitCount; + } else { + // new bitfield does not fit into the container unit, close it + size += lastType!.Size; + // and open a new container unit for the bitfield + fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); // TODO: _pack + totalBitCount = bitCount; + lastType = cdata; + } } else { - size += lastType!.Size; - prevSize = size; + // open a new container unit for the bitfield + fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); // TODO: _pack totalBitCount = bitCount; + lastType = cdata; } - } else { - totalBitCount = bitCount; + } else { // GCC bitfield alignment rules + // under the GCC rules, all bitfields are packed into the same container unit or an overlapping container unit of a different type, + // as long as they fit and match the alignment + int containerOffset = AlignBack(size, cdata.Alignment); // TODO: _pack + int containerBitCount = (totalBitCount ?? 0) + (size - containerOffset) * 8; + if (containerBitCount + bitCount > cdata.Size * 8) { + // the bitfield does not fit into the container unit at this offset, find the nearest allowed offset + int deltaOffset = cdata.Alignment; // TODO: _pack + int numOffsets = Math.Max(1, (containerBitCount + bitCount.Value - 1) / (deltaOffset * 8)); + containerOffset += numOffsets * deltaOffset; + containerBitCount = Math.Max(0, containerBitCount - numOffsets * deltaOffset * 8); + } + // the bitfield now fits into the container unit at this offset + Debug.Assert(containerBitCount + bitCount <= cdata.Size * 8); + fieldOffset = size = containerOffset; + totalBitCount = containerBitCount + bitCount; + lastType = cdata; } + alignment = Math.Max(alignment, lastType!.Alignment); // TODO: _pack } else { + // process a regular field if (totalBitCount != null) { + // last field was a bitfield; close its container unit to prepare for the next regular field size += lastType!.Size; - prevSize = size; totalBitCount = null; } if (_pack != null) { alignment = _pack.Value; - prevSize = size = PythonStruct.Align(size, _pack.Value); - + fieldOffset = size = PythonStruct.Align(size, _pack.Value); size += cdata.Size; } else { alignment = Math.Max(alignment, cdata.Alignment); - prevSize = size = PythonStruct.Align(size, cdata.Alignment); + fieldOffset = size = PythonStruct.Align(size, cdata.Alignment); size += cdata.Size; } + lastType = cdata; } - return prevSize; + return fieldOffset; } + [MemberNotNull(nameof(_fields), nameof(_size), nameof(_alignment))] internal void EnsureFinal() { if (_fields == null) { @@ -452,6 +522,12 @@ private void EnsureSizeAndAlignment() { throw new InvalidOperationException("size and alignment should always be initialized together"); } } + + private static int AlignBack(int length, int size) + => length & ~(size - 1); + + private static bool UseMsvcBitfieldAlignmentRules + => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } } } diff --git a/src/core/IronPython.Modules/_ctypes/UnionType.cs b/src/core/IronPython.Modules/_ctypes/UnionType.cs index afbc95d64..250dbd7f4 100644 --- a/src/core/IronPython.Modules/_ctypes/UnionType.cs +++ b/src/core/IronPython.Modules/_ctypes/UnionType.cs @@ -166,7 +166,7 @@ string INativeType.TypeFormat { private void SetFields(object? fields) { lock (this) { IList fieldDefList = GetFieldsList(fields); - IList? anonFields = StructType.GetAnonymousFields(this); + IList? anonFields = StructType.GetAnonymousFields(this); int size = 0, alignment = 1; List allFields = new List();//GetBaseSizeAlignmentAndFields(out size, out alignment); diff --git a/src/core/IronPython.Modules/_ctypes/_ctypes.cs b/src/core/IronPython.Modules/_ctypes/_ctypes.cs index 44a554de9..aed9c5941 100644 --- a/src/core/IronPython.Modules/_ctypes/_ctypes.cs +++ b/src/core/IronPython.Modules/_ctypes/_ctypes.cs @@ -597,21 +597,17 @@ private static void GetFieldInfo(INativeType type, object o, out string fieldNam if (pt.Count != 3) { bitCount = null; } else { - bitCount = CheckBits(cdata, pt); + bitCount = Converter.ConvertToInt32(pt[2]); + CheckBits(fieldName, cdata, bitCount.Value); } } /// /// Verifies that the provided bit field settings are valid for this type. /// - private static int CheckBits(INativeType cdata, PythonTuple pt) { - int bitCount = Converter.ConvertToInt32(pt[2]); - - if (!(cdata is SimpleType simpType)) { - throw PythonOps.TypeError("bit fields not allowed for type {0}", ((PythonType)cdata).Name); - } - - switch (simpType._type) { + private static void CheckBits(string fieldName, INativeType cdata, int bitCount) { + switch ((cdata as SimpleType)?._type) { + case null: case SimpleTypeKind.Object: case SimpleTypeKind.Pointer: case SimpleTypeKind.Single: @@ -620,13 +616,13 @@ private static int CheckBits(INativeType cdata, PythonTuple pt) { case SimpleTypeKind.CharPointer: case SimpleTypeKind.WChar: case SimpleTypeKind.WCharPointer: + case SimpleTypeKind.BStr: throw PythonOps.TypeError("bit fields not allowed for type {0}", ((PythonType)cdata).Name); } if (bitCount <= 0 || bitCount > cdata.Size * 8) { - throw PythonOps.ValueError("number of bits invalid for bit field"); + throw PythonOps.ValueError("number of bits invalid for bit field '{0}'", fieldName); } - return bitCount; } /// diff --git a/src/core/IronPython.StdLib/lib/ctypes/test/test_bitfields.py b/src/core/IronPython.StdLib/lib/ctypes/test/test_bitfields.py index b39d82cc0..8f74a541f 100644 --- a/src/core/IronPython.StdLib/lib/ctypes/test/test_bitfields.py +++ b/src/core/IronPython.StdLib/lib/ctypes/test/test_bitfields.py @@ -143,10 +143,11 @@ def test_c_wchar(self): def test_single_bitfield_size(self): for c_typ in int_types: result = self.fail_fields(("a", c_typ, -1)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + # IronPython: error message modified to match CPython 3.14 + self.assertEqual(result, (ValueError, "number of bits invalid for bit field 'a'")) result = self.fail_fields(("a", c_typ, 0)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + self.assertEqual(result, (ValueError, "number of bits invalid for bit field 'a'")) class X(Structure): _fields_ = [("a", c_typ, 1)] @@ -157,7 +158,7 @@ class X(Structure): self.assertEqual(sizeof(X), sizeof(c_typ)) result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + self.assertEqual(result, (ValueError, "number of bits invalid for bit field 'a'")) def test_multi_bitfields_size(self): class X(Structure): diff --git a/tests/IronPython.Tests/Cases/CPythonCasesManifest.ini b/tests/IronPython.Tests/Cases/CPythonCasesManifest.ini index 7641cef7d..9f885274a 100644 --- a/tests/IronPython.Tests/Cases/CPythonCasesManifest.ini +++ b/tests/IronPython.Tests/Cases/CPythonCasesManifest.ini @@ -7,9 +7,6 @@ Timeout=120000 # 2 minute timeout [CPython.ctypes.test_as_parameter] Ignore=true -[CPython.ctypes.test_bitfields] # IronPython.modules.type_related.test_bitfields_ctypes_stdlib -Ignore=true - [CPython.ctypes.test_errno] Ignore=true Reason=Current implementation of get_last_error needs to be debugged diff --git a/tests/suite/modules/type_related/test_bitfields_ctypes_stdlib.py b/tests/suite/modules/type_related/test_bitfields_ctypes_stdlib.py deleted file mode 100644 index 5492dc761..000000000 --- a/tests/suite/modules/type_related/test_bitfields_ctypes_stdlib.py +++ /dev/null @@ -1,30 +0,0 @@ -# Licensed to the .NET Foundation under one or more agreements. -# The .NET Foundation licenses this file to you under the Apache 2.0 License. -# See the LICENSE file in the project root for more information. - -## -## Run selected tests from test_bitfields from StdLib -## - -from iptest import is_ironpython, generate_suite, run_test, is_windows - -import ctypes.test.test_bitfields - -def load_tests(loader, standard_tests, pattern): - tests = loader.loadTestsFromModule(ctypes.test.test_bitfields) - - if is_ironpython: - failing_tests = [] - if not is_windows: # https://github.com/IronLanguages/ironpython3/issues/1442 - failing_tests += [ - ctypes.test.test_bitfields.BitFieldTest('test_mixed_1'), - ctypes.test.test_bitfields.BitFieldTest('test_mixed_4'), - ctypes.test.test_bitfields.C_Test('test_shorts'), - ] - - return generate_suite(tests, failing_tests) - - else: - return tests - -run_test(__name__) diff --git a/tests/suite/modules/type_related/test_ctypes.py b/tests/suite/modules/type_related/test_ctypes.py index 8c3a2208e..33147e3f3 100644 --- a/tests/suite/modules/type_related/test_ctypes.py +++ b/tests/suite/modules/type_related/test_ctypes.py @@ -6,6 +6,7 @@ Tests for CPython's ctypes module. ''' +import _ctypes from ctypes import * from array import array from struct import calcsize @@ -14,7 +15,7 @@ import unittest from decimal import Decimal -from iptest import IronPythonTestCase, is_posix, is_cli, is_mono, is_netcoreapp, big, myint, run_test +from iptest import IronPythonTestCase, is_posix, is_windows, is_cli, is_32, is_mono, is_netcoreapp, big, myint class MyInt: def __init__(self, value): @@ -42,6 +43,13 @@ class CTypesTest(IronPythonTestCase): export_error_msg = "Existing exports of data: object cannot be re-sized" if is_cli else "cannot resize an array that is exporting buffers" readonly_error_msg = "underlying buffer is not writable" + def check_bitfield(self, bitfield, fieldtype, offset, bitoffset, bitwidth): + self.assertEqual(repr(bitfield), "".format(fieldtype.__name__, offset, bitoffset, bitwidth)) + self.assertEqual(bitfield.offset, offset) + self.assertEqual(bitfield.size & 0xffff, bitoffset) + self.assertEqual(bitfield.size >> 16, bitwidth) + + def test_from_array(self): arr = array('i', range(16)) c = (c_int * 15).from_buffer(arr, sizeof(c_int)) @@ -204,7 +212,7 @@ class Test(Structure): msg = "int expected instead of float" self.assertRaisesMessage(TypeError, msg, Test, 2.3) - with self.assertRaisesMessage(ValueError, "number of bits invalid for bit field"): + with self.assertRaisesRegex(ValueError, "^number of bits invalid for bit field"): class Test(Structure): _fields_ = [("x", c_int, 0)] # if c_long and c_int are the same size, c_long is used @@ -217,6 +225,7 @@ class Test(Structure): self.assertEqual((Test.y.offset, Test.y.size), (0, (16 << 16) + 16)) self.assertEqual((Test.z.offset, Test.z.size), (4, (32 << 16) + 0)) + def test_bitfield_longlong(self): """Tests for bitfields of type c_longlong""" @@ -236,6 +245,666 @@ class TestU(Structure): self.assertEqual(TestU(-(1 << 64)).x, 0) self.assertEqual(TestU(-(1 << 64) - 1).x, 0x7fffffffffffffff) + + @unittest.skipUnless(is_cli, "packed bitfields canary") + def test_bitfield_mixed_packed_check(self): + with self.assertRaises(NotImplementedError): + # This test will fail when packed bitfields become supported + # In this case, delete this test and re-enable: + # * test_bitfield_mixed_E_packed + # * test_bitfield_mixed_G4_packed + class Test(Structure): + _pack_ = 1 + _fields_ = [ + ("a", c_int, 10), + ("b", c_int, 20), + ] + + + @unittest.skipIf(is_32 and is_posix, "assumes 64-bit long on POSIX") + def test_bitfield_mixed_B(self): + """ + struct B // GCC: 8, MSVC: 24 + { + long long a : 3; // GCC, MSVC: 0 (0:0) + int b : 4; // GCC: 3 (0:3) (fits in the same container as a) + // MSVC: 64 (8:0) (different type than a) + unsigned int c : 1; // GCC: 7 (0:7) (fits in the same container as a) + // MSVC: 68 (8:4) (different type than b but same size and alignment) + long d : 5; // GCC: 8 (1:0) (fits in the same container as a) + // MSVC: 69 (8:5) (different type than c, but same size and alignment) + long long e : 5; // GCC: 13 (1:5) (fits in the same container as a) + // MSVC: 128 (16:0) (different type than d) + long long f : 1; // GCC: 18 (2:2) (fits in the same container as a) + // MSVC: 133 (16:5) (fits in the same container as e) + ssize_t g : 2; // GCC: 19 (2:3) (fits in the same container as a) + // MSVC: 134 (16:6) (equivalent type, fits in the same container as e) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 3), + ("b", c_int, 4), + ("c", c_uint, 1), + ("d", c_long, 5), + ("e", c_longlong, 5), + ("f", c_longlong, 1), + ("g", c_ssize_t, 2), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 3) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_int, 0, 3, 4) + self.check_bitfield(Test.c, c_uint, 0, 7, 1) + else: # bug in CPython + self.check_bitfield(Test.b, c_int, 4, 3, 4) + self.check_bitfield(Test.c, c_uint, 4, 7, 1) + self.check_bitfield(Test.d, c_long, 0, 8, 5) + self.check_bitfield(Test.e, c_longlong, 0, 13, 5) + self.check_bitfield(Test.f, c_longlong, 0, 18, 1) + self.check_bitfield(Test.g, c_ssize_t, 0, 19, 2) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_int, 8, 0, 4) + self.check_bitfield(Test.c, c_uint, 8, 4, 1) + self.check_bitfield(Test.d, c_long, 8, 5, 5) + self.check_bitfield(Test.e, c_longlong, 16, 0, 5) + self.check_bitfield(Test.f, c_longlong, 16, 5, 1) + self.check_bitfield(Test.g, c_ssize_t, 16, 6, 2) + self.assertEqual(sizeof(Test), 24) + + + def test_bitfield_mixed_C(self): + """ + struct C // GCC: 8, MSVC: 8 + { + int x; + wchar_t a : 2; // GCC, MSVC: 32 (4:0) + unsigned short b : 3; // GCC: 34 (4:2) (fits in the same container as a) + // MSVC: 34 (4:2) (equivalent type, fits in the same container as a) + wchar_t c : 1; // GCC: 37 (4:5) (fits in the same container as a) + // MSVC: 37 (4:5) (equivalent type, fits in the same container as a) + unsigned short d : 5; // GCC: 38 (4:6) (fits in the same container as a) + // MSVC: 38 (4:6) (equivalent type, fits in the same container as a) + }; + """ + class Test(Structure): + _fields_ = [ + ("x", c_int), + ("a", c_short, 2), + ("b", c_ushort, 3), + ("c", c_short, 1), + ("d", c_ushort, 5), + ] + + self.check_bitfield(Test.a, c_short, 4, 0, 2) + self.check_bitfield(Test.b, c_ushort, 4, 2, 3) + self.check_bitfield(Test.c, c_short, 4, 5, 1) + self.check_bitfield(Test.d, c_ushort, 4, 6, 5) + self.assertEqual(sizeof(Test), 8) + + + @unittest.skipIf(is_32 and is_posix, "assumes 64-bit long on POSIX") + def test_bitfield_mixed_D1(self): + """ + struct D1 // GCC: 8, MSVC: 8 + { + long a : 3; // GCC, MSVC: 0 (0:0) + int b : 30; // GCC: 32 (4:0) (doesn't fit in the same container as a) + // MSVC: 32 (4:0) (same type but doesn't fit in the same container as a) + long c : 2; // GCC: 62 (7:6) (fits in the same container as a) + // MSVC: 62 (7:6) (fits in the same container as b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_long, 3), + ("b", c_int, 30), + ("c", c_long, 2), + ] + + self.check_bitfield(Test.a, c_long, 0, 0, 3) + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_int, 4, 0, 30) + else: # bug in CPython + self.check_bitfield(Test.b, c_int, 4, 3, 30) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.c, c_long, 0, 62, 2) + else: # bug in CPython + self.check_bitfield(Test.c, c_long, 0, 33, 2) + else: + self.check_bitfield(Test.c, c_long, 4, 30, 2) + self.assertEqual(sizeof(Test), 8) + + + def test_bitfield_mixed_D2(self): + """ + struct D2 // GCC: 16, MSVC: 24 + { + long long a : 3; // GCC, MSVC: 0 (0:0) + int b : 32; // GCC: 32 (4:0) (fits in the same container as a, padded to satisfy alignment) + // MSVC: 64 (8:0) (different type than a) + long long c : 2; // GCC: 64 (8:0) (doesn't fit in the same container as b) + // MSVC: 128 (16:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 3), + ("b", c_int, 32), + ("c", c_longlong, 2), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 3) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_int, 4, 0, 32) + self.check_bitfield(Test.c, c_longlong, 8, 0, 2) + self.assertEqual(sizeof(Test), 16) + else: # bug in CPython + self.check_bitfield(Test.b, c_int, 4, 3, 32) + self.check_bitfield(Test.c, c_longlong, 0, 35, 2) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_int, 8, 0, 32) + self.check_bitfield(Test.c, c_longlong, 16, 0, 2) + self.assertEqual(sizeof(Test), 24) + + + def test_bitfield_mixed_D3(self): + """ + struct D3 // GCC: 8, MSVC: 16 + { + char x; + char a : 3; // GCC: 8 (1:0) + // MSVC: 8 (1:0) + short b : 4; // GCC: 11 (1:3) (fits in the same container as a) + // MSVC: 16 (2:0) (different type than a) + long long c : 2; // GCC: 15 (1:7) (fits in the same container as a and b) + // MSVC: 64 (8:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("x", c_char), + ("a", c_byte, 3), + ("b", c_short, 4), + ("c", c_longlong, 2), + ] + + self.check_bitfield(Test.a, c_byte, 1, 0, 3) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_short, 0, 11, 4) + self.check_bitfield(Test.c, c_longlong, 0, 15, 2) + self.assertEqual(sizeof(Test), 8) + else: # bug in CPython + self.check_bitfield(Test.b, c_short, 1, 3, 4) + self.check_bitfield(Test.c, c_longlong, 1, 7, 2) + self.assertEqual(sizeof(Test), 9) + else: + self.check_bitfield(Test.b, c_short, 2, 0, 4) + self.check_bitfield(Test.c, c_longlong, 8, 0, 2) + self.assertEqual(sizeof(Test), 16) + + + def test_bitfield_mixed_E(self): + """ + struct E // GCC: 8, MSVC: 16 + { + long long a : 20; // GCC, MSVC: 0 (0:0) + short b : 2; // GCC: 20 (2:4) (fits in the same container as a) + // MSVC: 64 (8:0) (different type than a) + short c : 15; // GCC: 32 (4:0) (doesn't fit in the same container as b) + // MSVC: 80 (10:0) (doesn't fit in the same container as b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 20), + ("b", c_short, 2), + ("c", c_short, 15), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 20) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_short, 2, 4, 2) + self.check_bitfield(Test.c, c_short, 4, 0, 15) + else: # bug in CPython + self.check_bitfield(Test.b, c_short, 6, 20, 2) + self.check_bitfield(Test.c, c_short, 6, 22, 15) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_short, 8, 0, 2) + self.check_bitfield(Test.c, c_short, 10, 0, 15) + self.assertEqual(sizeof(Test), 16) + + + @unittest.skipIf(is_cli, "TODO: NotImplementedError: pack with bitfields") + def test_bitfield_mixed_E_packed(self): + """ + // same as E but packed along 1 byte + #pragma pack(push, 1) + struct E_packed // GCC: 5, MSVC: 12 + { + long long a : 20; // GCC, MSVC: 0 (0:0) + short b : 2; // GCC: 20 (2:4) (fits in the same container as a) + // MSVC: 64 (8:0) (different type than a) + short c : 15; // GCC: 22 (2:6) (straddles alignment boundary for `short`) + // MSVC: 80 (10:0) (doesn't fit in the same container as b) + }; + #pragma pack(pop) + """ + class Test(Structure): + _pack_ = 1 + _fields_ = [ + ("a", c_longlong, 20), + ("b", c_short, 2), + ("c", c_short, 15), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 20) + if is_posix and (is_cli or sys.version_info < (3, 14)): # CPython 3.14 implements MSVC behaviour (bug) + if is_cli: # GCC-compliant results + self.check_bitfield(Test.b, c_short, 2, 4, 2) + self.check_bitfield(Test.c, c_short, 2, 6, 15) + self.assertEqual(sizeof(Test), 5) + else: # bug in CPython + self.check_bitfield(Test.b, c_short, 6, 20, 2) + self.check_bitfield(Test.c, c_short, 6, 22, 15) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_short, 8, 0, 2) + self.check_bitfield(Test.c, c_short, 10, 0, 15) + self.assertEqual(sizeof(Test), 12) + + + def test_bitfield_mixed_F1(self): + """ + struct F1 // GCC: 16, MSVC: 24 + { + long long a : 3; // GCC, MSVC: 0 (0:0) + int b : 31; // GCC: 32 (4:0) (fits in the same container as a, padded to satisfy alignment) + // MSVC: 64 (8:0) (different type than a) + long long c : 3; // GCC: 64 (8:0) (doesn't fit in the same container as b) + // MSVC: 128 (16:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 3), + ("b", c_int, 31), + ("c", c_longlong, 3), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 3) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_int, 4, 0, 31) + self.check_bitfield(Test.c, c_longlong, 8, 0, 3) + self.assertEqual(sizeof(Test), 16) + else: # bug in CPython + self.check_bitfield(Test.b, c_int, 4, 3, 31) + self.check_bitfield(Test.c, c_longlong, 0, 34, 3) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_int, 8, 0, 31) + self.check_bitfield(Test.c, c_longlong, 16, 0, 3) + self.assertEqual(sizeof(Test), 24) + + + def test_bitfield_mixed_F2(self): + """ + struct F2 // GCC: 8, MSVC: 24 + { + long long a : 3; // GCC, MSVC: 0 (0:0) + int b : 29; // GCC: 3 (0:3) (fits in the same container as a) + // MSVC: 64 (8:0) (different type than a) + long long c : 3; // GCC: 32 (4:0) (doesn't fit in the same container as b, alignment 4) + // MSVC: 128 (16:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 3), + ("b", c_int, 29), + ("c", c_longlong, 3), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 3) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_int, 0, 3, 29) + else: # bug in CPython + self.check_bitfield(Test.b, c_int, 4, 3, 29) + self.check_bitfield(Test.c, c_longlong, 0, 32, 3) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_int, 8, 0, 29) + self.check_bitfield(Test.c, c_longlong, 16, 0, 3) + self.assertEqual(sizeof(Test), 24) + + + def test_bitfield_mixed_F3(self): + """ + struct F3 // GCC: 8, MSVC: 24 + { + long long a : 4; // GCC, MSVC: 0 (0:0) + int b : 29; // GCC: 32 (4:0) (doesn't fit in the same container as a) + // MSVC: 64 (8:0) (different type than a) + long long c : 3; // GCC: 61 (7:5) (fits in the same container as b) + // MSVC: 128 (16:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 4), + ("b", c_int, 29), + ("c", c_longlong, 3), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 4) + if is_posix: + if is_cli or sys.version_info >= (3, 14): # GCC-compliant results + self.check_bitfield(Test.b, c_int, 4, 0, 29) + self.check_bitfield(Test.c, c_longlong, 0, 61, 3) + else: # bug in CPython + self.check_bitfield(Test.b, c_int, 4, 4, 29) + self.check_bitfield(Test.c, c_longlong, 0, 33, 3) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_int, 8, 0, 29) + self.check_bitfield(Test.c, c_longlong, 16, 0, 3) + self.assertEqual(sizeof(Test), 24) + + + def test_bitfield_mixed_F4(self): + class Test(Structure): + _fields_ = [ + ("a", c_int), + ("b1", c_short, 3), + ("b2", c_short, 3), + ("c", c_int, 3), + ] + + self.assertEqual(Test.a.offset, 0) + self.assertEqual(Test.a.size, 4) + + self.check_bitfield(Test.b1, c_short, 4, 0, 3) + self.check_bitfield(Test.b2, c_short, 4, 3, 3) + if is_posix: + self.check_bitfield(Test.c, c_int, 4, 6, 3) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.c, c_int, 8, 0, 3) + self.assertEqual(sizeof(Test), 12) + + instance = Test() + self.assertTrue(isinstance(instance.a, int)) + instance.a = 1 + instance.b1 = 5 # equals -3 in 2-complement on 3 bits + instance.b2 = 7 # equals -1 in 2-complement on 3 bits + instance.c = 3 + self.assertEqual(instance.a, 1) + self.assertEqual(instance.b1, -3) + self.assertEqual(instance.b2, -1) + self.assertEqual(instance.c, 3) + + + def test_bitfield_mixed_G1(self): + """ + struct G1 // GCC, MSVC: 3 + { + char a : 7; // GCC, MSVC: 0 (0:0) + char b : 2; // GCC, MSVC: 8 (1:0) (does'n fit in the same byte as a) + char c : 7; // GCC, MSVC: 16 (2:0) (does'n fit in the same byte as b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_byte, 7), + ("b", c_byte, 2), + ("c", c_byte, 7), + ] + + self.check_bitfield(Test.a, c_byte, 0, 0, 7) + self.check_bitfield(Test.b, c_byte, 1, 0, 2) + self.check_bitfield(Test.c, c_byte, 2, 0, 7) + self.assertEqual(sizeof(Test), 3) + + + def test_bitfield_mixed_G2(self): + """ + struct G2 // GCC: 2, MSVC: 6 + { + char a : 7; // GCC, MSVC: 0 (0:0) + short b : 2; // GCC: 7 (0:7) (fits in the same container as a) + // MSVC: 16 (2:0) (different type than a) + char c : 7; // GCC: 9 (1:1) (fits in the same container as b) + // MSVC: 32 (4:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_byte, 7), + ("b", c_short, 2), + ("c", c_byte, 7), + ] + + self.check_bitfield(Test.a, c_byte, 0, 0, 7) + if is_posix: + self.check_bitfield(Test.b, c_short, 0, 7, 2) + if is_cli or sys.version_info >= (3, 14): # bug in CPython 3.13 and earlier + self.check_bitfield(Test.c, c_byte, 1, 1, 7) + self.assertEqual(sizeof(Test), 2) + else: + self.check_bitfield(Test.b, c_short, 2, 0, 2) + self.check_bitfield(Test.c, c_byte, 4, 0, 7) + self.assertEqual(sizeof(Test), 6) + + + def test_bitfield_mixed_G3(self): + """ + struct G3 // GCC, MSVC: 6 + { + char a : 7; // GCC, MSVC: 0 (0:0) + short b : 10; // GCC: 16 (2:0) (doesn't fit in the same container as a) + // MSVC: 16 (2:0) (different type than a) + char c : 7; // GCC: 32 (4:0) (doesn't fit in the same container as b) + // MSVC: 32 (4:0) (different type than b) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_byte, 7), + ("b", c_short, 10), + ("c", c_byte, 7), + ] + + self.check_bitfield(Test.a, c_byte, 0, 0, 7) + self.check_bitfield(Test.b, c_short, 2, 0, 10) + self.check_bitfield(Test.c, c_byte, 4, 0, 7) + self.assertEqual(sizeof(Test), 6) + + + def test_bitfield_mixed_G4(self): + """ + struct G4 // GCC: 16, MSVC: 32 + { + unsigned short a : 8; // GCC, MSVC: 0 (0:0) + int b : 16; // GCC: 8 (1:0) (fits in the same container as a) + // MSVC: 32 (4:0) (different type than a) + unsigned int c : 29; // GCC: 32 (4:0) (doesn't fit in the same container as b) + // MSVC: 64 (8:0) (different type than b) + long long d : 9; // GCC: 64 (8:0) (doesn't fit in the same container as a, b, or c) + // MSVC: 128 (16:0) (different type than c) + unsigned int e : 2; // GCC: 73 (9:1) (fits in the same container as d) + // MSVC: 192 (24:0) (different type than e) + unsigned int f : 31; // GCC: 96: (12:0) (fits in the same container as d) + // MSVC: 224 (28:0) (doesn't fit in the same container as e) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_ushort, 8), + ("b", c_int, 16), + ("c", c_uint, 29), + ("d", c_longlong, 9), + ("e", c_uint, 2), + ("f", c_uint, 31), + ] + + self.check_bitfield(Test.a, c_ushort, 0, 0, 8) + if is_posix: # GCC-compliant results + self.check_bitfield(Test.b, c_int, 0, 8, 16) + self.check_bitfield(Test.c, c_uint, 4, 0, 29) + if is_cli or sys.version_info >= (3, 14): # bug in CPython 3.13 and earlier + self.check_bitfield(Test.d, c_longlong, 8, 0, 9) + self.check_bitfield(Test.e, c_uint, 8, 9, 2) + self.check_bitfield(Test.f, c_uint, 12, 0, 31) + self.assertEqual(sizeof(Test), 16) + else: + self.check_bitfield(Test.b, c_int, 4, 0, 16) + self.check_bitfield(Test.c, c_uint, 8, 0, 29) + self.check_bitfield(Test.d, c_longlong, 16, 0, 9) + self.check_bitfield(Test.e, c_uint, 24, 0, 2) + self.check_bitfield(Test.f, c_uint, 28, 0, 31) + self.assertEqual(sizeof(Test), 32) + + + @unittest.skipIf(is_cli, "TODO: NotImplementedError: pack with bitfields") + def test_bitfield_mixed_G4_packed(self): + """ + #pragma pack(push, 4) + struct G4_packed // GCC: 12, MSVC: 28 + { + unsigned short a : 8; // GCC, MSVC: 0 (0:0) + int b : 16; // GCC: 8 (1:0) (fits in the same container as a) + // MSVC: 32 (4:0) (different type than a) + unsigned int c : 29; // GCC: 24 (3:0) (doesn't fit in the same container as b) + // MSVC: 64 (8:0) (different type than b) + long long d : 9; // GCC: 53 (6:5) (fits in the same container as c) + // MSVC: 96 (12:0) (different type than d) + unsigned int e : 2; // GCC: 62 (7:6) (fits in the same container as d) + // MSVC: 160 (20:0) (different type than e) + unsigned int f : 31; // GCC: 64: (8:0) (does not fit in the same container as e) + // MSVC: 192 (24:0) (does not fit in the same container as e) + }; + #pragma pack(pop) + """ + class Test(Structure): + _pack_ = 4 + _fields_ = [ + ("a", c_ushort, 8), + ("b", c_int, 16), + ("c", c_uint, 29), + ("d", c_longlong, 9), + ("e", c_uint, 2), + ("f", c_uint, 31), + ] + + self.check_bitfield(Test.a, c_ushort, 0, 0, 8) + if is_posix and is_cli: + self.check_bitfield(Test.b, c_int, 0, 8, 16) + self.check_bitfield(Test.c, c_uint, 3, 0, 29) # ?? + self.check_bitfield(Test.d, c_longlong, 4, 21, 9) + self.check_bitfield(Test.e, c_uint, 4, 30, 2) + self.check_bitfield(Test.f, c_uint, 8, 0, 31) + self.assertEqual(sizeof(Test), 12) + else: + if is_windows or sys.version_info >= (3, 14): # CPython 3.14 implements MSVC behavior even on POSIX (bug), CPython 3.13 and earlier is hopelessly incorrect + self.check_bitfield(Test.b, c_int, 4, 0, 16) + self.check_bitfield(Test.c, c_uint, 8, 0, 29) + self.check_bitfield(Test.d, c_longlong, 12, 0, 9) + self.check_bitfield(Test.e, c_uint, 20, 0, 2) + self.check_bitfield(Test.f, c_uint, 24, 0, 31) + self.assertEqual(sizeof(Test), 28) + + + def test_bitfield_mixed_H1(self): + """ + struct H1 // GCC: 8, MSVC: 16 + { + long long a : 52; // GCC, MSVC: 0 (0:0) + char b : 3; // GCC: 52 (6:4) (fits in the same container as a) + // MSVC: 64 (8:0) (different type than a) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 52), + ("b", c_byte, 3), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 52) + if is_posix: + if is_cli or sys.version_info >= (3, 14): + self.check_bitfield(Test.b, c_byte, 6, 4, 3) + else: # bug in CPython + self.check_bitfield(Test.b, c_byte, 7, 52, 3) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_byte, 8, 0, 3) + self.assertEqual(sizeof(Test), 16) + + + def test_bitfield_mixed_H2(self): + """ + struct H2 // GCC: 8, MSVC: 16 + { + long long a : 52; // GCC, MSVC: 0 (0:0) + char b : 4; // GCC: 52 (6:4) (fits in the same container as a, just fits in the shared byte) + // MSVC: 64 (8:0) (different type than a) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 52), + ("b", c_byte, 4), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 52) + if is_posix: + if is_cli or sys.version_info >= (3, 14): + self.check_bitfield(Test.b, c_byte, 6, 4, 4) + else: # bug in CPython + self.check_bitfield(Test.b, c_byte, 7, 52, 4) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_byte, 8, 0, 4) + self.assertEqual(sizeof(Test), 16) + + + def test_bitfield_mixed_H3(self): + """ + struct H3 // GCC: 8, MSVC: 16 + { + long long a : 52; // GCC, MSVC: 0 (0:0) + char b : 5; // GCC: 52 (7:0) (fits in the same container but padding bits added to prevent byte boundary crossing) + // MSVC: 64 (8:0) (different type than a) + }; + """ + class Test(Structure): + _fields_ = [ + ("a", c_longlong, 52), + ("b", c_byte, 5), + ] + + self.check_bitfield(Test.a, c_longlong, 0, 0, 52) + if is_posix: + if (is_cli or sys.version_info >= (3, 14)): + self.check_bitfield(Test.b, c_byte, 7, 0, 5) + else: # bug in CPython + self.check_bitfield(Test.b, c_byte, 7, 52, 5) + self.assertEqual(sizeof(Test), 8) + else: + self.check_bitfield(Test.b, c_byte, 8, 0, 5) + self.assertEqual(sizeof(Test), 16) + + @unittest.skipIf(is_posix, 'Windows specific test') def test_loadlibrary_error(self): with self.assertRaises(OSError) as cm: @@ -410,4 +1079,5 @@ def test_conversions_overflow(self): self.assertEqual(c_byte_value.value, -127) -run_test(__name__) +if __name__ == "__main__": + unittest.main()